21 KiB
name, description
| name | description |
|---|---|
| unidesk-sub2api | UniDesk Sub2API 平台运维技能。用户提到 Sub2API、sub2api、platform-infra sub2api、Codex pool、统一 API key、Sub2API FRP 暴露、Sub2API 管理 UI、配置 master ~/.codex 走 Sub2API、添加/删除 Codex 上游账号、校验 Sub2API /v1/models 时使用。 |
UniDesk Sub2API
UniDesk 在 G14 k3s platform-infra namespace 运维 Sub2API。日常操作统一使用 UniDesk CLI,不直接写 Kubernetes 资源或手工调用 Sub2API 管理 API。
固定入口: cd /root/unidesk && bun scripts/cli.ts platform-infra sub2api ...
先读边界
- 仓库长期开发边界见
docs/reference/platform-infra.md,本 skill 承担日常操作手册。 - 配置真相是 YAML:
config/platform-infra/sub2api.yaml和config/platform-infra/sub2api-codex-pool.yaml。 - 本 skill 目录下若存在
agents/*.yaml,只作为 skill/agent 展示与调用元数据,不是 Sub2API 或 Codex pool 运行配置;不要在 skill 目录维护第二份账号、capacity、priority、endpoint 或 Secret 配置。 - Runtime 在
G14:k3s的platform-infranamespace;master server 只是控制端和消费者,不部署 Sub2API/PostgreSQL/Redis。 - Secret、
~/.codex/config.toml*、~/.codex/auth.json*是运行时输入或本地状态,不提交。 - 默认
~/.codex/config.toml和~/.codex/auth.json只作为统一 Sub2API consumer 使用;config.toml必须指向https://sub2api.74-48-78-17.nip.io/,auth.json必须使用统一 pool API key。新增上游账号不得覆盖这两个默认文件,只能新增config.toml.<profile>/auth.json.<profile>并在 YAML 里声明。 - 输出只能包含 Secret 路径、长度、preview/fingerprint;禁止打印完整 API key、admin password、JWT secret、TOTP key。
部署与状态
bun scripts/cli.ts platform-infra sub2api plan
bun scripts/cli.ts platform-infra sub2api apply --dry-run
bun scripts/cli.ts platform-infra sub2api apply --confirm
bun scripts/cli.ts platform-infra sub2api status
bun scripts/cli.ts platform-infra sub2api validate
plan读取config/platform-infra/sub2api.yaml,渲染src/components/platform-infra/sub2api/sub2api.k8s.yaml,检查 no Ingress/NodePort/LoadBalancer/hostPort/hostNetwork/resource limits。apply --confirm默认创建异步 job;按返回的job status命令轮询,再跑status和validate。status --full|--raw只在需要展开远端 stdout/stderr 或原始 JSON 时使用。validate是按需验收,不是连续可用性探针。
镜像升级
- 修改
config/platform-infra/sub2api.yaml的image.repository、image.tag或pullPolicy。 - 执行
sub2api plan,确认策略检查通过。 - 执行
sub2api apply --confirm,轮询 job 完成。 - 执行
sub2api status,确认运行镜像等于 YAML 声明。 - 执行
sub2api validate或codex-pool validate做入口验收。
不要把镜像版本写进脚本常量、JSON 或 manifest 模板。
Codex Pool
bun scripts/cli.ts platform-infra sub2api codex-pool plan
bun scripts/cli.ts platform-infra sub2api codex-pool sync --confirm
bun scripts/cli.ts platform-infra sub2api codex-pool validate
bun scripts/cli.ts platform-infra sub2api codex-pool cleanup-probes --confirm
config/platform-infra/sub2api-codex-pool.yaml 控制:
pool.groupName: Sub2API group 名称。pool.apiKeySecretName/pool.apiKeySecretKey: 统一消费 API key 的 k3s Secret 位置,默认platform-infra/sub2api-codex-pool-api-key.API_KEY。pool.minOwnerBalanceUsd: pool key owner 最低余额,sync/validate 会补齐。pool.minOwnerConcurrency: 可选统一消费 API key owner 最低并发;省略时 CLI 自动使用所有已解析账号 capacity 的总和,sync/validate 会补齐。显式 YAML 值只作为 override,仍必须不小于账号 capacity 总和;未显式写profiles.entries[].capacity的账号会使用pool.defaultAccountCapacity参与求和,不要用提高某个 provider capacity 来掩盖用户并发层 WS 1013。pool.defaultTempUnschedulable: 默认账号级临时下线规则;只声明 Sub2API 已支持的错误路径能力,用于在上游返回容量、限流、overload、service unavailable、gateway timeout、稳定模型路由错误或认证状态异常时,让 Sub2API 冷却该账号并切换到同组其他账号。不要用 YAML、UniDesk CLI、k8s 热补或本地 fork 魔改 Sub2API 不支持的行为。- 自动冻结/切号失败时,必须修复
temp_unschedulable与 failover 机制本身,并用运行时证据证明失败账号被临时冻结且请求切到其他可调度账号;禁止通过手动禁用账号、删除账号、移除 YAML entry、降低 membership 或临时改调度策略来替代自动恢复。只有明确的上游退役或所有权变更才走删除/禁用上游流程。 - YAML 只选择和配置 Codex 上游,不声明
schedulable长期字段;schedulable=true只能作为codex-pool sync --confirm的过程控制基线恢复。自动冻结必须表现为temp_unschedulable_until/temp_unschedulable_reason,避免把永久不可调度误当成自动冻结。 profiles.entries: 从 master~/.codex/选择上游 profile 并映射到 Sub2API account。profiles.entries[].capacity: 可选 per-account concurrency override;不写则使用pool.defaultAccountCapacity。具体数值只以config/platform-infra/sub2api-codex-pool.yaml为准,skill 和长期参考只描述规则,不重复写当前值。profiles.entries[].loadFactor: 可选 per-account Sub2APIload_factoroverride;不写则使用pool.defaultAccountLoadFactor。具体数值只以config/platform-infra/sub2api-codex-pool.yaml为准,修改后必须codex-pool sync --confirm和codex-pool validate。- 除非用户明确要求修改配置,不要仅凭推断改账号 membership、priority、capacity、loadFactor、WebSocket mode 或其他调度策略;先保留 YAML,完成 provenance/runtime evidence 溯源,并把结论写回相关 issue 或 runbook 后再提出变更。
profiles.entries[].tempUnschedulable: 可选 per-account 临时下线规则覆盖;字段语义以docs/reference/platform-infra.md为权威。上游 Sub2API 不支持的成功体分类、调度策略或账号冷却行为不要在这里声明。profiles.entries[].openaiResponsesWebSocketsV2Mode: 需要 Responses WebSocket v2 的上游才设置,值为off、ctx_pool或passthrough。profiles.entries[].upstreamUserAgent: 少数要求 Codex CLI User-Agent 的上游才设置,不能含换行。
sync --confirm 会登录 Sub2API admin、创建/更新 group、创建/更新 YAML 中的 unidesk-codex-* accounts、创建/复用统一 API key Secret,并把 managed account 的 schedulable=true 恢复为过程控制基线;它默认不删除 YAML 中缺席的 managed account。只有明确退役上游时才使用 sync --confirm --prune-removed 删除缺席且 extra.unidesk_managed=true 的 unidesk-codex-* account。
sync --confirm 和 validate 可能超过单次 SSH/runtime 短连接窗口。必须继续使用 bun scripts/cli.ts platform-infra sub2api codex-pool ...,由 CLI 在 G14 远端提交作业并短轮询状态;不要改用裸 trans G14:k3s script 等一个长连接等待完整结果。若看到 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。
WebSocket v2 是账号能力集合,不是调度 pin。openaiResponsesWebSocketsV2Mode 只声明该账号可承担 Codex Responses WSv2 链路;只有 localCodex.supportsWebSockets=true / localCodex.responsesWebSocketsV2=true 时,codex-pool validate 才必须看到至少一个 webSocketsV2.schedulableEnabled 账号。真实可用性仍以 direct Codex WSv2 probe、Sub2API 日志和原入口 Codex smoke 为准。
Codex 启动时反复出现 WebSocket reconnect、HTTPS fallback、websocket closed by server before response.completed,或 Sub2API 日志出现 openai.websocket_proxy_failed / openai.websocket_account_select_failed / 上游 WS handshake 4xx/5xx 时,先按运行证据定位具体 account 和 transport。若账号的 WSv2 握手失败,优先只在 YAML 中把该账号的 openaiResponsesWebSocketsV2Mode 收敛为 off;若没有任何 direct Codex WSv2 probe 通过,则同时把 localCodex.supportsWebSockets 与 localCodex.responsesWebSocketsV2 收敛为 false,再 codex-pool sync --confirm。不要顺手改 membership、priority、capacity、Secret 或代码 fallback。
添加上游
- 在 master
~/.codex/准备带后缀的上游 profile 文件,例如config.toml.<profile>和auth.json.<profile>;禁止覆盖默认config.toml/auth.json。 - 在
config/platform-infra/sub2api-codex-pool.yaml添加profiles.entries项,指定profile、accountName、configFile、authFile。 - 如需要,给该项加
priority、capacity、loadFactor、tempUnschedulable、openaiResponsesWebSocketsV2Mode或upstreamUserAgent;capacity/loadFactor 的具体数值只写在 YAML。 - 如果新增账号会提高声明 capacity 总和,同步提高
pool.minOwnerConcurrency;codex-pool plan会拒绝 owner concurrency 低于总 capacity 的配置。 - 跑
codex-pool plan,确认 profile 可读、base_url和 API key 来源有效,且 stdout 未泄露完整 key。 - 跑
codex-pool sync --confirm。 - 跑
codex-pool validate。
普通新增上游是 YAML 操作,不走 CI/CD,不改代码。只有需要渲染或校验上游 Sub2API 已经存在的可复用能力时才修改 scripts/src/platform-infra-sub2api-codex.ts;Sub2API 本身不支持的能力不在 UniDesk 侧魔改实现。
删除上游
删除上游只用于明确退役、凭据所有权变更或用户明确要求移除 provider;不能作为上游 5xx、compact 失败、限流、模型路由失败或自动冻结/切号缺陷的恢复手段。
- 从
config/platform-infra/sub2api-codex-pool.yaml删除对应profiles.entries项。 - 跑
codex-pool plan检查 desired 列表。 - 跑
codex-pool sync --confirm --prune-removed。 - 确认输出
accounts.pruned只包含期望删除项。 - 跑
codex-pool validate。
CLI 默认保留缺席账号,避免把可用性问题误处理成删除;只有显式 --prune-removed 才会 prune name 以 unidesk-codex- 开头且 extra.unidesk_managed=true 的缺席账号。
FRP 暴露
bun scripts/cli.ts platform-infra sub2api codex-pool expose
bun scripts/cli.ts platform-infra sub2api codex-pool expose --confirm
- 由
publicExposureYAML 控制。默认公共端是publicBaseUrl,master 本地消费端是masterBaseUrl。 expose --confirm只为 YAML 指定的remotePort补 masterfrpsallow port,并在 G14 创建/更新sub2api-frpc。- master Caddy site 也由
publicExposure.masterCaddy渲染;responseHeaderTimeoutSeconds必须足够覆盖 Codex/responses/compact长请求,避免 Caddy 先返回 504 而 Sub2API 后台实际稍后成功。 - 同一个 FRP TCP 入口同时暴露 OpenAI-compatible API 和 Sub2API 管理 UI
/login。不要另开第二个管理端口,除非 YAML 明确声明新的暴露决策。 - Sub2API Kubernetes Service 继续保持 ClusterIP。
配置 master Codex 消费端
bun scripts/cli.ts platform-infra sub2api codex-pool configure-local
bun scripts/cli.ts platform-infra sub2api codex-pool configure-local --confirm
configure-local --confirm 会:
- 从
platform-infra/<apiKeySecretName>.<apiKeySecretKey>读取统一 API key。 - 把当前
~/.codex/config.toml和~/.codex/auth.json备份为.<backupSuffix>,默认.pre-sub2api。 - 重写默认
~/.codex消费端,固定指向https://sub2api.74-48-78-17.nip.io/,provider 名称和 wire API 来自localCodex。 - 按
localCodex写入 Codex transport 标记:supports_websockets与[features] responses_websockets_v2必须同开同关。只有至少一个上游通过 direct Codex WSv2 probe 时才启用;否则保持 HTTP Responses,避免每次原入口先经历无效 WS reconnect。 - 用统一 key 做一次 gateway 验证。
防递归规则:默认 config.toml / auth.json 是 Sub2API consumer,不得作为上游账号导回 pool;上游账号必须使用带后缀 profile 文件,并通过 config/platform-infra/sub2api-codex-pool.yaml 的 profiles.entries 增删。
验收口径
部署 closeout 至少包含:
sub2api status:Deployment/StatefulSet/Service/Secret 可见,运行镜像与 YAML 一致。sub2api validate:app、PostgreSQL、Redis 和 service proxy 基础检查通过。codex-pool validate:统一 key 的GET /v1/models成功,并用localCodex.responsesSmokeModel跑一次小的POST /v1/responsessmoke;owner balance / owner concurrency 已满足 YAML 最小值,capacity、WebSocket v2 和 temporary-unschedulable 运行时状态与 YAML 对齐;validation.gatewayResponsesRecent汇总最近 6 小时普通/responses和/v1/responses的 failover、forward failure、最终 4xx/5xx、慢 final error 与context canceled证据,validation.gatewayCompactRecent单独汇总/responses/compact证据。若当前 Responses smokeok=true但 recent 字段degraded=true,先区分是历史窗口残留还是新的 request id 正在失败;长期判定见docs/reference/platform-infra.md。- 若
publicExposure.enabled=true,确认 FRP path 可用;expose --confirm会用未带 key 的 public/v1/models401 作为网关可达性探针。
如果要证明真实模型请求可用,使用最小 /v1/responses 或等价 Codex smoke。不要把 group-level /v1/models 成功解释成每个上游 account 都健康。
排障
- profile invalid:先修
~/.codex/config.toml.<profile>的base_url、wire_api、model或auth.json.<profile>的 API key;不要在 YAML 中写密钥。 - pool key 401:跑
codex-pool sync --confirm重建 Sub2API key 与 k3s Secret 绑定,再跑codex-pool validate。 - 运行中过去的验证探针残留:只用
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 查询。 /responses/compact约 30 秒后返回 504 但 Sub2API 日志稍后记录codex.remote_compact.succeeded时,优先检查 master Caddyresponse_header_timeout是否由 YAMLpublicExposure.masterCaddy.responseHeaderTimeoutSeconds渲染,修正后跑codex-pool expose --confirm;这类边缘代理超时不会触发 Sub2API 账号级临时下线。- default profile 递归:检查 YAML default entry 是否使用
*.pre-sub2api备份文件;必要时恢复备份后重新configure-local --confirm。 - 上游需要 WebSocket v2:先做 direct Codex WSv2 probe;通过后才给该 profile 配
openaiResponsesWebSocketsV2Mode: ctx_pool|passthrough并跑sync --confirm;把它当 capability candidate,容量仍以 YAML 中的capacity或默认值为准。 - Codex 启动 WebSocket 回退:用原入口 Codex smoke 复现,再用 bounded Sub2API 日志确认 account;对 WS handshake 4xx/5xx、
openai.websocket_account_select_failed或 close-before-response.completed的账号关闭 YAML WSv2 能力后同步。若没有剩余 WSv2-capable account,把localCodex.supportsWebSockets和localCodex.responsesWebSocketsV2一起关掉,不把临时可用性推断写成调度配置。 - 上游要求 Codex User-Agent:只给该 profile 配
upstreamUserAgent,跑sync --confirm。 - 上游报 capacity/rate-limit/overload/Bad Gateway/Gateway Timeout 后没有切号或频繁先失败再恢复:先确认
codex-pool validate里tempUnschedulable.ok=true且目标 accountruntimeEnabled=true、规则数符合 YAML;再看validation.gatewayResponses.evidence.failovers的 account/upstream status。若 mismatch,跑codex-pool sync --confirm;若 runtime 规则已对齐但仍不冻结或不切号,继续修 Sub2API 自动冻结/failover 能力并复测,不要手工 patch Sub2API credentials,也不要手动禁用、删除或从 YAML 移除问题账号来绕过机制缺陷。 codex-pool sync --confirm或codex-pool validate超时:先区分 CLI 传输超时和 Sub2API 运行失败。受控 CLI 应返回远端作业进度和 stdout/stderr tail;如果只是低层trans60s 超时,不能据此判定 Sub2API failover 不工作。改用或修复 CLI 的远端 job/poll 路径后重跑,并以最终结构化结果作为证据。- Codex 报 weekly-limit、
less than 10% of your weekly limit left、Run /status for a breakdown等账号状态/软配额提示并要求切号:如果上游以 403/429 等错误状态返回,把稳定 body 关键词放进pool.defaultTempUnschedulable的对应规则,跑codex-pool sync --confirm,再用codex-pool validate确认每个 managed account 的 runtime 规则包含这些关键词。若该文案是 HTTP 200 成功内容,当前 Sub2API 不支持把它重分类为账号冷却;不要写 YAML 200 规则、不要热补 Sub2API、不要绕过 sync,必要时登记上游能力缺口 issue。 - 上游 400/503 响应体出现
invalid_encrypted_content、bad_response_status_code、invalid_request_error+ 稳定 unsupported-model 文案、unsupported-model、暂不支持/可用模型、model_not_found、No available channel for model ...或同类稳定模型路由 / Responses encrypted-content 兼容性失败:把稳定 body 关键词放进pool.defaultTempUnschedulable的对应 400/503 规则,跑codex-pool sync --confirm,再用codex-pool validate确认目标 account 的 runtime rule 包含这些关键词;不要用 account membership、priority、capacity、loadFactor、WebSocket mode 或 User-Agent 改动掩盖该错误族。 - 上游错误反复触发:默认错误冷却按严重程度分层;临时问题可从 10 分钟起步,网关/服务不可用/过载/模型路由类应更长,认证/权限/配额/账号状态/账号兼容类使用最长冷却。
invalid_encrypted_content、unsupported-model、Recovered upstream error ...、Bad Gateway、Gateway Timeout、Cloudflare524、Codex-facingUpstream request failed、Unknown error、context deadline exceeded、context canceled、model_not_found、No available channel for model、大上下文413和openai_error这类稳定包装文案都应留在对应 YAML 冷却政策里,特别是普通/responses与 compact 链路里上游兼容性错误或 524 可能最终表现为客户端 502/504 +Unknown error。具体数值只以 YAML 为准,修改后必须codex-pool sync --confirm和codex-pool validate。长期判定见docs/reference/platform-infra.md。 - Codex auto compact 后丢上下文:先确认 YAML
localCodex是否声明启用 WSv2;若启用,再确认本机~/.codex/config.toml是否有supports_websockets = true和responses_websockets_v2 = true,并看codex-pool validate的 WSv2 candidate 和 Sub2API 日志里的transport=responses_websockets_v2。若 YAML 当前禁用 WSv2,则按 HTTP Responses 稳定性排查,不把旧 WS 口径当成验收要求。 - Codex smoke 有 reconnect/1013:这是上游并发/可用性问题,和 HTTP-only compact context-loss 分开处理;记录 session/log 证据并关联专项 issue,不要用运行时手补覆盖 YAML 容量。
禁止事项
- 不用原生
kubectl apply/delete/patch作为正式操作入口。 - 不在 master server 部署或运行 Sub2API/PostgreSQL/Redis。
- 不新增 Ingress、NodePort、LoadBalancer、hostPort、hostNetwork 或宽 FRP 端口段。
- 不给 Sub2API manifest 添加 CPU/memory limits,除非有新的 YAML 化明确决策。
- 不打印完整 API key、admin password 或 Secret 明文。
- 不把普通上游增删做成代码变更、CI/CD、feature flag 或兼容双路径。
- 不把手动禁用账号、删除账号、移除 YAML entry、降低 membership 或临时改 priority/capacity/loadFactor 当作自动冻结/切号失败的修复。
- 不魔改 Sub2API:Sub2API 本身不支持的能力就不做,不通过 UniDesk 脚本、k8s 原地热补、本地 fork、YAML 伪声明或隐藏 fallback 代替上游实现。