Files
pikasTech-unidesk/docs/reference/master-server-ops.md
T
2026-07-11 11:32:44 +02:00

412 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Master Server 运维
本文记录 Master Server 的架构与判定规则。已安装的 Codex wrapper 负责命令语法,
本文负责 profile 边界、安全规则和验收标准。通用 server 生命周期与 GC 命令仍由
`unidesk-ops` skill 负责。禁止在本文记录 Secret、一次性故障流水或带日期的
changelog。
## 执行边界
- Master Server 是生产入口和控制面。
- 本机默认只执行轻量源码修改、Git 操作、状态检查、健康检查、日志、诊断、
JSON CLI 控制动作和本地 profile wrapper 维护。
- 重型构建和仓库级检查必须放到 G14、D601、CI/CD 或目标运行面。
- 本机 `/root/unidesk` checkout 可用于轻量 UniDesk CLI、`trans``tran`
和 helper 修改。
## Codex Provider Profile
- `mycx``oncx``/usr/local/bin/` 下的稳定系统 Codex wrapper。
- 两者复用同一个系统 `codex` binary。
- 两者的认证 home 已隔离,长期约束见“系统 Wrapper 与共享状态”。
- `dscx``mxcx``acx``/root/.local/bin/` 下的隔离 profile
wrapper。
- 它们是 host 级工具,不属于 UniDesk service 代码。
- `acx` 是统一多模型入口。
- `gocx` 保留为 OpenCode Zen Go 兼容入口。
- 旧的 `dscx-go``dfcx-go``glcx-go` 只保留兼容入口。
- `dscx`
- 使用 `CODEX_HOME=/root/.codex-deepseek-v4-pro`
- 使用模型 `deepseek-v4-pro` 和 Codex 自定义 provider `deepseek`
- 使用本机 Moon Bridge `http://127.0.0.1:38440/v1`
- `mxcx`
- 使用 `CODEX_HOME=/root/.codex-minimax-m3`
- 使用模型 `MiniMax-M3` 和 Codex 自定义 provider `minimax`
- 使用本机 Moon Bridge `http://127.0.0.1:38441/v1`
- `acx`
- 使用 `CODEX_HOME=/root/.codex-acx`
- 默认模型是 `gpt-5.5-only`
- GPT alias 通过 Codex 自定义 provider 直连 OpenAI-compatible Responses 上游;
- OpenCode Zen Go alias 仍通过本机 ACX router 到达 `gocx` / Moon Bridge。
- `gocx`
- 使用 `CODEX_HOME=/root/.codex-opencode-go-all`
- 默认模型是 `glm-5.1`
- 使用 Codex 自定义 provider `opencode`
- 使用本机 Moon Bridge `http://127.0.0.1:38447/v1`
- `dscx-go`
- 使用 `CODEX_HOME=/root/.codex-opencode-go`
- 使用模型 `deepseek-v4-pro`
- 使用本机 Moon Bridge `http://127.0.0.1:38443/v1`
- `dfcx-go`
- 使用 `CODEX_HOME=/root/.codex-opencode-flash`
- 使用模型 `deepseek-v4-flash`
- 使用本机 Moon Bridge `http://127.0.0.1:38444/v1`
- `glcx-go`
- 使用 `CODEX_HOME=/root/.codex-opencode-glm`
- 使用模型 `glm-5.1`
- 使用本机 Moon Bridge `http://127.0.0.1:38446/v1`
- `acx``model-catalog.json`
- 包含全部 OpenCode Zen Go upstream slug
- 包含 `gpt-5.5-only``gpt-5.5-sub2api`
- 允许在同一 profile 中通过 `/model``-m <model>` 切换。
- `acx` 的 GPT 路由:
- `gpt-5.5-only` 直连 `only` OpenAI-compatible Responses endpoint
- `gpt-5.5-sub2api` 直连 Sub2API pool endpoint
- 两者的 upstream model 都是 `gpt-5.5`
- GPT 请求禁止经过 Moon Bridge 或 ACX router。
- GPT direct provider
- 必须通过 `ACX_GPT_DIRECT_API_KEY` 一类环境键接收 API key
- wrapper 从对应的 `/root/.codex/auth.json.<profile>` 读取;
- 禁止回退到 `/root/.codex-acx/auth.json`
- `/root/.codex-acx/auth.json` 只允许保存本地 router dummy key。
- 隔离的 Moon Bridge 与 router wrapper
- 从 profile auth 文件读取 upstream API key
- 仅在兼容路径确有需要时由 wrapper 注入环境变量;
- 生成配置放在 profile 的 `.tmp/`,权限必须是 `0600`
- 此兼容规则不适用于 `mycx` 的 Pika command-backed auth。
- 每个 profile
- 必须在 `config.toml` 中设置 `model_catalog_json`
- 必须指向包含当前模型的 profile-local `model-catalog.json`
- 缺失时 Codex 会回退到默认 metadata,并输出
`Model metadata ... not found`
- Profile context metadata
- 必须与真实 upstream limit 对齐;
- 必须让 Codex auto-compact 在 provider 拒绝请求前触发。
- 当前 Master Server profile baseline
- `acx` 暴露的 GPT profile 使用
`model_context_window = 272000`
`model_auto_compact_token_limit = 240000`
- `deepseek-v4-pro``deepseek-v4-flash` 使用
`model_context_window = 1000000`
`model_auto_compact_token_limit = 900000`
- `glm-5.1``MiniMax-M3` 及其他本地 Moon Bridge profile 使用
`model_context_window = 200000`
`model_auto_compact_token_limit = 180000`
- Wrapper 生成的 Moon Bridge / router metadata
- 必须与 profile `config.toml``model-catalog.json` 对齐;
- 不一致时 Codex 与本地 admission layer 会对 compact 和请求大小产生分歧。
- Codex API 通道的 `NO_PROXY` / `no_proxy`
- 必须保留 `hyueapi.com``.hyueapi.com`
### 系统 Wrapper 与共享状态(`mycx` / `oncx`
- `mycx``oncx`
- 复用同一个系统 `codex` binary
- 使用不同的认证 `CODEX_HOME`
- 共享 SQLite session index 和 durable transcript。
- `/usr/local/bin/mycx`
- 使用 `CODEX_HOME=/root/.codex`
- 选择 profile `pika`
- 可写 profile 文件是 `/root/.codex/pika.config.toml`
- `/usr/local/bin/oncx`
- 使用 `CODEX_HOME=/root/.codex-only`
- 使用 `CODEX_SQLITE_HOME=/root/.codex`
- 选择 profile `only`
- 可写 profile 文件是 `/root/.codex-only/only.config.toml`
- `oncx` 的账号认证:
- 只使用 `/root/.codex-only/auth.json`
- `cli_auth_credentials_store` 必须是 `file`
- `forced_login_method` 必须是 `chatgpt`
- `auth.json` 权限必须是 `0600`
- wrapper 必须清理 `OPENAI_API_KEY``CODEX_API_KEY`
`CODEX_ACCESS_TOKEN``CRS_OAI_KEY`
- `oncx login``oncx login status``oncx logout` 必须直接操作
隔离 home,禁止附加仅适用于 runtime command 的 `--profile`
- `oncx` 的共享状态:
- `/root/.codex-only/sessions` 链接到 `/root/.codex/sessions`
- `/root/.codex-only/archived_sessions` 链接到
`/root/.codex/archived_sessions`
- `history.jsonl``skills``plugins` 链接到共享 home
- shell snapshot、认证文件和 profile 配置保持隔离。
- `/root/.codex/config.toml``/root/.codex/auth.json`
-`codex-pool configure-local` 管理;
- 只代表 Sub2API consumer
- 禁止作为 `oncx` 的 ChatGPT 登录凭据。
- Webterm
- `config/platform-infra/webterm.yaml` 启动 `mycx`
- YAML 是 target、镜像、端口、启动命令、fallback shell、挂载和公网暴露的唯一真相;
- 关闭公网暴露:
- `publicExposure.enabled: false` 时,DNS、FRP 和 Caddy 字段不参与解析与验证;
- `plan``status``validate` 必须明确返回 `skipped`
- target 校验:
- 按 YAML 中的实际端口和 `TERMINAL_SHELL` 只读挂载判断;
- 禁止把 `7683`、固定挂载数量或其他实例值写入代码门禁。
- PTY 所有权:
- `nsenter``chroot` 后必须由唯一的长期交互 Bash 持有 PTY;
- 禁止让外层非交互 Bash 等待内层交互 Bash,再在内层退出后创建 fallback shell。
- 启动命令中断:
- 持有 PTY 的 Bash 必须在启动命令运行期间显式处理 `SIGINT`
- 前台子命令退出后继续执行 fallback,再恢复默认 handler
- fallback shell 必须由原进程 `exec`,禁止创建新的等待层。
- Webterm launch check
- wrapper path 或启动方式变更必须保留原入口;
- 从真实 Web/xterm 入口启动 `mycx`、发送 `Ctrl+C` 并执行 marker
- 确认会话仍为 `running`
- 确认没有 `initialize_job_control``no job control` 或非主动 `logout`
- foreground process group 验证:
- 使用 Bash 内建 `read < /proc/$$/stat` 同时读取 `pgrp``tpgid`
- 禁止用外部 `ps` 读取后直接比较;
- `ps` 执行期间会暂时成为 foreground process group,导致错误的不相等结果。
- Shell startup 文件:
- 禁止定义同名 function 覆盖这些 executable
- 禁止在启动时重新生成 profile 文件;
- wrapper 变更后,旧 Codex 进程必须退出;
- 旧 shell 必须清除残留 function 或启动新 shell后再验收。
- Profile-v2 持久化:
- `/model``model` 与 reasoning effort 写入当前 profile 文件;
- `mycx``oncx` 必须独立记住最后一次选择;
- `-m``-c model...``-c model_reasoning_effort...` 只允许临时覆盖;
- 新启动、profile 切换和 resume 必须分别诊断。
- Codex 官方认证边界:
- `--profile` 只覆盖 profile TOML,不隔离 `auth.json`
- file-based credential storage 的 `auth.json``CODEX_HOME` 决定;
- 账号登录与 API-key consumer 必须使用不同的 `CODEX_HOME`
- 依据见
[Codex Manual](https://developers.openai.com/codex/codex-manual.md#configuration-auth-and-models)。
- Session discovery
- 共享 SQLite 和 transcript 不保证 `/resume` 结果完全相同;
- picker 还会按 provider ID、cwd、source 和 archive state 过滤;
- provider ID 是持久化且大小写敏感的 session bucket key
- `--all` 只移除 cwd 限制,不移除 provider 限制;
- Codex 升级后必须重新验证此契约;
- resume 使用当前 profile 的有效 model、provider 和 reasoning override
- resume 不保证恢复 session 最后记录的模型。
- Rollout JSONL
- 是 durable history
- 可以重新填充 SQLite index
- 禁止只修改 `state_5.sqlite` 合并 provider bucket
- history migration 必须停止活动 Codex 进程;
- 必须用结构化 JSON parser 修改 rollout metadata
- 必须事务化更新 SQLite index
- 必须保留 provenance 并在 reindex 后验证相同 session ID 集合。
- Provider identity
- 是兼容性契约;
- 修改 `CODEX_HOME`、profile name、provider ID 或大小写前必须预检历史;
- 会产生新隐藏 bucket 时必须告警或阻断;
- 禁止让不同真实 backend 仅为合并 picker list 而冒充同一 provider。
- Pika 认证:
- 必须使用 `[model_providers.Pika.auth]` 的 command-backed provider auth
- credential source 是 `/root/.codex/auth.json.pika`
- 文件权限必须是 `0600`
- 禁止把 Pika API key 导出到 wrapper 或 Codex 环境;
- shell snapshot 权限必须是 `0600`
- shell snapshot 禁止包含可复用 credential
- 发现 plaintext snapshot 时必须删除副本并轮换 credential。
- Secret-safe 诊断只允许报告:
- 有效 `CODEX_HOME`
- wrapper path
- 当前 profile 文件;
- provider ID
- model 与 reasoning
- 按 provider 统计的 session 数量;
- credential presence 或 fingerprint
- 相关文件权限。
- Profile 持久化验收:
- 选择一个非 baseline `/model`
- 退出后重新启动相同 wrapper;
- 确认选择保持;
- 确认另一 profile 文件未变化;
- 确认临时 CLI override 不写回 profile。
- `oncx` 认证验收:
- `oncx login status` 必须显示 `Logged in using ChatGPT`
- 注入无效 API-key 环境变量后仍必须显示 ChatGPT 登录;
- `oncx exec --ephemeral '<smoke prompt>'` 必须完成真实 Responses turn
- 共享 `codex login status` 必须继续显示 Sub2API API-key 登录;
- 全程禁止输出 token 或完整 API key。
- `mycx` 认证验收:
- 对应 API-key 环境变量缺失时,Pika command-backed auth 仍必须可用。
- `/resume` 差异诊断:
- 先比较 `CODEX_HOME`、provider ID、cwd、source 和 archive filter
- 禁止在这些边界未核对前归因为 storage loss 或 cache。
相关调查证据与源码级依据保留在
[UniDesk issue #1640](https://github.com/pikasTech/unidesk/issues/1640),本文是当前长期规则的
唯一权威入口。
## Moon Bridge
Moon Bridge 安装在 `/root/.local/bin/moonbridge`。该 binary 暴露 OpenAI Responses
endpoint,并把 Codex 请求桥接到 provider-specific upstream API。已安装的 profile
wrapper 负责 bridge 命令语法,本文只记录运行边界。
- 已安装 patch build 的本地源码:
- 路径是 `/root/src/moon-bridge-sanitize`
- 修改时必须保持范围收敛;
- 必须运行 package-level test
- 必须构建 replacement binary
- 必须备份旧 binary
- 必须重启受影响的 profile bridge
- 仍受 Master Server 重型构建限制。
### Profile 架构
- `dscx bridge-start`
- 渲染 profile 配置;
-`127.0.0.1:38440` 启动 Moon Bridge。
- `mxcx bridge-start`
- 渲染 profile 配置;
-`127.0.0.1:38441` 启动 Moon Bridge。
- `acx route-start`
- 渲染 ACX router 配置;
-`127.0.0.1:38448` 启动本地 routing service
- 只服务仍需 OpenCode Zen Go bridge path 的非 GPT alias。
- `gocx bridge-start`
- 渲染多模型 OpenCode Zen Go profile 配置;
-`127.0.0.1:38447` 启动 Moon Bridge。
- `dscx-go bridge-start`
-`127.0.0.1:38443` 启动 Moon Bridge。
- `dfcx-go bridge-start`
-`127.0.0.1:38444` 启动 Moon Bridge。
- `glcx-go bridge-start`
-`127.0.0.1:38446` 启动 Moon Bridge。
- Wrapper 启动 bridge
- 使用 `setsid`
- 使用 profile-local PID 文件;
- 日志写入 `<CODEX_HOME>/logs/moonbridge/`
- `dscx` 路由:
- 通过 Moon Bridge 使用 Anthropic-compatible upstream
- 使用 `deepseek_v4` extension。
- `mxcx` 路由:
- 通过 Moon Bridge 使用 `openai-response` upstream passthrough。
- OpenCode Zen Go 路由:
- `acx` 把模型路由到 `gocx` / Moon Bridge
- `gocx``dscx-go``dfcx-go``glcx-go` 使用
`openai-chat` upstream
- upstream 是 `https://opencode.ai/zen/go/v1/chat/completions`
- Codex 侧保持 `wire_api = "responses"`
- `acx` GPT 路由:
- 必须直连 OpenAI-compatible Responses endpoint
- 禁止经过 Moon Bridge 或本地 ACX router
- GPT mode 下 `acx status` 必须报告 `mode=gpt-direct`
- 必须报告 `routerRequired=false`
- `127.0.0.1:38448` 不应存在 listener。
- OpenCode Zen Go model ID
- 必须使用 upstream slug,例如 `glm-5.1`
- `GLM-5.1` 一类 display name 不是 profile model identifier。
- OpenCode Zen Go bridge
- 禁止保留手写 bridge script
- 禁止保留静态替代 `moonbridge.config.yml`
- 唯一支持路径是 wrapper 生成的 `.tmp/moonbridge.generated.yml`
`/root/.local/bin/moonbridge`
- 生成的 provider 配置必须显式设置 `user_agent`
- Moon Bridge context-window admission
- oversized request 必须在本地 Responses path 被拒绝;
- 预期状态是 HTTP `400`
- 错误类型是 `invalid_request_error`
- 错误 code 是 `context_length_exceeded`
- 禁止表现为 upstream-translated `502 Bad Gateway`
- MiniMax
- 未经 tool-enabled smoke 证明可用,禁止把 Codex CLI 切到
`openai-chat`
### Profile 验收
- `*-go raw-smoke`
- 直接验证 upstream OpenCode Zen Go Chat Completions API。
- `*-go bridge-smoke`
- 验证本地 Moon Bridge 的 `/v1/responses` translation path。
- `*-go exec '在吗'`
- 验证真实 Codex profile
- 输出禁止包含 `Model metadata ... not found`
- 最新 session 必须使用 profile catalog 提供的
`model_context_window`
- ACX GPT 验收:
- 使用 `acx status``acx models`
`acx gpt-only exec '在吗'``acx gpt-sub2api exec '在吗'`
默认 `acx exec '在吗'`
- 必须完成真实 Codex Responses turn
- repeated 或 resume traffic 必须出现非零 `cached_input_tokens`
- 禁止依赖 `127.0.0.1:38448` listener。
- ACX OpenCode Zen Go alias 验收:
- 使用 `acx route-start``acx route-status``acx models`
`acx -m <opencode-model> exec '在吗'`
- 验证 router 到 `gocx` 的路径。
- `gocx` 模型验收:
- 使用 `gocx raw-smoke [model]`
- 使用 `gocx bridge-smoke [model]`
- 使用 `gocx -m <model> exec '在吗'`
- 省略 `[model]` 时默认使用 `glm-5.1`
- `ReasoningSummaryDelta without active item`
- 是 reasoning summary event 的独立 adapter noise
- 不等同于 model metadata 缺失;
- 不能单独证明 profile 不可用。
### OpenAI Responses Tool Argument Sanitizer
- Moon Bridge
- 负责 MiniMax 风格错误 tool argument JSON 的在线 sanitizer
- 作用于 `openai-response` passthrough path
- 是第一道防线;
- wrapper-level session cleanup 只处理 sanitizer 安装前已损坏的 JSONL。
- Sanitizer
- 递归扫描 `ResponsesRequest.input`
- upstream 前修复 tool-call `arguments`
- 在 completed event 中修复 response-side `arguments`
- 禁止修改 delta chunk
- 无法恢复时回退到 `{}`
- response rewrite 必须删除 upstream `Content-Length`
## MiniMax Session 恢复
- `mxcx`
- 为损坏的 MiniMax-backed session JSONL 提供 cleanup 与 guard layer
- wrapper 负责命令语法;
- 本节只记录行为规则。
- `mxcx resume`
- 调用 Codex 前自动执行 `session-clean``session-guard`
- 使用 `CODEX_HOME=/root/.codex-minimax-m3`
- `session-clean`
- 只处理无效 tool-call `arguments`
- 包括 malformed JSON、MiniMax sentinel text 和 schema-invalid case
- 必须保留行顺序、非 tool message、reasoning、output、token record 和
session metadata
- 禁止 compact、summarize、truncate 或 reorder transcript。
- 新的无效 `arguments`
- 必须由 Moon Bridge sanitizer 在线阻止;
- `session-clean` 只用于恢复既有损坏 session。
- `session-guard`
- 注入 `mxcx-minimax-tool-and-apply-patch-guard-v2`
- 禁止重复注入。
## MiniMax Apply-Patch 操作
- MiniMax-backed session
- 必须使用与其他 agent 相同的 UniDesk 远端文本 patch 契约;
- route 在前,operation 在后;
- 默认使用 `apply-patch` v2。
- 稳定写入形式:
- `trans <provider>:/absolute/workspace apply-patch < patch.diff`
- 只读检查:
- 使用 `trans <provider>:/absolute/workspace nl -ba file` 或同等有界命令。
- `apply-patch` 报告 `failed to find expected lines` 时:
- 先读取目标当前精确内容;
- 再用更小的 `Update File` hunk 重试;
- 或使用 `@@ <unique anchor>`
- 或拆成多个小 hunk
- 这是正常 stale-context 恢复,禁止因此切换工具。
- 文本 patch 失败时禁止使用:
- `download` / `upload`
- 远端 Python、Perl 或 sed heredoc
- `cat >``tee` 整文件覆盖;
- `apply-patch-v1`
- 仅在以下情况允许例外:
- `apply-patch` 本身不可用;
- 目标不是文本;
- 目标是批量机械生成内容。