#!/bin/bash # Sync PostgreSQL from SOURCE_DB_URL -> DEST_DB_URL. # Source is read-only: only pg_dump (SELECT) with default_transaction_read_only=on. set -euo pipefail log() { echo "[$(date -Iseconds)] $*" >&2 } SOURCE_DB_URL="${SOURCE_DB_URL:-}" DEST_DB_URL="${DEST_DB_URL:-}" if [ -z "$SOURCE_DB_URL" ]; then log "ERROR: SOURCE_DB_URL must be set" exit 1 fi if [ -z "$DEST_DB_URL" ]; then log "ERROR: DEST_DB_URL must be set" exit 1 fi DUMP_FILE="${DUMP_FILE:-/tmp/db-sync.dump}" # Extra safety: force read-only transactions on the source connection. export PGOPTIONS_SOURCE="${PGOPTIONS_SOURCE:--c default_transaction_read_only=on}" log "Starting sync (source -> dest)" log "Dumping source (read-only)..." # pg_dump only reads from source. --clean/--if-exists apply DROP statements to DEST on restore. PGOPTIONS="$PGOPTIONS_SOURCE" pg_dump \ --format=custom \ --no-owner \ --no-acl \ --verbose \ --file="$DUMP_FILE" \ "$SOURCE_DB_URL" log "Restoring into destination (with --clean)..." pg_restore \ --clean \ --if-exists \ --no-owner \ --no-acl \ --verbose \ --dbname="$DEST_DB_URL" \ "$DUMP_FILE" || { # pg_restore returns non-zero on some non-fatal warnings (e.g. missing roles). # Treat exit code 1 as warning; fail hard on higher codes. status=$? if [ "$status" -gt 1 ]; then log "ERROR: pg_restore failed with exit code $status" rm -f "$DUMP_FILE" exit "$status" fi log "WARNING: pg_restore finished with warnings (exit $status)" } rm -f "$DUMP_FILE" log "Sync completed successfully"