6.0 KiB
6.0 KiB
部署前聚合体检与备份合同自动刷新:实施计划
0. 边界与回滚点
- 用户批准本规划后才运行
task.py start;批准规划不等于批准生产安装。 - 实施阶段只修改仓库代码、测试、spec/runbook;不连接生产、不改 root
authorized_keys、不安装 forced-command、不部署。 - 保留
deploy.sh首次初始化/应急兼容;deploy-existing.sh的自动强制启用与生产 forced-command 安装安排在另行批准的同一窗口。 - 不删除旧源码目录、旧环境文件、旧 unit 或备份制品。
1. 共享合同读取与聚合体检
- 提取六键 systemd Environment allowlist 的共享只读解析逻辑,供
deploy-existing.sh和新体检入口使用;任何输出不包含值。 - 新增聚合器与固定检查表,主流程不用
set -e,每项子 shell 隔离、显式收集状态,最后统一返回。 - 实现普通部署身份的仓库根、可写性、env 可读性、PGDATA/附件和运行容器 mount/build 核对。
- 实现独立
DEPLOY_REFRESH_SERVER的 transport/probe 核对,并把身份不可用与程序不可用分开分类。 - 让
deploy-existing.sh在任何远端写入前要求体检全绿;保留既有 remote preflight。
2. root-owned 自包含刷新程序
- 新增无仓库运行时依赖的 forced-command target:固定 PATH/umask、清理危险环境、精确解析
SSH_ORIGINAL_COMMAND。 probe验证 root 身份、固定工具、root-owned unit 与固定目标前提,只输出固定标识。refresh取得固定锁,从 root-owned unit 与唯一 running server 解析活动 build,拒绝调用方路径/SHA/unit 输入。- 以原子 root-owned systemd drop-in 覆盖四个 service 的两项 build 环境,daemon-reload 后验证最终 SHA。
- 验证四个 timer enabled/active;用稳定退出码区分 unit 未刷新和 timer 未运行。
- 不调用 repository installer,不使用
eval、动态 shell、动态 unit 名或部署用户可写可执行文件。
3. forced-command 安装器
- 将 target 安装到固定
/usr/local/sbin,验证 root ownership、mode 和非 symlink。 - 读取显式专用公钥,拒绝同 key blob 的不受限 root 授权;只原子维护自身 managed authorized_keys 行并保留其他 key。
- forced-command 选项禁用 agent/port/X11/PTY/user-rc,安装完成后执行本地静态合同检查;不把 installer 本身加入免密或 forced-command。
- 增加卸载/回滚说明,但不在无独立批准时自动删除授权行或 drop-in。
4. 部署后接线与状态表达
deploy-existing.sh只在deploy.sh完整成功后调用refresh。- 成功时才打印完整部署完成;失败时固定输出
DEPLOYED_BUT_CONTRACT_REFRESH_FAILED、四类之一并退出非零,不转发敏感 stderr。 - 不增加 optional/跳过刷新开关;新版已有部署入口缺少
DEPLOY_REFRESH_SERVER即在写前阻塞。生产窗口先安装并 probe forced-command,再运行新版 wrapper。 - 更新 runbook/checklist,明确两个 SSH 身份、forced-command 专用 key、部署前聚合报告、后刷新非零语义和生产安装单独批准。
5. 测试与门禁
- 新增聚合器 fixtures:多个同时失败、首项失败仍执行末项、依赖失败、固定输出顺序和最终退出码。
- 新增脱敏 fixtures:底层输出含伪造路径/主机/env/容器 ID 时,外层报告不泄露。
- 新增 forced-command fixtures:空命令、未知命令、附加参数、probe、refresh、锁、原子 drop-in、四 service SHA 与四 timer。
- 覆盖四类后刷新失败:root transport、程序 126/127、unit SHA、timer 状态;断言 wrapper 非零且不打印完整成功。
- 扩展 deployment safety contract,锁定体检在 mkdir/rsync 前,refresh 在
/ready、smoke 和公网检查后,且 helper 不执行仓库路径。 - 运行:
bash -n(所有新增/修改 shell)node deploy/trialrun/validate-env-contract.jscd server && npm test -- tests/unit/scripts/deployment-safety-contract.test.js- 新增聚合/forced-command fixture runner
bash scripts/qa/run-automatic-backup-contract.shpython3 ./.trellis/scripts/task.py validate 08-02-deployment-preflight-backup-contract-refresh- Trellis governance、process contract、docs registry、sensitive scan、
git diff --check
6. 审查、提交与生产门
- 实施后执行只读安全/部署 reviewer,重点核查 forced-command key 是否真正受限、root target 是否执行部署用户可写内容、聚合器是否短路、状态分类是否准确;reviewer 不改代码。
- 用
trellis-update-spec更新 deployment-trialrun 的稳定合同,避免只把知识留在 task。 - 中文提交并引用
08-02-deployment-preflight-backup-contract-refresh;不自动提交、不自动部署。 - 代码获批后另行申请生产窗口:先安装 forced-command/probe,再启用 wrapper 强制刷新,执行一次真实部署或受控无业务写入验证;任何 root/authorized_keys 写入和部署均需单独明确批准。
本地验证(2026-08-02):聚合/forced-command fixture 通过,覆盖 13 项汇总、多个并发失败不短路、脱敏输出、四类收尾映射、精确命令白名单、四个 build drop-in、四个 timer、managed key 更新及不受限重复 key 拒绝;deployment safety Vitest 9 passed;环境合同通过;automatic-backup 合同 73 checks passed;全部 shell/Node 语法通过。只读复查发现 forced-command target 缺少运行时自身 ownership/mode/path 校验,已补 validate_self();最终复核又将 systemd build 校验从子串匹配收紧为精确键与最后覆盖值,并用 OLD_BUILD_SHA 回归证明不会静默误判。相关聚合、环境合同和 Vitest 均复跑通过;复查未发现新的 root 提权、动态执行或部署顺序 blocker。生产未连接、未安装、未部署。