Ctrl K
ComunidadGuías…

scripts/restore-pg-drill.sh

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env bash
# scripts/restore-pg-drill.sh — restore drill (P0 → P1 critical gate).
#
# Fetches newest R2 backup, restores into a throwaway DB on dev compose,
# runs sanity checks, then drops the temp DB.  Exit 0=PASS, 1=FAIL.
#
# Refs:
#   pg_restore: https://www.postgresql.org/docs/16/app-pgrestore.html
#   rclone lsjson + jq: deterministic "newest object" pick
#     https://rclone.org/commands/rclone_lsjson/

set -euo pipefail

log()  { printf '[restore-drill] %s %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$*" >&2; }
fail() { log "FAIL: $*"; exit 1; }

REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$REPO_ROOT"
COMPOSE_FILE="docker-compose.dev.yml"
PG_SERVICE="postgres"
[[ -f "$COMPOSE_FILE" ]] || fail "compose file not found: $COMPOSE_FILE"

DB_USER="${POSTGRES_USER:-animego}"
DRILL_DB="animego_restore_drill"
R2_REMOTE="${R2_REMOTE:-r2}"
R2_BUCKET="${R2_BUCKET:-animego-backup}"
WORK_DIR="$(mktemp -d -t animego-restore-XXXXXX)"
LOCAL_DUMP="$WORK_DIR/latest.dump"

cleanup() {
  log "cleanup: dropping $DRILL_DB and removing $WORK_DIR"
  docker compose -f "$COMPOSE_FILE" exec -T "$PG_SERVICE" \
    psql -U "$DB_USER" -d postgres -v ON_ERROR_STOP=0 \
    -c "DROP DATABASE IF EXISTS $DRILL_DB;" >/dev/null 2>&1 || true
  rm -rf "$WORK_DIR"
}
trap cleanup EXIT

command -v rclone >/dev/null || fail "rclone not installed"
command -v jq >/dev/null     || fail "jq not installed (apt install jq | brew install jq)"
docker compose -f "$COMPOSE_FILE" ps "$PG_SERVICE" >/dev/null 2>&1 \
  || fail "dev postgres not running — start with: docker compose -f $COMPOSE_FILE up -d postgres"

# 1) Find newest backup within last 30 days using lsjson + jq (deterministic).
log "scanning ${R2_REMOTE}:${R2_BUCKET} for newest pg-*.dump (<=30d)"
NEWEST="$(rclone lsjson "$R2_REMOTE:$R2_BUCKET" --include "pg-*.dump" --max-age 30d \
  | jq -r 'sort_by(.ModTime) | reverse | .[0].Name // empty')"
[[ -n "$NEWEST" ]] || fail "no pg-*.dump found in last 30d"
log "newest=$NEWEST"

# 2) Download via rclone copy (resumable, integrity-checked).
DL_START="$(date +%s)"
rclone copy "$R2_REMOTE:$R2_BUCKET/$NEWEST" "$WORK_DIR/" --progress >&2 \
  || fail "rclone copy failed"
mv "$WORK_DIR/$NEWEST" "$LOCAL_DUMP"
DL_BYTES="$(stat -c%s "$LOCAL_DUMP" 2>/dev/null || stat -f%z "$LOCAL_DUMP")"
DL_SEC=$(( $(date +%s) - DL_START ))
log "downloaded ${DL_BYTES} bytes in ${DL_SEC}s"

# 3) Create throwaway DB and pg_restore into it.
log "creating throwaway DB: $DRILL_DB"
docker compose -f "$COMPOSE_FILE" exec -T "$PG_SERVICE" \
  psql -U "$DB_USER" -d postgres -v ON_ERROR_STOP=1 \
  -c "DROP DATABASE IF EXISTS $DRILL_DB;" \
  -c "CREATE DATABASE $DRILL_DB OWNER $DB_USER;" >/dev/null \
  || fail "could not create $DRILL_DB"

RESTORE_START="$(date +%s)"
log "pg_restore → $DRILL_DB (streaming dump via stdin)"
# --no-owner --no-acl: tolerate role differences between prod and dev.
# Exit nonzero on hard errors; warnings (e.g. extension missing) are OK.
docker compose -f "$COMPOSE_FILE" exec -T "$PG_SERVICE" \
  pg_restore -U "$DB_USER" -d "$DRILL_DB" --no-owner --no-acl \
  < "$LOCAL_DUMP" \
  || fail "pg_restore failed"
RESTORE_SEC=$(( $(date +%s) - RESTORE_START ))
log "pg_restore completed in ${RESTORE_SEC}s"

# 4) Sanity checks.
psql_drill() {
  docker compose -f "$COMPOSE_FILE" exec -T "$PG_SERVICE" \
    psql -U "$DB_USER" -d "$DRILL_DB" -At -v ON_ERROR_STOP=1 -c "$1"
}

PG_VERSION="$(psql_drill "SELECT version();")" || fail "SELECT version() failed"
log "version: $PG_VERSION"

TABLE_COUNT="$(psql_drill "SELECT COUNT(*) FROM pg_tables WHERE schemaname='public';")"
log "public table count: $TABLE_COUNT"
[[ "$TABLE_COUNT" -gt 0 ]] || fail "no tables in restored public schema"

# Robust per-table row counts: skip tables that don't exist (schema is P1 — TBD).
KNOWN_TABLES=(anime_cache users subscriptions)
declare -a ROW_REPORTS=()
for t in "${KNOWN_TABLES[@]}"; do
  EXISTS="$(psql_drill "SELECT to_regclass('public.${t}') IS NOT NULL;")"
  if [[ "$EXISTS" == "t" ]]; then
    RC="$(psql_drill "SELECT COUNT(*) FROM public.${t};")"
    ROW_REPORTS+=("${t}=${RC}")
  else
    ROW_REPORTS+=("${t}=ABSENT")
  fi
done

# Also list ALL public tables with row counts for completeness.
log "discovered tables:"
psql_drill "SELECT format('  %s (%s rows)', tablename,
    (xpath('/row/c/text()',
       query_to_xml(format('SELECT COUNT(*) AS c FROM public.%I', tablename), true, true, '')))[1]::text)
  FROM pg_tables WHERE schemaname='public' ORDER BY tablename;" \
  | while IFS= read -r line; do log "$line"; done

log "PASS download=${DL_BYTES}B dl=${DL_SEC}s restore=${RESTORE_SEC}s tables=${TABLE_COUNT} known=[${ROW_REPORTS[*]}]"
exit 0