chore(backup): 保存新版预审开发前的代码与文档基线
verify / test-and-build (push) Canceled after 0s

This commit is contained in:
2026-09-28 16:34:10 +08:00
parent b10093f9d8
commit 4d5a420671
37 changed files with 3890 additions and 10 deletions
+59
View File
@@ -0,0 +1,59 @@
"""List or remove stopped deployment rollback containers; never touches volumes."""
from __future__ import annotations
import argparse
import re
import shlex
from pathlib import Path
import paramiko
ROOT = Path(__file__).resolve().parents[1]
NAME = re.compile(r"^mini-openclaw-rollback-\d{14}$")
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--apply", action="store_true")
args = parser.parse_args()
values = {}
for raw in (ROOT / "server-deploy.conf").read_text(encoding="utf-8-sig").splitlines():
line = raw.strip()
if line and not line.startswith("#") and "=" in line:
key, value = line.split("=", 1)
values[key.strip()] = value.strip()
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
options = {"hostname": values["SERVER_HOST"], "port": int(values.get("SERVER_PORT", "22")), "username": values["SERVER_USER"], "timeout": 15}
options.update({"key_filename": values["SERVER_KEYFILE"]} if values.get("SERVER_KEYFILE") else {"password": values["SERVER_PASSWORD"]})
client.connect(**options)
_, stdout, _ = client.exec_command("docker ps -a --format '{{.Names}}|{{.State}}'", timeout=30)
lines = stdout.read().decode("utf-8", errors="replace").splitlines()
status = stdout.channel.recv_exit_status()
if status:
client.close()
raise RuntimeError("Unable to list containers")
targets = []
for line in lines:
name, _, state = line.partition("|")
if NAME.fullmatch(name) and state != "running":
targets.append(name)
print("rollback_targets=" + (",".join(targets) if targets else "none"))
if args.apply and targets:
command = "docker rm " + " ".join(shlex.quote(name) for name in targets)
_, stdout, stderr = client.exec_command(command, timeout=60)
output = stdout.read().decode("utf-8", errors="replace")
error = stderr.read().decode("utf-8", errors="replace")
code = stdout.channel.recv_exit_status()
if code:
client.close()
raise RuntimeError(error.strip() or "Unable to remove rollback containers")
print("removed=" + ",".join(output.splitlines()))
client.close()
return 0
if __name__ == "__main__":
raise SystemExit(main())
+237
View File
@@ -0,0 +1,237 @@
"""Deploy the official DeepSeek configuration without exposing credentials."""
from __future__ import annotations
import json
import posixpath
import shlex
import stat
import time
from datetime import datetime
from pathlib import Path
import paramiko
ROOT = Path(__file__).resolve().parents[1]
def load_config(path: Path) -> dict[str, str]:
values: dict[str, str] = {}
for raw in path.read_text(encoding="utf-8-sig").splitlines():
line = raw.strip()
if line and not line.startswith("#") and "=" in line:
key, value = line.split("=", 1)
values[key.strip()] = value.strip()
return values
def connect(config: dict[str, str]) -> paramiko.SSHClient:
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
options = {
"hostname": config["SERVER_HOST"],
"port": int(config.get("SERVER_PORT", "22")),
"username": config["SERVER_USER"],
"timeout": 20,
"banner_timeout": 20,
"auth_timeout": 20,
}
if config.get("SERVER_KEYFILE"):
options["key_filename"] = config["SERVER_KEYFILE"]
else:
options["password"] = config["SERVER_PASSWORD"]
client.connect(**options)
return client
def run(client: paramiko.SSHClient, command: str, timeout: int = 300) -> str:
_, stdout, stderr = client.exec_command(command, timeout=timeout)
output = stdout.read().decode("utf-8", errors="replace")
error = stderr.read().decode("utf-8", errors="replace")
code = stdout.channel.recv_exit_status()
if code:
raise RuntimeError((error or output or f"remote exit {code}").strip())
return output.strip()
def put_text(sftp: paramiko.SFTPClient, path: str, content: str, mode: int = 0o644) -> None:
temporary = path + ".tmp"
with sftp.file(temporary, "w") as handle:
handle.write(content)
handle.flush()
sftp.chmod(temporary, mode)
sftp.posix_rename(temporary, path)
def update_env(content: str, updates: dict[str, str]) -> str:
remaining = dict(updates)
result: list[str] = []
for line in content.splitlines():
if "=" in line and not line.lstrip().startswith("#"):
key = line.split("=", 1)[0].strip()
if key in remaining:
result.append(f"{key}={remaining.pop(key)}")
continue
result.append(line)
result.extend(f"{key}={value}" for key, value in remaining.items())
return "\n".join(result) + "\n"
def main() -> int:
config = load_config(ROOT / "server-deploy.conf")
secret = config.get("DEEPSEEK_API_KEY", "")
if not secret.startswith("sk-") or len(secret) < 20:
raise RuntimeError("DEEPSEEK_API_KEY is missing from server-deploy.conf")
base = config["SERVER_DEPLOY_PATH"].rstrip("/")
port = config.get("PORT", "3200")
stamp = datetime.now().strftime("%Y%m%d%H%M%S")
client = connect(config)
sftp = client.open_sftp()
try:
current_image = run(client, "docker inspect -f '{{.Config.Image}}' mini-openclaw")
if not current_image.startswith("mini-openclaw:"):
raise RuntimeError("Unexpected current image; deployment was not changed")
new_image = f"mini-openclaw:deepseek-official-{stamp}"
rollback_container = f"mini-openclaw-rollback-{stamp}"
local_files = (
"backend/services/assistant_model.py",
"backend/services/project_assistant.py",
"backend/api/risk_agent_config.py",
"backend/test_assistant_components.py",
"backend/test_project_assistant.py",
"backend/test_risk_agent_config.py",
"deploy/docker-compose.aliyun.yml",
"deploy/aliyun.env.example",
"src/components/xingbang/AgentNav.tsx",
"src/components/xingbang/BusinessAssistant.tsx",
"src/app/resetdata/layout.tsx",
"src/app/resetdata/page.tsx",
"src/app/resetdata/resetdata.css",
"scripts/test_resetdata_ui.cjs",
)
for relative in local_files:
remote = posixpath.join(base, relative.replace("\\", "/"))
try:
sftp.stat(remote)
run(client, f"cp -p {shlex.quote(remote)} {shlex.quote(remote + '.pre-deepseek-' + stamp)}")
except FileNotFoundError:
run(client, f"mkdir -p {shlex.quote(posixpath.dirname(remote))}")
sftp.put(str(ROOT / relative), remote)
env_path = posixpath.join(base, ".runtime.env")
with sftp.file(env_path, "r") as handle:
runtime_env = handle.read().decode("utf-8")
original_runtime_env = runtime_env
run(client, f"cp -p {shlex.quote(env_path)} {shlex.quote(env_path + '.pre-deepseek-' + stamp)}")
runtime_env = update_env(
runtime_env,
{
"DEEPSEEK_API_KEY": secret,
"DEEPSEEK_BASE_URL": "https://api.deepseek.com",
"DEEPSEEK_MODEL": "deepseek-chat",
"XINGBANG_LOCAL_MODEL_KEY": secret,
"XINGBANG_LOCAL_MODEL_URL": "https://api.deepseek.com",
"XINGBANG_LOCAL_MODEL_NAME": "deepseek-chat",
},
)
put_text(sftp, env_path, runtime_env, 0o600)
probe = r'''
import os
import sys
import httpx
response = httpx.post(
"https://api.deepseek.com/chat/completions",
headers={"Authorization": "Bearer " + os.environ["DEEPSEEK_API_KEY"]},
json={"model": "deepseek-chat", "messages": [{"role": "user", "content": "Reply with OK."}], "max_tokens": 2, "temperature": 0},
timeout=30,
trust_env=False,
)
print("deepseek_official_status=" + str(response.status_code))
sys.exit(0 if response.status_code == 200 else 1)
'''
encoded_probe = probe.encode("utf-8").hex()
probe_command = (
f"cd {shlex.quote(base)} && docker run --rm --env-file .runtime.env "
f"--entrypoint python {shlex.quote(current_image)} -c "
+ shlex.quote("exec(bytes.fromhex(" + repr(encoded_probe) + ").decode('utf-8'))")
)
print(run(client, probe_command, timeout=90))
dockerfile = (
"FROM node:18-bookworm-slim AS frontend-builder\n"
"WORKDIR /app/frontend\n"
"COPY package*.json ./\n"
"RUN npm ci\n"
"COPY src ./src\n"
"COPY next-env.d.ts next.config.mjs postcss.config.mjs tailwind.config.ts tsconfig.json ./\n"
"RUN mkdir -p public && npm run build\n"
f"FROM {current_image}\n"
"WORKDIR /app\n"
"COPY backend/services/assistant_model.py /app/backend/services/assistant_model.py\n"
"COPY backend/services/project_assistant.py /app/backend/services/project_assistant.py\n"
"COPY backend/api/risk_agent_config.py /app/backend/api/risk_agent_config.py\n"
"RUN rm -rf /app/frontend/.next /app/frontend/public\n"
"COPY --from=frontend-builder /app/frontend/.next /app/frontend/.next\n"
"COPY --from=frontend-builder /app/frontend/public /app/frontend/public\n"
)
dockerfile_path = posixpath.join(base, "Dockerfile.deepseek-official")
put_text(sftp, dockerfile_path, dockerfile)
print(run(client, f"cd {shlex.quote(base)} && docker build -f Dockerfile.deepseek-official -t {shlex.quote(new_image)} .", timeout=600))
state = posixpath.join(base, "state")
mounts = (
("workspace", "/app/backend/workspace"),
("config.json", "/app/backend/config.json"),
("tokenizer-cache", "/tmp/data-gym-cache"),
("xingbang_data", "/app/backend/xingbang_data"),
("logs", "/app/backend/logs"),
("sessions", "/app/backend/sessions"),
("raw_logs", "/app/backend/raw_logs"),
)
mount_args = " ".join(
"-v " + shlex.quote(posixpath.join(state, source) + ":" + target)
for source, target in mounts
)
launch = (
f"cd {shlex.quote(base)} && "
"docker stop mini-openclaw >/dev/null && "
f"docker rename mini-openclaw {shlex.quote(rollback_container)} && "
f"docker run -d --name mini-openclaw --restart unless-stopped --env-file .runtime.env "
f"-p {shlex.quote(port)}:3000 {mount_args} {shlex.quote(new_image)}"
)
run(client, launch, timeout=120)
healthy = False
last = "unknown"
for _ in range(45):
last = run(client, "docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' mini-openclaw")
if last == "healthy":
healthy = True
break
if last in {"unhealthy", "exited", "dead"}:
break
time.sleep(4)
if not healthy:
run(
client,
"docker rm -f mini-openclaw >/dev/null 2>&1 || true; "
f"docker rename {shlex.quote(rollback_container)} mini-openclaw; "
"docker start mini-openclaw >/dev/null",
)
put_text(sftp, env_path, original_runtime_env, 0o600)
raise RuntimeError(f"New container health is {last}; previous container was restored")
put_text(sftp, posixpath.join(base, ".deployed-version"), new_image + "\n")
print(json.dumps({"status": "deployed", "image": new_image, "health": "healthy", "rollback_container": rollback_container}))
return 0
finally:
sftp.close()
client.close()
if __name__ == "__main__":
raise SystemExit(main())
+73
View File
@@ -0,0 +1,73 @@
[CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = "High")]
param(
[string]$BaseUrl = "http://8.163.107.248:3200",
[string]$AccessToken = "",
[switch]$CheckOnly,
[string]$Confirmation = ""
)
$ErrorActionPreference = "Stop"
$apiRoot = $BaseUrl.TrimEnd("/") + "/api/risk-agent/project-assistant"
$headers = @{ Accept = "application/json" }
if ($AccessToken) {
$headers.Authorization = "Bearer $AccessToken"
} else {
$headers["X-Xingbang-Demo-Role"] = "admin"
}
function Get-DemoState {
$response = Invoke-RestMethod -Method Get -Uri "$apiRoot/projects" -Headers $headers -TimeoutSec 20
return @($response.projects | Where-Object { $_.id -in @("DEMO-A", "DEMO-B", "DEMO-C") } | Sort-Object id)
}
function Show-DemoState([string]$Title, $Projects) {
Write-Host ""
Write-Host $Title -ForegroundColor Cyan
$Projects | Select-Object id, customer, stage, workflow_state, revision, data_revision, open_findings, next_action | Format-Table -AutoSize
}
$before = Get-DemoState
if ($before.Count -ne 3) {
throw "Demo projects are incomplete: expected DEMO-A, DEMO-B and DEMO-C; received $($before.Count)."
}
Show-DemoState "Current Alibaba Cloud demo state" $before
if ($CheckOnly) {
Write-Host "Read-only check completed. No data was changed." -ForegroundColor Green
exit 0
}
Write-Warning "This restores DEMO-A, DEMO-B and DEMO-C facts, materials, runs, findings, reports and review decisions to the built-in baseline."
Write-Warning "Formal projects and published cards, skills, rules and templates are not reset. Demo audit and timing events return to the baseline and the prior state is archived."
if (-not $Confirmation) {
$Confirmation = Read-Host "Type RESET-DEMO-A-B-C to continue"
}
if ($Confirmation -ne "RESET-DEMO-A-B-C") {
throw "Confirmation text did not match. No reset was performed."
}
$target = "$apiRoot/demo/reset"
if (-not $PSCmdlet.ShouldProcess($target, "Reset the three demo projects")) {
Write-Host "Cancelled. No data was changed."
exit 0
}
$result = Invoke-RestMethod -Method Post -Uri $target -Headers ($headers + @{ "Content-Type" = "application/json" }) -Body (@{ confirmation = $Confirmation } | ConvertTo-Json -Compress) -TimeoutSec 30
if ($result.status -ne "reset" -or $result.projects -ne 3) {
throw "The server did not return the expected reset response: $($result | ConvertTo-Json -Compress)"
}
$after = Get-DemoState
Show-DemoState "Demo state after reset" $after
$expected = @{
"DEMO-A" = "report_ready"
"DEMO-B" = "supplementing"
"DEMO-C" = "under_review"
}
$unexpected = @($after | Where-Object { $expected[$_.id] -ne $_.workflow_state })
if ($unexpected.Count) {
throw "The reset API succeeded, but the project state does not match the baseline. Stop and investigate before the demo."
}
Write-Host "Reset completed. Refresh the browser. DEMO-B should be supplementing and retain the four demo risk conditions." -ForegroundColor Green
+39
View File
@@ -0,0 +1,39 @@
[CmdletBinding()]
param(
[string]$ConfigPath = (Join-Path (Split-Path -Parent $PSScriptRoot) "server-deploy.conf")
)
$ErrorActionPreference = "Stop"
$secret = Read-Host "DeepSeek official API Key" -AsSecureString
$pointer = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secret)
try {
$plain = [Runtime.InteropServices.Marshal]::PtrToStringBSTR($pointer)
if (-not $plain.StartsWith("sk-") -or $plain.Length -lt 20) {
throw "The value does not look like a DeepSeek API key. Nothing was changed."
}
$lines = if (Test-Path -LiteralPath $ConfigPath) {
@(Get-Content -LiteralPath $ConfigPath)
} else {
@()
}
$replacement = "DEEPSEEK_API_KEY=$plain"
$found = $false
$updated = @($lines | ForEach-Object {
if ($_ -match '^DEEPSEEK_API_KEY=') {
$found = $true
$replacement
} else {
$_
}
})
if (-not $found) {
$updated += $replacement
}
Set-Content -LiteralPath $ConfigPath -Value $updated -Encoding UTF8
Write-Host "DeepSeek key saved to the ignored local deployment config. The key was not printed." -ForegroundColor Green
} finally {
if ($pointer -ne [IntPtr]::Zero) {
[Runtime.InteropServices.Marshal]::ZeroFreeBSTR($pointer)
}
$plain = $null
}
@@ -0,0 +1,115 @@
"""Run a non-persistent Agent Loop test with synthetic business data."""
from __future__ import annotations
import shlex
from pathlib import Path
import paramiko
ROOT = Path(__file__).resolve().parents[1]
def config() -> dict[str, str]:
result: dict[str, str] = {}
for raw in (ROOT / "server-deploy.conf").read_text(encoding="utf-8-sig").splitlines():
line = raw.strip()
if line and not line.startswith("#") and "=" in line:
key, value = line.split("=", 1)
result[key.strip()] = value.strip()
return result
def main() -> int:
settings = config()
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
options = {
"hostname": settings["SERVER_HOST"],
"port": int(settings.get("SERVER_PORT", "22")),
"username": settings["SERVER_USER"],
"timeout": 20,
"banner_timeout": 20,
"auth_timeout": 20,
}
if settings.get("SERVER_KEYFILE"):
options["key_filename"] = settings["SERVER_KEYFILE"]
else:
options["password"] = settings["SERVER_PASSWORD"]
client.connect(**options)
synthetic_test = r'''
import json
from services import assistant_model
model = assistant_model.configuration()
skill = {
"instructions": "对虚构测试项目执行证据约束核查,只验证工具调用链。",
"methods": {
"synthetic-financial-leasing/SKILL.md": "读取数据卡证据,检查负债口径、现金流是否齐备及租赁物覆盖率;不得作审批决定。"
},
"allowed_tools": ["read_project_snapshot", "read_checks", "read_method"],
}
snapshot = {
"id": "snapshot-synthetic-safe",
"project_id": "SAFE-SYNTHETIC-TEST",
"values": {
"financial.total_liabilities": 100,
"due_diligence.liabilities": 90,
"lease.total_value": 120,
"facility.amount": 100,
"cashflow.net_operating": None,
},
"sources": {
"financial.total_liabilities": [{"id": "ev-safe-1", "source": "synthetic_finance", "value": 100}],
"due_diligence.liabilities": [{"id": "ev-safe-2", "source": "synthetic_due_diligence", "value": 90}],
"lease.total_value": [{"id": "ev-safe-3", "source": "synthetic_asset", "value": 120}],
"facility.amount": [{"id": "ev-safe-4", "source": "synthetic_facility", "value": 100}],
"cashflow.net_operating": [{"id": "ev-safe-5", "source": "synthetic_cashflow", "value": None}],
},
"documents": [],
}
checks = [
{"code": "synthetic.liability.consistency", "title": "负债口径差异", "result": "issue", "evidence_ids": ["ev-safe-1", "ev-safe-2"], "fields": ["financial.total_liabilities", "due_diligence.liabilities"], "suggested_action": "核实负债差异"},
{"code": "synthetic.cashflow.missing", "title": "现金流缺失", "result": "unknown", "evidence_ids": ["ev-safe-5"], "fields": ["cashflow.net_operating"], "suggested_action": "补充现金流数据"},
]
rules = [{"id": "rule-synthetic", "name": "虚构测试规则", "version": "1.0.0", "natural_language": "现金流缺失时提示补充,不作审批结论"}]
result, trace = assistant_model.invoke(
skill,
snapshot,
checks,
rules,
"读取全部授权事实、检查与方法,返回可追溯建议。",
[],
model,
{"timeout_seconds": 120, "max_tool_calls": 12},
)
print(json.dumps({
"status": "passed",
"endpoint": model["endpoint"],
"model": trace["model"],
"engine": trace["engine"],
"tool_calls": trace["tool_calls"],
"tools_read": trace["tools_read"],
"finding_count": len(result["findings"]),
"evidence_bound": all(item["evidence_ids"] for item in result["findings"] if item["result"] in ("pass", "issue")),
"requirement_response_present": bool(result["requirement_response"]),
}, ensure_ascii=True))
'''
encoded = synthetic_test.encode("utf-8").hex()
code = "exec(bytes.fromhex(" + repr(encoded) + ").decode('utf-8'))"
command = "docker exec mini-openclaw python -c " + shlex.quote(code)
_, stdout, stderr = client.exec_command(command, timeout=180)
output = stdout.read().decode("utf-8", errors="replace")
error = stderr.read().decode("utf-8", errors="replace")
status = stdout.channel.recv_exit_status()
client.close()
if status:
raise RuntimeError(error.strip() or output.strip() or f"remote exit {status}")
print(output.strip())
return 0
if __name__ == "__main__":
raise SystemExit(main())
+13
View File
@@ -0,0 +1,13 @@
const fs=require("fs"),assert=require("assert");
const page=fs.readFileSync("src/app/resetdata/page.tsx","utf8");
const nav=fs.readFileSync("src/components/xingbang/AgentNav.tsx","utf8");
const layout=fs.readFileSync("src/app/resetdata/layout.tsx","utf8");
assert(page.includes('/demo/reset'),"reset page must call the scoped demo reset API");
assert(page.includes('RESET-DEMO-A-B-C'),"reset confirmation must remain explicit");
assert(page.includes('window.confirm'),"reset must have a human confirmation step");
assert(page.includes('window.alert'),"reset result must be visible without adding page controls");
assert((page.match(/<button/g)||[]).length===1,"reset page must contain exactly one button");
assert(!page.includes('AgentNav'),"reset page must not expose application navigation");
assert(!nav.includes('/resetdata')&&!nav.includes('演示复位'),"navigation must not expose reset page");
assert(layout.includes('index:false')&&layout.includes('follow:false'),"reset page must opt out of search indexing");
console.log("resetdata UI checks passed");
+148
View File
@@ -0,0 +1,148 @@
"""Read-only verification of the Alibaba Cloud demo runtime.
Connection secrets are read from the ignored server-deploy.conf file. Model
credentials are read inside the running container and are never returned.
"""
from __future__ import annotations
import argparse
import json
import shlex
from pathlib import Path
import paramiko
ROOT = Path(__file__).resolve().parents[1]
def load_config(path: Path) -> dict[str, str]:
values: dict[str, str] = {}
for raw in path.read_text(encoding="utf-8-sig").splitlines():
line = raw.strip()
if not line or line.startswith("#") or "=" not in line:
continue
key, value = line.split("=", 1)
values[key.strip()] = value.strip()
return values
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--config", type=Path, default=ROOT / "server-deploy.conf")
args = parser.parse_args()
config = load_config(args.config)
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
connect = {
"hostname": config["SERVER_HOST"],
"port": int(config.get("SERVER_PORT", "22")),
"username": config["SERVER_USER"],
"timeout": 15,
"banner_timeout": 15,
"auth_timeout": 15,
}
if config.get("SERVER_KEYFILE"):
connect["key_filename"] = config["SERVER_KEYFILE"]
else:
connect["password"] = config["SERVER_PASSWORD"]
client.connect(**connect)
container_script = r'''
import json
import os
import httpx
base = os.getenv("DEEPSEEK_BASE_URL", "").rstrip("/")
key = os.getenv("DEEPSEEK_API_KEY", "")
model = os.getenv("DEEPSEEK_MODEL", "")
local_url = os.getenv("XINGBANG_LOCAL_MODEL_URL", "").rstrip("/")
local_key = os.getenv("XINGBANG_LOCAL_MODEL_KEY", "")
local_model = os.getenv("XINGBANG_LOCAL_MODEL_NAME", "")
result = {
"deepseek_base_url": base,
"official_url": base == "https://api.deepseek.com",
"deepseek_model": model,
"deepseek_key_configured": bool(key),
"business_model_url": local_url,
"business_model_name": local_model,
"business_model_key_configured": bool(local_key),
"configured_api_status": None,
"deepseek_official_api_status": None,
}
if base and key:
try:
response = httpx.post(
base + "/chat/completions",
headers={"Authorization": "Bearer " + key},
json={
"model": model or "deepseek-chat",
"messages": [{"role": "user", "content": "Reply with OK."}],
"max_tokens": 2,
"temperature": 0,
},
timeout=30,
trust_env=False,
)
result["configured_api_status"] = response.status_code
except Exception as exc:
result["configured_api_status"] = "connection_error:" + type(exc).__name__
try:
response = httpx.post(
"https://api.deepseek.com/chat/completions",
headers={"Authorization": "Bearer " + key},
json={
"model": "deepseek-chat",
"messages": [{"role": "user", "content": "Reply with OK."}],
"max_tokens": 2,
"temperature": 0,
},
timeout=30,
trust_env=False,
)
result["deepseek_official_api_status"] = response.status_code
except Exception as exc:
result["deepseek_official_api_status"] = "connection_error:" + type(exc).__name__
print(json.dumps(result, ensure_ascii=True))
'''
encoded = container_script.encode("utf-8").hex()
remote_python = (
"import os;exec(bytes.fromhex(" + repr(encoded) + ").decode('utf-8'))"
)
command = (
"set -eu; "
"printf 'container='; docker inspect -f '{{.State.Status}}' mini-openclaw; "
"printf 'health='; docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' mini-openclaw; "
"printf 'image='; docker inspect -f '{{.Config.Image}}' mini-openclaw; "
"printf 'restart_policy='; docker inspect -f '{{.HostConfig.RestartPolicy.Name}}' mini-openclaw; "
"printf 'port_bindings='; docker inspect -f '{{json .HostConfig.PortBindings}}' mini-openclaw; "
"printf 'mounts='; docker inspect -f '{{range .Mounts}}{{if .Name}}{{.Name}}{{else}}{{.Source}}{{end}}:{{.Destination}};{{end}}' mini-openclaw; "
"printf 'networks='; docker inspect -f '{{range $name, $value := .NetworkSettings.Networks}}{{$name}};{{end}}' mini-openclaw; "
"printf 'deploy_path='; test -d " + shlex.quote(config.get("SERVER_DEPLOY_PATH", "")) + " && echo present || echo missing; "
"printf 'compose_files='; docker inspect -f '{{index .Config.Labels \"com.docker.compose.project.config_files\"}}' mini-openclaw 2>/dev/null || true; "
"printf 'working_dir='; docker inspect -f '{{index .Config.Labels \"com.docker.compose.project.working_dir\"}}' mini-openclaw 2>/dev/null || true; "
"cd " + shlex.quote(config.get("SERVER_DEPLOY_PATH", "/")) + "; "
"if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then "
"printf 'git_head='; git rev-parse --short HEAD; "
"printf 'git_branch='; git branch --show-current; "
"printf 'git_dirty_files='; git status --porcelain | wc -l; "
"else echo 'git_head=not-a-repository'; fi; "
"echo 'deploy_files_begin'; find . -maxdepth 2 -type f -printf '%P\\n' | sort | head -80; echo 'deploy_files_end'; "
"docker exec mini-openclaw python -c " + shlex.quote(remote_python)
)
_, stdout, stderr = client.exec_command(command, timeout=60)
output = stdout.read().decode("utf-8", errors="replace")
error = stderr.read().decode("utf-8", errors="replace")
code = stdout.channel.recv_exit_status()
client.close()
if code:
raise RuntimeError(error.strip() or f"Remote verification failed with exit code {code}")
print(output.strip())
return 0
if __name__ == "__main__":
raise SystemExit(main())