# UniDesk Sub2API

## 目录

- [先看报表](#先看报表)
- [先读边界](#先读边界)
- [部署与状态](#部署与状态)
- [PK01 host-Docker target](#pk01-host-docker-target)
- [D601 Egress Proxy](#d601-egress-proxy)
- [镜像升级](#镜像升级)

UniDesk 通过 `platform-infra sub2api` 运维 YAML 选中的 Sub2API target。当前 active target 以 `config/platform-infra/sub2api.yaml` 为准；PK01 可作为 host-Docker active target 并通过 PK01 Caddy 本地反代提供 `api.pikapython.com`，D518/D601 等 k3s target 仍可按 YAML 声明为 external-active 或 retired，G14 由同一 YAML/CLI 控制为 standby predeploy。日常操作统一使用 UniDesk CLI，不直接写 Kubernetes 资源或手工调用 Sub2API 管理 API。

**固定入口**: `cd /root/unidesk && bun scripts/cli.ts platform-infra sub2api ...`

## 先看报表

查 Codex pool 哨兵状态、账号冻结/恢复、marker 命中、下一次 probe、最近 CronJob run、token/cost 账本时，优先使用这个低噪声报表入口，不要先翻 ConfigMap、CronJob 日志或 Sub2API UI：

```bash
bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-report
```

需要机器处理或完整字段时再加 `--raw`；需要更多最近运行记录时加 `--events N`。

追溯某个 Codex/Sub2API request id 的中断、上游账号、切号、临时不可调度、账号选择失败和同窗口账号池信号时，优先使用低噪声 trace 报表，不要先手写 `kubectl logs | grep`：

```bash
bun scripts/cli.ts platform-infra sub2api codex-pool trace --request-id <requestId>
```

默认输出类似 k8s/ps 的短表；机器处理用 `--raw` 读取 `.data.trace.*`；需要审计原始匹配日志时加 `--show-lines`；需要扩大搜索范围时使用 `--since 24h --tail 50000`。该命令只读：读取 Sub2API 日志、账号快照和 admin API 元数据，不改 `schedulable`、不清 runtime backoff、不中断请求。

## 先读边界

- 仓库长期开发边界见 `docs/reference/platform-infra.md`，本 skill 承担日常操作手册。
- 配置真相是 YAML：`config/platform-infra/sub2api.yaml` 和 `config/platform-infra/sub2api-codex-pool.yaml`。
- 业务策略和具体数值只以 YAML 为准。已有字段的数值调整只改 YAML 并跑 `plan` / `sync --confirm` / `validate`；不要自动补代码硬编码、schema 硬范围、合同测试、单元测试或长期参考文档。配置校验只校验格式、类型、必填和可渲染性，不判断数值策略是否“合理”。
- 本 skill 目录下若存在 `agents/*.yaml`，只作为 skill/agent 展示与调用元数据，不是 Sub2API 或 Codex pool 运行配置；不要在 skill 目录维护第二份账号、capacity、priority、endpoint 或 Secret 配置。
- Runtime target 由 `config/platform-infra/sub2api.yaml` 声明；默认 target 来自 YAML `defaults.targetId`，当前 `api.pikapython.com` 使用 PK01 host-Docker target。`D518:k3s`、`D601:k3s` 这类 k3s target 必须通过显式 `--target` 选择并按 YAML role 判定 active/retired/standby，`G14:k3s` 是 standby target。master server 只是控制端和消费者，不部署 Sub2API/PostgreSQL/Redis。
- Standby target 不部署本地 PostgreSQL，不运行 sentinel、FRP 管理入口或 HTTPS egress proxy；只能预部署 namespace、NetworkPolicy、Service，以及 replicas=0 的 Sub2API/Redis Deployment。Redis 激活后也只允许 ephemeral cache。External-active target 仍不部署本地 PostgreSQL，必须直连 YAML 声明的外置 DB，使用本地 ephemeral Redis，并且只有在 YAML 启用时才运行 frpc、egress proxy 和目标级 sentinel；多个 external-active target 可以并存，不得把一个 target 的 public exposure 当作另一个 target 的替代或回退。
- Secret、`~/.codex/config.toml*`、`~/.codex/auth.json*` 是运行时输入或本地状态，不提交。
- 默认 `~/.codex/config.toml` 和 `~/.codex/auth.json` 只作为统一 Sub2API consumer 使用；`config.toml` 必须指向 YAML-selected active target 的 consumer URL，`auth.json` 必须使用统一 pool API key。新增上游账号不得覆盖这两个默认文件，只能新增 `config.toml.<profile>` / `auth.json.<profile>` 并在 YAML 里声明。
- 输出只能包含 Secret 路径、key 名、presence、fingerprint 和 `valuesPrinted=false`；禁止打印完整 API key、admin password、JWT secret、TOTP key、base64 payload 或可复制的 preview。

## 部署与状态

```bash
bun scripts/cli.ts platform-infra sub2api plan
bun scripts/cli.ts platform-infra sub2api plan --target G14
bun scripts/cli.ts platform-infra sub2api image-prepull --target PK01
bun scripts/cli.ts platform-infra sub2api image-prepull --target PK01 --confirm
bun scripts/cli.ts platform-infra sub2api rollout --target PK01 --dry-run
bun scripts/cli.ts platform-infra sub2api rollout --target PK01 --confirm
bun scripts/cli.ts platform-infra sub2api smoke --target PK01
bun scripts/cli.ts platform-infra sub2api apply --dry-run
bun scripts/cli.ts platform-infra sub2api apply --target G14 --dry-run
bun scripts/cli.ts platform-infra sub2api apply --confirm
bun scripts/cli.ts platform-infra sub2api apply --target G14 --confirm
bun scripts/cli.ts platform-infra sub2api status
bun scripts/cli.ts platform-infra sub2api status --target G14
bun scripts/cli.ts platform-infra sub2api validate
bun scripts/cli.ts platform-infra sub2api validate --target G14
```

- `plan` 读取 `config/platform-infra/sub2api.yaml`，渲染 `src/components/platform-infra/sub2api/sub2api.k8s.yaml`，检查 no Ingress/NodePort/LoadBalancer/hostPort/hostNetwork/resource limits，并要求 `NetworkPolicy/allow-all` 随 manifest 受控创建。
- `apply --confirm` 默认创建异步 job；按返回的 `job status` 命令轮询，再跑 `status` 和 `validate`。
- `status --full|--raw` 只在需要展开远端 stdout/stderr 或原始 JSON 时使用。
- `validate` 是按需验收，不是连续可用性探针。对 standby target，`validate --target <id>` 验证预部署形态，不要求外置 DB 当前可连接；对 external-active target，必须验证外置 DB、ephemeral Redis、Sub2API service、YAML egress proxy 和目标级 public exposure。

## PK01 host-Docker target

PK01 host-Docker target 由 `config/platform-infra/sub2api.yaml` 的 target `runtimeMode: host-docker` 控制。`api.pikapython.com` 的当前路径是 `client -> PK01 Caddy -> 127.0.0.1:<YAML local upstream port> -> PK01 host-Docker Sub2API`，不是 D601 FRP 路径。优先用以下受控入口分层判断：

```bash
bun scripts/cli.ts platform-infra sub2api status --target PK01
bun scripts/cli.ts platform-infra sub2api validate --target PK01
```

PK01 没有 k3s control plane。`codex-pool sync --target PK01 --confirm` 和 `codex-pool validate --target PK01` 走 host-Docker adapter：通过本机 Sub2API admin API 和 YAML `hostDocker.envPath` 对齐账号池，不使用 k8s Secret/CronJob，也不重启容器。`sentinel-report`、`sentinel-probe`、`sentinel-image` 和部分 `trace` 能力仍可能依赖 k8s/kubectl；在这些命令上看到 `kubectl` 缺失时，应归类为 CLI host-Docker adapter 缺口，不要误判为 Sub2API app、Caddy、上游或账号池故障。临时排障只能做只读 admin API、DB join 表和最小公网 `/v1/responses` smoke，并且不得打印 admin password、API key 或账号凭据。

PK01 host-Docker apply 仍必须由 `platform-infra sub2api apply --target PK01 --confirm` 受控执行。若 dry-run 或 apply 输出显示 `docker compose is absent; apply will use raw docker run fallback`，这表示 CLI 选择了 host-Docker fallback，不是裸手工 Docker 操作；只要 YAML image、env、ports、Caddy managed block 和 `status/validate` 最终对齐，可作为受控滚动升级证据。不要改用手工 `docker run`、手工 compose 文件或直接编辑 PK01 Caddyfile。

`status` 同时兼容 Compose label 和受控 raw-Docker 名称发现，并按稳定容器身份合并结果；默认 `WORKLOADS` 和完整 summary 都必须保持一个实际容器一行。

- 正式镜像升级优先使用 `platform-infra sub2api rollout`：
  - 同一入口组合镜像 presence、plan、apply dry-run、预拉、apply、status、validate 和既有消费配置 smoke；
  - `--confirm` 默认返回 async job，显式 `--wait` 只用于 job 内部或同步调试；
  - 镜像预拉长连接预算读取 `defaults.rollout.imagePrepullTimeoutSeconds`；
  - 禁止依赖默认 60 秒 SSH 上限反复续拉。
- `image-prepull` 只作为单阶段诊断入口，不作为标准升级主路径。
- `apply --wait` 不属于镜像升级入口：
  - 版本变化时必须使用 `rollout`；
  - `apply` 只用于不含版本变化的声明式配置调和，或由 rollout job 在内部调用；
  - 禁止在调用侧用 `apply --wait` 持有超过 SSH 顶层预算的长连接。
- 禁止用临时 `trans <node> docker pull ...` 作为长期入口。

## D601 Egress Proxy

D601 的目标级 `egressProxy` 完全由 `config/platform-infra/sub2api.yaml` 控制。当前成熟形态是 master Docker `shadowsocks-rust` 作为加密出站源，D601 k3s 内 `sing-box` 暴露 HTTP/mixed ClusterIP proxy 给 Sub2API 和按 YAML 启用的 sentinel 使用。不要把 endpoint、端口、密码、健康探针或镜像 tag 写进 skill；只以 YAML 和 `config/platform-infra/sub2api-master-egress-proxy.compose.yaml` 为准。

master 侧 proxy 由 UniDesk checkout 内的 compose 文件管理：

```bash
docker compose -f config/platform-infra/sub2api-master-egress-proxy.compose.yaml up -d --force-recreate
bun scripts/cli.ts platform-infra sub2api apply --target D601 --confirm
bun scripts/cli.ts platform-infra sub2api validate --target D601
bun scripts/cli.ts platform-infra sub2api codex-pool sync --target D601 --confirm
bun scripts/cli.ts platform-infra sub2api codex-pool validate --target D601
```

proxy secret/config 文件只允许放在受控 Secret/state 路径，输出只能披露路径、presence、fingerprint 或摘要，不能打印密码、完整订阅或生成配置。若 D601 到上游的 TLS/SNI 路径被 reset，不要用临时 JS 或简陋 HTTP CONNECT proxy 作为最终方案；通过 YAML/compose 更换或修复成熟加密 proxy source，再跑上面的 apply/validate/sync/validate 闭环。

## 镜像升级

- 快速通道边界：
  - 只适用于单 target 的纯版本滚动；
  - 允许变更目标的 `image.repository`、`image.tag`、`pullPolicy` 和对应 TaskTree ExecutionReport；
  - 不允许夹带 Sub2API 源码、UniDesk CLI、账号池 runtime、代理、Secret、多 target、public-edge 或其他配置变更；
  - 超出边界时使用独立分支和 PR，不得直接 push `master`。
- 开始前同步：
  - 固定主 worktree 保持在 `master`；
  - 落后 remote 且存在脏改时，按主 worktree 规范执行 `git stash push -u`、`git pull --ff-only` 和 `git stash apply`；
  - 精确保护和处理并行改动，禁止 reset、覆盖式 checkout 或把无关文件带入版本提交。
- 目标 patch：
  - 只修改目标 target 的 `image.repository`、`image.tag` 或 `pullPolicy`；
  - 使用 target 附近的上下文 patch；
  - 不按第一个 `tag:` 命中，不改全局默认或其他 target。
- 发布 dry-run：
  - 只调用一次 `sub2api rollout --target <target> --dry-run`；
  - 默认摘要披露目标镜像、presence、plan 和 apply dry-run；
  - 为避免对目标节点重复捕获，当前运行镜像延后到 confirm job 的 verify 阶段读取；
  - 摘要完整时不再分别调用 `status`、`plan`、`image-prepull` 和 `apply --dry-run`。
- 发布确认：
  - 只调用一次 `sub2api rollout --target <target> --confirm`；
  - 按返回的唯一 `job status` 命令等待终态；
  - `job status` 默认投影 `prepull`、`apply`、`verify` 当前阶段、终态、验证子项和阶段耗时；只有原始 stderr/stdout 排障才使用 `--full`；
  - `apply` 失败摘要中的 `applied=yes` 表示声明已经进入运行面，后续字段会列出未通过的后置检查；
  - 看到 `applied=yes` 时禁止重复 apply，先等待就绪并按摘要中的失败步骤精确下钻；
  - job 内顺序完成预拉和 apply，并行完成 status、validate 与既有消费配置 smoke；
  - 不在外部重复逐阶段轮询。
- 共享数据库多节点升级：
  - 对 NC01 先执行完整的 dry-run、confirm、job status 和验收；
  - NC01 验收通过后，才对 PK01 重复同一流程；
  - 禁止两个节点并行滚动，或先滚动 PK01 再以 NC01 补齐版本；
  - public-edge 变更独立走 PR 和自动交付，不混入纯版本直接 push 快速通道。
- 运行面验收：
  - 以 job 终态中的 status、validate 和既有消费配置 smoke 为标准证据；
  - 只有终态缺少 smoke 证据时，补一次 `sub2api smoke --target <target>`；
  - rollout 或 smoke 未成功时不得提交和推送目标版本。
- 成功后收尾：
  - rollout 与 smoke 成功后再创建或补全 TaskTree Task 和 ExecutionReport；
  - 遗留 MDTODO 按 `$unidesk-tasktree` 迁移；目标镜像 YAML 仍单独精确提交；
  - 直接执行 `git push origin master`，不创建 draft PR、正式 PR 或 guarded merge；
  - 推送失败时先解决远端快进关系，不重复 rollout；
  - push 后核对远端 `master` 已包含该提交和目标镜像声明。
- 精确下钻：
  - 只有 rollout 终态缺少某项证据时，才使用输出给出的下钻命令；
  - 不固定重复执行 status、validate、public health 或 smoke。
- 镜像升级禁止配置对齐：
   - 不得执行 `codex-pool plan`、`codex-pool sync` 或 `codex-pool validate`；
   - 不得对齐账号、group、统一 key、capacity、load factor、WebSocket、临时不可调度规则、代理绑定或手工账号。
- 镜像升级的真实模型请求证据：
   - 只能复用升级前已经存在的消费配置执行无写入 smoke；
   - `rollout` 自动复用匹配目标 public URL 的 `~/.codex/config.toml` 和 `auth.json`；
   - 配置缺失或目标不匹配时只报告 skipped；
   - 不得为完成 smoke 创建、恢复、同步或覆盖任何配置。

- 正常热缓存升级的操作目标是 5 分钟内完成：
  - `rollout` 输出 `prepull`、`apply`、`verify` 和 `total` 分阶段耗时；
  - 超时先按失败 stage 下钻；
  - 不回退到旧的十余条手工串联流程。

不要把镜像版本写进脚本常量、JSON 或 manifest 模板。
