修复自动备份真实挂载解析(07-27-automatic-offhost-backup)
ci-e2e / e2e (push) Waiting to run
ci-minimal / check-minimal (push) Waiting to run

这个提交包含在:
2026-07-27 11:19:13 +08:00
父节点 b78f2a11ce
当前提交 9875bc37a4
共修改 5 个文件,包含 34 行新增和 5 行删除
@@ -943,6 +943,10 @@ test "$(printf '%s\n' "$actual" | LC_ALL=C sort)" = \
- Resolve one active Compose server/database pair and verify its build, PGDATA, - Resolve one active Compose server/database pair and verify its build, PGDATA,
storage mount and database identity before backup. Do not use fixed container storage mount and database identity before backup. Do not use fixed container
names or repository-relative runtime data. names or repository-relative runtime data.
- Parse Docker mount identity from `docker inspect` JSON and require exactly one
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.
- Transfer only a complete `auto-<UTC timestamp>` three-file batch to ARM - 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; inbox. The forced key allows write-only `rrsync` or an exact finalize command;
it rejects shell, sender/read and destructive rsync options. it rejects shell, sender/read and destructive rsync options.
@@ -986,6 +990,9 @@ test "$(printf '%s\n' "$actual" | LC_ALL=C sort)" = \
- Shell syntax plus `run-automatic-backup-contract.sh` using temporary files and - Shell syntax plus `run-automatic-backup-contract.sh` using temporary files and
mocked Docker/SSH boundaries; it must never contact real hosts. mocked Docker/SSH boundaries; it must never contact real hosts.
- 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 - Focused WeCom alert tests for stdin path redaction, preserved AUTO_BACKUP
issue identity, first/repeated failure and quarterly reminder behavior. issue identity, first/repeated failure and quarterly reminder behavior.
- Environment contract assertions for all four scripts, three timers, explicit - Environment contract assertions for all four scripts, three timers, explicit
@@ -55,3 +55,10 @@
- `scripts/check-minimal.sh` 通过:server 162 个测试文件、1004 项测试全部通过,client typecheck/build 通过。 - `scripts/check-minimal.sh` 通过:server 162 个测试文件、1004 项测试全部通过,client typecheck/build 通过。
- 环境合同、Trellis task/governance、敏感扫描、文档注册、流程合同和 `git diff --check` 均通过;文档注册保留既有 22 个未注册文件 warning,无本任务新增文档。 - 环境合同、Trellis task/governance、敏感扫描、文档注册、流程合同和 `git diff --check` 均通过;文档注册保留既有 22 个未注册文件 warning,无本任务新增文档。
- ARM 安装、生产 units、真实首批、timer enable、真实企微失败告警和第二人季度走查仍为外部门禁,未执行、不得写成已完成。 - ARM 安装、生产 units、真实首批、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 尚待验证。
+5
查看文件
@@ -326,11 +326,16 @@ for (const requiredFragment of [
'write_success_state "$run_id" "$manifest_sha"', 'write_success_state "$run_id" "$manifest_sha"',
'prune_local_backups "$batch_name"', 'prune_local_backups "$batch_name"',
'--summary-file=-', '--summary-file=-',
"docker inspect -f '{{json .Mounts}}'",
'if len(matches) != 1 or not matches[0]:',
]) { ]) {
if (!automaticBackupRunnerContent.includes(requiredFragment)) { if (!automaticBackupRunnerContent.includes(requiredFragment)) {
errors.push(`run-automatic-backup.sh 缺少运行时安全合同: ${requiredFragment}`); errors.push(`run-automatic-backup.sh 缺少运行时安全合同: ${requiredFragment}`);
} }
} }
if (automaticBackupRunnerContent.includes('println .Destination "\\t" .Source')) {
errors.push('run-automatic-backup.sh 不得用人类可读 Go-template 分隔符解析真实 Docker mount');
}
for (const requiredFragment of [ for (const requiredFragment of [
'"$original_command" == rsync\\ --server\\ *', '"$original_command" == rsync\\ --server\\ *',
+3 -3
查看文件
@@ -239,11 +239,11 @@ if [[ "${1:-}" == "inspect" ]]; then
printf 'POSTGRES_DB=hyb_oa\nPOSTGRES_USER=backup_user\nPOSTGRES_PASSWORD=fixture\n' printf 'POSTGRES_DB=hyb_oa\nPOSTGRES_USER=backup_user\nPOSTGRES_PASSWORD=fixture\n'
fi fi
;; ;;
*Mounts*) *json*.Mounts*)
if [[ "$container" == "server-id" ]]; then if [[ "$container" == "server-id" ]]; then
printf '/app/storage\t%s\n' "$MOCK_STORAGE" printf '[{"Destination":"/app/storage","Source":"%s"}]\n' "$MOCK_STORAGE"
else else
printf '/var/lib/postgresql/data\t%s\n' "$MOCK_PG" printf '[{"Destination":"/var/lib/postgresql/data","Source":"%s"}]\n' "$MOCK_PG"
fi fi
;; ;;
*Image*) printf 'sha256:%064d\n' 0 ;; *Image*) printf 'sha256:%064d\n' 0 ;;
+12 -2
查看文件
@@ -134,8 +134,18 @@ container_env() {
mount_source() { mount_source() {
local container_id="$1" local container_id="$1"
local destination="$2" local destination="$2"
docker inspect -f '{{range .Mounts}}{{println .Destination "\t" .Source}}{{end}}' "$container_id" \ docker inspect -f '{{json .Mounts}}' "$container_id" \
| awk -F '\t' -v expected="$destination" '$1 == expected { print $2 }' | python3 -c '
import json
import sys
destination = sys.argv[1]
mounts = json.load(sys.stdin)
matches = [mount.get("Source", "") for mount in mounts if mount.get("Destination") == destination]
if len(matches) != 1 or not matches[0]:
raise SystemExit(1)
print(matches[0])
' "$destination"
} }
resolve_active_runtime() { resolve_active_runtime() {