9.8 KiB
9.8 KiB
部署前聚合体检与备份合同自动刷新
Goal
让已有部署在任何远端写入前一次性看清合同、权限、数据挂载和备份构建身份的全部漂移,并在应用切换成功后自动刷新备份/WARCH3 service 的构建标识。部署不再依赖操作者逐个撞 fail-fast 护栏,也不再依赖人工记住“部署后重装 timer”。
Background
- 2026-08-02 的受控部署连续暴露三个历史漂移断面:冻结的
REMOTE_DIR不是仓库根、root 维护身份与部署身份未按实际通路区分、冻结的环境文件路径不可由部署用户读取。现有护栏串行 fail-fast,因此每修复一处才看到下一处。 - 同一轮恢复已完成一次全量对账:六键合同、163 个生产环境键、运行容器、PGDATA/附件挂载、4 个 service、4 个 timer、真实备份和异机恢复点均已对齐并跑绿。本任务把该对账方法变成可重复机制。
deploy.sh只把新BUILD_SHA注入应用容器,不重装 automatic-backup/WARCH3 units;备份 runner 又要求 unit 中冻结的 build 与活动容器一致。人工漏做刷新时,应用可正常运行而自动备份静默 fail-closed。- 仓库已有 forced-command 先例:异机备份接收端以 root-owned 可执行文件配合
SSH_ORIGINAL_COMMAND白名单和 SSH 转发/PTY 禁用选项限制授权面。
Scope
- 为已有部署增加一次性汇总的只读体检,并在任何远端写入前阻断全部已发现的合同、权限、挂载和 build 漂移。
- 建立独立 forced-command SSH 身份与 root-owned 自包含刷新程序,只更新四个固定 service 的 build drop-in并验证四个既有 timer。
- 把聚合体检接到
deploy-existing.sh前置,把自动刷新接到完整部署与公网检查之后,并补齐测试、runbook、checklist 和部署 spec。 - 保留
deploy.sh首次初始化/应急路径和既有 installers;生产安装、authorized_keys 修改和首次启用另行批准。
Requirements
1. 只读聚合体检
- 新增已有部署专用体检入口,由
deploy-existing.sh在任何mkdir、rsync、Compose、systemd 或其他远端写入前调用。 - 体检至少覆盖:六键合同完整;
REMOTE_DIR是完整仓库根且部署用户可写;REMOTE_ENV_FILE为普通文件且部署用户可读;PGDATA 含PG_VERSION;附件目录存在;运行中唯一 server/db 容器与合同中的 build、PGDATA、附件挂载一致;automatic-backup unit 的BUILD_SHA与活动 server 一致;普通部署 SSH 身份可用;独立 root forced-command 身份可用且收尾程序存在、可执行。 - 主流程不得使用
set -e实现检查遍历。每个检查必须在独立子 shell 中执行并显式收集退出码;前一项失败不能阻止无依赖的后续项执行。依赖事实缺失时,相关项仍以固定检查名报告 FAIL,不得静默跳过。 - 输出只包含固定检查项名、
PASS/FAIL、汇总计数和固定错误分类;不输出 SSH 别名、主机、路径、env 值或散列、容器 ID、密钥、unit 原文及原始命令响应。 - 任一必需检查失败,体检最终退出非零,
deploy-existing.sh不得继续同步或部署。现有 fail-fastpreflight-remote.sh保留为第二道防线。
2. 独立 root forced-command 通路
- 使用独立维护 SSH 身份,不以
sudo -n true代表 root 能力,不新增 NOPASSWD sudoers。 - 专用公钥必须只以 forced-command 形式授权;同一 key blob 若还存在不受限 root 授权,安装器必须 fail-closed。不得复用当前不受限 root 管理密钥来制造“看似受限”的入口。
- forced-command 指向
/usr/local/sbin下 root-owned、非符号链接、0755的自包含程序;程序不得 source、exec 或调用REMOTE_DIR及其他部署用户可写目录中的脚本、二进制或配置。 - SSH 入口禁用 agent forwarding、端口转发、X11、PTY 和 user rc;只接受
SSH_ORIGINAL_COMMAND的精确白名单子命令,首版仅允许无附加参数的probe与refresh。
3. 部署后自动刷新
deploy-existing.sh只有在应用部署、/ready、runtime/external smoke 与公网检查成功后,才通过独立 root forced-command 身份调用refresh。- root 程序从 root-owned systemd 合同和唯一活动 server 容器读取 Compose 身份及完整
BUILD_SHA,严格校验后仅更新四个固定 service 的 build 环境覆盖:automatic-backup daily、watchdog、drill-reminder 与 WARCH3。不得接受调用方传入路径、unit 名、SHA 或任意命令。 - 刷新必须原子写入 root-owned systemd drop-in,执行
daemon-reload,再验证四个 service 的最终BUILD_SHA/VITE_BUILD_SHA与活动容器一致,以及四个既有 timer 均为 enabled/active。不得调用仓库内 installer,也不得顺带重写其他 unit 字段。 - 刷新失败时
deploy-existing.sh必须退出非零并明确标记“应用已部署但合同未收口”,不得打印普通成功结论,也不得自动回滚已通过 smoke 的应用。 - 对外固定区分四类失败:root SSH 身份不可用;收尾程序不存在或不可执行;程序完成但 unit SHA 未更新;unit 已更新但 timer 未 enabled/active。
4. 安装、兼容与生产边界
- 提供一次性 root 安装器,将经过审核的自包含程序复制为 root-owned 文件并安装/更新 managed forced-command 授权行;安装器本身仍需显式 root 执行,不进入免密授权。
- 保留现有 automatic-backup/WARCH3 installers 作为首次安装、路径/完整合同变化和人工修复入口;自动收尾只覆盖每次发布必变的 build 标识。
- 现有
deploy.sh首次初始化/受控应急入口不强制依赖 forced-command;自动体检与刷新只接入deploy-existing.sh。 - 生产安装 forced-command、修改 root
authorized_keys或首次启用自动收尾均属于生产写入,实施完成后必须另行批准;本任务规划批准和代码批准不授权远端安装。
Acceptance Criteria
- 合成场景同时制造至少两个前置失败时,体检报告包含全部预期 FAIL 项,且最后一个独立检查确实执行;不能因
set -e、pipefail 或函数非零提前退出。 - 体检全绿时只输出固定项名和 PASS/汇总;敏感扫描与测试证明没有路径、主机、env 值/散列、容器 ID 或原始响应。
- 六键缺失、仓库根错误、env 不可读、源码根不可写、PGDATA/附件缺失、实际挂载漂移、unit build 漂移均能在远端写入前一次汇总发现。
- 普通部署身份和 root forced-command 身份分别验证;root transport 不可用与远端程序不存在/不可执行得到不同固定分类。
- forced-command 只接受精确
probe、refresh;空命令、带额外参数、其他子命令均拒绝。安装器拒绝同一公钥同时存在不受限 root 授权。 - 安装后的 root 程序为 root-owned、
0755、非符号链接,且静态/动态测试证明不执行仓库或其他部署用户可写路径中的程序。 - 成功
refresh从活动容器取得 build,原子更新四个固定 service 的 drop-in,四个最终 SHA 一致且四个 timer enabled/active;调用方无法指定 SHA、路径或 unit。 - 四类收尾失败分别有回归:SSH root 身份不可用、程序不存在/不可执行、unit SHA 未更新、timer 未运行。
- 任一收尾失败发生在应用已切换后,
deploy-existing.sh退出非零并输出固定“已部署但未收口”状态;成功时才输出部署完整完成。 deploy.sh直接初始化/应急路径保持兼容;automatic-backup/WARCH3 既有 installer、真实备份与 watchdog 行为不被改写。- shell syntax、部署安全单测、环境合同、聚合体检 fixtures、forced-command fixtures、Trellis/governance/process/sensitive/diff 门禁全部通过。
Out of Scope
- 不建设 CI/CD、蓝绿发布、自动回滚或新的通用远程执行框架。
- 不把体检挂成每周 timer;先观察它在后续真实部署中是否持续发现漂移,再单独评估第 5 个 unit。
- 不在本任务删除四个旧源码目录、旧环境文件或其他历史回退材料;体检跑绿也只形成后续清理的申请前提。
- 不通过
SYNC_ENV=1修复环境文件,不自动挑选、复制或轮换生产 Secret。 - 不让 root 收尾程序修改 Compose、数据库、附件、业务数据、备份制品或执行真实恢复。
Deferred Decision With Trigger
- 每周只读体检 timer 暂不建设。触发条件:本脚本上线后在后续真实部署中再次发现非一次性漂移,或出现人工改服务器导致合同在部署间隔内失配;届时单独立项评估定时运行、告警和第 5 个 unit 的维护成本。
- 旧目录/旧环境文件清理单独审批。至少要求聚合体检在规范根上跑绿、一次后续真实部署自动收尾跑绿,且当前异机备份仍新鲜。
Test Plan
bash -n检查新增/修改的所有部署 shell。- 扩展
server/tests/unit/scripts/deployment-safety-contract.test.js,锁定 preflight 在任何远端写入前、刷新在应用与公网检查后、失败必须向上传播。 - 新增纯 fixture 合同测试,使用假的 SSH/systemctl/docker 响应覆盖聚合不短路、脱敏输出、身份/程序四类失败、固定 unit/drop-in 和 timer 验证。
- 运行
node deploy/trialrun/validate-env-contract.js、相关 automatic-backup 合同测试及既有部署安全回归。 - 运行 Trellis task validate、governance、process contract、docs registry、sensitive content 与
git diff --check。
Notes
- Keep
prd.mdfocused on requirements, constraints, and acceptance criteria. - Lightweight tasks can remain PRD-only.
- For complex tasks, add
design.mdfor technical design andimplement.mdfor execution planning beforetask.py start.