docs: update sub2api pk01 host-docker runbook

This commit is contained in:
Codex
2026-06-29 03:09:56 +00:00
parent 484936e0f7
commit 631bbada5d
4 changed files with 45 additions and 38 deletions
+7 -7
View File
@@ -5,26 +5,26 @@ description: UniDesk Sub2API 平台运维技能。用户提到 Sub2API、sub2api
# UniDesk Sub2API # UniDesk Sub2API
UniDesk 在 k3s `platform-infra` namespace 运维 Sub2API。日常操作统一使用 UniDesk CLI,不直接写 Kubernetes 资源、不手工调用 Sub2API 管理 API、不打印 Secret。 UniDesk 通过 `platform-infra sub2api` 运维 YAML 选中的 Sub2API target;当前 active target 以 `config/platform-infra/sub2api.yaml` 为准,可能是 PK01 host-Docker 或 k3s target。日常操作统一使用 UniDesk CLI,不直接写 Kubernetes 资源、不手工调用 Sub2API 管理 API、不打印 Secret。
## 高频入口 ## 高频入口
```bash ```bash
bun scripts/cli.ts platform-infra sub2api report bun scripts/cli.ts platform-infra sub2api report
bun scripts/cli.ts platform-infra sub2api status --target D601 bun scripts/cli.ts platform-infra sub2api status --target PK01
bun scripts/cli.ts platform-infra sub2api plan --target D601 bun scripts/cli.ts platform-infra sub2api validate --target PK01
bun scripts/cli.ts platform-infra sub2api apply --target D601 --dry-run bun scripts/cli.ts platform-infra sub2api plan --target PK01
``` ```
先看报表和状态,再做计划或变更。部署、D601 egress proxy、镜像升级、Codex pool、账号代理、FRP 暴露、master Codex 消费端配置和排障细节见 [references/full.md](references/full.md)。 先看报表和状态,再做计划或变更。部署、PK01 host-Docker、D601/D518 k3s target、egress proxy、镜像升级、Codex pool、账号代理、FRP/Caddy 暴露、master Codex 消费端配置和排障细节见 [references/full.md](references/full.md)。
## 边界 ## 边界
- YAML 是 source of truthtarget、public exposure、Secret sourceRef、Codex pool 和 sentinel 配置都从 YAML 进入 CLI。 - YAML 是 source of truthtarget、public exposure、Secret sourceRef、Codex pool 和 sentinel 配置都从 YAML 进入 CLI。
- Secret 只输出对象名、key 名、presence、fingerprint 或 redacted prefix;禁止打印完整 token/key。 - Secret 只输出对象名、key 名、presence、fingerprint 或 redacted prefix;禁止打印完整 token/key。
- D601 是默认 active targetD518/G14 等 target 以 YAML 和 issue 明确目标为准 - 默认 active target 以 YAML `defaults.targetId` 和 target role 为准;当前 `api.pikapython.com` 对应 PK01 host-Docker target
- Codex pool、统一 API key、master `~/.codex` 配置、FRP/Caddy 暴露、账号增删都必须走本技能的受控 CLI。 - Codex pool、统一 API key、master `~/.codex` 配置、FRP/Caddy 暴露、账号增删都必须走本技能的受控 CLI。
- D601 public 502 或 `api.pikapython.com` 异常先区分 edge/app endpoint,并`status``validate``apply --confirm``codex-pool validate` 做分层恢复完整步骤见 [references/full.md](references/full.md) 的排障段。 - `api.pikapython.com` 异常先按 YAML target 区分 PK01 local edge/app、k3s FRP target 和账号池调度;`status``validate`受控 apply/sync 以及最小 `/v1/responses` smoke 做分层恢复完整步骤见 [references/full.md](references/full.md) 的排障段。
## 何时读取 reference ## 何时读取 reference
@@ -5,7 +5,7 @@ description: UniDesk Sub2API 平台运维技能。用户提到 Sub2API、sub2api
# UniDesk Sub2API # UniDesk Sub2API
UniDesk 在 k3s `platform-infra` namespace 运维 Sub2API。D601 是当前默认 target,使用外置 PostgreSQL 和目标级 public exposureD518 可作为独立 external-active target 与 D601 同时对外服务;G14 由同一 YAML/CLI 控制为 standby predeploy,默认缩容且不运行 sentinel、FRP 或 HTTPS egress proxy。日常操作统一使用 UniDesk CLI,不直接写 Kubernetes 资源或手工调用 Sub2API 管理 API。 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 或 retiredG14 由同一 YAML/CLI 控制为 standby predeploy。日常操作统一使用 UniDesk CLI,不直接写 Kubernetes 资源或手工调用 Sub2API 管理 API。
**固定入口**: `cd /root/unidesk && bun scripts/cli.ts platform-infra sub2api ...` **固定入口**: `cd /root/unidesk && bun scripts/cli.ts platform-infra sub2api ...`
@@ -33,10 +33,10 @@ bun scripts/cli.ts platform-infra sub2api codex-pool trace --request-id <request
- 配置真相是 YAML`config/platform-infra/sub2api.yaml``config/platform-infra/sub2api-codex-pool.yaml` - 配置真相是 YAML`config/platform-infra/sub2api.yaml``config/platform-infra/sub2api-codex-pool.yaml`
- 业务策略和具体数值只以 YAML 为准。已有字段的数值调整只改 YAML 并跑 `plan` / `sync --confirm` / `validate`;不要自动补代码硬编码、schema 硬范围、合同测试、单元测试或长期参考文档。配置校验只校验格式、类型、必填和可渲染性,不判断数值策略是否“合理”。 - 业务策略和具体数值只以 YAML 为准。已有字段的数值调整只改 YAML 并跑 `plan` / `sync --confirm` / `validate`;不要自动补代码硬编码、schema 硬范围、合同测试、单元测试或长期参考文档。配置校验只校验格式、类型、必填和可渲染性,不判断数值策略是否“合理”。
- 本 skill 目录下若存在 `agents/*.yaml`,只作为 skill/agent 展示与调用元数据,不是 Sub2API 或 Codex pool 运行配置;不要在 skill 目录维护第二份账号、capacity、priority、endpoint 或 Secret 配置。 - 本 skill 目录下若存在 `agents/*.yaml`,只作为 skill/agent 展示与调用元数据,不是 Sub2API 或 Codex pool 运行配置;不要在 skill 目录维护第二份账号、capacity、priority、endpoint 或 Secret 配置。
- Runtime target 由 `config/platform-infra/sub2api.yaml` 声明;默认 target `D601:k3s``D518:k3s` 这类 external-active target 必须通过显式 `--target` 选择,`G14:k3s` 是 standby target。master server 只是控制端和消费者,不部署 Sub2API/PostgreSQL/Redis。 - 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 的替代或回退。 - 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*` 是运行时输入或本地状态,不提交。 - Secret、`~/.codex/config.toml*``~/.codex/auth.json*` 是运行时输入或本地状态,不提交。
- 默认 `~/.codex/config.toml``~/.codex/auth.json` 只作为统一 Sub2API consumer 使用;`config.toml` 必须指向 YAML-selected active target 的 consumer URL当前由 `codex-pool configure-local --confirm` 写入 D601 target-level public URL`auth.json` 必须使用统一 pool API key。新增上游账号不得覆盖这两个默认文件,只能新增 `config.toml.<profile>` / `auth.json.<profile>` 并在 YAML 里声明。 - 默认 `~/.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。 - 输出只能包含 Secret 路径、key 名、presence、fingerprint 和 `valuesPrinted=false`;禁止打印完整 API key、admin password、JWT secret、TOTP key、base64 payload 或可复制的 preview。
## 部署与状态 ## 部署与状态
@@ -59,6 +59,17 @@ bun scripts/cli.ts platform-infra sub2api validate --target G14
- `status --full|--raw` 只在需要展开远端 stdout/stderr 或原始 JSON 时使用。 - `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。 - `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 或账号凭据。
## D601 Egress Proxy ## 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` 为准。 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` 为准。
@@ -87,23 +98,17 @@ proxy secret/config 文件只允许放在受控 Secret/state 路径,输出只
## Codex Pool ## Codex Pool
当前 codex-pool sync/validate/report/trace 适配器主要覆盖 k3s target。若 YAML 默认 target 是 PK01 host-Docker,不要直接把无 `--target` 的 codex-pool 命令当成验收入口;先使用 `sub2api status --target PK01``sub2api validate --target PK01` 和最小 public `/v1/responses` smoke。host-Docker codex-pool adapter 补齐前,k3s 账号池操作必须显式选择 k3s target:
```bash ```bash
bun scripts/cli.ts platform-infra sub2api codex-pool plan
bun scripts/cli.ts platform-infra sub2api codex-pool plan --target D601 bun scripts/cli.ts platform-infra sub2api codex-pool plan --target D601
bun scripts/cli.ts platform-infra sub2api codex-pool sync --confirm
bun scripts/cli.ts platform-infra sub2api codex-pool sync --target D601 --confirm bun scripts/cli.ts platform-infra sub2api codex-pool sync --target D601 --confirm
bun scripts/cli.ts platform-infra sub2api codex-pool validate
bun scripts/cli.ts platform-infra sub2api codex-pool validate --target D601 bun scripts/cli.ts platform-infra sub2api codex-pool validate --target D601
bun scripts/cli.ts platform-infra sub2api codex-pool trace --request-id <requestId> bun scripts/cli.ts platform-infra sub2api codex-pool trace --request-id <requestId>
bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-image status
bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-image status --target D601 bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-image status --target D601
bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-image build --confirm
bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-image build --target D601 --confirm bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-image build --target D601 --confirm
bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-probe --account unidesk-codex-hy --confirm
bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-probe --target D601 --account unidesk-codex-hy --confirm bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-probe --target D601 --account unidesk-codex-hy --confirm
bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-report
bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-report --target D601 bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-report --target D601
bun scripts/cli.ts platform-infra sub2api codex-pool cleanup-probes --confirm
bun scripts/cli.ts platform-infra sub2api codex-pool cleanup-probes --target D601 --confirm bun scripts/cli.ts platform-infra sub2api codex-pool cleanup-probes --target D601 --confirm
``` ```
@@ -138,7 +143,7 @@ bun scripts/cli.ts platform-infra sub2api codex-pool cleanup-probes --target D60
- `sentinel.freeze`: 失败冻结 TTL 指数退避配置。当前口径是初始 1 分钟,失败后 `1m -> 2m -> 4m -> 8m -> 10m`,最大 10 分钟;失败 probe 基本不消耗有效输出 token,因此冻结窗口保持短周期。冻结到期后只做恢复 probe,通过才自动恢复,不能仅靠 TTL 到期解封。 - `sentinel.freeze`: 失败冻结 TTL 指数退避配置。当前口径是初始 1 分钟,失败后 `1m -> 2m -> 4m -> 8m -> 10m`,最大 10 分钟;失败 probe 基本不消耗有效输出 token,因此冻结窗口保持短周期。冻结到期后只做恢复 probe,通过才自动恢复,不能仅靠 TTL 到期解封。
- `sentinel.pricing`: 直打上游时哨兵自己的 token/cost 估算价格。因为 direct upstream probe 不经过 Sub2API 普通用量账本,哨兵必须自己记录全局与 per-account token/cost;这些账本只用于观察,不作为跳过探测的预算门禁。 - `sentinel.pricing`: 直打上游时哨兵自己的 token/cost 估算价格。因为 direct upstream probe 不经过 Sub2API 普通用量账本,哨兵必须自己记录全局与 per-account token/cost;这些账本只用于观察,不作为跳过探测的预算门禁。
`sync --confirm` 会登录 Sub2API admin、创建/更新 group、创建/更新 YAML 中的 `unidesk-codex-*` accounts、创建/复用统一 API key Secret,并部署/更新哨兵资源;它不把既有 managed account 直接恢复为 `schedulable=true`。恢复只由哨兵在读取 Sub2API runtime `schedulable=false` 后触发 recovery probe,并在 marker 命中时执行。`sync` 默认不删除 YAML 中缺席的 managed account。只有明确退役上游时才使用 `sync --confirm --prune-removed` 删除缺席且 `extra.unidesk_managed=true``unidesk-codex-*` account。对 `manualAccounts.protected``sync` 只执行 YAML 显式允许的窄同步;当前允许项是从目标 `egressProxy` 创建/更新 Sub2API internal proxy 记录并绑定 `proxy_id`,以及把受保护手动账号加入当前 `pool.groupName`。它仍不接管该账号凭据、status、schedulable、priority/capacity/loadFactor 或哨兵状态。 对已支持的 k3s target`sync --confirm` 会登录 Sub2API admin、创建/更新 group、创建/更新 YAML 中的 `unidesk-codex-*` accounts、创建/复用统一 API key Secret,并部署/更新哨兵资源;它不把既有 managed account 直接恢复为 `schedulable=true`。恢复只由哨兵在读取 Sub2API runtime `schedulable=false` 后触发 recovery probe,并在 marker 命中时执行。`sync` 默认不删除 YAML 中缺席的 managed account。只有明确退役上游时才使用 `sync --confirm --prune-removed` 删除缺席且 `extra.unidesk_managed=true``unidesk-codex-*` account。对 `manualAccounts.protected``sync` 只执行 YAML 显式允许的窄同步;当前允许项是从目标 `egressProxy` 创建/更新 Sub2API internal proxy 记录并绑定 `proxy_id`,以及把受保护手动账号加入当前 `pool.groupName`。它仍不接管该账号凭据、status、schedulable、priority/capacity/loadFactor 或哨兵状态。PK01 host-Docker target 在 codex-pool adapter 补齐前不具备这条完整 sync 路径。
`sentinel-image status|build` 管理哨兵 Python 运行环境镜像。镜像由 YAML 的 `sentinel.image` 基础镜像和 `sentinel.sdk.openaiPythonVersion` 派生,发布到目标 runtime 的本地 registry`build --confirm` 会先检查 registry tag,存在则快速复用,不存在才在目标 host 构建并 push。CronJob 启动时只校验 SDK 版本,不在运行时 `pip install`。目标是否启用哨兵以 `config/platform-infra/sub2api.yaml``sentinel.enabledOnTargets` 为准;未启用的 target 在 `sync`/`validate` 中应显示 `skipped-target-disabled`,不得要求镜像构建、CronJob、Secret 或 state ConfigMap 存在。 `sentinel-image status|build` 管理哨兵 Python 运行环境镜像。镜像由 YAML 的 `sentinel.image` 基础镜像和 `sentinel.sdk.openaiPythonVersion` 派生,发布到目标 runtime 的本地 registry`build --confirm` 会先检查 registry tag,存在则快速复用,不存在才在目标 host 构建并 push。CronJob 启动时只校验 SDK 版本,不在运行时 `pip install`。目标是否启用哨兵以 `config/platform-infra/sub2api.yaml``sentinel.enabledOnTargets` 为准;未启用的 target 在 `sync`/`validate` 中应显示 `skipped-target-disabled`,不得要求镜像构建、CronJob、Secret 或 state ConfigMap 存在。
@@ -148,7 +153,7 @@ bun scripts/cli.ts platform-infra sub2api codex-pool cleanup-probes --target D60
`sentinel-report` 是只读低噪声报表,不触发 probe、不修改账号。默认输出类似 `ps` 的文本表,展示每个账号的探测次数、Sub2API runtime `schedulable`、最近 marker/HTTP/动作、冻结 TTL、成功退避、下一次 probe 和最近 run 事件;`SCH` 展示 Sub2API runtime schedulable`PROT` 展示账号级保护阈值,`P_FAIL` 展示最近一次保护确认中的失败次数/阈值;需要机器处理时使用 `sentinel-report --raw` `sentinel-report` 是只读低噪声报表,不触发 probe、不修改账号。默认输出类似 `ps` 的文本表,展示每个账号的探测次数、Sub2API runtime `schedulable`、最近 marker/HTTP/动作、冻结 TTL、成功退避、下一次 probe 和最近 run 事件;`SCH` 展示 Sub2API runtime schedulable`PROT` 展示账号级保护阈值,`P_FAIL` 展示最近一次保护确认中的失败次数/阈值;需要机器处理时使用 `sentinel-report --raw`
`sync --confirm``validate` 可能超过单次 SSH/runtime 短连接窗口。必须继续使用 `bun scripts/cli.ts platform-infra sub2api codex-pool ...`,由 CLI 在 G14 远端提交作业并短轮询状态;不要改用裸 `trans G14:k3s sh` 等一个长连接等待完整结果。若看到 `UNIDESK_SSH_RUNTIME_TIMEOUT`,先按 `docs/reference/platform-infra.md` 的规则处理为控制面可见性问题,修 CLI/job/poll 或重跑受控命令,不要手工 patch Sub2API credentials 或源码。 对已支持的 k3s target`sync --confirm``validate` 可能超过单次 SSH/runtime 短连接窗口。必须继续使用 `bun scripts/cli.ts platform-infra sub2api codex-pool ... --target <k3s-target>`,由 CLI 在目标远端提交作业并短轮询状态;不要改用裸 `trans <target>:k3s sh` 等一个长连接等待完整结果。若看到 `UNIDESK_SSH_RUNTIME_TIMEOUT`,先按 `docs/reference/platform-infra.md` 的规则处理为控制面可见性问题,修 CLI/job/poll 或重跑受控命令,不要手工 patch Sub2API credentials 或源码。
不要给 UniDesk-managed Codex accounts 开 Sub2API `pool_mode`。UniDesk 期望的 failover 是把失败账号临时标记为 unschedulable,让同组其他账号接手;`pool_mode` 会重试同一个 account path。 不要给 UniDesk-managed Codex accounts 开 Sub2API `pool_mode`。UniDesk 期望的 failover 是把失败账号临时标记为 unschedulable,让同组其他账号接手;`pool_mode` 会重试同一个 account path。
@@ -160,7 +165,7 @@ Codex 启动时反复出现 WebSocket reconnect、HTTPS fallback、`websocket cl
Sub2API 管理 UI 的账号连接测试使用账号级 `ProxyID` / proxy URL 配置上游 HTTP transport;账号没有绑定 proxy 时会直接出站,即使 Sub2API Pod 已经有 `HTTP_PROXY` / `HTTPS_PROXY` 环境变量。看到 WebUI 账号测试连 `chatgpt.com` 超时、但 Pod 内显式走目标 proxy 可通时,先检查该账号是否属于 `manualAccounts.protected` 并声明了 `proxyBinding`。如果同一账号用 `gpt-5.2-pro` 返回 ChatGPT OAuth 不支持 Codex 的模型能力错误,但默认/受支持模型能完成 `hi``/v1/responses` smoke,这不是代理失败;按模型映射/账号能力另行处理。 Sub2API 管理 UI 的账号连接测试使用账号级 `ProxyID` / proxy URL 配置上游 HTTP transport;账号没有绑定 proxy 时会直接出站,即使 Sub2API Pod 已经有 `HTTP_PROXY` / `HTTPS_PROXY` 环境变量。看到 WebUI 账号测试连 `chatgpt.com` 超时、但 Pod 内显式走目标 proxy 可通时,先检查该账号是否属于 `manualAccounts.protected` 并声明了 `proxyBinding`。如果同一账号用 `gpt-5.2-pro` 返回 ChatGPT OAuth 不支持 Codex 的模型能力错误,但默认/受支持模型能完成 `hi``/v1/responses` smoke,这不是代理失败;按模型映射/账号能力另行处理。
WebUI 账号连接测试也不经过统一消费 API key 的 pool group 选择器;账号测试正常不代表 PC Codex 客户端能选中该账号。看到 WebUI 账号测试正常、但 `/responses``/v1/responses``account-select-failed` / `no available accounts` 返回 503 时,先检查该手动账号是否声明了 `groupBinding.source: pool-group`,并通过 `sync --confirm` 加入当前 `pool.groupName` WebUI 账号连接测试也不经过统一消费 API key 的 pool group 选择器;账号测试正常不代表 PC Codex 客户端能选中该账号。看到 WebUI 账号测试正常、但 `/responses``/v1/responses``account-select-failed` / `no available accounts` 返回 503 时,先检查该手动账号是否声明了 `groupBinding.source: pool-group`,并确认 Sub2API `account_groups` join 里存在该账号与当前统一 API key `group_id` 的绑定。对已支持的 k3s target,通过 `sync --confirm` 加入当前 `pool.groupName`;对 PK01 host-Docker target,在 host-Docker codex-pool sync adapter 补齐前,只能用最小 admin API 写入 `group_ids` 做运行面恢复,且必须只输出 account id、group id、presence/fingerprint 和 smoke 状态,不打印密钥
受保护手动账号仍由人工在 Sub2API UI 维护 credentials/status 等字段;UniDesk 只允许通过 YAML 做代理和分组窄绑定: 受保护手动账号仍由人工在 Sub2API UI 维护 credentials/status 等字段;UniDesk 只允许通过 YAML 做代理和分组窄绑定:
@@ -203,15 +208,15 @@ bun scripts/cli.ts platform-infra sub2api codex-pool expose
bun scripts/cli.ts platform-infra sub2api codex-pool expose --confirm bun scripts/cli.ts platform-infra sub2api codex-pool expose --confirm
``` ```
- 由 YAML `publicExposure` 控制。Codex pool 默认公共端是 `publicBaseUrl`master 本地消费端是 `masterBaseUrl`external-active target 的目标级 public exposure 在 `config/platform-infra/sub2api.yaml` 中声明 - 由 YAML `publicExposure` 控制。Codex pool 默认公共端是 target `publicBaseUrl`host-Docker target 可以使用 `mode: pk01-local` 直接由 PK01 Caddy 反代本机 loopback appk3s external-active target 可以使用 FRP remotePort。不要把某个 target 的 exposure mode 推断成其它 target 的默认
- `expose --confirm` 只为 YAML 指定的 `remotePort` 补 master `frps` allow port,并在 G14 创建/更新 `sub2api-frpc` - `expose --confirm` 只为 YAML 指定的 `remotePort` 补 master `frps` allow port,并在 G14 创建/更新 `sub2api-frpc`
- master Caddy site 也由 `publicExposure.masterCaddy` 渲染;`responseHeaderTimeoutSeconds` 必须足够覆盖 Codex `/responses/compact` 长请求,避免 Caddy 先返回 504 而 Sub2API 后台实际稍后成功。具体数值只改 `config/platform-infra/sub2api-codex-pool.yaml`,修改后跑 `codex-pool expose --confirm`,再核对 Caddyfile 中渲染出的 `response_header_timeout` - master Caddy site 也由 `publicExposure.masterCaddy` 渲染;`responseHeaderTimeoutSeconds` 必须足够覆盖 Codex `/responses/compact` 长请求,避免 Caddy 先返回 504 而 Sub2API 后台实际稍后成功。具体数值只改 `config/platform-infra/sub2api-codex-pool.yaml`,修改后跑 `codex-pool expose --confirm`,再核对 Caddyfile 中渲染出的 `response_header_timeout`
- master Caddy 的短窗口边缘重试由 `publicExposure.masterCaddy.edgeRetry` 渲染;用于吸收 FRP remotePort 短暂关闭、`connect: connection refused`、EOF 或 connection reset 这类请求尚未稳定到达 Sub2API 的 502。具体 retry 时长、间隔和 `retryMatch` 范围只写 YAML,修改后跑 `codex-pool expose --confirm`,再核对 Caddyfile 中渲染出的 `lb_try_duration``lb_try_interval``lb_retry_match`。不要手工 patch `/etc/caddy/Caddyfile` - master Caddy 的短窗口边缘重试由 `publicExposure.masterCaddy.edgeRetry` 渲染;用于吸收 FRP remotePort 短暂关闭、`connect: connection refused`、EOF 或 connection reset 这类请求尚未稳定到达 Sub2API 的 502。具体 retry 时长、间隔和 `retryMatch` 范围只写 YAML,修改后跑 `codex-pool expose --confirm`,再核对 Caddyfile 中渲染出的 `lb_try_duration``lb_try_interval``lb_retry_match`。不要手工 patch `/etc/caddy/Caddyfile`
- PK01 `/etc/caddy/Caddyfile` 是 Sub2API、LangBot、n8n、HWLAB 等多 YAML 来源共享的 edge artifact。Sub2API apply/expose 只能更新自己的 managed block 并保留其他 blocks;同一 Sub2API 服务暴露多个 target 时,D601 保留既有 `# BEGIN unidesk managed sub2api`,非默认 target 必须使用 target-scoped owner(例如 `sub2api-d518`),避免 `api.pikapython.com``api2.pikapython.com` 互相覆盖。若 apply 输出显示 managed block 数异常,先停止 closeout,检查 PK01 Caddy 合并与 validation 结果,不要手工整文件覆盖。 - PK01 `/etc/caddy/Caddyfile` 是 Sub2API、LangBot、n8n、HWLAB 等多 YAML 来源共享的 edge artifact。Sub2API apply/expose 只能更新自己的 managed block 并保留其他 blocks;同一 Sub2API 服务暴露多个 target 时,D601 保留既有 `# BEGIN unidesk managed sub2api`,非默认 target 必须使用 target-scoped owner(例如 `sub2api-d518`),避免 `api.pikapython.com``api2.pikapython.com` 互相覆盖。若 apply 输出显示 managed block 数异常,先停止 closeout,检查 PK01 Caddy 合并与 validation 结果,不要手工整文件覆盖。
- 非幂等 POST 的 round-trip retry 必须收窄到 YAML `retryMatch` 声明的安全路径;普通 `/responses` 上游账号错误仍归 Sub2API failover / temp-unschedulable / sentinel 处理,不用 Caddy 重放整段推理请求来掩盖账号池问题。 - 非幂等 POST 的 round-trip retry 必须收窄到 YAML `retryMatch` 声明的安全路径;普通 `/responses` 上游账号错误仍归 Sub2API failover / temp-unschedulable / sentinel 处理,不用 Caddy 重放整段推理请求来掩盖账号池问题。
- 同一个 FRP TCP 入口同时暴露 OpenAI-compatible API 和 Sub2API 管理 UI `/login`。不要另开第二个管理端口,除非 YAML 明确声明新的暴露决策。 - 同一个公开入口同时暴露 OpenAI-compatible API 和 Sub2API 管理 UI `/login`FRP target 使用同一个 FRP TCP 入口;PK01 local target 使用 PK01 Caddy 到本机 app 的 managed block。不要另开第二个管理端口,除非 YAML 明确声明新的暴露决策。
- Sub2API Kubernetes Service 继续保持 ClusterIP。 - k3s target 的 Sub2API Kubernetes Service 继续保持 ClusterIP。
- External-active target 的公开路径是 `client -> PK01 Caddy -> PK01 frps remotePort -> target frpc -> Sub2API`不经过 pikanode,也不经过 master server 反代。PK01 Caddy 下载必须使用 YAML `publicExposure.pk01.caddyDownloadProxyUrl` 指定的 proxy;如果 Caddy 下载慢,先确认 apply 输出里是 `downloadProxy.mode=curl-proxy`。目标域名必须先解析到 YAML 声明的 PK01 公网地址,HTTPS 才能作为最终验证;D601 `api.pikapython.com` D518 `api2.pikapython.com` 应分别验收。 - k3s external-active target 的公开路径是 `client -> PK01 Caddy -> PK01 frps remotePort -> target frpc -> Sub2API`PK01 host-Docker target 的公开路径是 `client -> PK01 Caddy -> 127.0.0.1:<local upstream port> -> Sub2API`。两者都不经过 pikanode,也不经过 master server 反代。PK01 Caddy 下载必须使用 YAML `publicExposure.pk01.caddyDownloadProxyUrl` 指定的 proxy;如果 Caddy 下载慢,先确认 apply 输出里是 `downloadProxy.mode=curl-proxy`。目标域名必须先解析到 YAML 声明的 PK01 公网地址,HTTPS 才能作为最终验证;`api.pikapython.com``api2.pikapython.com` 应分别按各自 YAML target 验收。
## 配置 master Codex 消费端 ## 配置 master Codex 消费端
@@ -238,7 +243,7 @@ bun scripts/cli.ts platform-infra sub2api codex-pool configure-local --confirm
- `sub2api status`Deployment/StatefulSet/Service/Secret/NetworkPolicy 可见,运行镜像与 YAML 一致,`NetworkPolicy/allow-all` 符合 `podSelector: {}`、Ingress/Egress 全放行。 - `sub2api status`Deployment/StatefulSet/Service/Secret/NetworkPolicy 可见,运行镜像与 YAML 一致,`NetworkPolicy/allow-all` 符合 `podSelector: {}`、Ingress/Egress 全放行。
- `sub2api validate`app、PostgreSQL、Redis、service proxy、`NetworkPolicy/allow-all` 和临时跨 Pod PostgreSQL/Redis 连通性检查通过。 - `sub2api validate`app、PostgreSQL、Redis、service proxy、`NetworkPolicy/allow-all` 和临时跨 Pod PostgreSQL/Redis 连通性检查通过。
- `codex-pool validate`:统一 key 的 `GET /v1/models` 成功,并用 `localCodex.responsesSmokeModel` 跑一次小的 `POST /v1/responses` smokeowner balance / owner concurrency 已满足 YAML 最小值,capacity、WebSocket v2、Sub2API 内置 temporary-unschedulable 开关/规则和 sentinel runtime 状态与 YAML 对齐;`validation.gatewayResponsesRecent` 汇总最近 6 小时普通 `/responses``/v1/responses` 的 failover、forward failure、最终 4xx/5xx、慢 final error 与 `context canceled` 证据,`validation.gatewayCompactRecent` 单独汇总 `/responses/compact` 证据。若当前 Responses smoke `ok=true` 但 recent 字段 `degraded=true`,先区分是历史窗口残留还是新的 request id 正在失败;长期判定见 `docs/reference/platform-infra.md` - `codex-pool validate`:统一 key 的 `GET /v1/models` 成功,并用 `localCodex.responsesSmokeModel` 跑一次小的 `POST /v1/responses` smokeowner balance / owner concurrency 已满足 YAML 最小值,capacity、WebSocket v2、Sub2API 内置 temporary-unschedulable 开关/规则和 sentinel runtime 状态与 YAML 对齐;`validation.gatewayResponsesRecent` 汇总最近 6 小时普通 `/responses``/v1/responses` 的 failover、forward failure、最终 4xx/5xx、慢 final error 与 `context canceled` 证据,`validation.gatewayCompactRecent` 单独汇总 `/responses/compact` 证据。若当前 Responses smoke `ok=true` 但 recent 字段 `degraded=true`,先区分是历史窗口残留还是新的 request id 正在失败;长期判定见 `docs/reference/platform-infra.md`
-`publicExposure.enabled=true`,确认 FRP path 可用;`expose --confirm` 会用未带 key 的 public `/v1/models` 401 作为网关可达性探针 -`publicExposure.enabled=true`,确认 YAML 声明的 public path 可用。FRP target 检查 FRP pathPK01 local target 检查 PK01 Caddy managed block 和 loopback upstream。未带 key 的 public `/v1/models` 401 只能证明网关可达,不能证明账号池可调度
- 多 target 同时启用 public exposure 时,必须分别验证每个 target 的 root、`/health`、未带 key `/v1/models` 401,以及各自 `codex-pool validate --target <id>`;一个域名可用不能替代另一个域名的验收。 - 多 target 同时启用 public exposure 时,必须分别验证每个 target 的 root、`/health`、未带 key `/v1/models` 401,以及各自 `codex-pool validate --target <id>`;一个域名可用不能替代另一个域名的验收。
- 若目标声明了 `egressProxy.enabled=true`,确认 proxy Deployment/Service readySub2API 和 sentinel env 与 YAML 对齐,并通过 YAML 声明的 health URL 完成代理出站探针。 - 若目标声明了 `egressProxy.enabled=true`,确认 proxy Deployment/Service readySub2API 和 sentinel env 与 YAML 对齐,并通过 YAML 声明的 health URL 完成代理出站探针。
@@ -246,22 +251,22 @@ bun scripts/cli.ts platform-infra sub2api codex-pool configure-local --confirm
## 排障 ## 排障
- `api.pikapython.com` 或 D601 public exposure 返回 502 时,先判定是 edge 还是 app endpoint`sub2api status --target D601``sub2api validate --target D601``sub2api``sub2api-frpc``sub2api-redis``sub2api-egress-proxy` 出现 `0/1`,或 validate 显示 `no endpoints available for service "sub2api"` / app Pod 已终止,先用 `bun scripts/cli.ts platform-infra sub2api apply --target D601 --confirm` 重新收敛 YAML 资源,按返回的 `job status` 轮询,再跑 `status``validate` `codex-pool validate --target D601`。不要先改账号池、哨兵状态、Secret 或 Caddy。 - `api.pikapython.com` 返回 502/503 时,先按 YAML 判定 target 和 failure layer。PK01 host-Docker target 先`sub2api status --target PK01``sub2api validate --target PK01`,再分别检查 PK01 Caddy managed block、loopback app health、Docker container health、admin account availability 和最小 public `/v1/responses` smoke。k3s/FRP target 先跑对应 `sub2api status --target <id>``validate --target <id>``sub2api``sub2api-frpc``sub2api-redis``sub2api-egress-proxy` 出现 `0/1`,或 validate 显示 `no endpoints available for service "sub2api"` / app Pod 已终止,先用 `bun scripts/cli.ts platform-infra sub2api apply --target <id> --confirm` 重新收敛 YAML 资源,按返回的 `job status` 轮询,再跑 `status``validate`可用的 Codex-pool 验证。不要先改账号池、哨兵状态、Secret 或 Caddy。
- D601 快速恢复完成后,用分层证据 closeout:`https://api.pikapython.com/health` 应返回 200`codex-pool validate --target D601` 应证明内部 `GET /v1/models` 和最小 `POST /v1/responses` smoke 成功;若需要证明公网 OpenAI-compatible API,用 `trans D601:k3s sh``platform-infra/sub2api-codex-pool-api-key.API_KEY` 只读到临时 shell 变量后请求 public `/v1/models` 和最小 `/v1/responses` marker只输出 HTTP status、模型数量marker,不打印 key。不要为了公网验证运行 `configure-local --confirm`,它会重写本机 `~/.codex`;本机默认 `auth.json` key 返回 401 只能说明本机配置和公网统一 key 不一致,不能当作服务不可用证据。 - 快速恢复完成后,用分层证据 closeout:目标 public `/health` 应返回 200;最小公网 `/v1/responses` marker 应使用统一 key 或明确用户 key 返回 200只输出 HTTP status、模型数量marker、account id/group id 和 key fingerprint,不打印 key。不要为了公网验证运行 `configure-local --confirm`,它会重写本机 `~/.codex`;本机默认 `auth.json` key 返回 401 只能说明本机配置和公网统一 key 不一致,不能当作服务不可用证据。
- Codex pool 哨兵、账号冻结/恢复、marker-only 判断或 probe 周期看不清:第一步跑 `bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-report`。这个报表是主观察面;只有报表缺字段或需要底层证据时,才继续看 `--raw`、CronJob log、state ConfigMap 或 Sub2API 管理 UI。若看到“临时不可调度状态”且包含规则序号/匹配关键词,检查 Sub2API `account_temp_unschedulable` 日志和账号 `temp_unschedulable_*` 字段;sentinel 只解释 `schedulable=false` 的 active quarantine,不解释这类内置临时冷却。 - Codex pool 哨兵、账号冻结/恢复、marker-only 判断或 probe 周期看不清:第一步跑 `bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-report`。这个报表是主观察面;只有报表缺字段或需要底层证据时,才继续看 `--raw`、CronJob log、state ConfigMap 或 Sub2API 管理 UI。若看到“临时不可调度状态”且包含规则序号/匹配关键词,检查 Sub2API `account_temp_unschedulable` 日志和账号 `temp_unschedulable_*` 字段;sentinel 只解释 `schedulable=false` 的 active quarantine,不解释这类内置临时冷却。
- 只加强监控、不让哨兵自动冻结账号时,把 YAML `sentinel.actions.enabled=false``codex-pool sync --confirm`。此时 marker probe 和 gateway failure monitor 仍记录 `would-freeze` / observe-only 证据,但不会通过 Sub2API admin 写 `schedulable=false``/responses/compact``codex.remote_compact.failed` 和 compact 上游 5xx failover 只作为 `gateway-compact-*` 观察事件记录,不作为哨兵自动切换触发器。 - 只加强监控、不让哨兵自动冻结账号时,把 YAML `sentinel.actions.enabled=false``codex-pool sync --confirm`。此时 marker probe 和 gateway failure monitor 仍记录 `would-freeze` / observe-only 证据,但不会通过 Sub2API admin 写 `schedulable=false``/responses/compact``codex.remote_compact.failed` 和 compact 上游 5xx failover 只作为 `gateway-compact-*` 观察事件记录,不作为哨兵自动切换触发器。
- 单个 request id 报 502/503/中断/没有自动切号:第一步跑 `bun scripts/cli.ts platform-infra sub2api codex-pool trace --request-id <requestId>`。先看 `outcome``reason``FAILOVER``SELECT-FAILED``ACCOUNT SIGNALS``WINDOW STATS`;只有 trace 报表缺字段或需要审计原始日志时,才加 `--show-lines``--raw`。若 `reason=failover-attempted-no-candidate`,说明切号动作已发生,但 scheduler 在排除失败账号后没有可用候选;继续用 `sentinel-report``validate --full` 区分 sentinel quarantine、request-path temp-unschedulable、账号 status 或容量耗尽。 - 单个 request id 报 502/503/中断/没有自动切号:第一步跑 `bun scripts/cli.ts platform-infra sub2api codex-pool trace --request-id <requestId>`。先看 `outcome``reason``FAILOVER``SELECT-FAILED``ACCOUNT SIGNALS``WINDOW STATS`;只有 trace 报表缺字段或需要审计原始日志时,才加 `--show-lines``--raw`。若 `reason=failover-attempted-no-candidate`,说明切号动作已发生,但 scheduler 在排除失败账号后没有可用候选;继续用 `sentinel-report``validate --full` 区分 sentinel quarantine、request-path temp-unschedulable、账号 status 或容量耗尽。
- profile invalid:先修 `~/.codex/config.toml.<profile>``base_url``wire_api``model``auth.json.<profile>` 的 API key;不要在 YAML 中写密钥。 - profile invalid:先修 `~/.codex/config.toml.<profile>``base_url``wire_api``model``auth.json.<profile>` 的 API key;不要在 YAML 中写密钥。
- 手动 OAuth/API-key 账号的 WebUI account test 连 `chatgpt.com` 超时,但同一 Pod 显式 HTTP proxy 探针可通:不要只看 Pod `HTTP_PROXY` env,按“受保护手动账号代理与分组绑定”小节确认 `manualAccounts.protected[].proxyBinding`,跑 `codex-pool sync --target D601 --confirm` 后再用原账号测试复测。若复测不再 reset/timeout,而是 `gpt-5.2-pro` 这类指定模型返回 ChatGPT OAuth Codex 不支持的能力错误,用默认/受支持模型或统一 key smoke 验证代理,不要把模型错误当作代理仍坏。 - 手动 OAuth/API-key 账号的 WebUI account test 连 `chatgpt.com` 超时,但同一 Pod 显式 HTTP proxy 探针可通:不要只看 Pod `HTTP_PROXY` env,按“受保护手动账号代理与分组绑定”小节确认 `manualAccounts.protected[].proxyBinding`,跑 `codex-pool sync --target D601 --confirm` 后再用原账号测试复测。若复测不再 reset/timeout,而是 `gpt-5.2-pro` 这类指定模型返回 ChatGPT OAuth Codex 不支持的能力错误,用默认/受支持模型或统一 key smoke 验证代理,不要把模型错误当作代理仍坏。
- 手动 OAuth/API-key 账号 WebUI account test 正常,但 PC Codex 客户端通过统一 key 访问 `/responses` 返回 503 且 trace 是 `account-select-failed` / `no available accounts`:按“受保护手动账号代理与分组绑定”小节确认 `manualAccounts.protected[].groupBinding.source: pool-group`,跑 `codex-pool sync --target D601 --confirm` 后用 `codex-pool validate --target D601 --full` 复测统一 key。 - 手动 OAuth/API-key 账号 WebUI account test 正常,但 PC Codex 客户端通过统一 key 访问 `/responses` 返回 503 且 trace 是 `account-select-failed` / `no available accounts`:按“受保护手动账号代理与分组绑定”小节确认该账号已绑定统一 key 使用的 pool group。WebUI group 列表和账号详情不一定足以证明 scheduler 可调度;必要时核对 admin account availability 与 `account_groups` join。k3s target 通过 `codex-pool sync --target <id> --confirm` 后用 `codex-pool validate --target <id> --full` 复测统一 keyPK01 host-Docker 在 sync/validate adapter 补齐前,用最小 admin API/DB evidence 恢复并以 public `/v1/responses` smoke 验收
- Sub2API 卡在 `wait-postgres` / `wait-redis` 或服务内大量 `context deadline exceeded`:先跑 `sub2api status``networkPolicy.ok`,再跑 `sub2api validate``postgresCrossPodPgIsReady` / `redisCrossPodPing`;缺失或异常时用 `sub2api apply --confirm` 恢复受控 `NetworkPolicy/allow-all`,不要保留手工 iptables bypass 作为长期修复。 - Sub2API 卡在 `wait-postgres` / `wait-redis` 或服务内大量 `context deadline exceeded`:先跑 `sub2api status``networkPolicy.ok`,再跑 `sub2api validate``postgresCrossPodPgIsReady` / `redisCrossPodPing`;缺失或异常时用 `sub2api apply --confirm` 恢复受控 `NetworkPolicy/allow-all`,不要保留手工 iptables bypass 作为长期修复。
- pool key 401:跑 `codex-pool sync --confirm` 重建 Sub2API key 与 k3s Secret 绑定,再跑 `codex-pool validate` - pool key 401:跑 `codex-pool sync --confirm` 重建 Sub2API key 与 k3s Secret 绑定,再跑 `codex-pool validate`
- pool key、admin password 或 k8s Secret `.data` 被 stdout、日志、issue 或本地 transcript 打印时,按泄露处理:撤销对应 Sub2API key 或 token,删除/重建受影响的 target Secret,通过 `codex-pool sync --target <id> --confirm` 或相应 YAML sourceRef 重新下发,再用 fingerprint、presence 和 `valuesPrinted=false` 作为 closeout 证据;不要复述旧值或新值。 - pool key、admin password 或 k8s Secret `.data` 被 stdout、日志、issue 或本地 transcript 打印时,按泄露处理:撤销对应 Sub2API key 或 token,删除/重建受影响的 target Secret,通过 `codex-pool sync --target <id> --confirm` 或相应 YAML sourceRef 重新下发,再用 fingerprint、presence 和 `valuesPrinted=false` 作为 closeout 证据;不要复述旧值或新值。
- 运行中过去的验证探针残留:只用 `codex-pool cleanup-probes --confirm` 清理 `unidesk-probe-*` 临时资源;不要把真实 managed account 删除当作探针清理或可用性恢复。 - 运行中过去的验证探针残留:只用 `codex-pool cleanup-probes --confirm` 清理 `unidesk-probe-*` 临时资源;不要把真实 managed account 删除当作探针清理或可用性恢复。
- FRP 不通:先看 `codex-pool expose --confirm` 输出的 `masterFrps``masterCaddy``sub2api-frpc` 和 public 401 probe;需要低层证据时只用 `trans G14:k3s` 做 bounded 查询。 - FRP 不通:先看 `codex-pool expose --confirm` 输出的 `masterFrps``masterCaddy``sub2api-frpc` 和 public 401 probe;需要低层证据时只用 `trans G14:k3s` 做 bounded 查询。
- D601 external-active `api.pikapython.com` 不通:先区分 DNS/TLS/Caddy/FRP/Sub2API。DNS 未解析到 YAML 声明的 PK01 地址时,Caddy ACME 会失败,`https://api.pikapython.com` 不能算完成;可用 PK01 loopback FRP 端口和 PK01 公网 remotePort 证明 D601 FRP 数据路径,但最终仍要等 DNS 生效后重跑 HTTPS health、`/v1/models``/v1/responses` - k3s external-active target 的 public URL 不通:先区分 DNS/TLS/Caddy/FRP/Sub2API。DNS 未解析到 YAML 声明的 PK01 地址时,Caddy ACME 会失败,HTTPS 不能算完成;可用 PK01 loopback FRP 端口和 PK01 公网 remotePort 证明 FRP 数据路径,但最终仍要等 DNS 生效后重跑 HTTPS health、`/v1/models``/v1/responses`PK01 host-Docker local target 不走 FRP,不能用 FRP 端口探针替代本机 loopback/Caddy/app 验证。
- D601 external-active apply 后其他 PK01 HTTPS 服务消失:优先怀疑共享 Caddy managed block 合并失败或旧整文件写入路径复现。用受控 Sub2API apply 输出和 PK01 Caddy managed block markers 取证,再通过各服务自己的 YAML apply/public-exposure 入口恢复;不要手工复制某一份 Caddyfile 作为长期修复。 - D601 external-active apply 后其他 PK01 HTTPS 服务消失:优先怀疑共享 Caddy managed block 合并失败或旧整文件写入路径复现。用受控 Sub2API apply 输出和 PK01 Caddy managed block markers 取证,再通过各服务自己的 YAML apply/public-exposure 入口恢复;不要手工复制某一份 Caddyfile 作为长期修复。
- Caddy 下载慢或失败:先确认 `config/platform-infra/sub2api.yaml` 已设置 `publicExposure.pk01.caddyDownloadProxyUrl`,并重跑 `sub2api apply --target D601 --confirm` 看 PK01 apply summary 中的 `downloadProxy.mode=curl-proxy`。不要反复裸连 GitHub release。 - Caddy 下载慢或失败:先确认 `config/platform-infra/sub2api.yaml`为对应 target 设置 `publicExposure.pk01.caddyDownloadProxyUrl`,并重跑 `sub2api apply --target <id> --confirm` 看 PK01 apply summary 中的 `downloadProxy.mode=curl-proxy`。不要反复裸连 GitHub release。
- `/responses/compact` 在接近 master Caddy `response_header_timeout` 的固定时长后返回 504,或 Sub2API 日志稍后记录 `codex.remote_compact.succeeded` 时,优先检查 master Caddy `response_header_timeout` 是否由 YAML `publicExposure.masterCaddy.responseHeaderTimeoutSeconds` 渲染,修正后跑 `codex-pool expose --confirm`;这类边缘代理超时不会触发 Sub2API 账号级临时下线。reload 前已经在途的 compact 请求仍可能按旧 timeout 结束,判断修复是否生效时只看 reload 之后新发起的请求。 - `/responses/compact` 在接近 master Caddy `response_header_timeout` 的固定时长后返回 504,或 Sub2API 日志稍后记录 `codex.remote_compact.succeeded` 时,优先检查 master Caddy `response_header_timeout` 是否由 YAML `publicExposure.masterCaddy.responseHeaderTimeoutSeconds` 渲染,修正后跑 `codex-pool expose --confirm`;这类边缘代理超时不会触发 Sub2API 账号级临时下线。reload 前已经在途的 compact 请求仍可能按旧 timeout 结束,判断修复是否生效时只看 reload 之后新发起的请求。
- `/responses/compact` 或普通 public URL 在几秒窗口内出现 502Caddy 日志显示 `dial tcp 127.0.0.1:<remotePort>: connect: connection refused``EOF``connection reset by peer`,同时 frps 日志出现 `platform-infra-sub2api proxy closing` / `listener is closed` / `new proxy ... success`,说明失败在 master Caddy 与 FRP remotePort 边缘层,Sub2API 和 sentinel 可能完全看不到。先确认 `publicExposure.masterCaddy.edgeRetry` 已按 YAML 渲染并 `codex-pool expose --confirm` 生效;若仍频繁发生,再继续查 G14 `sub2api-frpc` 到 master `frps` 的控制连接稳定性。不要把这类边缘 502 误判成账号池上游错误,也不要通过禁用账号恢复。 - `/responses/compact` 或普通 public URL 在几秒窗口内出现 502Caddy 日志显示 `dial tcp 127.0.0.1:<remotePort>: connect: connection refused``EOF``connection reset by peer`,同时 frps 日志出现 `platform-infra-sub2api proxy closing` / `listener is closed` / `new proxy ... success`,说明失败在 master Caddy 与 FRP remotePort 边缘层,Sub2API 和 sentinel 可能完全看不到。先确认 `publicExposure.masterCaddy.edgeRetry` 已按 YAML 渲染并 `codex-pool expose --confirm` 生效;若仍频繁发生,再继续查 G14 `sub2api-frpc` 到 master `frps` 的控制连接稳定性。不要把这类边缘 502 误判成账号池上游错误,也不要通过禁用账号恢复。
- default profile 递归:检查 YAML default entry 是否使用 `*.pre-sub2api` 备份文件;必要时恢复备份后重新 `configure-local --confirm` - default profile 递归:检查 YAML default entry 是否使用 `*.pre-sub2api` 备份文件;必要时恢复备份后重新 `configure-local --confirm`
+4 -2
View File
@@ -66,11 +66,13 @@ PK01 may act as the public Caddy edge for several YAML-declared UniDesk services
If one public service fails while other services still work, restore the missing route through that service's YAML-controlled apply/public-exposure command, not by replacing the whole Caddyfile. A diagnostic check may list managed block markers with `grep '^# BEGIN unidesk managed ' /etc/caddy/Caddyfile` and run `sudo caddy validate --config /etc/caddy/Caddyfile`, but those checks are evidence only; durable repair belongs to the owning CLI. If one public service fails while other services still work, restore the missing route through that service's YAML-controlled apply/public-exposure command, not by replacing the whole Caddyfile. A diagnostic check may list managed block markers with `grep '^# BEGIN unidesk managed ' /etc/caddy/Caddyfile` and run `sudo caddy validate --config /etc/caddy/Caddyfile`, but those checks are evidence only; durable repair belongs to the owning CLI.
In this mode, public ports `80` and `443` belong to Caddy. The existing `pikanode` container must be bound to a loopback HTTP port and used only as the apex PikaPython/PikaNode upstream. The `api.pikapython.com` site must reverse proxy directly to the YAML-declared FRP remote port, so API traffic follows `client -> PK01 Caddy -> PK01 frps remote port -> D601 frpc -> D601 Sub2API`. It must not pass through pikanode or a master-server reverse proxy. In this mode, public ports `80` and `443` belong to Caddy. The existing `pikanode` container must be bound to a loopback HTTP port and used only as the apex PikaPython/PikaNode upstream. Public platform routes must be read from their owning YAML. For the current Sub2API PK01 host-Docker target, `api.pikapython.com` uses `publicExposure.mode=pk01-local` and API traffic follows `client -> PK01 Caddy -> 127.0.0.1:<YAML local upstream port> -> PK01 host-Docker Sub2API`. For k3s external-active targets, the route may instead be `client -> PK01 Caddy -> PK01 frps remote port -> target frpc -> target Sub2API`. Neither path may pass through pikanode or a master-server reverse proxy.
Caddy binary installation is also YAML-controlled. If `publicExposure.pk01.caddyDownloadProxyUrl` is set, PK01 Caddy downloads must use that proxy URL; the PK01 loopback provider egress proxy is the preferred source. A slow or failing Caddy download should first be treated as missing proxy use, not as a reason to keep retrying a naked GitHub release download. Caddy binary installation is also YAML-controlled. If `publicExposure.pk01.caddyDownloadProxyUrl` is set, PK01 Caddy downloads must use that proxy URL; the PK01 loopback provider egress proxy is the preferred source. A slow or failing Caddy download should first be treated as missing proxy use, not as a reason to keep retrying a naked GitHub release download.
The public certificate depends on DNS. The `api.pikapython.com` record must resolve to the YAML-declared PK01 public address before Caddy can complete ACME issuance. If the DNS record is absent or stale, local probes such as PK01 `127.0.0.1:<frp-remote-port>` and public-IP remote-port checks can prove the FRP data path, but final `https://api.pikapython.com` validation remains blocked until DNS is corrected. The public certificate depends on DNS. The `api.pikapython.com` record must resolve to the YAML-declared PK01 public address before Caddy can complete ACME issuance. If the DNS record is absent or stale, local probes such as PK01 loopback app health checks, loopback Caddy `--resolve` checks, or target-specific FRP remote-port checks can prove parts of the data path, but final `https://api.pikapython.com` validation remains blocked until DNS is corrected.
For PK01 host-Docker Sub2API, `platform-infra sub2api status --target PK01` and `validate --target PK01` are the first-line app and edge checks. If a unified OpenAI-compatible key returns `account_select_failed` or `no available accounts` while a Sub2API WebUI account test succeeds, distinguish account test from gateway scheduling: the WebUI test loads one account directly, while gateway traffic uses the API key's group and the `account_groups` join table. Check the admin account availability view and the group membership for the target accounts, and verify public `/v1/responses` with a key fingerprint only. Do not print admin passwords, API keys, or account credentials while diagnosing this path.
## Host PostgreSQL ## Host PostgreSQL
+5 -5
View File
@@ -1,6 +1,6 @@
# Platform Infra # Platform Infra
`platform-infra` is the k3s namespace for UniDesk-operated shared platform services. Runtime placement is service-specific and YAML-selected. For Sub2API, D601 is the active externally backed target and G14 is a predeployed standby target scaled to zero; other platform services may still declare G14 as their active runtime in their own YAML. It is separate from HWLAB runtime lanes, AgentRun lanes, D601 user services, and legacy `devops-infra` control-plane helpers. New shared infra should land here first; old `devops-infra` resources migrate gradually only when a concrete owner and validation path exists. `platform-infra` is the shared UniDesk-operated platform service area. Runtime placement is service-specific and YAML-selected: some services run in a k3s `platform-infra` namespace, while host-Docker services such as the current PK01 Sub2API target still belong to the same platform-infra control surface. For Sub2API, the active target is selected by `config/platform-infra/sub2api.yaml` and may be a host-Docker or k3s target; G14 remains a predeployed standby target scaled to zero. Other platform services may still declare G14 or another node as their active runtime in their own YAML. It is separate from HWLAB runtime lanes, AgentRun lanes, D601 user services, and legacy `devops-infra` control-plane helpers. New shared infra should land here first; old `devops-infra` resources migrate gradually only when a concrete owner and validation path exists.
## Source Of Truth ## Source Of Truth
@@ -22,14 +22,14 @@
## Sub2API Deployment Boundary ## Sub2API Deployment Boundary
- Sub2API is a platform service operated by UniDesk in namespace `platform-infra`. It is not a HWLAB lane workload, AgentRun workload, D601 user service, or master server daemon. - Sub2API is a platform service operated by UniDesk through the `platform-infra sub2api` control surface. It is not a HWLAB lane workload, AgentRun workload, D601 user service, or master server daemon.
- The canonical deployment entrypoint is `bun scripts/cli.ts platform-infra sub2api plan|apply|status|validate|codex-pool`. Runtime targets are selected with `--target`; the Sub2API active target is the target whose YAML role/database mode enables active replicas, currently `D601`, and `G14` is kept as a standby predeploy. Daily operation procedures live in `$unidesk-sub2api` at `.agents/skills/unidesk-sub2api/SKILL.md`. This reference keeps only development boundaries and project-specific source-of-truth rules. - The canonical deployment entrypoint is `bun scripts/cli.ts platform-infra sub2api plan|apply|status|validate|codex-pool`. Runtime targets are selected with `--target`; the Sub2API active target is the target whose YAML role/database mode enables active replicas. Daily operation procedures live in `$unidesk-sub2api` at `.agents/skills/unidesk-sub2api/SKILL.md`. This reference keeps only development boundaries and project-specific source-of-truth rules.
- Raw `kubectl` through `trans <target>:k3s` is only for bounded diagnosis and evidence, not a formal mutate path. - Raw `kubectl` through `trans <target>:k3s` is only for bounded diagnosis and evidence, not a formal mutate path.
- The image version is controlled by `config/platform-infra/sub2api.yaml`. Image update procedures are daily operations owned by `$unidesk-sub2api`; the development boundary is that image choices remain YAML-controlled. - The image version is controlled by `config/platform-infra/sub2api.yaml`. Image update procedures are daily operations owned by `$unidesk-sub2api`; the development boundary is that image choices remain YAML-controlled.
- Sub2API should stay ClusterIP-only by default. Do not add Ingress, NodePort, LoadBalancer, or broad FRP exposure unless a YAML-controlled public exposure decision exists. - k3s Sub2API targets should stay ClusterIP-only by default. Host-Docker targets should bind app ports to loopback or a YAML-declared host interface and use a managed edge such as PK01 Caddy for public HTTPS. Do not add Ingress, NodePort, LoadBalancer, hostPort, or broad FRP exposure unless a YAML-controlled public exposure decision exists.
- Sub2API currently has no resource limits by design. Do not add CPU or memory limits unless a later explicit decision changes that policy and stores the new policy in YAML. - Sub2API currently has no resource limits by design. Do not add CPU or memory limits unless a later explicit decision changes that policy and stores the new policy in YAML.
- Master server is a consumer/control host, not the runtime location. Do not deploy Sub2API, PostgreSQL, Redis, or heavy validation loops on master server. - Master server is a consumer/control host, not the runtime location. Do not deploy Sub2API, PostgreSQL, Redis, or heavy validation loops on master server.
- Sub2API active/standby placement is selected by YAML, not by ad hoc runtime patches. A standby target must render without a local PostgreSQL StatefulSet, keep the Sub2API app and local Redis cache scaled to zero, use only ephemeral Redis storage if Redis is later activated, and omit public FRP, HTTPS egress proxy, and account sentinel resources unless YAML explicitly promotes that target. An externally backed active target connects directly to the YAML-declared external PostgreSQL endpoint with `sslmode=require`, keeps durable app state outside the k3s node, and uses local Redis only as ephemeral cache. Multiple externally backed active targets may coexist when YAML declares distinct target ids, host routes, public URLs, FRP remote ports and Secret sources; target-scoped operations must use `--target <id>` and must not treat one target's URL or Secret as a fallback for another. Promotion or failback must be applied by editing `config/platform-infra/sub2api.yaml` and running the same `platform-infra sub2api --target <id>` CLI path. - Sub2API active/standby placement is selected by YAML, not by ad hoc runtime patches. A standby target must render without a local PostgreSQL StatefulSet, keep the Sub2API app and local Redis cache scaled to zero, use only ephemeral Redis storage if Redis is later activated, and omit public exposure, HTTPS egress proxy, and account sentinel resources unless YAML explicitly promotes that target. An externally backed active target connects directly to the YAML-declared external PostgreSQL endpoint with `sslmode=require`, keeps durable app state outside the runtime node, and uses local Redis only as ephemeral cache. Host-Docker active targets such as PK01 are still Sub2API platform targets, but k3s-only Codex-pool helper paths must not be assumed to work there until the CLI implements host-Docker adapters. Multiple externally backed active targets may coexist when YAML declares distinct target ids, host routes, public URLs, FRP remote ports or local edge bindings, and Secret sources; target-scoped operations must use `--target <id>` and must not treat one target's URL or Secret as a fallback for another. Promotion or failback must be applied by editing `config/platform-infra/sub2api.yaml` and running the same `platform-infra sub2api --target <id>` CLI path.
- External platform PostgreSQL endpoints for Sub2API are produced by the platform DB YAML and its `platform-db postgres` CLI. Cross-node Sub2API consumers connect directly to that endpoint; the master server is not a PostgreSQL data-plane relay. DNS aliases are optional when the exported `DATABASE_URL` uses a reachable IP with `sslmode=require`; current PK01-specific rules live in `docs/reference/pk01.md`. - External platform PostgreSQL endpoints for Sub2API are produced by the platform DB YAML and its `platform-db postgres` CLI. Cross-node Sub2API consumers connect directly to that endpoint; the master server is not a PostgreSQL data-plane relay. DNS aliases are optional when the exported `DATABASE_URL` uses a reachable IP with `sslmode=require`; current PK01-specific rules live in `docs/reference/pk01.md`.
- Sub2API account sentinel, public exposure, and HTTPS egress proxy are target-scoped YAML decisions. The active target may run them when YAML enables them; the standby G14 target must stay deployed but inactive until YAML promotion. `sentinel.enabledOnTargets` is the authority for where Codex-pool sentinel image, CronJob, Secret and state resources are expected; disabled targets should report sentinel validation as skipped instead of failing on missing runtime sentinel objects. Do not create a second sentinel, FRP client, public management surface, or edge proxy by hand; enable or move those resources only through the target YAML and the `platform-infra sub2api` / `codex-pool --target` CLI paths. - Sub2API account sentinel, public exposure, and HTTPS egress proxy are target-scoped YAML decisions. The active target may run them when YAML enables them; the standby G14 target must stay deployed but inactive until YAML promotion. `sentinel.enabledOnTargets` is the authority for where Codex-pool sentinel image, CronJob, Secret and state resources are expected; disabled targets should report sentinel validation as skipped instead of failing on missing runtime sentinel objects. Do not create a second sentinel, FRP client, public management surface, or edge proxy by hand; enable or move those resources only through the target YAML and the `platform-infra sub2api` / `codex-pool --target` CLI paths.