文件
hyb-oa/.trellis/tasks/archive/2026-07/07-27-offhost-backup-final-fixes/design.md
T
wangsli cf63d3cb1b
ci-e2e / e2e (push) Waiting to run
ci-minimal / check-minimal (push) Waiting to run
归档异机备份最终收尾任务(07-27-offhost-backup-final-fixes)
2026-07-27 18:08:44 +08:00

5.7 KiB

异机备份最终收尾修复设计

边界与顺序

本任务只修三类已确认缺陷:凭证进入 argv、紧急恢复卡口径漂移、finalize 被强杀后的孤儿事务。receiver 的恢复入口固定为:校验配置 → 取得 .finalize.lock → 恢复或拒绝历史孤儿事务 → 校验本次批次 → 发布/取代 → 周检查点 → 保留轮转。没有锁时不得扫描或改变孤儿对象。

凭证传递合同

生产 wrapper

capture_command 仍负责将 stdout/stderr 写入 root-only 诊断,但不再把 env 当作被执行命令。调用方进入隔离子 shell,在子 shell 内导出 DATABASE_URL、Docker DB 身份、备份目录和构建标识,再调用:

(
  export DATABASE_URL DB_TOOL_MODE DB_CONTAINER POSTGRES_DB POSTGRES_USER \
    POSTGRES_PASSWORD STORAGE_DIR_HOST BACKUP_DIR BACKUP_RUN_ID BUILD_SHA \
    SOURCE_IMAGE_DIGEST
  capture_command paired_backup "$ROOT_DIR/scripts/backup-all.sh"
)

敏感值仍只存在于执行进程环境和内存,不写入命令 argv、诊断、summary 或日志。本任务不宣称对 root 或同执行身份读取 /proc/<pid>/environ 提供额外隔离。

Docker DB 工具

生产 Docker 已证明无值 -e VAR 能继承客户端环境。三处 Docker 调用统一为:

PGPASSWORD="$POSTGRES_PASSWORD" docker exec [-i] -e PGPASSWORD \
  "$DB_CONTAINER" <pg-command-without-password>

PGPASSWORD 的值不得出现在 Docker CLI 参数,mock 测试同时检查参数不含合成密码、容器命令能看到继承值。测试夹具用于启动命令的普通 env KEY=value 不在本轮机械改写范围。

日归档孤儿恢复

receiver 在锁内扫描 .daily-replacement-YYYYMMDD.*。每个孤儿必须是受控 archive 根的一层普通目录且非链接;从其 manifest 取得原 runId,执行与正式 finalize 相同的精确文件集、大小和 SHA-256 自校验,并验证日期与孤儿名一致。

现场 含义 正确动作
孤儿存在且当日正式归档存在、两者均有效 新批次已换入,只缺最后清理 保留正式归档,删除孤儿
孤儿存在且当日正式归档缺失、孤儿有效 旧归档已移开,新批次尚未换入 按孤儿 manifest 的 runId 原子恢复旧正式归档
两者都在但当日正式归档校验不过 无法判断换入结果 fail closed,两个都保留
孤儿无效、同日多个正式归档、同日多个孤儿、名称/manifest 日期不符或任一对象为链接 状态歧义或受污染 fail closed,所有对象保留

恢复旧归档或确认新归档后,才允许继续处理本次 partial。任何删除前再次验证目标仍是刚才检查过的严格路径和类型。

周检查点孤儿恢复

周检查点是日归档的硬链接派生物,但仍不得猜测或直接删除。每个 week-YYYY-Www.new.* / .rollback.* 先按正式周名分组,要求每类最多一个普通目录且非链接。正式周点和 rollback 必须自校验 manifest,且 manifest 日期属于该 ISO 周;.new 是从未提交的 staging,内容可能因强杀而不完整,不能作为恢复来源,也不要求内容有效,但只有在另一个有效权威来源已经确定后才可清理。

现场 含义 正确动作
rollback 存在、正式周点缺失、rollback 有效 正式周点已移开,新点尚未换入 原子恢复 rollback 为正式周点;.new 路径/类型安全时删除 staging,不使用其内容
rollback 存在、正式周点存在且正式点有效 新周点已换入,只缺清理 保留正式周点,删除 rollback;.new 路径/类型安全时删除 staging
仅 .new 存在、正式周点存在且有效 staging 未提交或残留 保留正式周点;.new 路径/类型安全时删除 staging
仅 .new 存在、正式周点缺失 没有已提交周点 先找到该 ISO 周唯一有效的正式日归档来源;能唯一确定时删除 staging 并从该日归档重建,不能唯一确定时 fail closed 并保留 .new
正式周点无效、rollback 无效、多个候选、日期/周不符或任一对象为链接 状态歧义或受污染 fail closed,全部保留

若日归档恢复动作影响某 ISO 周,必须从最终选定且已验证的正式日归档重建该周检查点后才算 reconciliation 成功。周点恢复/重建失败时不删除日归档 rollback。

失败传播与可观测性

reconciliation 任一歧义返回非零,receiver 不处理本次 partial。源端 SSH finalize 随即在 offhost_finalize 阶段失败,capture_command 保留 0600 诊断,on_error 通过独立企微群机器人发送脱敏类别,且不写 last-success.json、不清理源端批次。若故障持续,ARM dead-man 在超龄窗口独立告警。该双层链路已经真实验证,本任务只用合成测试证明返回值和源端状态传播,不再次制造真实断链。

文档与单管理员恢复卡

runbook 标题改为“紧急恢复卡(单管理员离线版)”,正文只保留唯一系统管理员、ARM 隔离影子恢复、真实生产恢复另行批准和不粘贴敏感内容的约束。复制到 ARM 与离线副本是后续可选运维动作,不写死真实路径、不进入代码验收。

兼容、部署与回滚

  • 不改变批次、manifest、receiver forced-command、timer、保留数量或告警 payload 合同。
  • 本地提交后先停在外部门禁;推送、生产 runner/DB 脚本部署、ARM receiver 更新分别请求批准。
  • 代码回滚只回滚本任务提交;已存在的正式归档、周检查点、partial 和孤儿对象均不删除。
  • 完成后归档本任务并结束运维加固主线。