#!/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())