#!/usr/bin/env bash set -euo pipefail CONTAINER="geointel" BACKUP_DIR="" usage() { cat <<'EOF' Usage: bash scripts/verify_release_backup.sh --backup-dir PATH [--container NAME] Verifies checksums, manifest shape and PostgreSQL archive readability. This command is read-only and never creates, restores or drops a database. EOF } while [ "$#" -gt 0 ]; do case "$1" in --backup-dir) BACKUP_DIR="$2"; shift 2 ;; --container) CONTAINER="$2"; shift 2 ;; --help|-h) usage; exit 0 ;; *) echo "Unknown argument: $1" >&2; usage >&2; exit 2 ;; esac done if [ -z "$BACKUP_DIR" ]; then usage >&2 exit 2 fi for required in docker python3 sha256sum; do command -v "$required" >/dev/null 2>&1 || { echo "Missing required command: $required" >&2 exit 2 } done BACKUP_DIR="$(python3 -c 'import pathlib,sys; print(pathlib.Path(sys.argv[1]).expanduser().resolve())' "$BACKUP_DIR")" for required_file in manifest.json database.dump database.list database-metadata.tsv table-counts.tsv CHECKSUMS.sha256; do test -s "$BACKUP_DIR/$required_file" || { echo "Missing or empty backup artifact: $required_file" >&2 exit 3 } done ( cd "$BACKUP_DIR" sha256sum -c CHECKSUMS.sha256 ) python3 - "$BACKUP_DIR/manifest.json" <<'PY' import json import pathlib import sys payload = json.loads(pathlib.Path(sys.argv[1]).read_text(encoding="utf-8")) required = { "schema_version", "release_id", "created_at", "read_only_source", "database_name", "database_user", "image_id", "git_commit", } missing = sorted(required - payload.keys()) if missing: raise SystemExit(f"Backup manifest misses: {', '.join(missing)}") if payload["schema_version"] != 1 or payload["read_only_source"] is not True: raise SystemExit("Unsupported or unsafe backup manifest") PY if [ "$(docker inspect -f '{{.State.Running}}' "$CONTAINER" 2>/dev/null || true)" != "true" ]; then echo "Container '$CONTAINER' is required to run pg_restore --list." >&2 exit 3 fi TMP_LIST="$(mktemp)" trap 'rm -f -- "$TMP_LIST"' EXIT docker exec -i "$CONTAINER" pg_restore --list \ < "$BACKUP_DIR/database.dump" \ > "$TMP_LIST" cmp -s "$TMP_LIST" "$BACKUP_DIR/database.list" || { echo "The current pg_restore listing differs from the retained listing." >&2 exit 3 } grep -q "TABLE DATA" "$TMP_LIST" || { echo "Archive contains no table data entries." >&2 exit 3 } echo "Release backup verified read-only: $BACKUP_DIR"