19 KiB
Offsite Backup / Credential Escrow 操作手冊
版本:2026-05-19.v4 適用範圍:110 備份中心、Google Drive/rclone 離機備份、credential escrow 覆核 marker
目標
這份手冊用來把「本地備份已完成」推進到「整台 110 遺失時仍可恢復」。
它處理兩個缺口:
- 離機備份:13 個本地 restic repo 必須至少有一份可到達 110 以外的位置。
- 憑證金庫:restic password、Google Drive rclone.conf/OAuth、break-glass admin、DNS/registrar/OAuth recovery 必須在密碼管理器或離線加密金庫可找到、可解密、可用。
本手冊不保存任何 secret。所有指令都不得把密碼、token、recovery code、private key 貼到 shell transcript、LOGBOOK、Telegram、Prometheus label 或 repo。
絕對禁止
- 禁止把 Google Drive OAuth token、rclone config、restic password、OAuth recovery code 寫進 git。
- 禁止把 secret 當成
evidence-id或note傳給mark-credential-escrow-verified.sh。 - 禁止在 Google Drive/rclone 未配置或 gate blocked 時跑 full sync。
- 禁止由子備份腳本或臨時手動指令刪除遠端備份。唯一例外是
/backup/scripts/sync-offsite-backups.sh --mode sync,它在 full/partial gate 通過後用OFFSITE_SYNC_DELETE_OLD=1鏡像本地 latest-only restic repo,刪除 Google Drive 上已不屬於最新 repo 狀態的舊檔。 - 禁止把 restore 直接套到 production DB、production namespace 或正式 volume。
- 禁止為了清告警假造 escrow marker。marker 只能在人工確認金庫項目可用後建立。
狀態判讀
| 狀態 | 意義 | 下一步 |
|---|---|---|
READY_WITH_WARNINGS |
本地 repo 可檢查,但 Google Drive/rclone 或 escrow 還沒完成 | 可以繼續設定 Google Drive/rclone / 金庫,不可 full sync |
BLOCKED |
必要條件缺失,例如 rclone remote 未配置卻要求 dry-run/full sync | 先修 blocked 項目 |
READY |
Google Drive/rclone、small repo、marker、金庫覆核都符合 gate | 可排小範圍 sync 或 full sync review |
Prometheus 裡的 BackupOffsiteCopyNotConfigured 與 BackupCredentialEscrowEvidenceMissing 是恢復能力缺口,不代表網站立即故障;但如果長期存在,代表「災難時可能無法復原」。repo 工作站可用 live visibility check 確認缺口告警真的進入 Prometheus / Alertmanager:
python3 scripts/ops/backup-alert-live-visibility-check.py --prometheus-url http://192.168.0.110:9090 --alertmanager-url http://192.168.0.110:9093
這支檢查只讀 API,不送測試告警、不改 route、不改 silence。它會在缺口 metric 存在時要求告警 firing/active;如果 Google Drive/rclone 或 escrow 已補齊,對應告警不需要繼續 firing。
備份保留策略固定為 latest-only:本地 restic repo 在新 snapshot 成功後執行 --group-by "" --keep-last 1 --prune;188 MOMO PostgreSQL 檔案備份在新檔成功後只留最新一份;Google Drive/rclone full sync 以本地 repo 為準鏡像,成功後刪除遠端舊檔,且 RCLONE_DRIVE_USE_TRASH=false,避免舊備份只進 Google Drive 垃圾桶。Prometheus 指標 awoooi_backup_retention_latest_only 與 awoooi_backup_retention_offsite_delete_old_enabled 必須為 1,且每個 110 restic repo 的 awoooi_backup_job_snapshot_count 必須小於等於 1,否則 retention 告警會進 Telegram。
Phase 0:確認本地備份綠燈
在 110 上執行:
/backup/scripts/offsite-escrow-evidence-report.sh --no-color
/backup/scripts/backup-offsite-readiness-gate.sh --status --no-color
grep -E 'awoooi_backup_last_run_failed_count|awoooi_backup_job_fresh|awoooi_backup_integrity_fresh' /home/wooo/node_exporter_textfiles/backup_health.prom
在 repo 工作站執行:
SSH_BATCH_MODE=yes bash scripts/reboot-recovery/full-stack-cold-start-check.sh --monitor-read-only --no-color --watch --interval 1 --max-attempts 1
SSH_BATCH_MODE=yes bash scripts/reboot-recovery/p3-controlled-release-gate.sh --no-color
成功條件:
awoooi_backup_last_run_failed_count{exported_job="backup_all"} = 0- 110 有 13 個
awoooi_backup_job_fresh - restic check / restore drill fresh
- cold-start gate 沒有 blocked
offsite-escrow-evidence-report.sh會輸出目前NEXT_STEP,且不含任何 credential 值
Phase 0.5:產出可交接 evidence report
每次 Google Drive/rclone 設定、small dry-run、partial sync、escrow 覆核、full sync 前後,都先產出一份紅acted report。這份 report 可以貼到 LOGBOOK 或交接訊息,但仍要先目視確認沒有 secret。
110 每日 06:15 也會自動產生同一份 report 到 /backup/logs/offsite-escrow-evidence-report.log。這條 cron 只做本機只讀判讀,不會查 remote、不會上傳、不會寫 success marker;backup-health exporter 會把 cron 是否存在納入 awoooi_backup_job_configured。
/backup/scripts/offsite-escrow-evidence-report.sh --no-color
如果已經設定 Google Drive/rclone,且需要確認 remote 可列出,才加:
/backup/scripts/offsite-escrow-evidence-report.sh --include-remote-status --no-color
--include-remote-status 只會跑 sync-offsite-backups.sh --mode status,不會上傳、不會寫 success marker;但它會查 remote,因此只在 Google Drive/rclone 已設定後使用。
在 repo 工作站也可以產生全站收斂 scorecard:
bash scripts/reboot-recovery/full-stack-recovery-scorecard.sh
若要把目前 DR 缺口直接轉成 operator 可照做的下一步命令,使用只讀 checklist:
bash scripts/reboot-recovery/dr-offsite-operator-checklist.sh --no-color
這支 checklist 會彙整 repo scorecard、Prometheus recording rule、110 紅acted evidence report,並依 NEXT_STEP 印出下一段應在 110 TTY 執行的命令。它不會查詢或輸出 secret、不會上傳資料、不會寫 provider / escrow / sync marker;真正的寫入與同步仍必須由 operator 在 110 本機明確執行。
同一個 next-step 也會進入 110 textfile metric,讓 AI 巡檢不用解析人工 log:
awoooi_backup_dr_next_step_info{host="110"}
awoooi_backup_offsite_partial_fresh{host="110",provider="rclone"}
awoooi_backup_dr_credential_escrow_missing_count{host="110"}
這些 metric 只描述階段與缺口,不包含 Google Drive token、restic password 或 evidence-id。
若輸出 RECOVERY_STATE=CORE_READY_DR_OFFSITE_PENDING,代表網站與 cold-start gate 已恢復,但本手冊的 Google Drive/rclone / escrow / full offsite marker 還沒完成。此狀態不可當成 DR 完成,只能當成核心服務恢復完成。
要防止人為誤判,使用嚴格 gate:
bash scripts/reboot-recovery/full-stack-recovery-scorecard.sh --require-dr
人工完成 5 個 credential escrow marker 後,用最終 gate 做收斂判定。這條命令會同時檢查 repo scorecard、110 Prometheus recovery recording rule、備份告警可見性與 110 紅acted evidence report;任何一層不同步都會失敗。
bash scripts/reboot-recovery/dr-offsite-operator-checklist.sh --require-dr
如果 marker 剛寫完,Prometheus scrape、recording rule 與 Alertmanager 可能需要幾分鐘才會同步。這時不要手動猜狀態,也不要重複亂改 marker;在 repo 工作站執行 post-marker 等待器,讓它只讀輪詢到四層 gate 一致:
bash scripts/reboot-recovery/wait-dr-offsite-ready.sh --timeout-seconds 900 --interval-seconds 30 --no-color
這支腳本只讀 full-stack-recovery-scorecard.sh --require-dr、recovery-scorecard-contract-check.py --expect-dr-ready、backup-alert-live-visibility-check.py 與 dr-offsite-operator-checklist.sh --require-dr。它不會建立 escrow marker、不會上傳或刪除備份、不會列印 credential;若 timeout 時仍顯示 ESCROW_MISSING_COUNT>0,代表人工作業尚未完成,不可偽造 marker。
在 OFFSITE_CONFIGURED=0、ESCROW_MISSING_COUNT>0 或 FULL_MARKER_PRESENT=0 時,這條指令必須失敗;這是預期行為,不可用 fake marker 清掉。
Prometheus 最終合約也必須同步驗證:
python3 scripts/ops/recovery-scorecard-contract-check.py --prometheus-url http://192.168.0.110:9090 --expect-core-ready --expect-dr-ready
在 full sync / escrow 還沒完成前,--expect-dr-ready 必須失敗;完成後才應通過。
Prometheus 也會用 awoooi_recovery_dr_offsite_ready{host="110"} 呈現同一個 DR gate。此值目前應為 0;只有 Phase 7 full sync 完成且 Phase 5 escrow marker 全部 fresh 後,才應變為 1。
判讀重點:
NEXT_STEP |
意義 |
|---|---|
configure_google_drive_rclone_on_110_tty |
還沒設定 Google Drive/rclone,回 Phase 1 |
run_small_dry_run_then_partial_sync |
rclone remote 已配置,尚未證明小範圍 offsite sync |
complete_credential_escrow_review |
offsite 小範圍已證明,還缺金庫覆核 marker |
pre_full_sync_review |
可安排低峰 full sync 前檢查 |
offsite_and_escrow_ready |
離機備份與金庫證據皆已到位 |
Phase 1:在 110 本機設定 Google Drive/rclone
優先使用互動模式。不要把 Google Drive OAuth token 或 rclone.conf 貼到聊天或文件。
ssh wooo@192.168.0.110
/backup/scripts/configure-offsite-rclone.sh --interactive
/backup/scripts/configure-offsite-rclone.sh --status
configure-offsite-b2.sh是 legacy 相容工具;目前預設用 Google Drive/rclone,不需要B2_ACCOUNT_ID。
Phase 1.5:建立 Google Drive root-scoped remote
Google Drive 帳號若檔案很多,gdrive:awoooi-backups/restic/... 可能每次都花數分鐘解析資料夾路徑。OAuth 完成後,建立一個只指向 awoooi-backups/restic 的 root-scoped remote,後續備份使用 gdrive_awoooi_restic:,避免 full sync 被 Drive 根目錄查找拖慢。
OFFSITE_RCLONE_SOURCE_REMOTE=gdrive \
OFFSITE_RCLONE_ROOT_REMOTE=gdrive_awoooi_restic \
OFFSITE_RCLONE_ROOT_PATH=awoooi-backups/restic \
/backup/scripts/configure-offsite-rclone.sh --create-root-remote
/backup/scripts/configure-offsite-rclone.sh --status
成功條件:
ROOT_SCOPED_REMOTE_READY=gdrive_awoooi_restic:
OFFSITE_RCLONE_REMOTE=gdrive_awoooi_restic
OFFSITE_REMOTE_ROOT=gdrive_awoooi_restic:
RCLONE_REMOTE_CONFIGURED=1
這個步驟會複用既有 gdrive remote 的 OAuth token,並在 host-local rclone.conf 寫入 root_folder_id;不會把 token 寫進 /backup/scripts/offsite.env、repo、LOGBOOK 或 Telegram。
成功條件:
RCLONE_PRESENT=1
OFFSITE_PROVIDER=rclone
OFFSITE_RCLONE_REMOTE=gdrive
RCLONE_REMOTE_CONFIGURED=1
OFFSITE_ENV_PRESENT=1
OFFSITE_ENV_MODE_OK=1
如果必須用環境變數寫入,只能在受控 shell 中操作,並確認 shell history 不會保存 secret。完成後立刻檢查檔案權限:
ls -l /backup/scripts/offsite.env
/backup/scripts/configure-offsite-rclone.sh --status
Phase 2:Google Drive/rclone 設定後跑 readiness gate
/backup/scripts/backup-offsite-readiness-gate.sh --status --require-configured --no-color
成功條件:
- 沒有
BLOCKED - rclone remote 已配置,例如
gdrive: - rclone command 存在
ai-artifacts與public-routes本地 repo 存在
如果只有 escrow marker warning,可以繼續做 rclone dry-run;但仍需在 full sync 前完成金庫覆核。
Phase 3:小範圍 dry-run
先只測很小的 repo,不碰 87G 全量資料。
/backup/scripts/backup-offsite-readiness-gate.sh --dry-run-small --no-color
這會對 ai-artifacts public-routes 跑 rclone dry-run。成功後再執行明確的小範圍 dry-run:
/backup/scripts/sync-offsite-backups.sh --mode dry-run --repos "ai-artifacts public-routes"
成功條件:
- rclone dry-run 完成
- 沒有 authentication error
- 沒有 remote/path permission error
- 沒有本地 repo 缺失
安全護欄:
sync-offsite-backups.sh --mode sync預設會先檢查 1 分鐘 load,不得高於OFFSITE_SYNC_MAX_LOAD_1=12。/backup使用率不得高於OFFSITE_SYNC_MAX_BACKUP_DISK_USED_PCT=92。- full 13 repo sync 不得與本地備份程序重疊,且必須距離下一次備份排程至少
OFFSITE_SYNC_FULL_MIN_RUNWAY_MINUTES=270分鐘;手動執行時若接近 08:00/14:00/20:00 AWOOOI 高頻備份,gate 會 BLOCKED,應等待 03:00 gated cron 或下一個低峰窗口。 - 成功通知預設不送 Telegram;證據留在 log、textfile、Prometheus。失敗仍會告警。
Phase 4:小範圍 partial sync
小 repo dry-run 成功後,才做 partial sync:
/backup/scripts/sync-offsite-backups.sh --mode sync --repos "ai-artifacts public-routes"
/backup/scripts/backup-offsite-readiness-gate.sh --status --require-configured --no-color
預期結果:
- 寫入
/backup/offsite/rclone-partial-last-success - 寫入 per-repo marker
- 不會寫
/backup/offsite/rclone-last-success
full success marker 只能在 13 repo 全部同步成功後建立,避免 partial sync 誤清 full offsite stale。
Phase 5:Credential escrow 覆核
人工確認密碼管理器或離線加密金庫後,才寫 marker。marker 只能放證據 ID,不放 secret。
先看缺口:
/backup/scripts/mark-credential-escrow-verified.sh --status
/backup/scripts/mark-credential-escrow-verified.sh --missing-commands
逐項覆核後寫入 marker;建議直接使用 --missing-commands 印出的缺失項目模板,只替換 EVIDENCE_ID_FOR_*。直接使用 placeholder 會被拒絕;正式寫入前可先加 --dry-run 驗證 evidence-id,不會建立 marker:
/backup/scripts/mark-credential-escrow-verified.sh --missing-commands
# 將輸出的 EVIDENCE_ID_FOR_* 換成不含 secret 的證據 ID 後,可先加 --dry-run 驗證其中一條。
正式寫入 marker 後,腳本會嘗試立即刷新 110 的 backup_health.prom,讓 awoooi_backup_credential_escrow_fresh、awoooi_backup_dr_credential_escrow_missing_count 與 Prometheus 告警更快收斂;如果 exporter 暫時不可用,marker 仍會保留,下一輪 cron 會補刷新。輸出應包含 MARKER_WRITTEN,且在 exporter 可用時包含 TEXTFILE_REFRESHED。
可接受的 evidence-id:
- 密碼管理器項目 ID
- 工單 ID
- sealed envelope ID
- recovery checklist ID
不可接受的 evidence-id:
- 密碼、token、recovery code、secret URL
- private key、OAuth token、rclone.conf 內容
- 任何可直接登入或還原的秘密值
EVIDENCE_ID_FOR_*、VAULT-ITEM-ID、TODO、CHANGE_ME等 placeholder
不可接受:
- 密碼、token、API key
- recovery code
- private key
- 含 secret 的 URL
Phase 6:Full sync 前檢查
全 13 repo 約 87G。只能在低峰窗口與 operator review 後執行。
先跑不會上傳的 full sync 前檢查:
/backup/scripts/backup-offsite-readiness-gate.sh --pre-full-sync --require-configured --require-escrow --no-color
成功條件:
- 13 個本地 repo 都存在
- Google Drive/rclone 配置完整
- escrow marker 都 fresh
- 110 host load 低於 gate
- 沒有正在執行的本地備份程序,且距離下一次備份排程有足夠 runway
- P3 gate 沒有 blocked
若手動 full sync 已經開始,但實測速度顯示大型 repo 會撞到 02:00 / 08:00 / 14:00 / 20:00 備份窗口,優先保護本地備份。做法是停止目前的 sync-offsite-backups.sh --mode sync 與其 rclone child,清掉 /tmp/awoooi-offsite-backup.lock,並寫入 /backup/offsite/rclone-manual-protective-stop.status,至少包含 status、timestamp、completed_or_verified_repos、remaining_repos 與 next_step。不得手寫 /backup/offsite/rclone-last-success;full marker 只能由完整 13 repo sync 成功後自動產生。
再確認容量:
du -sh /backup/awoooi /backup/configs /backup/gitea /backup/harbor /backup/momo /backup/langfuse /backup/monitoring /backup/signoz /backup/open-webui /backup/clawbot /backup/sentry /backup/ai-artifacts /backup/public-routes
Phase 7:Full sync
只有 Phase 6 全綠、確認低峰窗口、且人工明確啟用 full sync marker 後才執行:
install -d -m 750 /backup/offsite
touch /backup/offsite/enable-rclone-sync
/backup/scripts/sync-offsite-backups.sh --mode sync
enable-rclone-sync 是第二層保險,避免有人或 cron 在未審核時直接啟動 13 repo 全量同步。若要臨時只做人工 full sync 而不啟用每日 03:00 gated cron,必須改用受控環境變數:
OFFSITE_SYNC_REQUIRE_ENABLE_MARKER_FOR_FULL=0 /backup/scripts/sync-offsite-backups.sh --mode sync
除非當下有人盯著負載與 log,否則不要用這個覆寫。
完成後驗證:
/backup/scripts/offsite-escrow-evidence-report.sh --include-remote-status --no-color
/backup/scripts/verify-offsite-full-sync.sh --write-textfile --no-color
/backup/scripts/backup-offsite-readiness-gate.sh --status --require-configured --require-escrow --no-color
grep -E 'awoooi_backup_offsite_|awoooi_backup_credential_escrow_' /home/wooo/node_exporter_textfiles/backup_health.prom
grep -E 'awoooi_backup_offsite_remote_|awoooi_backup_offsite_full_verify_' /home/wooo/node_exporter_textfiles/offsite_full_sync_verify.prom
預期:
/backup/offsite/rclone-last-success存在且 freshawoooi_backup_offsite_fresh{provider="rclone"} = 1awoooi_backup_offsite_remote_verify_ok{provider="rclone"} = 1- 13 個
awoooi_backup_offsite_remote_snapshot_count{provider="rclone"}都等於1 BackupOffsiteCopyNotConfigured解除BackupOffsiteCopyStale不 firingBackupOffsiteFullVerifyFailed不 firingBackupOffsiteRemoteSnapshotRetentionExceeded不 firing- escrow 五項 fresh 後,
BackupCredentialEscrowEvidenceMissing解除
故障處理
| 症狀 | 判讀 | 處理 |
|---|---|---|
Google Drive/rclone remote not configured |
110 尚未完成 rclone Google Drive OAuth 或 remote 名稱不符 | 回 Phase 1 |
小 repo 只有數 MB 但 rclone copy 花數分鐘 |
Drive 根目錄路徑解析過慢 | 執行 Phase 1.5,改用 gdrive_awoooi_restic: |
rclone 未安裝 |
host package 缺失 | 先由 Ansible/ops 安裝 rclone,再重跑 gate |
directory not found 或 permission denied |
Google Drive remote/path 權限不符 | 修 rclone remote 或 Drive folder 權限,不要改 repo |
| small dry-run 成功但 full pre-check blocked | 13 repo 或 escrow 不完整 | 先修 blocked 項目 |
| full sync 中 host load 過高 | 同步窗口不合適 | 中止後改低峰窗口;不要降低資料庫/ClickHouse memory 來硬跑 |
| Prometheus 還在 pending | alert 有 for 時間或 exporter 未刷新 |
先刷新 exporter,再查 /api/v1/alerts |
完成定義
離機備份與金庫不能只靠一次手動成功。真正完成需滿足:
- Google Drive/rclone remote 存在於 110 host-local
rclone.conf,offsite.env只保存非 secret remote/path,mode0600 - small dry-run 成功
- small partial sync 成功
- full sync 在低峰窗口成功
- full sync 後
verify-offsite-full-sync.sh --write-textfile成功,並證明 Google Drive 13 個 repo 皆只保留 1 份 snapshot - full offsite marker fresh
- 五個 credential escrow marker fresh
- Prometheus offsite / escrow warning 清除
- LOGBOOK 記錄 snapshot / marker / gate 證據,但不含任何 secret