774 lines
36 KiB
Python
774 lines
36 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
IwoooS backup / restore / escrow / retention repo-only 清冊。
|
||
|
||
本工具只讀取已提交的 repo 檔案,整理 backup orchestration、service
|
||
backup scripts、restic retention、offsite sync、credential escrow、Velero
|
||
restore drill、alert rules 與 DR runbook evidence。它不執行 backup、不
|
||
restore、不呼叫 rclone、不寫 escrow marker、不跑 restic prune、不連
|
||
K8s、不 SSH、不讀 secret value。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import argparse
|
||
import hashlib
|
||
import json
|
||
import subprocess
|
||
import sys
|
||
from datetime import datetime, timedelta, timezone
|
||
from pathlib import Path
|
||
from typing import Any
|
||
|
||
|
||
TAIPEI = timezone(timedelta(hours=8))
|
||
|
||
|
||
SURFACES: list[dict[str, Any]] = [
|
||
{
|
||
"surface_id": "backup_all_orchestrator",
|
||
"label": "全服務備份總控",
|
||
"source_path": "scripts/backup/backup-all.sh",
|
||
"expected_scope": "110_backup_host_all_services",
|
||
"config_kind": "backup_orchestrator",
|
||
"control_tier": "C0",
|
||
"current_state": "write_capable_orchestrator_visible_not_executed",
|
||
"backup_scope": ["gitea", "momo", "harbor", "awoooi", "langfuse", "monitoring", "signoz", "open-webui", "clawbot"],
|
||
"requires_live_evidence": True,
|
||
"requires_owner_response": True,
|
||
"next_owner_action": "補 backup owner、cron owner、失敗通知 owner、restore drill owner、rollback owner 與 post-check 指標。",
|
||
},
|
||
{
|
||
"surface_id": "backup_common_restic_retention",
|
||
"label": "Restic 共用設定與 GFS retention",
|
||
"source_path": "scripts/backup/common.sh",
|
||
"expected_scope": "restic_password_b2_retention_common",
|
||
"config_kind": "backup_common_policy",
|
||
"control_tier": "C0",
|
||
"current_state": "retention_and_credential_metadata_visible_secret_values_absent",
|
||
"backup_scope": ["RESTIC_PASSWORD_FILE", "B2 metadata", "KEEP_DAILY=30", "KEEP_WEEKLY=12", "KEEP_MONTHLY=24"],
|
||
"requires_live_evidence": True,
|
||
"requires_owner_response": True,
|
||
"next_owner_action": "補 restic password owner、B2 / rclone owner、retention owner、prune window 與 no-secret-value evidence。",
|
||
},
|
||
{
|
||
"surface_id": "backup_gitea_service_script",
|
||
"label": "Gitea 備份腳本",
|
||
"source_path": "scripts/backup/backup-gitea.sh",
|
||
"expected_scope": "gitea_database_and_repositories",
|
||
"config_kind": "service_backup_script",
|
||
"control_tier": "C0",
|
||
"current_state": "service_backup_script_visible_gate_closed",
|
||
"backup_scope": ["Gitea DB", "repositories", "app.ini redaction boundary"],
|
||
"requires_live_evidence": True,
|
||
"requires_owner_response": True,
|
||
"next_owner_action": "補 Gitea backup owner、freshness evidence、restore target isolation 與 secret redaction proof。",
|
||
},
|
||
{
|
||
"surface_id": "backup_momo_service_script",
|
||
"label": "MOMO PostgreSQL 備份腳本",
|
||
"source_path": "scripts/backup/backup-momo.sh",
|
||
"expected_scope": "momo_postgresql",
|
||
"config_kind": "service_backup_script",
|
||
"control_tier": "C0",
|
||
"current_state": "service_backup_script_visible_gate_closed",
|
||
"backup_scope": ["MOMO PostgreSQL", "188 database path"],
|
||
"requires_live_evidence": True,
|
||
"requires_owner_response": True,
|
||
"next_owner_action": "補 MOMO backup owner、188 DB access boundary、restore drill target 與 rollback owner。",
|
||
},
|
||
{
|
||
"surface_id": "backup_harbor_service_script",
|
||
"label": "Harbor 備份腳本",
|
||
"source_path": "scripts/backup/backup-harbor.sh",
|
||
"expected_scope": "harbor_registry_and_database",
|
||
"config_kind": "service_backup_script",
|
||
"control_tier": "C0",
|
||
"current_state": "service_backup_script_visible_gate_closed",
|
||
"backup_scope": ["Harbor registry", "Harbor DB", "image registry recovery"],
|
||
"requires_live_evidence": True,
|
||
"requires_owner_response": True,
|
||
"next_owner_action": "補 Harbor backup owner、registry restore smoke、robot account secret boundary 與 image rollback owner。",
|
||
},
|
||
{
|
||
"surface_id": "backup_awoooi_service_script",
|
||
"label": "AWOOOI PostgreSQL 完整備份腳本",
|
||
"source_path": "scripts/backup/backup-awoooi.sh",
|
||
"expected_scope": "awoooi_postgresql_and_k3s_datastore",
|
||
"config_kind": "service_backup_script",
|
||
"control_tier": "C0",
|
||
"current_state": "service_backup_script_visible_gate_closed",
|
||
"backup_scope": ["awoooi_prod", "awoooi_dev", "k3s datastore"],
|
||
"requires_live_evidence": True,
|
||
"requires_owner_response": True,
|
||
"next_owner_action": "補 AWOOOI DB backup owner、RPO owner、restore drill isolation 與 data masking policy。",
|
||
},
|
||
{
|
||
"surface_id": "backup_awoooi_frequent_script",
|
||
"label": "AWOOOI PostgreSQL 高頻備份腳本",
|
||
"source_path": "scripts/backup/backup-awoooi-frequent.sh",
|
||
"expected_scope": "awoooi_postgresql_high_frequency",
|
||
"config_kind": "service_backup_script",
|
||
"control_tier": "C0",
|
||
"current_state": "high_frequency_backup_script_visible_gate_closed",
|
||
"backup_scope": ["awoooi_prod", "6h RPO", "latest-only interaction"],
|
||
"requires_live_evidence": True,
|
||
"requires_owner_response": True,
|
||
"next_owner_action": "補高頻備份 owner、cron owner、latest-only retention owner 與 freshness evidence。",
|
||
},
|
||
{
|
||
"surface_id": "backup_langfuse_service_script",
|
||
"label": "Langfuse 備份腳本",
|
||
"source_path": "scripts/backup/backup-langfuse.sh",
|
||
"expected_scope": "langfuse_ai_trace_database",
|
||
"config_kind": "service_backup_script",
|
||
"control_tier": "C0",
|
||
"current_state": "service_backup_script_visible_gate_closed",
|
||
"backup_scope": ["Langfuse DB", "AI trace evidence"],
|
||
"requires_live_evidence": True,
|
||
"requires_owner_response": True,
|
||
"next_owner_action": "補 Langfuse backup owner、trace privacy boundary、restore smoke 與 secret redaction proof。",
|
||
},
|
||
{
|
||
"surface_id": "backup_monitoring_service_script",
|
||
"label": "Monitoring 備份腳本",
|
||
"source_path": "scripts/backup/backup-monitoring.sh",
|
||
"expected_scope": "prometheus_grafana_alertmanager",
|
||
"config_kind": "service_backup_script",
|
||
"control_tier": "C0",
|
||
"current_state": "service_backup_script_visible_gate_closed",
|
||
"backup_scope": ["Prometheus", "Grafana", "Alertmanager"],
|
||
"requires_live_evidence": True,
|
||
"requires_owner_response": True,
|
||
"next_owner_action": "補 observability backup owner、Grafana secret boundary、alert route restore smoke 與 rollback owner。",
|
||
},
|
||
{
|
||
"surface_id": "backup_signoz_service_script",
|
||
"label": "SigNoz 備份腳本",
|
||
"source_path": "scripts/backup/backup-signoz.sh",
|
||
"expected_scope": "signoz_clickhouse_and_sqlite",
|
||
"config_kind": "service_backup_script",
|
||
"control_tier": "C0",
|
||
"current_state": "service_backup_script_visible_gate_closed",
|
||
"backup_scope": ["SigNoz ClickHouse", "SigNoz SQLite"],
|
||
"requires_live_evidence": True,
|
||
"requires_owner_response": True,
|
||
"next_owner_action": "補 SigNoz disruptive guard owner、ClickHouse restore owner、告警靜音邊界與 post-check 指標。",
|
||
},
|
||
{
|
||
"surface_id": "backup_open_webui_service_script",
|
||
"label": "Open-WebUI 備份腳本",
|
||
"source_path": "scripts/backup/backup-open-webui.sh",
|
||
"expected_scope": "open_webui_volume",
|
||
"config_kind": "service_backup_script",
|
||
"control_tier": "C0",
|
||
"current_state": "service_backup_script_visible_gate_closed",
|
||
"backup_scope": ["Open-WebUI volume", "LLM conversation data"],
|
||
"requires_live_evidence": True,
|
||
"requires_owner_response": True,
|
||
"next_owner_action": "補 Open-WebUI data privacy owner、188 read boundary、restore target isolation 與 retention owner。",
|
||
},
|
||
{
|
||
"surface_id": "backup_clawbot_service_script",
|
||
"label": "ClawBot Redis 備份腳本",
|
||
"source_path": "scripts/backup/backup-clawbot.sh",
|
||
"expected_scope": "clawbot_redis_state",
|
||
"config_kind": "service_backup_script",
|
||
"control_tier": "C0",
|
||
"current_state": "service_backup_script_visible_gate_closed",
|
||
"backup_scope": ["ClawBot Redis", "agent state cache"],
|
||
"requires_live_evidence": True,
|
||
"requires_owner_response": True,
|
||
"next_owner_action": "補 ClawBot state owner、Redis restore owner、agent state masking 與 rollback owner。",
|
||
},
|
||
{
|
||
"surface_id": "backup_sentry_service_script",
|
||
"label": "Sentry 備份腳本",
|
||
"source_path": "scripts/backup/backup-sentry.sh",
|
||
"expected_scope": "sentry_self_hosted",
|
||
"config_kind": "service_backup_script",
|
||
"control_tier": "C0",
|
||
"current_state": "service_backup_script_visible_gate_closed",
|
||
"backup_scope": ["Sentry", "ClickHouse / Postgres / Redis dependency boundary"],
|
||
"requires_live_evidence": True,
|
||
"requires_owner_response": True,
|
||
"next_owner_action": "補 Sentry backup owner、multi-store restore owner、admin secret boundary 與 route smoke。",
|
||
},
|
||
{
|
||
"surface_id": "backup_ai_artifacts_script",
|
||
"label": "AI artifacts 備份腳本",
|
||
"source_path": "scripts/backup/backup-ai-artifacts.sh",
|
||
"expected_scope": "ai_artifacts",
|
||
"config_kind": "service_backup_script",
|
||
"control_tier": "C0",
|
||
"current_state": "artifact_backup_script_visible_gate_closed",
|
||
"backup_scope": ["AI artifacts", "model / evaluation outputs"],
|
||
"requires_live_evidence": True,
|
||
"requires_owner_response": True,
|
||
"next_owner_action": "補 artifact owner、retention owner、模型資料外送邊界與 restore validation。",
|
||
},
|
||
{
|
||
"surface_id": "backup_public_routes_script",
|
||
"label": "Public routes 備份腳本",
|
||
"source_path": "scripts/backup/backup-public-routes.sh",
|
||
"expected_scope": "public_route_reconstruction",
|
||
"config_kind": "service_backup_script",
|
||
"control_tier": "C0",
|
||
"current_state": "route_backup_script_visible_gate_closed",
|
||
"backup_scope": ["public routes", "Nginx route reconstruction", "frontend/API smoke evidence"],
|
||
"requires_live_evidence": True,
|
||
"requires_owner_response": True,
|
||
"next_owner_action": "補 route reconstruction owner、public/admin/API smoke、rollback ref 與 no-internal-transcript proof。",
|
||
},
|
||
{
|
||
"surface_id": "config_backup_capture",
|
||
"label": "Host / service / K8s 設定備份",
|
||
"source_path": "scripts/backup/backup-configs.sh",
|
||
"expected_scope": "110_188_120_121_cluster_configs",
|
||
"config_kind": "config_backup_script",
|
||
"control_tier": "C0",
|
||
"current_state": "config_capture_visible_blocked_until_owner_evidence",
|
||
"backup_scope": ["systemd", "docker", "nginx", "cron", "k8s", "host configs"],
|
||
"requires_live_evidence": True,
|
||
"requires_owner_response": True,
|
||
"next_owner_action": "補 config capture owner、secret redaction proof、120 blocked disposition、restore validation 與 retention owner。",
|
||
},
|
||
{
|
||
"surface_id": "backup_status_reporter",
|
||
"label": "備份狀態彙整腳本",
|
||
"source_path": "scripts/backup/backup-status.sh",
|
||
"expected_scope": "110_188_backup_status_summary",
|
||
"config_kind": "backup_status_reporter",
|
||
"control_tier": "C0",
|
||
"current_state": "status_reporter_visible_not_executed",
|
||
"backup_scope": ["freshness", "failure", "integrity", "restore drill", "offsite", "escrow"],
|
||
"requires_live_evidence": True,
|
||
"requires_owner_response": True,
|
||
"next_owner_action": "補 backup status owner、read-only execution window、SSH read boundary、notification owner 與 false-green 防線。",
|
||
},
|
||
{
|
||
"surface_id": "backup_integrity_check",
|
||
"label": "Restic integrity check",
|
||
"source_path": "scripts/backup/check-backup-integrity.sh",
|
||
"expected_scope": "restic_integrity_check",
|
||
"config_kind": "integrity_check_script",
|
||
"control_tier": "C0",
|
||
"current_state": "integrity_check_visible_not_executed",
|
||
"backup_scope": ["restic check", "read-data subset", "integrity evidence"],
|
||
"requires_live_evidence": True,
|
||
"requires_owner_response": True,
|
||
"next_owner_action": "補 integrity check owner、執行窗口、資源上限、結果證據與 restore drill 前置條件。",
|
||
},
|
||
{
|
||
"surface_id": "latest_only_retention_enforcer",
|
||
"label": "Latest-only retention enforcer",
|
||
"source_path": "scripts/backup/enforce-latest-only-retention.sh",
|
||
"expected_scope": "latest_only_retention",
|
||
"config_kind": "retention_enforcer",
|
||
"control_tier": "C0",
|
||
"current_state": "delete_capable_retention_script_visible_gate_closed",
|
||
"backup_scope": ["keep latest", "local delete", "retention marker"],
|
||
"requires_live_evidence": True,
|
||
"requires_owner_response": True,
|
||
"next_owner_action": "補 retention owner、刪除窗口、restore runway、offsite mirror interaction 與 rollback / stop condition。",
|
||
},
|
||
{
|
||
"surface_id": "offsite_sync_controller",
|
||
"label": "Offsite rclone sync controller",
|
||
"source_path": "scripts/backup/sync-offsite-backups.sh",
|
||
"expected_scope": "google_drive_rclone_offsite_mirror",
|
||
"config_kind": "offsite_sync_controller",
|
||
"control_tier": "C0",
|
||
"current_state": "remote_write_and_delete_capable_sync_visible_gate_closed",
|
||
"backup_scope": ["13 repos", "rclone sync", "remote delete", "success markers"],
|
||
"requires_live_evidence": True,
|
||
"requires_owner_response": True,
|
||
"next_owner_action": "補 offsite owner、remote delete owner、runway check、full sync window、rclone credential escrow 與 verifier evidence。",
|
||
},
|
||
{
|
||
"surface_id": "offsite_full_sync_verifier",
|
||
"label": "Offsite full sync verifier",
|
||
"source_path": "scripts/backup/verify-offsite-full-sync.sh",
|
||
"expected_scope": "offsite_full_sync_verification",
|
||
"config_kind": "offsite_verifier",
|
||
"control_tier": "C0",
|
||
"current_state": "remote_read_and_textfile_write_capable_verifier_visible_gate_closed",
|
||
"backup_scope": ["remote repo count", "latest-only evidence", "textfile metrics"],
|
||
"requires_live_evidence": True,
|
||
"requires_owner_response": True,
|
||
"next_owner_action": "補 verifier owner、remote read window、metric write owner、failure notification owner 與 evidence retention。",
|
||
},
|
||
{
|
||
"surface_id": "offsite_readiness_gate",
|
||
"label": "Offsite readiness gate",
|
||
"source_path": "scripts/backup/backup-offsite-readiness-gate.sh",
|
||
"expected_scope": "offsite_preflight_and_escrow_gate",
|
||
"config_kind": "offsite_readiness_gate",
|
||
"control_tier": "C0",
|
||
"current_state": "readiness_gate_visible_not_executed",
|
||
"backup_scope": ["status", "dry-run-small", "pre-full-sync", "escrow markers"],
|
||
"requires_live_evidence": True,
|
||
"requires_owner_response": True,
|
||
"next_owner_action": "補 readiness owner、dry-run scope、escrow owner、load/runway policy 與 accepted evidence refs。",
|
||
},
|
||
{
|
||
"surface_id": "offsite_escrow_evidence_report",
|
||
"label": "Offsite / escrow evidence report",
|
||
"source_path": "scripts/backup/offsite-escrow-evidence-report.sh",
|
||
"expected_scope": "offsite_escrow_redacted_report",
|
||
"config_kind": "offsite_escrow_report",
|
||
"control_tier": "C0",
|
||
"current_state": "redacted_report_visible_default_no_remote_status",
|
||
"backup_scope": ["script presence", "offsite marker", "escrow marker", "redacted output"],
|
||
"requires_live_evidence": True,
|
||
"requires_owner_response": True,
|
||
"next_owner_action": "補 evidence report owner、remote status opt-in owner、redaction proof 與 blocked marker disposition。",
|
||
},
|
||
{
|
||
"surface_id": "credential_escrow_marker",
|
||
"label": "Credential escrow marker writer",
|
||
"source_path": "scripts/backup/mark-credential-escrow-verified.sh",
|
||
"expected_scope": "credential_escrow_markers",
|
||
"config_kind": "credential_escrow_marker",
|
||
"control_tier": "C0",
|
||
"current_state": "marker_write_capable_script_visible_gate_closed",
|
||
"backup_scope": ["restic password", "offsite provider", "break-glass admin", "DNS recovery", "OAuth / AI provider recovery"],
|
||
"requires_live_evidence": True,
|
||
"requires_owner_response": True,
|
||
"next_owner_action": "補 escrow owner、non-secret evidence id、reviewer acceptance、marker write approval 與 retention owner。",
|
||
},
|
||
{
|
||
"surface_id": "offsite_rclone_config",
|
||
"label": "rclone offsite config helper",
|
||
"source_path": "scripts/backup/configure-offsite-rclone.sh",
|
||
"expected_scope": "rclone_config_metadata",
|
||
"config_kind": "offsite_rclone_config",
|
||
"control_tier": "C0",
|
||
"current_state": "credential_config_helper_visible_secret_values_not_collected",
|
||
"backup_scope": ["rclone remote", "Google Drive", "offsite.env metadata"],
|
||
"requires_live_evidence": True,
|
||
"requires_owner_response": True,
|
||
"next_owner_action": "補 rclone config owner、secret store owner、file mode evidence、no-value collection proof 與 recovery owner。",
|
||
},
|
||
{
|
||
"surface_id": "offsite_b2_config",
|
||
"label": "B2 offsite config helper",
|
||
"source_path": "scripts/backup/configure-offsite-b2.sh",
|
||
"expected_scope": "b2_config_metadata",
|
||
"config_kind": "offsite_b2_config",
|
||
"control_tier": "C0",
|
||
"current_state": "credential_config_helper_visible_secret_values_not_collected",
|
||
"backup_scope": ["Backblaze B2 metadata", "offsite env", "fallback provider"],
|
||
"requires_live_evidence": True,
|
||
"requires_owner_response": True,
|
||
"next_owner_action": "補 B2 provider owner、credential escrow owner、provider cost boundary 與 no-value collection proof。",
|
||
},
|
||
{
|
||
"surface_id": "backup_health_textfile_exporter",
|
||
"label": "Backup health textfile exporter",
|
||
"source_path": "scripts/ops/backup-health-textfile-exporter.py",
|
||
"expected_scope": "backup_health_prometheus_textfile",
|
||
"config_kind": "backup_health_exporter",
|
||
"control_tier": "C0",
|
||
"current_state": "textfile_write_capable_exporter_visible_gate_closed",
|
||
"backup_scope": ["freshness metrics", "restore drill metrics", "offsite metrics", "escrow metrics"],
|
||
"requires_live_evidence": True,
|
||
"requires_owner_response": True,
|
||
"next_owner_action": "補 exporter owner、textfile path owner、metric freshness SLO、false-green guard 與 alert owner。",
|
||
},
|
||
{
|
||
"surface_id": "velero_restore_test_cronjob",
|
||
"label": "Velero restore dry-run CronJob",
|
||
"source_path": "k8s/awoooi-prod/16-cronjob-backup-restore-test.yaml",
|
||
"expected_scope": "velero_weekly_restore_dry_run",
|
||
"config_kind": "velero_restore_cronjob",
|
||
"control_tier": "C0",
|
||
"current_state": "k8s_cronjob_manifest_visible_not_applied_by_this_inventory",
|
||
"backup_scope": ["Velero restore dry-run", "weekly schedule", "textfile metrics"],
|
||
"requires_live_evidence": True,
|
||
"requires_owner_response": True,
|
||
"next_owner_action": "補 Velero owner、dry-run namespace isolation、CronJob live evidence、restore approval 與 post-check 指標。",
|
||
},
|
||
{
|
||
"surface_id": "velero_restore_test_script_configmap",
|
||
"label": "Velero restore script ConfigMap",
|
||
"source_path": "k8s/awoooi-prod/17-configmap-backup-restore-scripts.yaml",
|
||
"expected_scope": "velero_restore_script_configmap",
|
||
"config_kind": "velero_restore_script_configmap",
|
||
"control_tier": "C0",
|
||
"current_state": "configmap_script_visible_timestamp_format_needs_owner_disposition",
|
||
"backup_scope": ["restore dry-run script", "13-digit textfile timestamp risk", "Prometheus textfile"],
|
||
"requires_live_evidence": True,
|
||
"requires_owner_response": True,
|
||
"next_owner_action": "補 ConfigMap owner、timestamp format disposition、CronJob rollout owner、metric scrape proof 與 rollback owner。",
|
||
},
|
||
{
|
||
"surface_id": "velero_standalone_restore_test_script",
|
||
"label": "Velero restore dry-run standalone script",
|
||
"source_path": "scripts/cron_backup_restore_test.sh",
|
||
"expected_scope": "velero_standalone_restore_script",
|
||
"config_kind": "velero_restore_standalone_script",
|
||
"control_tier": "C0",
|
||
"current_state": "standalone_script_visible_uses_seconds_textfile_timestamp_not_executed",
|
||
"backup_scope": ["restore dry-run", "Prometheus textfile seconds timestamp", "failure metric"],
|
||
"requires_live_evidence": True,
|
||
"requires_owner_response": True,
|
||
"next_owner_action": "補 standalone / ConfigMap drift disposition、restore drill owner、textfile owner 與 proof of isolation。",
|
||
},
|
||
{
|
||
"surface_id": "velero_credentials_manifest",
|
||
"label": "Velero MinIO credential manifest",
|
||
"source_path": "k8s/velero/01-credentials.yaml",
|
||
"expected_scope": "velero_minio_credentials_metadata",
|
||
"config_kind": "velero_credentials_manifest",
|
||
"control_tier": "C0",
|
||
"current_state": "placeholder_secret_manifest_visible_values_not_collected",
|
||
"backup_scope": ["MinIO credential names", "placeholder values", "External Secrets / Sealed Secrets recommendation"],
|
||
"requires_live_evidence": True,
|
||
"requires_owner_response": True,
|
||
"next_owner_action": "補 Velero credential owner、secret manager source、rotation owner、no-value collection proof 與 restore boundary。",
|
||
},
|
||
{
|
||
"surface_id": "velero_install_manifest",
|
||
"label": "Velero install manifest",
|
||
"source_path": "k8s/velero/02-velero-install.yaml",
|
||
"expected_scope": "velero_install_and_minio_storage",
|
||
"config_kind": "velero_install_manifest",
|
||
"control_tier": "C0",
|
||
"current_state": "cluster_admin_velero_manifest_visible_gate_closed",
|
||
"backup_scope": ["Velero Deployment", "cluster-admin binding", "MinIO s3Url", "backup storage location"],
|
||
"requires_live_evidence": True,
|
||
"requires_owner_response": True,
|
||
"next_owner_action": "補 Velero RBAC owner、MinIO endpoint owner、least privilege review、install window 與 rollback owner。",
|
||
},
|
||
{
|
||
"surface_id": "backup_restore_alert_rules",
|
||
"label": "Backup / restore alert rules",
|
||
"source_path": "ops/monitoring/alerts.yml",
|
||
"expected_scope": "backup_restore_prometheus_alerts",
|
||
"config_kind": "backup_restore_alert_rules",
|
||
"control_tier": "C0",
|
||
"current_state": "alert_rule_source_visible_reload_not_authorized",
|
||
"backup_scope": ["BackupRestoreTestFailed", "Velero freshness", "offsite freshness", "restore stale"],
|
||
"requires_live_evidence": True,
|
||
"requires_owner_response": True,
|
||
"next_owner_action": "補 alert rule owner、receiver owner、reload owner、silence boundary 與 failure-only notification policy。",
|
||
},
|
||
{
|
||
"surface_id": "backup_dr_readiness_contract",
|
||
"label": "Backup / DR readiness matrix",
|
||
"source_path": "docs/evaluations/backup_dr_readiness_matrix_2026-06-04.json",
|
||
"expected_scope": "backup_dr_readiness_contract",
|
||
"config_kind": "dr_readiness_contract",
|
||
"control_tier": "C0",
|
||
"current_state": "readiness_contract_visible_action_required_items_not_accepted",
|
||
"backup_scope": ["readiness matrix", "blocked targets", "restore drill status"],
|
||
"requires_live_evidence": True,
|
||
"requires_owner_response": True,
|
||
"next_owner_action": "補 readiness owner、blocked target disposition、freshness evidence、restore drill owner 與 accepted refs。",
|
||
},
|
||
{
|
||
"surface_id": "backup_restore_drill_approval_template",
|
||
"label": "Restore drill approval package template",
|
||
"source_path": "docs/evaluations/backup_restore_drill_approval_package_template_2026-06-05.json",
|
||
"expected_scope": "restore_drill_approval_template",
|
||
"config_kind": "restore_drill_approval_template",
|
||
"control_tier": "C0",
|
||
"current_state": "approval_template_visible_no_restore_execution",
|
||
"backup_scope": ["database restore", "configuration restore", "credential escrow", "K8s restore", "observability restore"],
|
||
"requires_live_evidence": True,
|
||
"requires_owner_response": True,
|
||
"next_owner_action": "補 owner response 實際封包、隔離環境、observer、rollback owner 與 restore stop condition。",
|
||
},
|
||
{
|
||
"surface_id": "offsite_escrow_readiness_contract",
|
||
"label": "Offsite / escrow readiness status",
|
||
"source_path": "docs/evaluations/offsite_escrow_readiness_status_2026-06-05.json",
|
||
"expected_scope": "offsite_escrow_readiness_contract",
|
||
"config_kind": "offsite_escrow_readiness_contract",
|
||
"control_tier": "C0",
|
||
"current_state": "offsite_verified_but_escrow_and_velero_blocked",
|
||
"backup_scope": ["offsite_rclone_full_sync", "credential_escrow_markers", "velero_k8s_resources"],
|
||
"requires_live_evidence": True,
|
||
"requires_owner_response": True,
|
||
"next_owner_action": "補 escrow marker owner、Velero metric binding、remote evidence expiry owner 與 offsite sync approval boundary。",
|
||
},
|
||
{
|
||
"surface_id": "backup_status_runbook",
|
||
"label": "Backup status runbook",
|
||
"source_path": "docs/runbooks/BACKUP-STATUS.md",
|
||
"expected_scope": "backup_status_runbook",
|
||
"config_kind": "backup_status_runbook",
|
||
"control_tier": "C0",
|
||
"current_state": "runbook_visible_contains_live_refresh_notes_needs_revalidation",
|
||
"backup_scope": ["110 backup center", "latest-only", "Google Drive / rclone", "credential escrow", "120 blocker"],
|
||
"requires_live_evidence": True,
|
||
"requires_owner_response": True,
|
||
"next_owner_action": "補截至本次階段的 owner-provided live refresh、stale evidence disposition、escrow blocker owner 與 validation refs。",
|
||
},
|
||
{
|
||
"surface_id": "cold_start_sop",
|
||
"label": "Full-stack cold-start SOP",
|
||
"source_path": "docs/runbooks/FULL-STACK-COLD-START-SOP.md",
|
||
"expected_scope": "cold_start_backup_restore_recovery",
|
||
"config_kind": "cold_start_sop",
|
||
"control_tier": "C0",
|
||
"current_state": "sop_visible_contains_backup_commands_not_authorized",
|
||
"backup_scope": ["cold start", "backup-all", "sync-offsite", "restore guard", "schedules"],
|
||
"requires_live_evidence": True,
|
||
"requires_owner_response": True,
|
||
"next_owner_action": "補 cold-start commander owner、backup command approval boundary、restore stop condition、rollback owner 與 post-start validation。",
|
||
},
|
||
]
|
||
|
||
|
||
FALSE_BOUNDARIES = {
|
||
"runtime_execution_authorized": False,
|
||
"host_write_authorized": False,
|
||
"backup_run_authorized": False,
|
||
"restore_run_authorized": False,
|
||
"restore_drill_authorized": False,
|
||
"offsite_sync_authorized": False,
|
||
"offsite_remote_delete_authorized": False,
|
||
"credential_escrow_marker_write_authorized": False,
|
||
"retention_change_authorized": False,
|
||
"restic_prune_authorized": False,
|
||
"rclone_config_authorized": False,
|
||
"velero_restore_authorized": False,
|
||
"velero_backup_authorized": False,
|
||
"kubectl_action_authorized": False,
|
||
"ssh_read_authorized": False,
|
||
"ssh_write_authorized": False,
|
||
"secret_value_collection_allowed": False,
|
||
"active_scan_authorized": False,
|
||
"action_buttons_allowed": False,
|
||
}
|
||
|
||
|
||
WRITE_CAPABLE_KINDS = {
|
||
"backup_orchestrator",
|
||
"service_backup_script",
|
||
"config_backup_script",
|
||
"retention_enforcer",
|
||
"offsite_sync_controller",
|
||
"offsite_verifier",
|
||
"credential_escrow_marker",
|
||
"offsite_rclone_config",
|
||
"offsite_b2_config",
|
||
"backup_health_exporter",
|
||
"velero_restore_cronjob",
|
||
"velero_restore_script_configmap",
|
||
"velero_restore_standalone_script",
|
||
"velero_credentials_manifest",
|
||
"velero_install_manifest",
|
||
}
|
||
|
||
BACKUP_SCRIPT_KINDS = {
|
||
"backup_orchestrator",
|
||
"service_backup_script",
|
||
"config_backup_script",
|
||
}
|
||
|
||
RESTORE_DRILL_KINDS = {
|
||
"velero_restore_cronjob",
|
||
"velero_restore_script_configmap",
|
||
"velero_restore_standalone_script",
|
||
"restore_drill_approval_template",
|
||
}
|
||
|
||
OFFSITE_ESCROW_KINDS = {
|
||
"offsite_sync_controller",
|
||
"offsite_verifier",
|
||
"offsite_readiness_gate",
|
||
"offsite_escrow_report",
|
||
"credential_escrow_marker",
|
||
"offsite_rclone_config",
|
||
"offsite_b2_config",
|
||
"offsite_escrow_readiness_contract",
|
||
}
|
||
|
||
VELERO_KINDS = {
|
||
"velero_restore_cronjob",
|
||
"velero_restore_script_configmap",
|
||
"velero_restore_standalone_script",
|
||
"velero_credentials_manifest",
|
||
"velero_install_manifest",
|
||
}
|
||
|
||
RETENTION_SURFACE_IDS = {
|
||
"backup_common_restic_retention",
|
||
"latest_only_retention_enforcer",
|
||
"offsite_sync_controller",
|
||
}
|
||
|
||
CREDENTIAL_SURFACE_IDS = {
|
||
"backup_common_restic_retention",
|
||
"credential_escrow_marker",
|
||
"offsite_rclone_config",
|
||
"offsite_b2_config",
|
||
"velero_credentials_manifest",
|
||
}
|
||
|
||
DR_CONTRACT_KINDS = {
|
||
"dr_readiness_contract",
|
||
"restore_drill_approval_template",
|
||
"offsite_escrow_readiness_contract",
|
||
}
|
||
|
||
|
||
def git_short_sha(root: Path) -> str:
|
||
try:
|
||
result = subprocess.run(
|
||
["git", "rev-parse", "--short", "HEAD"],
|
||
cwd=root,
|
||
check=True,
|
||
capture_output=True,
|
||
text=True,
|
||
)
|
||
return result.stdout.strip()
|
||
except Exception:
|
||
return "unknown"
|
||
|
||
|
||
def file_metadata(root: Path, source_path: str) -> dict[str, Any]:
|
||
path = root / source_path
|
||
exists = path.exists()
|
||
if not exists:
|
||
return {"source_exists": False, "line_count": 0, "sha256": None}
|
||
content = path.read_bytes()
|
||
return {
|
||
"source_exists": True,
|
||
"line_count": len(content.decode("utf-8", errors="replace").splitlines()),
|
||
"sha256": hashlib.sha256(content).hexdigest(),
|
||
}
|
||
|
||
|
||
def build_surface(root: Path, surface: dict[str, Any]) -> dict[str, Any]:
|
||
metadata = file_metadata(root, surface["source_path"])
|
||
return {
|
||
**surface,
|
||
**metadata,
|
||
"owner_response_received": False,
|
||
"owner_response_accepted": False,
|
||
"live_evidence_received": False,
|
||
"restore_drill_accepted": False,
|
||
"offsite_sync_accepted": False,
|
||
"credential_escrow_accepted": False,
|
||
"retention_change_accepted": False,
|
||
"maintenance_window_accepted": False,
|
||
"rollback_owner_accepted": False,
|
||
"runtime_gate_open": False,
|
||
"action_buttons_allowed": False,
|
||
}
|
||
|
||
|
||
def count_kind(surfaces: list[dict[str, Any]], kinds: set[str]) -> int:
|
||
return sum(1 for surface in surfaces if surface["config_kind"] in kinds)
|
||
|
||
|
||
def build_report(root: Path, generated_at: str | None) -> dict[str, Any]:
|
||
report_time = generated_at or datetime.now(TAIPEI).isoformat(timespec="seconds")
|
||
surfaces = [build_surface(root, surface) for surface in SURFACES]
|
||
expected_scopes = sorted({surface["expected_scope"] for surface in surfaces})
|
||
write_capable = [surface for surface in surfaces if surface["config_kind"] in WRITE_CAPABLE_KINDS]
|
||
|
||
return {
|
||
"schema_version": "backup_restore_escrow_inventory_v1",
|
||
"generated_at": report_time,
|
||
"git_commit": git_short_sha(root),
|
||
"status": "repo_only_inventory_ready",
|
||
"source_scope": "committed_repo_files_only",
|
||
"summary": {
|
||
"surface_count": len(surfaces),
|
||
"source_exists_count": sum(1 for surface in surfaces if surface["source_exists"]),
|
||
"expected_scope_count": len(expected_scopes),
|
||
"backup_script_surface_count": count_kind(surfaces, BACKUP_SCRIPT_KINDS),
|
||
"restore_drill_surface_count": count_kind(surfaces, RESTORE_DRILL_KINDS),
|
||
"offsite_escrow_surface_count": count_kind(surfaces, OFFSITE_ESCROW_KINDS),
|
||
"velero_surface_count": count_kind(surfaces, VELERO_KINDS),
|
||
"retention_surface_count": sum(1 for surface in surfaces if surface["surface_id"] in RETENTION_SURFACE_IDS),
|
||
"credential_surface_count": sum(1 for surface in surfaces if surface["surface_id"] in CREDENTIAL_SURFACE_IDS),
|
||
"alert_surface_count": sum(1 for surface in surfaces if surface["config_kind"] == "backup_restore_alert_rules"),
|
||
"dr_readiness_contract_surface_count": count_kind(surfaces, DR_CONTRACT_KINDS),
|
||
"write_capable_surface_count": len(write_capable),
|
||
"surfaces_requiring_owner_response_count": sum(1 for surface in surfaces if surface["requires_owner_response"]),
|
||
"surfaces_requiring_live_evidence_count": sum(1 for surface in surfaces if surface["requires_live_evidence"]),
|
||
"owner_response_received_count": 0,
|
||
"owner_response_accepted_count": 0,
|
||
"live_evidence_received_count": 0,
|
||
"restore_drill_accepted_count": 0,
|
||
"offsite_sync_accepted_count": 0,
|
||
"credential_escrow_accepted_count": 0,
|
||
"retention_change_accepted_count": 0,
|
||
"maintenance_window_accepted_count": 0,
|
||
"rollback_owner_accepted_count": 0,
|
||
"runtime_gate_count": 0,
|
||
"action_button_count": 0,
|
||
"coverage_percent_before_inventory": 52,
|
||
"coverage_percent_after_inventory": 58,
|
||
},
|
||
"execution_boundaries": FALSE_BOUNDARIES,
|
||
"expected_scopes": expected_scopes,
|
||
"backup_surfaces": surfaces,
|
||
"write_capable_surfaces": [
|
||
{
|
||
"surface_id": surface["surface_id"],
|
||
"label": surface["label"],
|
||
"config_kind": surface["config_kind"],
|
||
"expected_scope": surface["expected_scope"],
|
||
"required_gate": "owner_response_plus_maintenance_window_plus_rollback_owner",
|
||
}
|
||
for surface in write_capable
|
||
],
|
||
"next_collection_order": [
|
||
"backup_common_restic_retention",
|
||
"offsite_sync_controller",
|
||
"credential_escrow_marker",
|
||
"velero_restore_test_script_configmap",
|
||
"velero_credentials_manifest",
|
||
"backup_health_textfile_exporter",
|
||
"backup_restore_alert_rules",
|
||
"backup_restore_drill_approval_template",
|
||
"backup_status_runbook",
|
||
"cold_start_sop",
|
||
],
|
||
"operator_interpretation": [
|
||
"這是 repo-only backup / restore / escrow / retention 清冊,不是 live backup、remote provider 或 cluster truth。",
|
||
"source_exists=true 只代表 repo 檔案存在;不代表備份已成功、restore drill 已執行、offsite sync 已授權或 escrow marker 已可寫入。",
|
||
"write-capable surface 可見代表需要資安控管,不代表 backup、restore、rclone sync、remote delete、restic prune、Velero restore 或 kubectl 已授權。",
|
||
"所有 owner response、live evidence、restore drill acceptance、offsite sync acceptance、credential escrow acceptance、retention change acceptance 與 runtime gate 仍為 0。",
|
||
],
|
||
}
|
||
|
||
|
||
def parse_args() -> argparse.Namespace:
|
||
parser = argparse.ArgumentParser(description="產生 IwoooS backup / restore / escrow / retention repo-only 清冊")
|
||
parser.add_argument("--root", default=".", help="repo root")
|
||
parser.add_argument("--output", help="輸出 JSON 檔")
|
||
parser.add_argument("--generated-at", help="固定 generated_at")
|
||
return parser.parse_args()
|
||
|
||
|
||
def main() -> int:
|
||
args = parse_args()
|
||
root = Path(args.root).resolve()
|
||
report = build_report(root, args.generated_at)
|
||
text = json.dumps(report, ensure_ascii=False, indent=2, sort_keys=True)
|
||
if args.output:
|
||
Path(args.output).write_text(text + "\n", encoding="utf-8")
|
||
else:
|
||
print(text)
|
||
print(
|
||
"BACKUP_RESTORE_ESCROW_INVENTORY_OK "
|
||
f"surfaces={report['summary']['surface_count']} "
|
||
f"backup_scripts={report['summary']['backup_script_surface_count']} "
|
||
f"offsite_escrow={report['summary']['offsite_escrow_surface_count']} "
|
||
f"runtime_gate={report['summary']['runtime_gate_count']}",
|
||
file=sys.stderr,
|
||
)
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
raise SystemExit(main())
|