From 54c32be901aa428769fd5107a64677e3b27576f6 Mon Sep 17 00:00:00 2001 From: Codex Date: Mon, 18 May 2026 03:45:29 +0000 Subject: [PATCH] fix: run cli core probes via frontend container --- docs/reference/cli.md | 4 ++-- scripts/src/debug.ts | 12 ++++++------ scripts/src/microservices.ts | 4 ++-- scripts/src/ssh.ts | 6 ++++-- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/docs/reference/cli.md b/docs/reference/cli.md index a39bc6a1..36c45d9b 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -58,11 +58,11 @@ UniDesk 的统一 CLI 入口是根目录 `scripts/cli.ts`,运行方式固定 ## Debug Contract -`debug` 子命令必须复用真实模块与真实端点,禁止维护平行实现。`debug health` 会摘要展示 `/api/nodes/system-status` 和 `/api/nodes/docker-status`,避免输出完整快照造成信息爆炸。`debug dispatch` 会在 backend-core 容器内调用内部 `/api/dispatch`,core 再通过 WebSocket 将 `docker.ps`、`provider.upgrade`、`host.ssh`、`microservice.http` 或 `echo` 任务下发给 provider gateway,因此它可以验证核心调度闭环,同时不需要公开 core REST API。`provider.upgrade` 默认使用 `mode: "plan"` 预检;需要验证一键升级时必须显式加 `--mode schedule`,并通过 `--wait-ms` 或 `debug task` 确认任务进入 `succeeded`、result 中包含 updater 容器信息和 `policy: "always-enabled"`。`host.ssh` 默认使用 `mode: "probe"` 做短超时维护桥自检;需要执行明确命令时使用 `--ssh-command` 进入 `mode: "exec"`,并配合 `--wait-ms` 和 `debug task` 查看 stdout、stderr、exitCode 与 probeLine。`microservice.http` 只用于开发调试 provider-gateway 私有 HTTP 代理,正式用户入口应使用 `microservice` CLI 或 frontend 的用户服务页面。 +`debug` 子命令必须复用真实模块与真实端点,禁止维护平行实现。`debug health` 会摘要展示 `/api/nodes/system-status` 和 `/api/nodes/docker-status`,避免输出完整快照造成信息爆炸。`debug dispatch` 会通过 frontend 容器的 Docker 内网访问 backend-core `/api/dispatch`,core 再通过 WebSocket 将 `docker.ps`、`provider.upgrade`、`host.ssh`、`microservice.http` 或 `echo` 任务下发给 provider gateway,因此它可以验证核心调度闭环,同时不需要公开 core REST API,也不要求 backend-core 运行镜像携带 Bun CLI。`provider.upgrade` 默认使用 `mode: "plan"` 预检;需要验证一键升级时必须显式加 `--mode schedule`,并通过 `--wait-ms` 或 `debug task` 确认任务进入 `succeeded`、result 中包含 updater 容器信息和 `policy: "always-enabled"`。`host.ssh` 默认使用 `mode: "probe"` 做短超时维护桥自检;需要执行明确命令时使用 `--ssh-command` 进入 `mode: "exec"`,并配合 `--wait-ms` 和 `debug task` 查看 stdout、stderr、exitCode 与 probeLine。`microservice.http` 只用于开发调试 provider-gateway 私有 HTTP 代理,正式用户入口应使用 `microservice` CLI 或 frontend 的用户服务页面。 ## SSH Command -`ssh [ssh-like args...]` 是面向人的终端透传入口,不包装 JSON 输出。CLI 会在宿主机启动一个 `docker exec -i unidesk-backend-core bun -e ...` broker,broker 只连接 backend-core 的 Docker 内网 `/ws/ssh`,core 再把 stdin/stdout/stderr 流量通过目标 provider 的既有 WebSocket 转发到 provider-gateway,provider-gateway 最终执行维护用 SSH 连接宿主或 WSL sshd。TTY 策略固定为交互登录 shell 使用 `ssh -tt`,带远端命令的会话使用 `ssh -T`;脚本 stdin、`apply-patch` 和 `py` 这类命令模式不得被伪终端回显或注入控制字符。该入口不新增 core 公网端口,不暴露 database,也不改变 frontend/provider ingress 之外的公网边界。 +`ssh [ssh-like args...]` 是面向人的终端透传入口,不包装 JSON 输出。CLI 会在宿主机启动一个 `docker exec -i unidesk-frontend bun -e ...` broker,broker 只连接 backend-core 的 Docker 内网 `/ws/ssh`,core 再把 stdin/stdout/stderr 流量通过目标 provider 的既有 WebSocket 转发到 provider-gateway,provider-gateway 最终执行维护用 SSH 连接宿主或 WSL sshd。TTY 策略固定为交互登录 shell 使用 `ssh -tt`,带远端命令的会话使用 `ssh -T`;脚本 stdin、`apply-patch` 和 `py` 这类命令模式不得被伪终端回显或注入控制字符。该入口不新增 core 公网端口,不暴露 database,也不改变 frontend/provider ingress 之外的公网边界。 `bun scripts/cli.ts ssh D518` 应表现为登录 D518 WSL 的 shell;`bun scripts/cli.ts ssh D518 hostname` 应像 `ssh D518 hostname` 一样只输出远端命令结果并返回远端 exit code。Provider ID 前的目标选择由 UniDesk 节点清单决定,`-p`、`-i`、`-l`、`-o` 等传统 ssh 传输参数由 provider-gateway 部署配置统一管理,CLI 会兼容性消费这些参数但不会覆盖节点侧维护桥配置。 diff --git a/scripts/src/debug.ts b/scripts/src/debug.ts index c1627994..d52526b5 100644 --- a/scripts/src/debug.ts +++ b/scripts/src/debug.ts @@ -24,7 +24,7 @@ async function readJson(url: string, init?: RequestInit): Promise { function coreInternalFetch(path: string, init?: { method?: string; body?: unknown }): unknown { const code = ` - const res = await fetch(${JSON.stringify(`http://127.0.0.1:8080${path}`)}, ${JSON.stringify({ + const res = await fetch(${JSON.stringify(`http://backend-core:8080${path}`)}, ${JSON.stringify({ method: init?.method ?? "GET", headers: init?.body === undefined ? undefined : { "content-type": "application/json" }, body: init?.body === undefined ? undefined : JSON.stringify(init.body), @@ -34,7 +34,7 @@ function coreInternalFetch(path: string, init?: { method?: string; body?: unknow try { body = text ? JSON.parse(text) : null; } catch { body = { text }; } console.log(JSON.stringify({ ok: res.ok, status: res.status, body })); `; - const result = runCommand(["docker", "exec", "unidesk-backend-core", "bun", "-e", code], repoRoot); + const result = runCommand(["docker", "exec", "unidesk-frontend", "bun", "-e", code], repoRoot); if (result.exitCode !== 0) { return { ok: false, exitCode: result.exitCode, stdoutTail: result.stdout.slice(-1200), stderrTail: result.stderr.slice(-1200) }; } @@ -47,7 +47,7 @@ function coreInternalFetch(path: string, init?: { method?: string; body?: unknow function coreDockerStatusSummary(): unknown { const code = ` - const res = await fetch('http://127.0.0.1:8080/api/nodes/docker-status'); + const res = await fetch('http://backend-core:8080/api/nodes/docker-status'); const text = await res.text(); let body = null; try { body = text ? JSON.parse(text) : null; } catch { body = { text }; } @@ -77,7 +77,7 @@ function coreDockerStatusSummary(): unknown { }); console.log(JSON.stringify({ ok: res.ok, status: res.status, body: { ok: body?.ok === true, dockerStatuses } })); `; - const result = runCommand(["docker", "exec", "unidesk-backend-core", "bun", "-e", code], repoRoot); + const result = runCommand(["docker", "exec", "unidesk-frontend", "bun", "-e", code], repoRoot); if (result.exitCode !== 0) { return { ok: false, exitCode: result.exitCode, stdoutTail: result.stdout.slice(-1200), stderrTail: result.stderr.slice(-1200) }; } @@ -90,7 +90,7 @@ function coreDockerStatusSummary(): unknown { function coreSystemStatusSummary(): unknown { const code = ` - const res = await fetch('http://127.0.0.1:8080/api/nodes/system-status?limit=24'); + const res = await fetch('http://backend-core:8080/api/nodes/system-status?limit=24'); const text = await res.text(); let body = null; try { body = text ? JSON.parse(text) : null; } catch { body = { text }; } @@ -114,7 +114,7 @@ function coreSystemStatusSummary(): unknown { }); console.log(JSON.stringify({ ok: res.ok, status: res.status, body: { ok: body?.ok === true, systemStatuses } })); `; - const result = runCommand(["docker", "exec", "unidesk-backend-core", "bun", "-e", code], repoRoot); + const result = runCommand(["docker", "exec", "unidesk-frontend", "bun", "-e", code], repoRoot); if (result.exitCode !== 0) { return { ok: false, exitCode: result.exitCode, stdoutTail: result.stdout.slice(-1200), stderrTail: result.stderr.slice(-1200) }; } diff --git a/scripts/src/microservices.ts b/scripts/src/microservices.ts index b70155df..1cd28cfd 100644 --- a/scripts/src/microservices.ts +++ b/scripts/src/microservices.ts @@ -7,7 +7,7 @@ export function coreInternalFetch(path: string, init?: { method?: string; body?: if (!path.startsWith("/")) throw new Error("core internal path must start with /"); const maxResponseBytes = Math.max(1024, Math.floor(init?.maxResponseBytes ?? 5_000_000)); const code = ` - const res = await fetch(${JSON.stringify(`http://127.0.0.1:8080${path}`)}, ${JSON.stringify({ + const res = await fetch(${JSON.stringify(`http://backend-core:8080${path}`)}, ${JSON.stringify({ method: init?.method ?? "GET", headers: init?.body === undefined ? undefined : { "content-type": "application/json" }, body: init?.body === undefined ? undefined : JSON.stringify(init.body), @@ -49,7 +49,7 @@ export function coreInternalFetch(path: string, init?: { method?: string; body?: } console.log(JSON.stringify({ ok: res.ok, status: res.status, responseTruncated, responseBytesRead: bytes, responseContentLength: res.headers.get("content-length"), body })); `; - const result = runCommand(["docker", "exec", "unidesk-backend-core", "bun", "-e", code], repoRoot); + const result = runCommand(["docker", "exec", "unidesk-frontend", "bun", "-e", code], repoRoot); if (result.exitCode !== 0) { return { ok: false, exitCode: result.exitCode, stdoutTail: result.stdout.slice(-1200), stderrTail: result.stderr.slice(-1200) }; } diff --git a/scripts/src/ssh.ts b/scripts/src/ssh.ts index e5a9a68e..544ab2ef 100644 --- a/scripts/src/ssh.ts +++ b/scripts/src/ssh.ts @@ -662,7 +662,7 @@ function brokerSource(): string { return String.raw` const open = JSON.parse(process.argv[2] || process.argv[1] || "{}"); const token = process.env.PROVIDER_TOKEN || ""; -const url = "ws://127.0.0.1:8080/ws/ssh?token=" + encodeURIComponent(token); +const url = "ws://backend-core:8080/ws/ssh?token=" + encodeURIComponent(token); const ws = new WebSocket(url); let exitCode = 255; let canSend = false; @@ -805,7 +805,9 @@ export async function runSsh(config: UniDeskConfig, providerId: string, args: st const child = spawn("docker", [ "exec", "-i", - "unidesk-backend-core", + "-e", + `PROVIDER_TOKEN=${config.providerGateway.token}`, + "unidesk-frontend", "bun", "-e", brokerSource(),