fix(code-queue): sync codex auth into runtime home
This commit is contained in:
@@ -154,7 +154,7 @@ Baidu Netdisk 在 UniDesk 语境中按纯后端服务管理:不得暴露百度
|
||||
- 上线纪律:Code Queue 相关的前端或后端改进必须在同一任务内正式上线并验证公网 frontend 或 live API,不能只停留在源码、构建产物或“后续再上线”。修改 Code Queue 自身时不得等待当前 Code Queue task 结束、等待 queue idle 或等待 `0 running` 后才重启;D601 active 实例的正式后端部署入口是 `bun scripts/cli.ts codex deploy <commitId>`,它按已 push 的 remote commit 做 build-first 镜像替换、k3s image import、manifest apply、rollout 和健康验证,并用 v3s adapter、Code Queue live API 或公网 frontend 证明任务和队列仍可读可继续。
|
||||
- 期望状态部署:新的通用入口是 `bun scripts/cli.ts deploy apply --service code-queue`,它从 `deploy.json` 读取 repo 与 commit,再按 `docs/reference/deploy.md` 的 target-side build 规范在 D601 构建、导入 k3s、rollout 并验证 live commit。`codex deploy <commitId>` 是兼容入口,后续实现应复用同一个 reconciler,不得维护第二套部署语义。
|
||||
- 更名与灾备恢复:旧版 Codex 队列服务名只允许作为兼容诊断和一次性迁移来源;`code-queue-backend` 容器自身 `/health` 正常但 `microservice health code-queue` 返回 provider 直连错误时,优先判定为 backend-core 仍加载旧 `MICROSERVICES_JSON` 或 adapter manifest 未刷新,必须刷新 `.state/docker-compose.env`、重建/替换 `backend-core` 与 `v3sctl-adapter`,随后用 `microservice list` 验证 `code-queue` 的 `runtime.orchestrator=v3sctl`、`backend.proxyMode=v3sctl-adapter-http` 和无业务容器直连摘要。
|
||||
- Codex 认证:容器只从 D601 的 `/home/ubuntu/.codex/config.toml` 同步 Codex provider 配置到 D601 `.state/code-queue/codex-home`,并通过 D601 `.state/code-queue-d601.env` 透传 `OPENAI_API_KEY`、`CRS_OAI_KEY` 等 provider 所需变量;这些 provider 环境变量不得写入仓库,必须由 D601 Compose env-file 注入,确保容器重建和重启后不会丢失认证。新增 provider 的 `env_key` 时必须增加同类运行时透传和 Compose env 持久化,禁止把 Codex 或 MiniMax 密钥写入仓库文件。Code Queue 容器必须只读挂载 D601 host 的 SSH 目录到 `/root/.ssh`(默认 `/home/ubuntu/.ssh`),让容器内 `git push`、`ssh -T git@github.com` 与 host 使用同一套 GitHub SSH key/known_hosts;不得把私钥复制进镜像或仓库。
|
||||
- Codex 认证:容器必须从 D601 的 `/home/ubuntu/.codex/config.toml` 同步 Codex provider 配置到 D601 `.state/code-queue/codex-home/config.toml`,并只读挂载 `/home/ubuntu/.codex/auth.json` 到容器 `/root/.codex/auth.json` 后同步到 `.state/code-queue/codex-home/auth.json`,让 `codex app-server` 使用与 host 一致的 provider 登录态;同时通过 D601 `.state/code-queue-d601.env` 或 k8s `code-queue-env` secret 透传 `OPENAI_API_KEY`、`CRS_OAI_KEY` 等 provider 所需变量。这些 provider 环境变量和 auth 文件不得写入仓库,必须由 D601 运行时文件或 k8s secret 注入,确保容器重建和重启后不会丢失认证。新增 provider 的 `env_key` 时必须增加同类运行时透传和 Compose/k8s 持久化,禁止把 Codex 或 MiniMax 密钥写入仓库文件。Code Queue 容器必须只读挂载 D601 host 的 SSH 目录到 `/root/.ssh`(默认 `/home/ubuntu/.ssh`),让容器内 `git push`、`ssh -T git@github.com` 与 host 使用同一套 GitHub SSH key/known_hosts;不得把私钥复制进镜像或仓库。
|
||||
- Develop-ready 镜像:Code Queue 镜像必须在启动前预装 UniDesk/Pipeline 调试所需工具,至少包含 `codex`、`bun`、`node`、`npm`/`npx`、`git`、`rg`、`curl`、`python3`/`pip3`、`docker`、`docker compose`、`docker-compose`、`jq`、`ssh`、`rsync`、`make`、`gcc`/`g++`、`iptables`、`tar`、`gzip` 和 `unzip`;不得依赖 Codex 任务运行时再 `apt-get install` 这些基础环境。
|
||||
- 远程开发容器与任务执行 Provider:Code Queue 必须能通过 live API 拉起 D601 等计算节点上的开发容器,入口为 `POST /api/dev-containers/<providerId>/start`,默认 Provider 为 `D601`。该流程由 Code Queue 调用 UniDesk `ssh <providerId>` 维护桥在目标节点创建 `unidesk-codex-dev-<providerId>`,并在 Code Queue 所在节点与开发容器之间建立 `ssh -w` TUN 点对点链路;服务所在节点负责对开发容器的 TUN 源地址做 NAT/MASQUERADE,开发容器默认路由和 DNS 改走该 TUN,从而让 `ping google.com`、DNS、HTTP(S) 等出网都经主 server 全局代理,而不是依赖 D601 本地网络。提交 Code Queue 任务时必须支持选择执行 Provider:`D601` 在 D601 `code-queue-backend` 容器中本机执行,默认工作目录为 `/workspace`;其他 Provider 在对应 `unidesk-codex-dev-<providerId>` 容器中执行,默认工作目录为 `/home/ubuntu`,可按任务覆盖 `cwd`。远程任务启动前必须自动复用或拉起该 Provider 的开发容器、同步 Codex 配置和允许的运行时 provider 环境变量,并通过同一 master TUN/NAT 链路出网;目标 host 存在 `/mnt` 时,开发容器必须挂载 host `/mnt:/mnt`,确保 D601 这类 WSL 节点的 Windows 盘符路径如 `/mnt/f/Work/ConStart` 在任务容器内可见,避免 agent 因缺少真实工作区而搜索到无关项目。TUN 建立必须幂等处理 stale 状态:启动前清理旧 `tun<id>`、默认路由、旧 tunnel SSH 进程和旧 OUTPUT 跳转,缺失旧设备不能导致失败,冷启动运行时准备要有有界但足够的 timeout。TUN 建立后必须创建 `UD-CQ-EGRESS-<provider>` OUTPUT 链,规则只允许 loopback、既有连接、`tun<id>` 出口以及到 master server 的 SSH tunnel 控制连接,随后 reject 其他 IPv4/IPv6 出站包;这条网络层封口是开发/执行容器的权威外网边界,不能用 `HTTP_PROXY`/`NO_PROXY` 环境变量替代,容器镜像也必须使用已解析出的唯一 `unidesk-code-queue:<provider>` 或显式 `image`,缺失时直接失败,禁止 provider-gateway image、`latest` 或其他隐式镜像 fallback。验收必须保留三类日志:容器建隧道后 `ping google.com` 成功、强制指定原 Docker 网卡直连外网被 `sealed_direct_ping=blocked_expected` 拦截、服务所在节点上对应 `UNIDESK-CODEX-DEV-<providerId>` NAT 链或 `tun<id>` 计数在 ping 前后增长;涉及 WSL 工作区任务时还必须在开发容器内验证目标 `/mnt/...` 路径可读。`GET /api/dev-containers/<providerId>/status` 必须展示默认路由、`route_8_8_8_8`、`egressFirewallChain` 和 OUTPUT 链跳转。开发容器代理密钥只生成到 `.state/code-queue/dev-proxy/` 与目标节点用户目录,不得提交到仓库。
|
||||
- 远程维护桥调用:Code Queue 已迁移到 D601 后,`code-queue-backend` 容器内没有主 server 的 `unidesk-backend-core` 容器,不能再把 `bun scripts/cli.ts ssh ...` 实现为本地 `docker exec unidesk-backend-core`。Code Queue 后端发起的 provider 维护命令必须通过主 server frontend `/api/dispatch` 进入 backend-core,再由目标 provider-gateway 执行 `host.ssh`;需要传递脚本时必须使用 base64 临时文件,超过 Host SSH 单命令长度上限时分块上传到目标 `/tmp` 后再执行,避免恢复到本地 Docker broker、交互 stdin 或手工 shell fallback。
|
||||
|
||||
@@ -77,6 +77,7 @@ services:
|
||||
- ${CODE_QUEUE_WORKSPACE_PATH:-../../../..}:/root/unidesk
|
||||
- ${CODE_QUEUE_WORKSPACE_PATH:-../../../..}:/workspace
|
||||
- ${CODE_QUEUE_CODEX_CONFIG_PATH:-/home/ubuntu/.codex/config.toml}:/root/.codex/config.toml:ro
|
||||
- ${CODE_QUEUE_CODEX_AUTH_PATH:-/home/ubuntu/.codex/auth.json}:/root/.codex/auth.json:ro
|
||||
- ${CODE_QUEUE_SSH_DIR:-/home/ubuntu/.ssh}:/root/.ssh:ro
|
||||
- ${CODE_QUEUE_LOG_DIR:-../../../../.state/code-queue/logs}:/var/log/unidesk
|
||||
- ${CODE_QUEUE_STATE_DIR:-../../../../.state/code-queue}:/var/lib/unidesk/code-queue
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// 重构前 index.ts 只读参考:commit 6a04144d3f5103014f75b637d7e6bc2f45bf007f,blob 56e590c1a6b5ca7ad128bf2c992f60e46c355a58;可用 `git show 6a04144d3f5103014f75b637d7e6bc2f45bf007f:src/components/microservices/code-queue/src/index.ts` 查看。
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import { dirname, resolve } from "node:path";
|
||||
import { Database } from "bun:sqlite";
|
||||
import postgres from "postgres";
|
||||
import { createHourlyJsonlWriter, logRetentionBytesForService } from "../../../shared/src/rotating-jsonl";
|
||||
@@ -1702,6 +1702,12 @@ function prepareCodexHome(): void {
|
||||
} else {
|
||||
logger("warn", "codex_config_source_missing", { sourceCodexConfig: config.sourceCodexConfig });
|
||||
}
|
||||
const sourceCodexAuth = resolve(dirname(config.sourceCodexConfig), "auth.json");
|
||||
if (existsSync(sourceCodexAuth)) {
|
||||
copyFileSync(sourceCodexAuth, resolve(config.codexHome, "auth.json"));
|
||||
} else {
|
||||
logger("warn", "codex_auth_source_missing", { sourceCodexAuth });
|
||||
}
|
||||
}
|
||||
|
||||
function openCodeXdgEnv(root = config.opencodeXdgDir): Record<string, string> {
|
||||
@@ -1769,7 +1775,10 @@ function collectDevReady(): JsonValue {
|
||||
const composeProbe = runProbe("docker", ["compose", "version"]);
|
||||
const workdirExists = existsSync(config.defaultWorkdir);
|
||||
const dockerSocketExists = existsSync("/var/run/docker.sock");
|
||||
const codexConfigReady = existsSync(config.sourceCodexConfig) || existsSync(resolve(config.codexHome, "config.toml"));
|
||||
const homeCodexConfig = resolve(config.codexHome, "config.toml");
|
||||
const sourceCodexAuth = resolve(dirname(config.sourceCodexConfig), "auth.json");
|
||||
const homeCodexAuth = resolve(config.codexHome, "auth.json");
|
||||
const codexConfigReady = existsSync(config.sourceCodexConfig) || existsSync(homeCodexConfig);
|
||||
const sshKeyProbe = runProbe("sh", ["-lc", "test -d /root/.ssh && find /root/.ssh -maxdepth 1 -type f \\( -name 'id_*' ! -name '*.pub' \\) -perm -400 -print -quit"]);
|
||||
const githubKnownHostProbe = runProbe("ssh-keygen", ["-F", "github.com", "-f", "/root/.ssh/known_hosts"]);
|
||||
const sshSharedReady = existsSync("/root/.ssh") && sshKeyProbe.ok && sshKeyProbe.output.trim().length > 0;
|
||||
@@ -1790,8 +1799,12 @@ function collectDevReady(): JsonValue {
|
||||
codexConfig: {
|
||||
sourcePath: config.sourceCodexConfig,
|
||||
sourceExists: existsSync(config.sourceCodexConfig),
|
||||
homeConfigPath: resolve(config.codexHome, "config.toml"),
|
||||
homeConfigExists: existsSync(resolve(config.codexHome, "config.toml")),
|
||||
homeConfigPath: homeCodexConfig,
|
||||
homeConfigExists: existsSync(homeCodexConfig),
|
||||
sourceAuthPath: sourceCodexAuth,
|
||||
sourceAuthExists: existsSync(sourceCodexAuth),
|
||||
homeAuthPath: homeCodexAuth,
|
||||
homeAuthExists: existsSync(homeCodexAuth),
|
||||
ready: codexConfigReady,
|
||||
},
|
||||
ssh: {
|
||||
|
||||
@@ -393,6 +393,9 @@ spec:
|
||||
- name: codex-config
|
||||
mountPath: /root/.codex/config.toml
|
||||
readOnly: true
|
||||
- name: codex-auth
|
||||
mountPath: /root/.codex/auth.json
|
||||
readOnly: true
|
||||
- name: ssh-dir
|
||||
mountPath: /root/.ssh
|
||||
readOnly: true
|
||||
@@ -440,6 +443,10 @@ spec:
|
||||
hostPath:
|
||||
path: /home/ubuntu/.codex/config.toml
|
||||
type: File
|
||||
- name: codex-auth
|
||||
hostPath:
|
||||
path: /home/ubuntu/.codex/auth.json
|
||||
type: File
|
||||
- name: ssh-dir
|
||||
hostPath:
|
||||
path: /home/ubuntu/.ssh
|
||||
@@ -619,6 +626,9 @@ spec:
|
||||
- name: codex-config
|
||||
mountPath: /root/.codex/config.toml
|
||||
readOnly: true
|
||||
- name: codex-auth
|
||||
mountPath: /root/.codex/auth.json
|
||||
readOnly: true
|
||||
- name: ssh-dir
|
||||
mountPath: /root/.ssh
|
||||
readOnly: true
|
||||
@@ -666,6 +676,10 @@ spec:
|
||||
hostPath:
|
||||
path: /home/ubuntu/.codex/config.toml
|
||||
type: File
|
||||
- name: codex-auth
|
||||
hostPath:
|
||||
path: /home/ubuntu/.codex/auth.json
|
||||
type: File
|
||||
- name: ssh-dir
|
||||
hostPath:
|
||||
path: /home/ubuntu/.ssh
|
||||
|
||||
Reference in New Issue
Block a user