fix(replicas): use PUBLISHER_DSN when DATABASE_URL breaks pg_dump

Prisma DATABASE_URL passwords with @ confuse libpq URI parsing (host
Montime@127.0.0.1). Repair scripts now prefer explicit PUBLISHER_DSN.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-18 12:41:23 +03:30
co-authored by Cursor
parent 693b1ef93e
commit 26630cecf6
2 changed files with 39 additions and 8 deletions
+5
View File
@@ -9,3 +9,8 @@ postgresql://USER:PASSWORD@SLAVE_2_IP:5432/superadmin"
# Read-only app user on replicas (program-settings API). Grants re-applied after data repair. # Read-only app user on replicas (program-settings API). Grants re-applied after data repair.
# REPLICA_APP_DB_USER=montime # REPLICA_APP_DB_USER=montime
# Publisher DB for pg_dump / count checks. Required when DATABASE_URL password has @
# (Prisma tolerates it; libpq URI parsing breaks → host "Montime@127.0.0.1").
# Use postgres superuser on localhost; URL-encode special chars (@ → %40).
# PUBLISHER_DSN="postgresql://postgres:PASS@127.0.0.1:5432/superadmin"
+33 -7
View File
@@ -45,10 +45,30 @@ if [[ -z "${DATABASE_URL:-}" && -f "$API_DIR/.env" ]]; then
set -a && source "$API_DIR/.env" && set +a set -a && source "$API_DIR/.env" && set +a
fi fi
if [[ -z "${DATABASE_URL:-}" ]]; then # Prisma DATABASE_URL often has @ in the password (e.g. Admin@12345) which breaks
echo "DATABASE_URL is empty (publisher)." >&2 # libpq URI parsing → host "Montime@127.0.0.1". Use an explicit publisher DSN.
exit 1 resolve_publisher_dsn() {
if [[ -n "${PUBLISHER_DSN:-}" ]]; then
echo "$PUBLISHER_DSN"
return 0
fi fi
if [[ -n "${PGHOST:-}" && -n "${PGUSER:-}" ]]; then
echo "host=${PGHOST} port=${PGPORT:-5432} dbname=${PGDATABASE:-superadmin} user=${PGUSER} password=${PGPASSWORD:-}"
return 0
fi
if [[ -n "${DATABASE_URL:-}" ]]; then
local url="${DATABASE_URL%%\?*}"
if [[ "$url" == postgresql://* || "$url" == postgres://* ]]; then
echo "$url"
return 0
fi
fi
echo "Set PUBLISHER_DSN in deploy/replicas.env (postgresql://…@127.0.0.1:5432/superadmin)." >&2
echo "Prisma DATABASE_URL alone is not reliable for pg_dump when the password contains @." >&2
return 1
}
PUBLISHER="$(resolve_publisher_dsn)" || exit 1
mapfile -t REPLICAS < <( mapfile -t REPLICAS < <(
printf '%s\n' "$REPLICA_DSNS" | tr ',' '\n' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | grep -v '^$' printf '%s\n' "$REPLICA_DSNS" | tr ',' '\n' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | grep -v '^$'
@@ -76,6 +96,12 @@ label() {
echo "${host:-?}:${port:-5432}/${db:-?}" echo "${host:-?}:${port:-5432}/${db:-?}"
} }
if ! run_psql "$(libpq_dsn "$PUBLISHER")" -v ON_ERROR_STOP=1 -At -c "SELECT 1" >/dev/null 2>&1; then
echo "Cannot connect to publisher ($(label "$PUBLISHER"))." >&2
echo "Add PUBLISHER_DSN to deploy/replicas.env — URL-encode @ in passwords as %40." >&2
exit 1
fi
table_count() { table_count() {
local dsn="$1" table="$2" local dsn="$1" table="$2"
run_psql "$(libpq_dsn "$dsn")" -v ON_ERROR_STOP=1 -At -c \ run_psql "$(libpq_dsn "$dsn")" -v ON_ERROR_STOP=1 -At -c \
@@ -102,7 +128,7 @@ grant_app_user() {
copy_table_from_publisher() { copy_table_from_publisher() {
local replica_dsn="$1" table="$2" local replica_dsn="$1" table="$2"
local pub_dsn local pub_dsn
pub_dsn="$(libpq_dsn "$DATABASE_URL")" pub_dsn="$(libpq_dsn "$PUBLISHER")"
pg_dump "$pub_dsn" \ pg_dump "$pub_dsn" \
-t "public.\"$table\"" \ -t "public.\"$table\"" \
--data-only \ --data-only \
@@ -127,7 +153,7 @@ repair_replica() {
fi fi
for table in "${SYNC_TABLES[@]}"; do for table in "${SYNC_TABLES[@]}"; do
pub="$(table_count "$DATABASE_URL" "$table")" pub="$(table_count "$PUBLISHER" "$table")"
replica="$(table_count "$replica_dsn" "$table")" replica="$(table_count "$replica_dsn" "$table")"
if [[ "$pub" != "$replica" ]]; then if [[ "$pub" != "$replica" ]]; then
drifted+=("$table") drifted+=("$table")
@@ -155,7 +181,7 @@ repair_replica() {
run_psql "$(libpq_dsn "$replica_dsn")" -v ON_ERROR_STOP=1 -q \ run_psql "$(libpq_dsn "$replica_dsn")" -v ON_ERROR_STOP=1 -q \
-c "TRUNCATE TABLE public.\"$table\" CASCADE" -c "TRUNCATE TABLE public.\"$table\" CASCADE"
copy_table_from_publisher "$replica_dsn" "$table" copy_table_from_publisher "$replica_dsn" "$table"
pub="$(table_count "$DATABASE_URL" "$table")" pub="$(table_count "$PUBLISHER" "$table")"
replica="$(table_count "$replica_dsn" "$table")" replica="$(table_count "$replica_dsn" "$table")"
if [[ "$pub" != "$replica" ]]; then if [[ "$pub" != "$replica" ]]; then
echo " ! repair failed for $table (publisher=$pub replica=$replica)" >&2 echo " ! repair failed for $table (publisher=$pub replica=$replica)" >&2
@@ -194,7 +220,7 @@ check_replicas() {
FROM pg_stat_subscription" 2>/dev/null || echo " (no pg_stat_subscription)" FROM pg_stat_subscription" 2>/dev/null || echo " (no pg_stat_subscription)"
for table in "${SYNC_TABLES[@]}"; do for table in "${SYNC_TABLES[@]}"; do
pub="$(table_count "$DATABASE_URL" "$table")" pub="$(table_count "$PUBLISHER" "$table")"
replica="$(table_count "$dsn" "$table")" replica="$(table_count "$dsn" "$table")"
if [[ "$pub" == "$replica" ]]; then if [[ "$pub" == "$replica" ]]; then
echo " $table OK ($pub)" echo " $table OK ($pub)"