18 KiB
18 KiB
Real Host Acceptance Runbook
日期:2026-05-21
先读哪几份文档
docs/EXECUTION_BOARD.md- 当前 gate、最新阻断、最新 live 真相以它为准。
docs/PRODUCTION_CLOSURE_BOARD.md- 看是否已经达到可上线口径,以及哪些只是历史 PASS。
docs/PROVIDER_ONBOARDING_PLAYBOOK.md- 当你是在“新增 provider”或“宿主升级后重新适配”场景下工作时,先看这份。
- 它定义稳定的 onboarding / rerun 顺序,而不是只定义一次性的验收动作。
docs/REAL_HOST_ACCEPTANCE_CHECKLIST.md- 每次 real-host 验收先走这一页。
- 适合快速确认红线、三层证据和最短诊断顺序。
docs/REAL_HOST_ACCEPTANCE_LEARNINGS.md- 看已经调通的细节、典型误判点、推荐诊断顺序。
docs/OPENCLAW_EXTERNAL_VALIDATION.md- 当真实宿主、公网域名与最终用户 key 已经打通后,继续看这份。
- 它定义的是 OpenClaw 最后一跳真实使用验证与升级后自检,不再局限于 relay-manager/host 导入层。
目标
把当前 CONDITIONAL_APPROVED 的剩余外部门禁收敛为一套可直接执行的真实宿主验收流程,覆盖:
- 真实 sub2api 宿主接入探测
- pack 安装
- preview/import 验证
- access preview / access status 验证
- reconcile 验证
- rollback smoke
前置条件
控制面
sub2api-cn-relay-manager已启动CRM_BASE_URL可访问,例如http://127.0.0.1:8080- 已设置
CRM_ADMIN_TOKEN
真实宿主
- 已知真实宿主
HOST_BASE_URL - 已知宿主管理认证:
HOST_API_KEY或HOST_BEARER_TOKEN
- 至少一个真实 provider key
- 已知 pack 路径,例如
/app/packs/openai-cn-pack
推荐执行方式
0. 先跑脚本回归自检(避免把 harness 漂移带进真实宿主结论)
cd /path/to/sub2api-cn-relay-manager
bash ./scripts/test/test_real_host_scripts.sh
说明:
- 当前推荐显式用
bash调起,确保在不同机器上不会因为脚本执行位差异把 harness 回归误报成逻辑失败。 - 只有这一步通过后,再继续真实宿主验收。
1. 构建本地容器镜像(适用于代理/离线开发机)
cd /path/to/sub2api-cn-relay-manager
scripts/deploy/build_local_image.sh
默认输出:
- 二进制:
bin/sub2api-cn-relay-manager - 镜像:
sub2api-cn-relay-manager:local
2. remote43 patched host / CRM 固定启动脚本
当目标是复现 2026-05-25 那条 remote43 + patched overlay + remote CRM 验收链路时,优先先跑固定脚本,不要再手工拼 /tmp/*.sh:
cd /path/to/sub2api-cn-relay-manager
HOST_BINARY=/path/to/sub2api-patched \
CRM_BINARY=./server \
bash ./scripts/deploy/setup_remote43_patched_stack.sh
脚本会:
- 把本地 pack 镜像到
/tmp/openai-cn-pack-<stack>并同步到 remote43 同路径 - 把当前本地
main分支打成git bundle,并在 remote43 固定维护仓库工作副本:/home/ubuntu/sub2api-cn-relay-manager-git-current
- 上传 patched 宿主二进制与当前 CRM server 二进制
- 在 remote43 拉起新的 Postgres / Redis / patched host
- 在 remote43 启动独立 SQLite 的临时 CRM
- 生成两个本地辅助文件:
local operator env filelocal tunnel script
说明:
- 以后 provider 草稿发布链默认依赖这个固定 repo 根,不再依赖人工创建的时间戳 checkout 目录。
后续按脚本输出执行:
set -a; source /tmp/remote43-patched-stack-18139.env; set +a
bash /tmp/remote43-patched-stack-18139.tunnel.sh
然后再跑:
bash ./scripts/acceptance/import_remote43_provider.sh kimi-a7m kimi-k2.6 A7M_KIMI_API_KEY /path/to/keyfile
3. 先 dry-run 检查真实验收参数
CRM_BASE_URL=http://127.0.0.1:8080 \
CRM_ADMIN_TOKEN=replace-me \
HOST_NAME=prod-sub2api \
HOST_BASE_URL=https://sub2api.example.com \
HOST_API_KEY=host-admin-key \
PACK_PATH=/app/packs/openai-cn-pack \
PROVIDER_ID=deepseek \
KEYS=sk-live-1,sk-live-2 \
ACCESS_MODE=self_service \
ACCESS_API_KEY=user-gateway-key \
DRY_RUN=1 \
bash ./scripts/acceptance/real_host_acceptance.sh
4. 执行真实验收
CRM_BASE_URL=http://127.0.0.1:8080 \
CRM_ADMIN_TOKEN=replace-me \
HOST_NAME=prod-sub2api \
HOST_BASE_URL=https://sub2api.example.com \
HOST_API_KEY=host-admin-key \
PACK_PATH=/app/packs/openai-cn-pack \
PROVIDER_ID=deepseek \
KEYS=sk-live-1,sk-live-2 \
ACCESS_MODE=self_service \
ACCESS_API_KEY=user-gateway-key \
bash ./scripts/acceptance/real_host_acceptance.sh
5. 订阅模式示例
CRM_BASE_URL=http://127.0.0.1:8080 \
CRM_ADMIN_TOKEN=replace-me \
HOST_NAME=prod-sub2api \
HOST_BASE_URL=https://sub2api.example.com \
HOST_BEARER_TOKEN=host-bearer-token \
PACK_PATH=/app/packs/openai-cn-pack \
PROVIDER_ID=deepseek \
KEYS=sk-live-1 \
ACCESS_MODE=subscription \
SUBSCRIPTION_USERS=user-a,user-b \
SUBSCRIPTION_DAYS=30 \
bash ./scripts/acceptance/real_host_acceptance.sh
6. 导入后自动补 access 前置(可选)
当真实宿主需要额外完成“普通用户余额 / key-group 绑定 / 订阅写入 / 缓存失效”等宿主侧动作时,可在 import 完成后插入自定义 hook:
AFTER_IMPORT_HOOK_COMMAND='bash /path/to/host-access-hook.sh' \
... \
scripts/acceptance/real_host_acceptance.sh
hook 执行时会额外导出:
BATCH_IDBATCH_DETAIL_FILE(若非 dry-run,会指向05a-batch-detail-pre-access.json)PROVIDER_IDHOST_BASE_URLCRM_BASE_URLACCESS_MODEMODEARTIFACT_DIR
标准产物会新增:
05a-batch-detail-pre-access.json05b-after-import-hook.stdout.txt05b-after-import-hook.stderr.txt
7. OpenClaw 最后一跳外部验证
当你已经确认:
- remote43 或本地 patched host 验收通过
- 公网根地址已暴露
- 真实用户可以自助注册并创建 key
不要在 21-summary.json 停止,继续追加一轮 OpenClaw 外部真实验证:
~/.openclaw/bin/apply-openclaw-minimax-proxy-fix.sh doctor
~/.openclaw/bin/openclaw-minimax-post-upgrade-check.sh
openclaw infer model run --local --model "tksea-gpt/gpt-5.4" --prompt "reply with pong only" --json
openclaw infer model run --local --model "tksea-gpt/gpt-5.4-mini" --prompt "reply with pong only" --json
openclaw infer model run --local --model "tksea-minimax/MiniMax-M2.5-highspeed" --prompt "reply with pong only" --json
openclaw infer model run --local --model "tksea-minimax/MiniMax-M2.7-highspeed" --prompt "reply with pong only" --json
当前推荐把这一步的口径记录到:
docs/OPENCLAW_EXTERNAL_VALIDATION.md- 或对应批次 artifact 的补充说明中
产物
脚本会把每一步 JSON 响应落到:
artifacts/real-host-acceptance/<timestamp>/
Artifact 安全模式
默认:
ARTIFACT_SECURITY_MODE=safe
ARTIFACT_INCLUDE_SECRETS=0
含义:
safe:主 artifact 目录只允许落脱敏后的验收证据,可作为仓库内长期保留材料。debug:允许额外生成本地敏感调试材料;这类材料不得作为默认主证据提交或长期保留。ARTIFACT_INCLUDE_SECRETS=1只允许用于本地短时调试;一旦开启,产物不再默认视为可入库证据。
safe 模式下的硬规则:
- 不落完整 upstream / managed / user API key
- 不落完整 bearer token
- 不落可直接复用的 SQL 明文 key 语句
- 不落 Redis cache key 原文
- header 文件必须去掉
Authorization/Cookie/Set-Cookie/x-api-key
默认文件顺序:
01-create-host.json02-probe-host.json03-install-pack.json04-preview-import.json05-import.json05a-batch-detail-pre-access.json(若拿到了batch_id且非 dry-run)05b-after-import-hook.stdout.txt/05b-after-import-hook.stderr.txt(若配置了 hook)06-access-preview.json07-access-status.json08-provider-status.json09-reconcile.json10-batch-detail.json11-rollback.json(若未跳过)
remote43 / 本地缩圈脚本若需要额外证据,会在同目录追加:
00-local-key-source.json(只保留 redacted key 指纹/前后缀)01-runtime-context.json(仅保留 hash 后的 user/admin/managed 身份)05-subscription-access-prep.summary.json(替代默认明文 SQL)07-redis-targeted-invalidation.json(只保留失效动作结果,不保留 cache key 原文)08-subscription-group-state.json(已裁剪 key 明文)21-summary.json/99-semantic-summary.json(推荐长期保留的摘要证据)- 若你是在清理旧目录,而不是生成新验收产物,优先运行:
它会把主目录中的历史敏感材料迁到
python3 scripts/acceptance/migrate_historical_artifacts.py artifacts/real-host-acceptanceartifacts/real-host-acceptance-sensitive/,并在原目录生成安全摘要版。 - 历史目录迁移脚本当前已覆盖两层:
- 固定命名标准 artifact(runtime-context / key-source / redis invalidation / group-state / sql summary / headers)
- 复杂业务快照与 JSON-in-string 字段(
summary.json、99-summary.json、99-semantic-summary.json、05a-batch-detail-pre-access.json、07-access-status.json、10-batch-detail.json以及其中的DetailsJSON/details_json/probe_summary_json)
- 若迁移后仍看到类似
00-managed-key-corrected.txt的手工 probe 文本,它们属于非标准人工产物,当前仍建议迁到artifacts/real-host-acceptance-sensitive/或直接删除。
通过标准
至少同时满足:
probe-host返回宿主版本与 capability 快照install-pack成功import返回batch_id,且 batch/provider 状态不为failedaccess-preview返回available=true或 access status 进入:subscription_readyself_service_readyfully_ready
reconcile不返回关键失败rollback smoke成功(若本次需要验证回滚链路)
推荐额外落盘的三层证据
为了避免把不同语义混为一谈,真实宿主验收建议每次都额外记录下面三层证据:
- account 单体视角
GET /api/v1/admin/accounts/:idGET /api/v1/admin/accounts/:id/models
- group/普通用户聚合视角
- 用真实可用的普通用户 key 或 subscription managed key 请求
GET /v1/models
- 用真实可用的普通用户 key 或 subscription managed key 请求
- completion smoke
- 用同一条普通用户访问链路请求
POST /v1/chat/completions
- 用同一条普通用户访问链路请求
注意:
GET /api/v1/admin/accounts/:id/models正确,不等于普通用户/v1/models一定正确/v1/models正确,也不等于/v1/chat/completions一定正确- 这三层必须分开记证据、分开下结论
当前门禁解释
- 若以上脚本在真实宿主环境全部通过:
- 可以把当前项目推进到 真实环境放行
- 若脚本未执行:
- 仍然不能把最新代码直接视作真实宿主已放行
- 若脚本执行但失败:
- 失败应被归类为真实宿主兼容性 / 凭据 / 网络 / pack 内容问题,而不是再泛化成“代码是否已完成”
注意事项
- 默认会执行 rollback smoke;若当前环境不允许回滚,设置:
SKIP_ROLLBACK=1 scripts/acceptance/real_host_acceptance.sh
PACK_PATH必须是控制面进程可读路径,不是用户本地概念路径。- 如果控制面部署在容器中,确保 pack 目录已经挂载进去。
HOST_API_KEY与HOST_BEARER_TOKEN二选一即可;脚本会自动推导auth.type=apikey|bearer。ACCESS_API_KEY必须使用真实未脱敏的普通用户 gateway key;不能直接复用数据库/列表接口中的展示值。- 真实宿主初始化只会准备管理员账号;普通用户账号/密码不会自动生成,验收前必须显式创建并留存可复用凭据。
self_service验证除普通用户 key 外,还需要该 key 绑定目标 group;若目标 group 是标准计费组,还需要用户侧具备可用余额,否则/v1/models可能从“未授权”转为INSUFFICIENT_BALANCE。subscription验证需要目标 group 本身是subscription类型,并且完成“普通用户订阅分配 + 普通用户 key 绑定该 group”;仅有管理员主体或未绑定 key 不足以通过/v1/models。- 若需要验证
reconcile收敛,优先在干净宿主场景或隔离 group 下执行,避免历史残留资源把结果污染成status=drifted/extra_count>0。 scripts/acceptance/import_remote43_provider.sh现已内置 remote43 的 subscription 验收补全动作:会根据 import batch 自动解析目标 group,执行“普通用户最低余额补齐 + key/group 绑定 + user_subscriptions upsert + 定向 Redis 缓存失效(auth / balance / subscription)”,并把 SQL / host state 证据写入 artifact 目录。- 当 CRM 进程与 operator 到 host 的访问地址不一致时,优先显式设置
CRM_HOST_BASE,避免把 CRM 侧探测地址和本地运维隧道地址混用。 - 对
Upstream service temporarily unavailable一类 502,不要先认定是上游聊天链路故障;先看脚本落盘的09-models.headers.txt/10-models.body.json。若/v1/models已返回了别的 provider 模型集(例如 GPT 系列而不是预期的 DeepSeek/Minimax 模型),先检查普通用户 key/group 绑定,也要检查 CRM 导入时是否把 provider 的channel_template.model_mapping、restrict_models、billing_model_source一并下发到宿主 channel。 - subscription 场景里,closure 最终用于 gateway probe 的 key 是宿主侧 managed key,不一定是请求体里外部传入的
ACCESS_API_KEY。如果你拿原始 key 直打/v1/models收到403 not assigned to any group,先不要判定 CRM 主链路失败。 - 对“既有 channel 没自动补
model_pricing”这类 live 现象,先核对在线 CRM 进程的启动时间与 git 提交时间;之前 MiniMax 的该现象最终被确认是 stale CRM 进程导致,而不是源码缺逻辑。 - 当 CRM 切换到本机运行时,
PACK_PATH也必须切换到控制面本机可读路径;继续沿用远端/home/ubuntu/...会触发stat pack path ... no such file or directory,这是验收 harness 参数问题,不是导入业务逻辑问题。 - 若要把 DeepSeek 的“host
/v1/models=200 但 host/v1/chat/completions=502,而 upstream 直探/chat/completions=200”做成可提 issue 的最小复现,直接运行scripts/acceptance/check_deepseek_completion_split.sh。它会同时落盘 host/v1/models、host/v1/chat/completions、upstream/chat/completions三层证据,并在summary.json里给出host_compatibility_gap|upstream_key_quota_issue|unknown分类。 - 若 reconcile 面对的是“非 latest batch 的同前缀旧账号”,最新代码会把它们记为
stale_noise_count/stale_noise_accounts并保留raw_extra_count,而不是继续把它们算进extra_count造成 drift 误报;因此应优先看extra_count是否归零,再看probe_failures/access_status是否仍有真实异常。 - self_service 场景里,普通用户 gateway key 访问宿主
/v1/models//v1/chat/completions时,真实语义是Authorization: Bearer <gateway-key>;若 CRM 的 self_service closure 仍显示401/403 broken,优先排查 gateway probe 是否错误复用了x-api-key。 - fresh-host 管理员 bearer token 过期时,最前面的
POST /api/hosts/probe-host可能直接表现成 CRM 侧502。遇到这类现象,先刷新 host bearer token,再继续验收,不要先把它归因为最新代码故障。 - shared fresh-host 上若
05-import.json/07-access-status.json已经 ready,而09-reconcile.json仍是status=drifted,优先把它解释为历史残留资源噪音;PRD 首版放行判断应以 import/access 闭环是否打通为主。 - 如果 CRM 本身运行在本机,而宿主运行在远端 SSH 隧道后面,必须同时明确 2 个地址:
CRM_HOST_BASE:本地 CRM 实际访问宿主时使用的地址,例如http://127.0.0.1:18089REMOTE_HOST_BASE:远端 SSH 会话内部访问宿主时使用的地址,例如http://127.0.0.1:18097- 两者不能混用;混用后
POST /api/hosts往往会先在get host version/probe host capabilities处直接变成500
- 如果远端同时存在
relaymgr和fresh-host两套容器栈,不要手填REMOTE_PG_CONTAINER/REMOTE_REDIS_CONTAINER到旧的 relaymgr 容器。优先按目标宿主端口自动解析同栈的postgres/redis;否则最容易回到“错库取 key 导致统一 401”。 - 若同一个 provider 已在本地 CRM 状态库里跑过多个宿主样本,尾部查询必须带
host_id:
GET /api/providers/{provider}/statusGET /api/providers/{provider}/access/statusPOST /api/providers/{provider}/access/preview- 否则很容易在最后一步收到
provider exists on multiple hosts; host_id is required
- 不要把“隧道端口还在 LISTEN”误判成“链路可用”。
- 若
curl -I --max-time 5 $CRM_HOST_BASE/healthz完全收不到 header - 就应先判定为 tunnel 失活或远端链路异常
- 这类现象会在
03-import.body.json中表现为get host version ... context deadline exceeded
- 当前 artifact 安全模式默认是
safe:
- 主目录
artifacts/real-host-acceptance/只能保留脱敏后证据 - 若为了缩圈必须看原始 SQL / headers / token 相关细节,应显式切到
ARTIFACT_SECURITY_MODE=debug并把产物视为本地敏感材料,不进入仓库主证据区
建议固定执行的快速诊断顺序
- 先看环境
- CRM 是否是最新提交对应的在线进程
PACK_PATH是否是 CRM 本机可读路径CRM_HOST_BASE是否与 CRM 到 host 的实际访问地址一致- 如果走 SSH 隧道,
CRM_HOST_BASE是否真的可在本机curl -I --max-time 5读到响应 - 若脚本还要在 SSH 会话里执行 host probe,
REMOTE_HOST_BASE是否与远端主机看到的地址一致
- 再看宿主落库
- account
credentials.model_mapping GET /api/v1/admin/accounts/:id/models- channel
model_mapping/model_pricing/restrict_models/billing_model_source
- 最后看普通用户流量
/v1/models/v1/chat/completions
若需要背景解释、误判案例和已调通经验,直接看:docs/REAL_HOST_ACCEPTANCE_LEARNINGS.md