HA 阶段 1:worktree_host + 跨主机 /pr 队列化 (#50)

* 部署自动化:唯一发布链路 deploy.py + host_deploy.sh,sha/commit 双重验真,失败回滚

旧链路三次栽过的坑:通用 tar 路径导致旧包误部署、传输不验 sha、
主机脚本多份漂移、health 通过不等于新代码上线。本单把四点变硬门禁:
- 产物唯一命名 codex-app-server-<commit>.tar.gz + .sha256,远端唯一路径
- git archive 产物 + DEPLOYED_COMMIT 清单,默认拒绝脏工作区
- 传输链 scp -> scp_run -> chunk 分片,每路传完远端验 sha,不符删掉重来
- 主机侧唯一脚本:先验 sha/清单,staging 解包 + 编译检查,换目录上线,
  work/ 原样保留,health + 上线 commit 不符即回滚并退出非零
- deploy.py 成功后独立 ssh 再读一次 live 清单二次核对
- CI:host-verify 加 deploy/tests;release.yml 只在手动/tag + production
  environment 审批时部署,主干推送绝不自动部署生产
- deploy.sh 标记为旧链路弃用
- health 的 commit 字段已在父提交 HA 阶段 0 里随 server.py 落地,
  本分支整体包含它;commit 为空时只靠文件核对,门禁同样成立
测试:host 32、cloud 487(4 跳过)、deploy 9,全部通过;
host_deploy.sh 仿真覆盖成功/坏 sha/清单不符/health commit 不符回滚。
生产实机部署未做,等确认后再用新链路推。

* HA stage 1: worktree_host + queued cross-host PR jobs

---------

Co-authored-by: host <host@test.local>
This commit is contained in:
openqjandhost authored and GitHub committed 2026-10-27 00:05:18 +08:00
1 parent 455513dd33
commit fc8e337d0f
14 files changed
+1639 -67

No files matched your search

+62 -1
View File
@@ -4,7 +4,68 @@
传代码、备份、重启、验证,不改任务逻辑;运维脚本只负责检查与演练,
不跑模型、不推代码。
## 部署:deploy.sh
## 发布链路(新,默认用这个):deploy.py + host_deploy.sh
VM 侧运行,一条命令走完构建、传输、验真、部署、回滚核对:
```sh
python3 deploy/deploy.py <host> [user]
# 干跑(只构建、不传输、不动主机):
python3 deploy/deploy.py <host> --dry-run
# 只构建发布包(给 CI artifact / 手动检查用):
python3 deploy/deploy.py --build-only --out-dir work/external-install/releases
# 部署已有发布包(CI release 作业就是这么调的):
python3 deploy/deploy.py <host> --tarball <包> --skip-tests
```
它修掉的正是旧手工链路三次栽过的坑:
- **产物唯一命名,旧包无法冒充新包。** 发布包是
`codex-app-server-<commit>.tar.gz` + `.sha256`,远端路径是
`/tmp/codex-app-server-releases/<commit>.tar.gz`,不再用
`/tmp/codex-app-server-code.tar.gz` 这种通用路径。scp 断了、
旧包残留,都不可能被下一步误当成新包。
- **传输、主机、二次核对三道 sha/commit 门。** 传输链自动
fallback:scp(SSH_PROXY_CMD)→ ssh-helpers/scp_run.py(此前
实测最稳)→ ssh-helpers/chunk_send.py(base64 分片);每路
传完都在远端跑 `sha256sum` 比对,不符就删掉该次产物再试
下一路,绝不把错包交给主机脚本。主机侧只用
`deploy/host_deploy.sh` 这一份(VM 侧随包一起传过去,不依赖
主机旧代码里的版本),它先验 tar sha、再验包内
`DEPLOYED_COMMIT`、再 staging 解包 + 编译检查,任何一步
不符都直接退出、旧代码原地不动。
- **换目录代替原地 rm + 解包。** 主机侧先停服务,再把 live
换成 old、staging 换成 live,`work/` 与
`host-config-real.json`(如有)原样搬回;中途失败不会留
半解包的 live。备份名含旧 commit + 时间戳,留最近 5 份。
- **health 通过不再等于上线。** 主机脚本要求 health
`ok=true` 且 `backend=postgres` 且 `db_ok=true`,且 live 的
`DEPLOYED_COMMIT` 等于期望 commit;deploy.py 成功后还要
独立 ssh 再读一次 live 清单核对,两处都对上才打印成功。
旧链路“health 通过但跑的是旧代码”在新链路里会触发回滚
并退出非零。health 的 `commit` 字段已在父提交 HA 阶段 0
里随 server.py 一起落地(`_deployed_commit`,无文件时为
null);本单不依赖它也能成立,commit 为空时只靠文件核对。
- **产物来自 git archive,默认拒绝脏工作区。** 部署的永远是
已提交的 HEAD,不是工作区里没提交的临时改动;`--allow-dirty`
也只告警,部署的仍是 HEAD,不会把脏文件偷偷带上去。
- **CI 发布与手工发布同一套脚本。** `.github/workflows/release.yml`
只在手动 workflow_dispatch 或打 `v*` tag 时触发,走
`environment: production` 审批;主干推送绝不自动部署生产。
Actions 里没配 `DEPLOY_HOST` / `DEPLOY_SSH_KEY` 时,build
作业仍产出发布包 artifact,deploy 作业明确失败并提示改走
VM 侧 deploy.py,绝不静默跳过又报成功。
多机滚动(HA 阶段 2 的前置):当前 deploy.py 一次只推一台
主机,多机时按主机逐台跑、每台 health + commit 都通过再推
下一台,第一台失败就停,不要并行推两台。`--hosts` 批量滚动
是后续一单,不在本轮做。
密钥规则与旧脚本一致:deploy.py 绝不接受密码参数、不读
env 文件、不打印密钥;SSH 走 key,或调用方已有的
ssh-helpers pty 通道(密码只走 `SSH_PASSWORD` 环境变量)。
## 部署:deploy.sh(旧链路,已弃用,新发布不要再用)
VM 侧运行: