Fix six code review issues in sync-prod-db.sh
- Add upfront check for pg_dump/pg_restore/psql availability (#5) - Fix password masking to use sed instead of broken bash glob (#6) - Replace DROP/CREATE DATABASE with pg_restore --clean --if-exists, which works on managed cloud databases without superuser access (#2) - Remove dead ADMIN_DB assignment that was immediately overwritten (#3) - Handle pg_restore exit code 1 (non-fatal warnings) gracefully instead of aborting via set -e (#1) - Count webhook URLs before the UPDATE for an accurate cleared count (#4) https://claude.ai/code/session_01EiYh5ZiuWAnpo1PND45Yi1
This commit is contained in:
parent
521597a306
commit
3d0a1d5e44
1 changed files with 42 additions and 36 deletions
|
|
@ -25,6 +25,11 @@ warn() { echo -e "${YELLOW}$*${RESET}"; }
|
||||||
success() { echo -e "${GREEN}$*${RESET}"; }
|
success() { echo -e "${GREEN}$*${RESET}"; }
|
||||||
error() { echo -e "${RED}ERROR: $*${RESET}" >&2; exit 1; }
|
error() { echo -e "${RED}ERROR: $*${RESET}" >&2; exit 1; }
|
||||||
|
|
||||||
|
# ── Check required tools (fix #5) ────────────────────────────────────────────
|
||||||
|
for cmd in pg_dump pg_restore psql; do
|
||||||
|
command -v "$cmd" &>/dev/null || error "'$cmd' not found. Install PostgreSQL client tools (e.g. brew install libpq)."
|
||||||
|
done
|
||||||
|
|
||||||
# ── Load .env ─────────────────────────────────────────────────────────────────
|
# ── Load .env ─────────────────────────────────────────────────────────────────
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
ENV_FILE="$SCRIPT_DIR/../.env"
|
ENV_FILE="$SCRIPT_DIR/../.env"
|
||||||
|
|
@ -50,12 +55,18 @@ if [[ "$PROD_DB" == "$DEV_DB" ]]; then
|
||||||
error "PROD_DATABASE_URL and DATABASE_URL are the same. Aborting to prevent data loss."
|
error "PROD_DATABASE_URL and DATABASE_URL are the same. Aborting to prevent data loss."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# ── Mask password for display (fix #6) ───────────────────────────────────────
|
||||||
|
# Replaces only the password portion (between : and @) using sed, not bash globs.
|
||||||
|
mask_url() {
|
||||||
|
echo "$1" | sed 's|\(://[^:]*\):[^@]*@|\1:***@|'
|
||||||
|
}
|
||||||
|
|
||||||
# ── Confirmation prompt ───────────────────────────────────────────────────────
|
# ── Confirmation prompt ───────────────────────────────────────────────────────
|
||||||
echo ""
|
echo ""
|
||||||
warn "⚠️ WARNING: This will OVERWRITE your development database with production data."
|
warn "⚠️ WARNING: This will OVERWRITE your development database with production data."
|
||||||
echo ""
|
echo ""
|
||||||
echo -e " Source (prod): ${BOLD}${PROD_DB//:*@/:***@}${RESET}" # mask password
|
echo -e " Source (prod): ${BOLD}$(mask_url "$PROD_DB")${RESET}"
|
||||||
echo -e " Target (dev): ${BOLD}${DEV_DB//:*@/:***@}${RESET}"
|
echo -e " Target (dev): ${BOLD}$(mask_url "$DEV_DB")${RESET}"
|
||||||
echo ""
|
echo ""
|
||||||
warn "After restore, the script will:"
|
warn "After restore, the script will:"
|
||||||
echo " • NULL out all Discord webhook URLs (prevents notification leaks)"
|
echo " • NULL out all Discord webhook URLs (prevents notification leaks)"
|
||||||
|
|
@ -74,7 +85,7 @@ DUMP_FILE=$(mktemp /tmp/brackt_prod_dump_XXXXXX.dump)
|
||||||
trap 'rm -f "$DUMP_FILE"' EXIT
|
trap 'rm -f "$DUMP_FILE"' EXIT
|
||||||
|
|
||||||
info ""
|
info ""
|
||||||
info "1/4 Dumping production database…"
|
info "1/3 Dumping production database…"
|
||||||
pg_dump \
|
pg_dump \
|
||||||
--no-owner \
|
--no-owner \
|
||||||
--no-acl \
|
--no-acl \
|
||||||
|
|
@ -83,48 +94,43 @@ pg_dump \
|
||||||
--file="$DUMP_FILE"
|
--file="$DUMP_FILE"
|
||||||
success " Dump complete: $(du -h "$DUMP_FILE" | cut -f1)"
|
success " Dump complete: $(du -h "$DUMP_FILE" | cut -f1)"
|
||||||
|
|
||||||
# ── Drop & recreate dev DB ────────────────────────────────────────────────────
|
# ── Restore (fix #1, #2, #3) ─────────────────────────────────────────────────
|
||||||
|
# Uses --clean --if-exists to drop and recreate all objects within the existing
|
||||||
|
# database, without needing DROP DATABASE / CREATE DATABASE permissions.
|
||||||
|
# This works on managed cloud databases (Railway, Render, Neon, Supabase, etc.).
|
||||||
|
#
|
||||||
|
# pg_restore exits with code 1 for non-fatal warnings (e.g. "role X does not
|
||||||
|
# exist" with --no-owner), so we capture the exit code and only abort on
|
||||||
|
# unexpected failures, not routine restore warnings.
|
||||||
info ""
|
info ""
|
||||||
info "2/4 Recreating development database…"
|
info "2/3 Restoring production dump to development database…"
|
||||||
|
RESTORE_EXIT=0
|
||||||
# Extract the database name from the URL (last path component)
|
|
||||||
DEV_DB_NAME="${DEV_DB##*/}"
|
|
||||||
DEV_DB_NAME="${DEV_DB_NAME%%\?*}" # strip any query params
|
|
||||||
|
|
||||||
# Build a connection string to the postgres maintenance DB on the same host
|
|
||||||
# by replacing the DB name with "postgres"
|
|
||||||
ADMIN_DB="${DEV_DB%/$DEV_DB_NAME*}/postgres${DEV_DB#*/$DEV_DB_NAME}"
|
|
||||||
# Simpler: just swap the last path segment
|
|
||||||
ADMIN_DB=$(echo "$DEV_DB" | sed "s|/$DEV_DB_NAME\$|/postgres|;s|/$DEV_DB_NAME?|/postgres?|")
|
|
||||||
|
|
||||||
# Terminate existing connections, then drop and recreate
|
|
||||||
psql "$ADMIN_DB" \
|
|
||||||
-c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '$DEV_DB_NAME' AND pid <> pg_backend_pid();" \
|
|
||||||
-c "DROP DATABASE IF EXISTS \"$DEV_DB_NAME\";" \
|
|
||||||
-c "CREATE DATABASE \"$DEV_DB_NAME\";" \
|
|
||||||
--quiet
|
|
||||||
success " Database '$DEV_DB_NAME' recreated."
|
|
||||||
|
|
||||||
# ── Restore ───────────────────────────────────────────────────────────────────
|
|
||||||
info ""
|
|
||||||
info "3/4 Restoring production dump to development database…"
|
|
||||||
pg_restore \
|
pg_restore \
|
||||||
--no-owner \
|
--no-owner \
|
||||||
--no-acl \
|
--no-acl \
|
||||||
|
--clean \
|
||||||
|
--if-exists \
|
||||||
--dbname="$DEV_DB" \
|
--dbname="$DEV_DB" \
|
||||||
"$DUMP_FILE"
|
"$DUMP_FILE" || RESTORE_EXIT=$?
|
||||||
success " Restore complete."
|
|
||||||
|
|
||||||
# ── Sanitize ──────────────────────────────────────────────────────────────────
|
if [[ $RESTORE_EXIT -eq 0 ]]; then
|
||||||
|
success " Restore complete."
|
||||||
|
elif [[ $RESTORE_EXIT -eq 1 ]]; then
|
||||||
|
warn " Restore completed with warnings (non-fatal). Continuing…"
|
||||||
|
else
|
||||||
|
error "pg_restore failed with exit code $RESTORE_EXIT. Aborting."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Sanitize (fix #4) ────────────────────────────────────────────────────────
|
||||||
info ""
|
info ""
|
||||||
info "4/4 Sanitizing development database…"
|
info "3/3 Sanitizing development database…"
|
||||||
|
|
||||||
SANITIZE_RESULT=$(psql "$DEV_DB" --tuples-only --no-align << 'SQL'
|
SANITIZE_RESULT=$(psql "$DEV_DB" --tuples-only --no-align << 'SQL'
|
||||||
BEGIN;
|
BEGIN;
|
||||||
|
|
||||||
-- 1. Disable Discord webhook notifications for all leagues
|
-- 1. Count leagues with webhooks before clearing (accurate count)
|
||||||
|
SELECT 'discord_webhooks_nulled:' || COUNT(*) FROM leagues WHERE discord_webhook_url IS NOT NULL;
|
||||||
UPDATE leagues SET discord_webhook_url = NULL;
|
UPDATE leagues SET discord_webhook_url = NULL;
|
||||||
SELECT 'discord_webhooks_nulled:' || COUNT(*) FROM leagues;
|
|
||||||
|
|
||||||
-- 2. Anonymize PII for non-admin users only
|
-- 2. Anonymize PII for non-admin users only
|
||||||
-- (admin accounts are untouched so admin pages remain accessible)
|
-- (admin accounts are untouched so admin pages remain accessible)
|
||||||
|
|
@ -149,7 +155,7 @@ ADMIN_COUNT=$(echo "$SANITIZE_RESULT" | grep 'admin_users_kept:' | cut
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
success "✓ Sync complete. Sanitization summary:"
|
success "✓ Sync complete. Sanitization summary:"
|
||||||
echo " • Discord webhook URLs nulled: ${LEAGUES_COUNT:-?} leagues"
|
echo " • Discord webhook URLs cleared: ${LEAGUES_COUNT:-?} leagues"
|
||||||
echo " • Non-admin users anonymized: ${ANON_COUNT:-?} users"
|
echo " • Non-admin users anonymized: ${ANON_COUNT:-?} users"
|
||||||
echo " • Admin accounts preserved: ${ADMIN_COUNT:-?} users"
|
echo " • Admin accounts preserved: ${ADMIN_COUNT:-?} users"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue