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:
@@ -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"
|
||||||
|
|||||||
@@ -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)"
|
||||||
|
|||||||
Reference in New Issue
Block a user