Files
awoooi/docs/runbooks/OFFSITE-BACKUP-ESCROW-RUNBOOK.md
Your Name cfb866d055
Some checks failed
Ansible Lint / lint (push) Successful in 35s
CD Pipeline / tests (push) Failing after 13s
CD Pipeline / build-and-deploy (push) Has been skipped
CD Pipeline / post-deploy-checks (push) Has been skipped
Code Review / ai-code-review (push) Failing after 11s
feat(governance): add agent market automation surfaces
2026-06-04 21:50:55 +08:00

19 KiB
Raw Permalink Blame History

Offsite Backup / Credential Escrow 操作手冊

版本2026-05-19.v4 適用範圍110 備份中心、Google Drive/rclone 離機備份、credential escrow 覆核 marker


目標

這份手冊用來把「本地備份已完成」推進到「整台 110 遺失時仍可恢復」。

它處理兩個缺口:

  1. 離機備份13 個本地 restic repo 必須至少有一份可到達 110 以外的位置。
  2. 憑證金庫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-idnote 傳給 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 裡的 BackupOffsiteCopyNotConfiguredBackupCredentialEscrowEvidenceMissing 是恢復能力缺口不代表網站立即故障但如果長期存在代表「災難時可能無法復原」。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 --prune188 MOMO PostgreSQL 檔案備份在新檔成功後只留最新一份Google Drive/rclone full sync 以本地 repo 為準鏡像,成功後刪除遠端舊檔,且 RCLONE_DRIVE_USE_TRASH=false,避免舊備份只進 Google Drive 垃圾桶。Prometheus 指標 awoooi_backup_retention_latest_onlyawoooi_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 markerbackup-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-drrecovery-scorecard-contract-check.py --expect-dr-readybackup-alert-live-visibility-check.pydr-offsite-operator-checklist.sh --require-dr。它不會建立 escrow marker、不會上傳或刪除備份、不會列印 credential若 timeout 時仍顯示 ESCROW_MISSING_COUNT>0,代表人工作業尚未完成,不可偽造 marker。

OFFSITE_CONFIGURED=0ESCROW_MISSING_COUNT>0FULL_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 2Google Drive/rclone 設定後跑 readiness gate

/backup/scripts/backup-offsite-readiness-gate.sh --status --require-configured --no-color

成功條件:

  • 沒有 BLOCKED
  • rclone remote 已配置,例如 gdrive:
  • rclone command 存在
  • ai-artifactspublic-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 5Credential 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_freshawoooi_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-IDTODOCHANGE_ME 等 placeholder

不可接受:

  • 密碼、token、API key
  • recovery code
  • private key
  • 含 secret 的 URL

Phase 6Full 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,至少包含 statustimestampcompleted_or_verified_reposremaining_reposnext_step。不得手寫 /backup/offsite/rclone-last-successfull 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 7Full 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 存在且 fresh
  • awoooi_backup_offsite_fresh{provider="rclone"} = 1
  • awoooi_backup_offsite_remote_verify_ok{provider="rclone"} = 1
  • 13 個 awoooi_backup_offsite_remote_snapshot_count{provider="rclone"} 都等於 1
  • BackupOffsiteCopyNotConfigured 解除
  • BackupOffsiteCopyStale 不 firing
  • BackupOffsiteFullVerifyFailed 不 firing
  • BackupOffsiteRemoteSnapshotRetentionExceeded 不 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.confoffsite.env 只保存非 secret remote/pathmode 0600
  • 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