Files
geointel/scripts/verify_release_backup.sh
T

98 lines
2.9 KiB
Bash

#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
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",
}
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")
tool_revision = payload.get("backup_tool_revision", payload.get("git_commit"))
if not isinstance(tool_revision, str) or len(tool_revision) < 7:
raise SystemExit("Backup manifest lacks its backup-tool revision")
running_revision = payload.get("running_image_revision")
if running_revision is not None and not isinstance(running_revision, str):
raise SystemExit("Backup manifest has an invalid running-image revision")
PY
python3 "$ROOT/scripts/release_backup_snapshot.py" verify-backup --backup-dir "$BACKUP_DIR"
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"