# UniDesk Sub2API

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 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`、`codex-pool validate`、`sentinel-report` 和 `trace` 的部分实现仍依赖 k8s/kubectl 远端脚本；在 PK01 host-Docker target 上看到 `kubectl` 缺失时，应归类为 CLI host-Docker adapter 缺口，不要误判为 Sub2API app、Caddy、上游或账号池故障。正式修复应补 host-Docker 版 codex-pool sync/validate/report/trace；临时排障只能做只读 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。

## 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 闭环。

## 镜像升级

1. 先定位目标 target 的 `id`、`publicBaseUrl` 和当前 `image` block。`config/platform-infra/sub2api.yaml` 同时有全局默认镜像和多个 target-scoped 镜像；只升级单个 target 时必须用 `id: <target>` 附近的上下文 patch，不能按第一个 `tag:` 命中。
2. 只修改目标 target 的 `image.repository`、`image.tag` 或 `pullPolicy`；不要顺手改全局默认、PK01 host-Docker target、standby target 或其它共用 `api2.pikapython.com` 历史 target。
3. 提交前用 `git diff -- config/platform-infra/sub2api.yaml` 和 `rg -n "id: <target>|tag: <version>|publicBaseUrl"` 确认只有目标 target 变化，且要保护的公开入口没有被误改。
4. 执行 `sub2api plan --target <target>` 和 `sub2api apply --target <target> --dry-run`，确认策略检查通过。
5. 提交并推送 YAML source truth 后，执行 `sub2api apply --target <target> --confirm`，按返回的 `job status` 命令轮询完成。
6. 执行 `sub2api status --target <target>`，确认运行镜像等于 YAML 声明；滚动过程中短暂 `0/1` 先等待并复查，不要立即改账号池、Caddy 或 Secret。
7. 执行 `sub2api validate --target <target>`、目标 public `/health` 和最小 public `/v1/responses` smoke；若需要确认未影响其它公开入口，再跑对应 target 的 `validate`。

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