修复异机备份凭证与中断恢复(07-27-offhost-backup-final-fixes)
ci-e2e / e2e (push) Waiting to run
ci-minimal / check-minimal (push) Waiting to run

这个提交包含在:
2026-07-27 17:58:11 +08:00
父节点 f1e8cf138a
当前提交 01f1259557
共修改 13 个文件,包含 748 行新增和 16 行删除
+42 -3
查看文件
@@ -952,6 +952,12 @@ test "$(printf '%s\n' "$actual" | LC_ALL=C sort)" = \
destination match. Do not split human-formatted Go-template output on tabs or
spaces: `println` inserts argument spacing that can make a visually correct
line fail exact path comparison on a real daemon.
- Database connection values used by the automatic wrapper must be exported in
an isolated child shell before `capture_command`; do not invoke `env` with a
complete `DATABASE_URL` or password assignment in its argv. Docker backup and
shadow-restore commands inherit `PGPASSWORD` with
`PGPASSWORD="$POSTGRES_PASSWORD" docker exec -e PGPASSWORD ...`; never use a
value-bearing `-e PGPASSWORD=...` Docker argument.
- Transfer only a complete `auto-<UTC timestamp>` three-file batch to ARM
inbox. The forced key allows write-only `rrsync` or an exact finalize command;
it rejects shell, sender/read and destructive rsync options.
@@ -962,6 +968,22 @@ test "$(printf '%s\n' "$actual" | LC_ALL=C sort)" = \
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.
- After taking the finalize lock and before reading the incoming partial, ARM
reconciles interrupted same-filesystem transactions. A valid
`.daily-replacement-<UTC-day>.*` is deleted only when one valid canonical
archive for that day exists; when the canonical is absent it is renamed back
from its own verified manifest identity. Invalid, linked, duplicate or
otherwise ambiguous daily state fails closed and preserves every object.
- Weekly `.rollback.*` is authoritative only after full batch validation and is
restored when the canonical checkpoint is absent. Weekly `.new.*` is never an
authoritative source; it may be removed only after a valid canonical,
rollback or unique valid daily source is established. Invalid canonical,
rollback, duplicate candidate, week mismatch or non-unique rebuild source
fails closed without deleting evidence. A reconciled daily point forces its
ISO-week checkpoint to be rebuilt before finalize continues. When a valid
canonical daily point and its old rollback coexist, defer deleting that daily
rollback until every required weekly reconciliation has succeeded; a weekly
validation or rebuild failure must leave the daily rollback in place.
- 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.
@@ -995,6 +1017,14 @@ test "$(printf '%s\n' "$actual" | LC_ALL=C sort)" = \
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.
- Interrupted daily replacement with valid canonical -> remove the verified old
rollback only after rebuilding the weekly point; canonical missing -> restore
the verified rollback; canonical invalid, weekly rebuild failure or candidates
ambiguous -> preserve the remaining rollback evidence and fail.
- Interrupted weekly replacement with valid canonical -> remove safe staging
and verified rollback; canonical missing + valid rollback -> restore it;
staging-only + one valid daily source -> rebuild; any invalid or ambiguous
authority -> preserve all objects and fail.
- 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
@@ -1025,9 +1055,15 @@ test "$(printf '%s\n' "$actual" | LC_ALL=C sort)" = \
runner. A simplified tab-delimited mock is not valid evidence for real mount
parsing.
- 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.
weekly hard-link rebuild, daily/weekly interrupted-transaction recovery,
rollback preservation when weekly reconciliation fails, ambiguous-state
preservation, 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.
- Mock Docker assertions must prove synthetic connection values are absent from
wrapper/backup/restore argv while an inherited `PGPASSWORD` reaches the
mocked container command. Test-fixture `env KEY=value` launch arrays do not
need mechanical rewriting because they contain only synthetic values.
- 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
@@ -1044,6 +1080,7 @@ test "$(printf '%s\n' "$actual" | LC_ALL=C sort)" = \
```bash
compose exec server send-alert --recipient=<personal-employee-number>
rsync -a <batch>/ backup-host:<archive>/
docker exec -e PGPASSWORD="$POSTGRES_PASSWORD" db pg_dump ...
```
#### Correct
@@ -1057,4 +1094,6 @@ rsync -a <batch>/ backup-host:<archive>/
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
# The Docker CLI inherits the value without placing it in argv.
PGPASSWORD="$POSTGRES_PASSWORD" docker exec -e PGPASSWORD db pg_dump ...
```