fix(replicas): parse Prisma DATABASE_URL and dump tables by name
Passwords with @ broke libpq URIs; convert to keyword conninfo. pg_dump -t public."table" produced empty dumps so replicas stayed at 0. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -10,7 +10,6 @@ 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 @
|
# Publisher DB. Prefer this if DATABASE_URL password contains @ (must be URL-encoded here).
|
||||||
# (Prisma tolerates it; libpq URI parsing breaks → host "Montime@127.0.0.1").
|
# Master on montech-01 listens on 15432:
|
||||||
# Use postgres superuser on localhost; URL-encode special chars (@ → %40).
|
# PUBLISHER_DSN="postgresql://montime:09128081392%40Montime@127.0.0.1:15432/superadmin"
|
||||||
# PUBLISHER_DSN="postgresql://postgres:PASS@127.0.0.1:5432/superadmin"
|
|
||||||
|
|||||||
+56
-15
@@ -45,8 +45,24 @@ 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
|
||||||
|
|
||||||
# Prisma DATABASE_URL often has @ in the password (e.g. Admin@12345) which breaks
|
# Prisma DATABASE_URL often has @ in the password (09128081392@Montime).
|
||||||
# libpq URI parsing → host "Montime@127.0.0.1". Use an explicit publisher DSN.
|
# libpq URI parsing then treats host as "Montime@127.0.0.1". Convert to keyword form.
|
||||||
|
uri_to_conninfo() {
|
||||||
|
local u="${1%%\?*}"
|
||||||
|
if [[ "$u" != postgresql://* && "$u" != postgres://* ]]; then
|
||||||
|
echo "$u"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
local rest="${u#*://}"
|
||||||
|
if [[ "$rest" =~ ^([^:]+):(.+)@([^:/]+):([0-9]+)/([^/?]+) ]]; then
|
||||||
|
printf 'host=%s port=%s dbname=%s user=%s password=%s' \
|
||||||
|
"${BASH_REMATCH[3]}" "${BASH_REMATCH[4]}" "${BASH_REMATCH[5]}" \
|
||||||
|
"${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
echo "$u"
|
||||||
|
}
|
||||||
|
|
||||||
resolve_publisher_dsn() {
|
resolve_publisher_dsn() {
|
||||||
if [[ -n "${PUBLISHER_DSN:-}" ]]; then
|
if [[ -n "${PUBLISHER_DSN:-}" ]]; then
|
||||||
echo "$PUBLISHER_DSN"
|
echo "$PUBLISHER_DSN"
|
||||||
@@ -57,14 +73,10 @@ resolve_publisher_dsn() {
|
|||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
if [[ -n "${DATABASE_URL:-}" ]]; then
|
if [[ -n "${DATABASE_URL:-}" ]]; then
|
||||||
local url="${DATABASE_URL%%\?*}"
|
echo "$DATABASE_URL"
|
||||||
if [[ "$url" == postgresql://* || "$url" == postgres://* ]]; then
|
|
||||||
echo "$url"
|
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
fi
|
echo "Set DATABASE_URL or PUBLISHER_DSN (publisher Postgres)." >&2
|
||||||
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
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,8 +91,7 @@ run_psql() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
libpq_dsn() {
|
libpq_dsn() {
|
||||||
local u="$1"
|
uri_to_conninfo "$1"
|
||||||
echo "${u%%\?*}"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
label() {
|
label() {
|
||||||
@@ -125,17 +136,45 @@ grant_app_user() {
|
|||||||
" 2>/dev/null || true
|
" 2>/dev/null || true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
grant_replica_writer() {
|
||||||
|
local dsn="$1"
|
||||||
|
local user=""
|
||||||
|
if [[ "$dsn" == *"://"* ]]; then
|
||||||
|
user="$(sed -E 's#^[a-z]+://([^:/]+).*#\1#' <<< "$dsn")"
|
||||||
|
else
|
||||||
|
user="$(sed -nE 's/.*user=([^ ]+).*/\1/p' <<< "$dsn")"
|
||||||
|
fi
|
||||||
|
[[ -n "$user" ]] || return 0
|
||||||
|
echo " grant write to $user"
|
||||||
|
run_psql "$(libpq_dsn "$dsn")" -v ON_ERROR_STOP=1 -q -c "
|
||||||
|
GRANT USAGE ON SCHEMA public TO ${user};
|
||||||
|
GRANT SELECT, INSERT, UPDATE, DELETE, TRUNCATE ON ALL TABLES IN SCHEMA public TO ${user};
|
||||||
|
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO ${user};
|
||||||
|
" || true
|
||||||
|
}
|
||||||
|
|
||||||
copy_table_from_publisher() {
|
copy_table_from_publisher() {
|
||||||
local replica_dsn="$1" table="$2"
|
local replica_dsn="$1" table="$2"
|
||||||
local pub_dsn
|
local dump
|
||||||
pub_dsn="$(libpq_dsn "$PUBLISHER")"
|
dump="$(mktemp)"
|
||||||
pg_dump "$pub_dsn" \
|
pg_dump "$(libpq_dsn "$PUBLISHER")" \
|
||||||
-t "public.\"$table\"" \
|
-t "$table" \
|
||||||
--data-only \
|
--data-only \
|
||||||
--no-owner \
|
--no-owner \
|
||||||
--no-privileges \
|
--no-privileges \
|
||||||
|
--column-inserts \
|
||||||
--disable-triggers \
|
--disable-triggers \
|
||||||
| run_psql "$(libpq_dsn "$replica_dsn")" -v ON_ERROR_STOP=1 -q
|
>"$dump"
|
||||||
|
local bytes
|
||||||
|
bytes="$(wc -c <"$dump" | tr -d ' ')"
|
||||||
|
echo " dumped $table (${bytes} bytes)"
|
||||||
|
if [[ "${bytes:-0}" -lt 20 ]]; then
|
||||||
|
echo " ! pg_dump produced empty output for $table" >&2
|
||||||
|
rm -f "$dump"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
run_psql "$(libpq_dsn "$replica_dsn")" -v ON_ERROR_STOP=1 -f "$dump"
|
||||||
|
rm -f "$dump"
|
||||||
}
|
}
|
||||||
|
|
||||||
repair_replica() {
|
repair_replica() {
|
||||||
@@ -176,6 +215,8 @@ repair_replica() {
|
|||||||
-c "ALTER SUBSCRIPTION \"${sub}\" DISABLE"
|
-c "ALTER SUBSCRIPTION \"${sub}\" DISABLE"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
grant_replica_writer "$replica_dsn"
|
||||||
|
|
||||||
for table in "${drifted[@]}"; do
|
for table in "${drifted[@]}"; do
|
||||||
echo " truncating + copying $table"
|
echo " truncating + copying $table"
|
||||||
run_psql "$(libpq_dsn "$replica_dsn")" -v ON_ERROR_STOP=1 -q \
|
run_psql "$(libpq_dsn "$replica_dsn")" -v ON_ERROR_STOP=1 -q \
|
||||||
|
|||||||
Reference in New Issue
Block a user