文件
hyb-oa/.trellis/tasks/07-27-automatic-offhost-backup/design.md
T
wangsli a0006f07b8
ci-e2e / e2e (push) Waiting to run
ci-minimal / check-minimal (push) Waiting to run
完善异机备份告警与同日恢复点取代(07-27-automatic-offhost-backup)
2026-07-27 13:54:16 +08:00

7.5 KiB
原始文件 Blame 文件历史

每日自动异机备份设计

总体边界

本任务不建设备份平台。生产主机负责生成和推送一个不可变成对批次,ARM 用受限 SSH 接收身份把传输区与归档区隔离;systemd 负责每日执行、缺失监测和季度提醒。

production 02:30
  resolve active Compose identity
  -> flock
  -> backup-all.sh (DB + storage + manifest)
  -> local verify
  -> rsync to ARM inbox/<run-id>.partial
  -> restricted finalize <run-id> <manifest-sha>
       -> ARM verify exact files/manifest/hash
       -> atomic move to archive/<run-id>
       -> daily + weekly retention
  -> write safe success state
  -> prune local daily copies

production 07:10 watchdog
  -> recovery point missing/corrupt/stale/project drift ? fail + group alert
  -> build SHA differs ? one finding + group alert

ARM independent watchdog
  -> newest finalized archive <= 26h ? pass : direct group alert
  -> source host/server/container not required

quarterly reminder
  -> one redacted WeCom reminder per calendar quarter
  -> human runs ARM shadow restore separately

生产备份编排

  • 新增一个宿主 wrapper,所有正式输入由安装器写入 systemd:仓库根、Compose project/file/env、活动 PGDATA/附件目录、完整/短构建 SHA、备份根、状态根、SSH alias/key/known-hosts 和告警开关。
  • wrapper 通过 Compose label 解析唯一 server/db 容器,并把实际 DB/附件 mount、server build SHA 与安装时冻结值逐项比较。任一漂移在调用 backup-all.sh 前停止。
  • DB 名称/用户和密码只从活动容器环境在内存中取得,传给现有 Docker 备份路径;不得 source 漂移的旧部署 env,不得输出取值。
  • 每个自动批次使用 auto-<UTC timestamp>,源端根和状态根均为显式安全绝对路径、权限 0700。flock 拒绝同一任务重入。
  • 只有 ARM finalize 成功后才写 last-success.json 并清理源端旧日批次。失败保留本次完整源批次供重试,不产生异机成功状态。

ARM 受限接收边界

  • ARM 一次性安装 rsync,创建密码锁定的专用账号、独立 inbox/archive/weekly 目录和一把专用公钥。该公钥的 authorized_keys 强制执行仓库提供的 receiver,并禁用 PTY、端口转发、agent 转发和 X11 转发。
  • receiver 只接受两类 SSH_ORIGINAL_COMMAND:rsync server 接收模式,或 hyb-backup-finalize <run-id> <manifest-sha256>。其他命令全部拒绝。
  • rsync 通过 rrsync -wo 只能进入 inbox;archive 和 weekly 位于 rrsync 根之外。即使生产端密钥泄露,也不能用 rsync 读取或删除已归档批次。
  • finalize 只接受 auto- 安全批次号和完整 manifest SHA。它要求 inbox 中恰有三个普通文件、无链接,manifest 的 runId/status/schema、相对文件名、大小和 SHA-256 全部匹配;同 run_id 重复 finalize 继续保持幂等。
  • 同 UTC 日已有不同 run_id 时,先完整校验并冻结新批次权限,再把旧日归档 rename 到同文件系统临时回滚名、新批次 rename 到正式归档名;新批次发布失败立即把旧归档 rename 回原名。新批次发布成功后才删除旧回滚名,并重建该 ISO 周硬链接检查点,使每日一个恢复点和周检查点都指向当前有效批次。
  • archive 保留最近 7 个日批次;每个 ISO 周首次成功批次用只读硬链接快照形成周检查点,保留最近 4 周。清理不得删除最后一个 archive 或当前 finalize 批次。
  • 旧手工/审计批次不进入自动前缀和自动轮转范围。

传输与主机身份

  • 生产 root 使用一把仅用于本接收器的私钥,权限 0600;SSH config 使用专用 alias,known_hosts 由人工核对的固定主机指纹生成,timer 固定 StrictHostKeyChecking=yes,不得使用 accept-new。
  • rsync 只发送完整源批次到同名 .partial inbox。网络中断可续传同一 partial;远端 archive 同名存在时 finalize 失败,不覆盖。
  • 仓库、任务和报告只记录 alias 类别、布尔值和批次数量,不记录地址、账号、密钥、公钥原文或完整运行目录。

告警与静默失败检测

  • 新增一个小型宿主告警 helper,使用 Python 标准库从 root-only Webhook 文件读取完整 URL,并向企微运维群机器人发送固定白名单文本;不复用 OA server 容器、员工表或个人工号。
  • 生产 wrapper 无论成功或失败都生成字段白名单 summary;即时失败和源端 watchdog 直接调用宿主 helper。Webhook 文件缺失、权限不是 0600、不是普通文件或内容格式不合法时 fail closed,并在本机诊断中留类别,不打印 URL。
  • ARM receiver 安装器同时安装独立 dead-man service/timer。watchdog 读取 archive 中最新有效自动批次的完成时间,超过 26 小时、没有批次或 manifest 无法验证时直接调用同一 helper;状态文件实现首次失败、重复降噪和恢复通知。
  • 生产 stale watchdog 仍验证 schema/status/project/时间。build SHA 不同输出 PASS_WITH_FINDINGS,不返回失败;恢复点缺失、损坏、未来时间、超过阈值或 project 不同继续返回失败。
  • 生产外部命令的 stdout/stderr 只写状态目录下的临时诊断,失败时原子保留为 last-error.log 并设为 0600,成功后清理本次临时诊断。summary 和企微文本不得复制诊断正文。
  • Webhook secret 同时存在于两台主机的 root-only 文件,仓库只保存“文件路径”合同;.env.example 和 systemd Environment= 都不得出现 URL/key。
  • 季度提醒以年份+季度作为 finding key,确保每季度一次且同季度不重复;只提醒人工影子恢复,不执行恢复。

安装与回滚

  1. 本地先实现合成测试、受限 receiver、告警适配和两个安装器,不接触生产/ARM。
  2. 获批后先由用户在企微创建运维群机器人,并分别把同一 Webhook 写入两台主机 root-only 文件;不向 Codex、仓库或普通 .env 提供其内容。
  3. 在 ARM 安装 rsync、受限账号/receiver/公钥、dead-man timer 和目标目录;使用无业务数据的合成批次验证拒绝 shell、同日安全取代和 dead-man 状态逻辑。
  4. 再在生产安装专用私钥/known_hosts、每日备份 service/timer、stale watchdog 和季度提醒;首次安装先保持 timer disabled。
  5. 手工启动一次获批自动备份,核对源/ARM 哈希、状态和无敏感输出;再 enable timers。
  6. 真正停止一次源端 timer 或故意让一次传输失败,等待/模拟超过 26 小时并确认 ARM 群告警;恢复链路后验证恢复通知和新批次成功。

回滚只 disable/stop 新 timer、移除 unit 和专用密钥授权;保留已生成的完整备份、状态报告和 ARM archive,不删除历史数据。旧手工 backup-all.sh 入口保持兼容。

取舍

  • 选择生产推送而非 ARM 拉取,是为了让一次任务同时掌握生成、传输、目标校验和即时告警结果。
  • 不给生产普通 ARM shell,而用 inbox + 强制 receiver + archive 隔离,抵消推送方案最主要的跨机删除风险。
  • 使用 rsync 而不是新备份服务;当前压缩批次规模较小,收益主要是断点续传和成熟 SSH 传输,不追求块级去重。
  • 不自动恢复。季度人工影子恢复才是可恢复性证据,日 timer 只负责生成恢复点。
  • 运维加固在 dead-man、真实失败链路、timer、恢复卡第二人走查完成后硬收口;不继续新增平台化备份或告警能力,后续回到产品 backlog。