From 9f6c0312424a4ce6cb010fc57d804ccc7e1cae6a Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 18 Jul 2026 17:11:04 +0800 Subject: [PATCH] fix(backup): run all required backup domains --- infra/ansible/playbooks/110-devops.yml | 8 +++ scripts/backup/backup-all.sh | 38 +++++++++++++- .../tests/test_backup_all_required_domains.py | 50 +++++++++++++++++++ 3 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 scripts/backup/tests/test_backup_all_required_domains.py diff --git a/infra/ansible/playbooks/110-devops.yml b/infra/ansible/playbooks/110-devops.yml index 54ef9d725..894e51d90 100644 --- a/infra/ansible/playbooks/110-devops.yml +++ b/infra/ansible/playbooks/110-devops.yml @@ -230,7 +230,14 @@ - backup-momo.sh - backup-awoooi.sh - backup-awoooi-frequent.sh + - backup-langfuse.sh + - backup-monitoring.sh - backup-signoz.sh + - backup-open-webui.sh + - backup-clawbot.sh + - backup-sentry.sh + - backup-ai-artifacts.sh + - backup-public-routes.sh - clickhouse-native-backup.sh - clickhouse-native-restore-drill.sh - clickhouse-restore-inventory.py @@ -239,6 +246,7 @@ - check-backup-integrity.sh - verify-gitea-backup.sh - sync-offsite-backups.sh + - backup-offsite-readiness-gate.sh - offsite-escrow-evidence-report.sh - verify-offsite-full-sync.sh tags: backup_jobs diff --git a/scripts/backup/backup-all.sh b/scripts/backup/backup-all.sh index 1a92af69d..2e137dafa 100755 --- a/scripts/backup/backup-all.sh +++ b/scripts/backup/backup-all.sh @@ -14,7 +14,7 @@ source "$(dirname "$0")/common.sh" main() { local start_time=$(date +%s) local failed=0 - local total=9 + local total=13 log_info "╔══════════════════════════════════════════════════════════════╗" log_info "║ WOOO AIOps - 全服務備份開始 (v3.0) ║" @@ -107,6 +107,42 @@ main() { failed=$((failed+1)) fi + # Sentry data plane (Postgres, ClickHouse, Kafka, Redis, object state) + log_info ">>> [10/${total}] 備份 Sentry..." + if /backup/scripts/backup-sentry.sh; then + log_success " Sentry 備份成功" + else + log_error " Sentry 備份失敗" + failed=$((failed+1)) + fi + + # AI model/tooling manifests from 188 (model blobs remain excluded by policy) + log_info ">>> [11/${total}] 備份 AI artifacts (188)..." + if /backup/scripts/backup-ai-artifacts.sh; then + log_success " AI artifacts 備份成功" + else + log_error " AI artifacts 備份失敗" + failed=$((failed+1)) + fi + + # Host, K3s, service, and recovery configuration state + log_info ">>> [12/${total}] 備份主機與服務設定..." + if /backup/scripts/backup-configs.sh; then + log_success " 主機與服務設定備份成功" + else + log_error " 主機與服務設定備份失敗" + failed=$((failed+1)) + fi + + # Public route, TLS, DNS-answer, and edge inventory evidence + log_info ">>> [13/${total}] 備份 public routes..." + if /backup/scripts/backup-public-routes.sh; then + log_success " Public routes 備份成功" + else + log_error " Public routes 備份失敗" + failed=$((failed+1)) + fi + local end_time=$(date +%s) local duration=$((end_time - start_time)) diff --git a/scripts/backup/tests/test_backup_all_required_domains.py b/scripts/backup/tests/test_backup_all_required_domains.py new file mode 100644 index 000000000..41d853d01 --- /dev/null +++ b/scripts/backup/tests/test_backup_all_required_domains.py @@ -0,0 +1,50 @@ +from __future__ import annotations + +import re +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[3] +BACKUP_ALL = ROOT / "scripts" / "backup" / "backup-all.sh" +PLAYBOOK = ROOT / "infra" / "ansible" / "playbooks" / "110-devops.yml" + +REQUIRED_BACKUP_SCRIPTS = ( + "backup-gitea.sh", + "backup-momo.sh", + "backup-harbor.sh", + "backup-awoooi.sh", + "backup-langfuse.sh", + "backup-monitoring.sh", + "backup-signoz.sh", + "backup-open-webui.sh", + "backup-clawbot.sh", + "backup-sentry.sh", + "backup-ai-artifacts.sh", + "backup-configs.sh", + "backup-public-routes.sh", +) + + +def test_backup_all_runs_every_required_domain_once() -> None: + source = BACKUP_ALL.read_text(encoding="utf-8") + + assert "local total=13" in source + assert [int(value) for value in re.findall(r">>> \[(\d+)/\$\{total\}\]", source)] == list( + range(1, 14) + ) + for script in REQUIRED_BACKUP_SCRIPTS: + assert source.count(f"/backup/scripts/{script}") == 1 + + +def test_ansible_deploys_every_script_called_by_backup_all() -> None: + playbook = PLAYBOOK.read_text(encoding="utf-8") + + for script in REQUIRED_BACKUP_SCRIPTS: + assert f"- {script}" in playbook + + +def test_backup_all_does_not_publish_or_delete_offsite_data() -> None: + source = BACKUP_ALL.read_text(encoding="utf-8") + + for forbidden in ("rclone ", "sync-offsite-backups.sh", "rclone-last-success"): + assert forbidden not in source