CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history
backup-restore-drill.sh
Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.
| 1e79799 | 1 | #!/usr/bin/env bash |
| 2 | # Backup-restore drill. Proves the backup pipeline works end-to-end. | |
| 3 | # See docs/BACKUP_RESTORE_DRILL.md for the full runbook. | |
| 4 | ||
| 5 | set -uo pipefail | |
| 6 | ||
| 7 | KEEP_DUMP=0 | |
| 8 | VERBOSE=0 | |
| 9 | for arg in "$@"; do | |
| 10 | case "$arg" in | |
| 11 | --keep-dump) KEEP_DUMP=1 ;; | |
| 12 | --verbose) VERBOSE=1 ;; | |
| 13 | *) ;; | |
| 14 | esac | |
| 15 | done | |
| 16 | ||
| 17 | DATABASE_URL="${DATABASE_URL:-}" | |
| 18 | SCRATCH_DATABASE_URL="${SCRATCH_DATABASE_URL:-}" | |
| 19 | ||
| 20 | if [[ -z "$DATABASE_URL" || -z "$SCRATCH_DATABASE_URL" ]]; then | |
| 21 | echo "ERROR: DATABASE_URL and SCRATCH_DATABASE_URL must both be set." >&2 | |
| 22 | exit 2 | |
| 23 | fi | |
| 24 | ||
| 25 | if ! command -v pg_dump >/dev/null 2>&1; then | |
| 26 | echo "ERROR: pg_dump not found. apt-get install -y postgresql-client" >&2 | |
| 27 | exit 2 | |
| 28 | fi | |
| 29 | if ! command -v psql >/dev/null 2>&1; then | |
| 30 | echo "ERROR: psql not found." >&2 | |
| 31 | exit 2 | |
| 32 | fi | |
| 33 | ||
| 34 | ts="$(date +%s)" | |
| 35 | start="$(date -u +%Y-%m-%dT%H:%M:%SZ)" | |
| 36 | start_seconds="$(date +%s)" | |
| 37 | dump="/tmp/gluecron-drill-${ts}.dump" | |
| 38 | stamp_dir="/var/lib/gluecron" | |
| 39 | ||
| 40 | log() { | |
| 41 | printf '[drill %s] %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$*" | |
| 42 | } | |
| 43 | ||
| 44 | log "dumping prod DB ..." | |
| 45 | if [[ "$VERBOSE" -eq 1 ]]; then | |
| 46 | pg_dump --format=custom --no-owner --no-privileges --file="$dump" "$DATABASE_URL" | |
| 47 | else | |
| 48 | pg_dump --format=custom --no-owner --no-privileges --file="$dump" "$DATABASE_URL" 2>/dev/null | |
| 49 | fi | |
| 50 | ||
| 51 | if [[ ! -s "$dump" ]]; then | |
| 52 | log "FATAL: dump file is empty or missing." | |
| 53 | exit 1 | |
| 54 | fi | |
| 55 | dump_size=$(du -h "$dump" | cut -f1) | |
| 56 | log "dump complete: ${dump_size} at $dump" | |
| 57 | ||
| 58 | log "restoring into scratch ..." | |
| 59 | # Wipe the scratch DB schema (public only). Idempotent. | |
| 60 | psql -v ON_ERROR_STOP=1 "$SCRATCH_DATABASE_URL" >/dev/null <<'SQL' | |
| 61 | DROP SCHEMA IF EXISTS public CASCADE; | |
| 62 | CREATE SCHEMA public; | |
| 63 | GRANT ALL ON SCHEMA public TO PUBLIC; | |
| 64 | SQL | |
| 65 | ||
| 66 | if [[ "$VERBOSE" -eq 1 ]]; then | |
| 67 | pg_restore --no-owner --no-privileges --dbname="$SCRATCH_DATABASE_URL" "$dump" || true | |
| 68 | else | |
| 69 | pg_restore --no-owner --no-privileges --dbname="$SCRATCH_DATABASE_URL" "$dump" >/dev/null 2>&1 || true | |
| 70 | fi | |
| 71 | log "restore complete" | |
| 72 | ||
| 73 | q() { | |
| 74 | local url="$1" sql="$2" | |
| 75 | psql -At "$url" -c "$sql" 2>/dev/null | tr -d '[:space:]' | |
| 76 | } | |
| 77 | ||
| 78 | compare() { | |
| 79 | local label="$1" sql="$2" | |
| 80 | local a b | |
| 81 | a="$(q "$DATABASE_URL" "$sql")" | |
| 82 | b="$(q "$SCRATCH_DATABASE_URL" "$sql")" | |
| 83 | if [[ "$a" == "$b" && -n "$a" ]]; then | |
| 84 | printf ' \033[32mPASS\033[0m %-22s prod=%-12s scratch=%s\n' "$label" "$a" "$b" | |
| 85 | return 0 | |
| 86 | fi | |
| 87 | printf ' \033[31mFAIL\033[0m %-22s prod=%-12s scratch=%s\n' "$label" "$a" "$b" | |
| 88 | return 1 | |
| 89 | } | |
| 90 | ||
| 91 | log "verification:" | |
| 92 | fails=0 | |
| 93 | compare "users count" "SELECT COUNT(*) FROM users;" || fails=$((fails+1)) | |
| 94 | compare "repositories count" "SELECT COUNT(*) FROM repositories;" || fails=$((fails+1)) | |
| 95 | compare "site_admins count" "SELECT COUNT(*) FROM site_admins;" || fails=$((fails+1)) | |
| 96 | compare "schema tables" "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='public';" || fails=$((fails+1)) | |
| 97 | ||
| 98 | elapsed=$(( $(date +%s) - start_seconds )) | |
| 99 | ||
| 100 | if [[ "$KEEP_DUMP" -ne 1 ]]; then | |
| 101 | rm -f "$dump" | |
| 102 | fi | |
| 103 | ||
| 104 | if [[ "$fails" -eq 0 ]]; then | |
| 105 | log "All checks passed. Took ${elapsed}s." | |
| 106 | mkdir -p "$stamp_dir" 2>/dev/null || true | |
| 107 | date +%s > "$stamp_dir/drill-last-success" 2>/dev/null || true | |
| 108 | exit 0 | |
| 109 | fi | |
| 110 | ||
| 111 | log "${fails} check(s) failed. Investigate before trusting backups." | |
| 112 | exit 1 |