完善异机备份告警与同日恢复点取代(07-27-automatic-offhost-backup)
ci-e2e / e2e (push) Waiting to run
ci-minimal / check-minimal (push) Waiting to run

这个提交包含在:
2026-07-27 13:54:16 +08:00
父节点 2526052e73
当前提交 a0006f07b8
共修改 15 个文件,包含 882 行新增和 105 行删除
+63 -23
查看文件
@@ -927,13 +927,18 @@ test "$(printf '%s\n' "$actual" | LC_ALL=C sort)" = \
- Production runner: `scripts/run-automatic-backup.sh` with `run`,
`check-stale` and `remind-drill` modes.
- ARM forced receiver: `scripts/receive-offhost-backup.sh`.
- Shared host alert helper: `scripts/send-wecom-group-alert.py` with explicit
`--webhook-file`, `--summary-file` and `--state-file` paths.
- ARM watchdog: `scripts/check-offhost-backup-deadman.py` with archive root,
state root, alert helper, Webhook file and a default `93600` second threshold.
- Installers: `install-automatic-backup-timer.sh` and
`install-offhost-backup-receiver.sh`.
- Local contract: `bash scripts/qa/run-automatic-backup-contract.sh`.
- Production required environment: `REMOTE_DIR`, `REMOTE_ENV_FILE`,
`COMPOSE_PROJECT_NAME`, `POSTGRES_DATA_DIR`, `STORAGE_HOST_DIR`, `BUILD_SHA`,
`VITE_BUILD_SHA`, `AUTOMATIC_BACKUP_DIR`, `AUTOMATIC_BACKUP_STATE_DIR`,
`OFFHOST_TARGET`, `OFFHOST_SSH_KEY` and `OFFHOST_KNOWN_HOSTS`.
`OFFHOST_TARGET`, `OFFHOST_SSH_KEY`, `OFFHOST_KNOWN_HOSTS`,
`ALERT_WEBHOOK_FILE` and `ALERT_HELPER`.
- Receiver config: independent absolute `BACKUP_INBOX_DIR`,
`BACKUP_ARCHIVE_DIR`, `BACKUP_WEEKLY_DIR`, `RRSYNC_BIN`, `PYTHON_BIN`,
`DAILY_KEEP` and `WEEKLY_KEEP`.
@@ -952,14 +957,32 @@ test "$(printf '%s\n' "$actual" | LC_ALL=C sort)" = \
it rejects shell, sender/read and destructive rsync options.
- ARM revalidates manifest identity, exact regular-file set, sizes and SHA-256,
then atomically moves the batch to archive. Archive and weekly directories are
outside the rsync root; an archived name is never overwritten.
outside the rsync root. A different run ID for the same UTC day may replace
that day's archive only after full candidate validation: rename the old daily
point to a same-filesystem rollback name, publish the candidate, rebuild that
ISO week's hard-link checkpoint, then remove the rollback. Any publication or
weekly-checkpoint failure restores the prior daily point.
- Write production `last-success.json` and prune source copies only after ARM
finalize succeeds. Keep seven automatic source/ARM daily points and four ARM
weekly checkpoints; unrelated manual batches are outside automatic retention.
- Daily, freshness and quarterly reminder timers are separate. Alert summaries
contain categories only. AUTO_BACKUP sends the first failure, worsening/new
findings and the first quarterly reminder, while suppressing an identical
repeated failure or reminder.
contain categories only. A shared Python helper reads the complete WeCom group
robot URL only from a regular, executing-account-owned `0600` file and never
prints it. It sends the first failure, worsening/new findings, one recovery
notice and the first quarterly reminder, while suppressing an identical
repeated state. It does not call the OA server container or resolve an employee.
- ARM runs its own 26-hour dead-man as root. It verifies the newest archive's
exact file set, manifest identity, sizes, checksums and timestamp, so a stopped
production host/server can still cause a group alert. Missing, corrupt, stale
or future-dated archives fail; recovery sends once.
- Production command diagnostics for paired backup, rsync and finalize stay only
in the state directory as `last-error.log` mode `0600`. The summary and group
message contain only the safe phase category. A later successful off-host run
removes the retained diagnostic.
- The production freshness check hard-fails missing, malformed, future, stale or
Compose-project-mismatched state. A fresh recovery point with a different
build SHA is `PASS_WITH_FINDINGS`, because deploy-time code identity and
recoverable data freshness are separate facts.
- Installer `install` is disabled-by-default; `enable`, real transfer, real
WeCom alert and all server/account/key changes remain `EXTERNAL_GATED`.
@@ -967,24 +990,32 @@ test "$(printf '%s\n' "$actual" | LC_ALL=C sort)" = \
- Source mount/build/project drift, lock conflict or incomplete backup -> fail
before success state and source retention.
- Transfer/finalize failure, wrong/extra/link artifact, bad hash or second batch
for one UTC day -> leave no off-host success state and preserve source batch.
- Missing, stale or project/build-mismatched success record -> watchdog finding.
- Transfer/finalize failure, wrong/extra/link artifact or bad hash -> leave no
off-host success state, preserve the source batch and retain a root-only local
diagnostic. A corrupt same-day candidate leaves the previous archive intact.
- Valid same-day retry -> replace only that UTC day's point and rebuild its ISO
weekly checkpoint; concurrent finalize -> `flock` rejection.
- Missing, corrupt, stale, future or project-mismatched success record -> hard
watchdog failure. Fresh build mismatch -> one finding without a failing exit.
- Missing/non-regular/symlink/non-`0600` Webhook file -> fail closed without
printing the file value. Identical alert state -> update/evaluate without send.
- Ordinary shell, rsync sender/read/delete or archive overwrite -> receiver
rejection without changing archive.
- Host/network/WeCom all unavailable at once -> self-hosted alert cannot prove
its own outage; this is a documented physical boundary, not a green state.
- ARM host/network/WeCom all unavailable at once -> the group robot path cannot
prove its own outage; this physical boundary is not a green state.
### 5. Good/Base/Bad Cases
- Good: one verified active source creates a complete batch, write-only rsync
resumes inside inbox, ARM finalize verifies and archives it, then production
writes success and prunes old automatic source points.
writes success and prunes old automatic source points. If production later
stops, ARM independently raises one dead-man alert and one recovery notice.
- Base: local implementation and synthetic contracts pass while both server
installers remain disabled and explicitly externally gated.
- Bad: give production a normal ARM login key, put archive below inbox, accept
a new host fingerprint automatically, or prune local recovery points after
rsync but before target-side finalize.
- Bad: send through `compose exec server`, hard-code a personal employee number,
persist a robot URL in `.env`/unit/repository, give production a normal ARM
login key, put archive below inbox, accept a new host fingerprint automatically,
or prune local points before target-side finalize.
### 6. Tests Required
@@ -993,28 +1024,37 @@ test "$(printf '%s\n' "$actual" | LC_ALL=C sort)" = \
- Docker mocks must return the real `{{json .Mounts}}` shape consumed by the
runner. A simplified tab-delimited mock is not valid evidence for real mount
parsing.
- Focused WeCom alert tests for stdin path redaction, preserved AUTO_BACKUP
issue identity, first/repeated failure and quarterly reminder behavior.
- Environment contract assertions for all four scripts, three timers, explicit
project/data/build/SSH binding and disabled-by-default installation.
- Real ARM setup, first paired batch, failure alert and quarterly shadow restore
are separately approved operational evidence.
- Contract assertions for same-day replacement, corrupt-candidate preservation,
weekly hard-link rebuild, source diagnostic mode, build mismatch finding,
Webhook mode/symlink rejection and group alert behavior without a server
container. Test transport must be explicitly gated and local-only.
- Environment contract assertions for both helpers, both installers, production
timers, ARM dead-man timer, explicit project/data/build/SSH/Webhook-file
binding, disabled-by-default installation and absence of a literal complete
robot URL in tracked or untracked repository files.
- Real ARM setup, first paired batch, deliberate source-timer stop or transfer
failure, dead-man first/repeated/recovery behavior and quarterly shadow restore
are separately approved operational evidence. A direct robot send test alone
is not dead-man evidence.
### 7. Wrong vs Correct
#### Wrong
```bash
compose exec server send-alert --recipient=<personal-employee-number>
rsync -a <batch>/ backup-host:<archive>/
rm -rf <old-local-batches>
```
#### Correct
```bash
# Install only with explicit active identity; default remains disabled.
<explicit-project-data-build-and-ssh-env> \
# Install only with explicit active identity and a root-only Webhook file path;
# default remains disabled.
<explicit-project-data-build-ssh-and-webhook-file-env> \
bash deploy/trialrun/install-automatic-backup-timer.sh install
# ARM separately verifies archive freshness even when production is unavailable.
python3 scripts/check-offhost-backup-deadman.py <explicit-safe-path-arguments>
# The runner writes success and prunes only after restricted target finalize.
bash scripts/run-automatic-backup.sh run
```
@@ -19,7 +19,12 @@ production 02:30
-> prune local daily copies
production 07:10 watchdog
-> last off-host success <= 24h ? pass : alert
-> 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
@@ -39,8 +44,8 @@ quarterly reminder
- 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 全部匹配;已存在 archive 目标时拒绝覆盖。
- 校验通过后在同一文件系统内原子移动到 archive。每个自然日最多接受一个正式自动批次进入保留计算,避免失陷源端通过短时间伪造大量批次提前挤掉历史。
- 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 批次。
- 旧手工/审计批次不进入自动前缀和自动轮转范围。
@@ -52,19 +57,22 @@ quarterly reminder
## 告警与静默失败检测
- wrapper 无论成功或失败都生成字段白名单 summary;失败 trap 尝试通过活动 server 容器调用现有企微运维告警 CLI,并使用备份专属 state file,避免与 WARCH3 降噪状态互相覆盖。
- 现有告警 CLI 小范围泛化为接受 stdin summary、保留 summary issueId,并为备份失败/过期提供正确建议;发送仍受既有 `WARCH3_OPS_ALERT_SEND=1` 双门控制。
- 独立 watchdog timer 在每日巡检前读取宿主 `last-success.json`。文件缺失、格式错误、构建/项目不匹配或成功时间超过 24 小时均产生失败 summary 并尝试告警。
- 若 Docker、整机网络或企微本身同时不可用,主机无法自证并外发,这是自托管告警的物理边界;watchdog 能覆盖 timer 未运行和短暂失败,但不替代外部主机存活监控。
- 新增一个小型宿主告警 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. 获批后先在 ARM 安装 rsync、受限账号/receiver/公钥和目标目录;使用无业务数据的合成批次验证拒绝 shell、拒绝 archive 覆盖和 finalize。
3. 再在生产安装专用私钥/known_hosts、每日备份 service/timer、stale watchdog 和季度提醒;首次安装先保持 timer disabled。
4. 手工启动一次获批自动备份,核对源/ARM 哈希、状态和无敏感输出;再 enable timers。
5. 用不包含业务正文的可控失败验证企微告警后恢复链路,最终确认下次执行时间。
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` 入口保持兼容。
@@ -74,3 +82,4 @@ quarterly reminder
- 不给生产普通 ARM shell,而用 inbox + 强制 receiver + archive 隔离,抵消推送方案最主要的跨机删除风险。
- 使用 rsync 而不是新备份服务;当前压缩批次规模较小,收益主要是断点续传和成熟 SSH 传输,不追求块级去重。
- 不自动恢复。季度人工影子恢复才是可恢复性证据,日 timer 只负责生成恢复点。
- 运维加固在 dead-man、真实失败链路、timer、恢复卡第二人走查完成后硬收口;不继续新增平台化备份或告警能力,后续回到产品 backlog。
@@ -9,9 +9,16 @@
- [x] 增加合成契约测试:来源漂移、锁冲突、不完整/损坏/重复批次、续传 partial、拒绝普通 shell、保留轮转、stale 和季度降噪。
- [x] 更新部署环境契约、runbook、checklist、管理员手册的最小操作入口和 `.trellis/spec/hyb-oa/deployment-trialrun.md`;不新增平行长文档。
- [x] 生成一页脱敏恢复卡内容,优先放在现有 runbook 的首部紧急段;由用户复制到公司知识库并安排第二人走查。
- [ ] 完成本地全量门禁并提交;生产、ARM 安装另行请求批准。
- [x] 完成本地全量门禁并提交;生产、ARM 安装另行请求批准。
- [ ] 获批后先安装 ARM 接收端并用合成批次验证,再安装生产 units,手工运行首次真实自动批次,最后 enable timers。
- [ ] 获批执行一次可控传输失败告警并恢复正常链路;不读取附件正文、不执行业务或企微其他写入。
- [x] 删除个人工号默认值,新增生产/ARM 共用的 root-only 企微群机器人 helper;两端 systemd 只绑定 Webhook 文件路径。
- [x] 增加 ARM 26 小时 dead-man service/timer,覆盖缺批次、超龄、损坏、重复降噪和恢复通知。
- [x] 将 buildSha 不同降级为 finding;缺失/损坏/超龄/project 不同继续硬失败,并支持部署后立即运行一次备份。
- [x] 为 backup/rsync/finalize 保存 `0600` 最近失败诊断,summary 与群消息只保留脱敏类别。
- [x] 把同 UTC 日不同 run_id 从拒绝改为“完整校验后原子取代”,覆盖失败回滚和周检查点重建。
- [x] 增加 Webhook 不进入 `.env.example`/仓库配置/报告的否定断言,并在 runbook 记录调整 `BACKUP_RUN_AT` 后需人工确认次日窗口。
- [x] 本地门禁通过并完成只读复核后提交;不推送、不部署、不配置真实 Webhook。
## 预计文件边界
@@ -22,6 +29,7 @@
- 新增 `scripts/qa/run-automatic-backup-contract.sh`:纯合成正负向测试。
- 修改 `server/src/scripts/send-wecom-ops-alert.js` 及其单测,仅做通用脱敏 summary 兼容。
- 修改 `deploy/trialrun/validate-env-contract.js`、现有 runbook/checklist/管理员手册和部署规约。
- 新增一个不含 Secret 的宿主告警 helper;只读取安装时传入的 root-only Webhook 文件。
## 验证命令
@@ -37,28 +45,31 @@
- `node scripts/qa/audit-docs.mjs --check-registry`
- `node scripts/qa/check-process-contract.mjs --baseline --json`
- `git diff --check`
- 合成测试必须模拟源端不再产生 finalize,并由 ARM dead-man 得到失败;不能只直接调用“发送消息”路径。
## 外部门禁与回滚点
- 安装 ARM rsync/账号/authorized_keys/目录属于远端配置写入,须单独批准;不得复用普通登录账号或本地工作站私钥。
- 安装生产 SSH key/config/systemd units、首次真实备份、真实异机传输和企微失败告警均须单独批准。
- 创建企微运维群机器人及把 Webhook 写入两台主机 root-only 文件由用户手工完成;Codex 不读取、不回显、不记录其内容。
- 首次真实执行前记录现有手工批次数量、磁盘余量、活动容器/mount/build 和现有 timer;只输出脱敏计数/布尔值。
- 任何目标身份、主机指纹、活动 mount、manifest 或哈希不匹配立即停止,不猜路径、不接受新指纹、不清理源端批次。
- 回滚新 timer/receiver 时只撤销调度和专用授权;ARM archive、源端完整批次和原 WARCH3 timer 保留。
## 本地实施与验证证据
- 已实现生产 wrapper、ARM forced receiver、两端安装器和 3 组 systemd timer;安装器默认只安装 disabled units。
- `run-automatic-backup-contract.sh` 使用临时目录与 mock Docker/SSH/rsync 完成 28 项正负向合同,不连接真实主机。
- 已实现生产 wrapper、ARM forced receiver、独立群机器人 helper、ARM dead-man 和两端安装器;生产 3 组 timer 与 ARM 1 组 timer 均默认只安装为 disabled。
- `run-automatic-backup-contract.sh` 使用临时目录与 mock Docker/SSH/rsync 完成 46 项正负向合同,不连接真实主机;覆盖同日取代与回滚、坏候选保留、周检查点重建、Webhook 权限/链接/格式负例、build finding、状态损坏、源端停止、重复降噪和恢复通知。
- 企微告警聚焦单测 15 项通过;stdin 路径被替换为安全类别,AUTO_BACKUP 首次失败/季度提醒发送且相同重复状态降噪。
- `run-backup-restore-drill.sh` 本地合成 DB/附件往返通过,本次合成 RTO 为 8 秒;该结果不是生产 RTO 或 ARM 恢复证据。
- `scripts/check-minimal.sh` 通过:server 162 个测试文件、1004 项测试全部通过,client typecheck/build 通过。
- 环境合同、Trellis task/governance、敏感扫描、文档注册、流程合同和 `git diff --check` 均通过;文档注册保留既有 22 个未注册文件 warning,无本任务新增文档。
- ARM 安装、生产 units、真实首批、timer enable、真实企微失败告警和第二人季度走查仍为外部门禁,未执行、不得写成已完成。
- 最终只读复核修正了三个本地缺陷:生产安装器残留个人工号校验、ARM 重装可能改变 dead-man 状态文件属主、stale 状态未校验 run id/manifest SHA;修正后 46 项合同与 `scripts/check-minimal.sh` 全量复跑通过。
- 更新后的 ARM dead-man/helper、生产 helper/runner、真实有效首批、四组 timer enable、真实企微失败链路和第二人季度走查仍为外部门禁,未执行、不得写成已完成。
### 首次远端执行增量
- ARM 已安装 rsync、密码锁定专用账号和 forced-command receiver;从生产验证普通 shell、sender/read、delete 均拒绝,写通道 dry-run 通过且未生成归档。
- 生产三组 units 已安装为 disabled/inactive,并绑定活动 Compose project、PGDATA、附件挂载、构建标识和固定 SSH 身份。
- 首次真实备份在 `runtime_identity` 阶段 fail-closed,未生成源端或 ARM 批次;真实 Docker `println` 输出的参数间空格导致制表符拆分后路径不精确相等。
- runner 已改为解析 `docker inspect` mount JSON 并要求唯一匹配;合成 mock 同步改为真实 JSON 形状。重新执行真实首批和 timer enable 尚待验证。
- 首次真实备份曾在 `runtime_identity` 阶段因 Docker `println` 参数间空格 fail-closed;runner 改用 `docker inspect` mount JSON 后,后续真实源端成对批次已生成并传输。
- ARM finalize 随后因同 UTC 日已有不同 run id 而拒绝,因此尚无可作为自动备份闭环证据的新批次;本轮已在本地实现“完整校验后同日取代”,更新后的远端脚本、首批和 timer enable 仍待另行批准。
@@ -12,6 +12,8 @@
- 两台主机之间当前没有可直接复用的免密 SSH 别名或密钥链路,不能把本地工作站的 SSH 配置误当作服务器间自动化能力。
- 当前手工正确批次和 ARM 影子恢复已证明格式可用,但不能满足 RPO 24 小时或自动失败发现。
- 已确认采用“生产主动推送到 ARM 的专用受限接收身份”:生产保留即时传输结果,ARM 的已归档批次不暴露给 rsync 写入根。
- 生产切换已经完成并应用 61 项 migration;本地当前为 62 项,`2526052` 后续部署只新增 1 项 migration。自动备份三组 units 已安装但仍未形成可靠启用闭环。
- 首次真实自动批次已证明源端成对备份可生成,但 ARM 因“同一 UTC 日仅允许一个不同 run_id”拒绝重试批次;现有即时告警还依赖活动 server 容器,不能覆盖容器或整机故障。
## Scope
@@ -28,7 +30,11 @@
- 完整批次通过固定主机指纹和专用受限密钥自动推送到 ARM;该身份无普通 shell,只能把文件写入 inbox 或请求校验/归档,不能通过 rsync 读取或删除 archive。
- 传输端和目标端都要校验 manifest、文件大小和 SHA-256;同名已归档目标不得覆盖。中断传输可以在 inbox 内续传,但只有完整校验后才能原子进入 archive。
- 生产保留最近 7 个成功日批次;ARM 至少保留最近 7 个日批次和 4 个周检查点。清理只能发生在本次异机成功确认之后,且不得删除最后一个已知成功批次。
- 备份或异机校验失败必须生成脱敏状态报告,并复用现有企微运维告警能力;独立 stale watchdog 应在现有 07:30 巡检前检查最近一次异机成功时间,防止备份 timer 未运行或即时告警自身失败造成静默缺口。
- 生产即时告警和 ARM dead-man 告警统一使用独立企微运维群机器人;Webhook 只从 root-only 文件读取,不依赖 OA server、数据库、员工绑定或默认个人工号,也不得进入仓库、`.env.example`、Trellis、日志或聊天。
- ARM 安装独立 26 小时 dead-man timer,直接检查最近归档完成时间;生产整机或容器不可用、timer 未运行、传输未 finalize 时仍能从另一台主机发出告警。
- 生产 watchdog 的“恢复点缺失/损坏/超龄/Compose project 不匹配”继续硬失败;仅 `buildSha` 早于当前部署时降级为一次 finding,不得误报为没有恢复点。部署后仍推荐立即触发一次获批备份。
- backup、rsync 和 finalize 的失败输出必须写入状态目录下权限 `0600` 的最近失败诊断;企微告警仍只包含阶段、状态、finding 数和脱敏建议,不附原始输出。
- 同一 UTC 日已有不同自动归档时,新批次必须先完整通过 manifest、文件集、大小和 SHA-256 校验,再以可回滚的同文件系统 rename 顺序原子取代旧日批次;任一替换步骤失败必须恢复旧批次或保留可诊断状态。最终仍只保留每日一个恢复点。
- 告警只包含阶段、状态、finding 数和脱敏建议,不包含数据库凭证、完整目录、SSH 信息、真实业务数据、附件名或原始命令输出。
- 季度恢复保持人工触发,在 ARM 隔离目标执行;季度 timer 只发送一次脱敏提醒,不得自动运行恢复,更不得触碰生产库。
- 形成一页脱敏恢复卡,说明主机故障后的负责人、备份位置类别、恢复顺序和验收项,供另一名人员存入公司知识库。
@@ -38,7 +44,11 @@
- [x] 本地合成测试证明每日 wrapper 只接受唯一活动来源、互斥锁有效,并只发布完整备份批次。
- [x] 传输测试证明不完整批次、哈希不一致、重复批次和不可达 ARM 均失败,且不产生异机成功状态。
- [x] 保留测试覆盖 7 个日批次、4 个周检查点、最后成功批次保护和仅在异机确认后清理。
- [x] 告警测试证明首次失败和状态恶化会告警,重复同一失败降噪;07:30 stale 检查能发现超过 24 小时没有异机成功记录。
- [x] 告警测试证明首次失败和状态恶化会告警,重复同一失败降噪;07:10 stale 检查能发现超过 24 小时没有异机成功记录。
- [x] 本地合成测试证明同日不同 run_id 的完整新批次可取代旧批次,损坏新批次不会影响旧归档,且周检查点与日归档不产生悬空或错误指向。
- [x] 本地合成测试证明 buildSha 不同仅产生 finding,恢复点缺失/损坏/超龄/project 不同仍失败;失败诊断为 `0600` 且不进入告警摘要。
- [x] 环境契约证明 Webhook 相关键和值不出现在 `.env.example`、仓库配置或报告;两端安装器只接受 root-only Webhook 文件路径。
- [x] ARM 26 小时 dead-man 合成验证覆盖“源端停止/不产生新 finalize”而非仅验证机器人能发送,并覆盖首次失败、重复降噪与恢复通知。
- [ ] systemd 安装器显式绑定 Compose project、活动数据路径、构建标识、备份根和受限远端身份;安装后 timer enabled/active,但部署验证不手工执行真实告警。
- [ ] 经单独生产授权后完成一次自动日备份:源端完整、ARM 目标端哈希一致、状态报告脱敏,且未读取附件正文或修改业务数据。
- [ ] 模拟传输失败能在当天产生一条脱敏企微告警;测试后恢复正常链路并完成成功批次。
@@ -49,12 +59,15 @@
- 不引入备份平台、对象存储、MinIO、云厂商 SDK、Kubernetes 或持续复制。
- 不自动执行生产恢复、数据库 reset、迁移改写或附件回写。
- 不把真实业务正文、附件清单、凭证、主机地址、完整目录或 SSH 私钥写入仓库、报告或聊天。
- 不把企微群机器人 Webhook URL、key 或配置文件正文写入仓库、`.env.example`、Trellis、报告、日志或聊天。
- 不在本任务继续企微本人授权、员工角色、付款、报销或其他产品功能。
- 不因自动备份完成而取消季度人工影子恢复。
## Test Plan
- Shell 语法与合成文件系统测试,覆盖锁、来源解析、传输失败、目标重复、哈希和保留轮转。
- 合成 receiver 测试覆盖同日完整批次取代、损坏候选保留旧批次、替换失败恢复以及周检查点一致性。
- 合成双主机告警测试覆盖生产无 server 容器、ARM 26 小时无新归档、重复降噪、恢复通知和 root-only Webhook 文件门禁。
- 复用 `scripts/qa/run-backup-restore-drill.sh` 验证成对备份合同未回退。
- 聚焦测试现有企微运维告警的备份状态与 stale finding 适配。
- `node deploy/trialrun/validate-env-contract.js`。