diff --git a/deploy/replicas.env.example b/deploy/replicas.env.example index ceecd28..5c60be0 100644 --- a/deploy/replicas.env.example +++ b/deploy/replicas.env.example @@ -1,6 +1,8 @@ # Copy to deploy/replicas.env on the publisher host (gitignored — holds passwords). # -# Every logical-replication subscriber of superadmin_pub, space/comma/newline separated. +# One DSN per line (or comma). Do not put spaces between URLs. +# Prefer URI form. Password must not contain @ : / # ? — or URL-encode it. +# Keyword form (host=...) is OK only because the script splits on newlines, not spaces. # The user must be able to run ALTER TABLE (table owner or superuser). REPLICA_DSNS="postgresql://USER:PASSWORD@SLAVE_1_IP:5433/superadmin postgresql://USER:PASSWORD@SLAVE_2_IP:5432/superadmin" diff --git a/deploy/sync-replica-schema.sh b/deploy/sync-replica-schema.sh index 91f0e50..3386540 100644 --- a/deploy/sync-replica-schema.sh +++ b/deploy/sync-replica-schema.sh @@ -47,7 +47,10 @@ if [[ -z "${DATABASE_URL:-}" && -f "$API_DIR/.env" ]]; then set -a && source "$API_DIR/.env" && set +a fi -read -r -a REPLICAS <<< "$(tr ',\n' ' ' <<< "$REPLICA_DSNS")" +# One DSN per line or comma (NOT spaces — keyword conninfo has spaces). +mapfile -t REPLICAS < <( + printf '%s\n' "$REPLICA_DSNS" | tr ',' '\n' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | grep -v '^$' +) # psql would otherwise swallow the loop input it is called from run_psql() { @@ -56,7 +59,16 @@ run_psql() { # host:port/db only — DSNs carry passwords label() { - sed -E 's#^[a-z]+://([^@]*@)?##; s#\?.*$##' <<< "$1" + local dsn="$1" + if [[ "$dsn" == *"://"* ]]; then + sed -E 's#^[a-z]+://([^@]*@)?##; s#\?.*$##' <<< "$dsn" + return + fi + local host port db + host="$(sed -nE 's/.*host=([^ ]+).*/\1/p' <<< "$dsn")" + port="$(sed -nE 's/.*port=([^ ]+).*/\1/p' <<< "$dsn")" + db="$(sed -nE 's/.*dbname=([^ ]+).*/\1/p' <<< "$dsn")" + echo "${host:-?}:${port:-5432}/${db:-?}" } # every list feeding comm is sorted with the same collation @@ -97,13 +109,19 @@ apply_migration() { } column_fingerprint() { - run_psql "$1" -v ON_ERROR_STOP=1 -At -c " + run_psql "$(libpq_dsn "$1")" -v ON_ERROR_STOP=1 -At -c " SELECT table_name || '.' || column_name FROM information_schema.columns WHERE table_schema = 'public' AND table_name NOT IN ('_replica_ddl_applied')" | LC_ALL=C sort } +# Prisma DATABASE_URL uses ?schema=public; libpq rejects that query param. +libpq_dsn() { + local u="$1" + echo "${u%%\?*}" +} + if [[ "$MODE" == "--check" ]]; then for dsn in "${REPLICAS[@]}"; do echo "== $(label "$dsn")" diff --git a/src/program-settings/program-settings.service.ts b/src/program-settings/program-settings.service.ts index 148d72a..183d50f 100644 --- a/src/program-settings/program-settings.service.ts +++ b/src/program-settings/program-settings.service.ts @@ -144,7 +144,9 @@ export class ProgramSettingsService { importedAt: '', source: 'khanegi_admin', apiBase: '', - items: raw.filter((row) => this.isSystemSetting(row as ProgramSettingRow)), + items: this.asSettingsArray(raw).filter((row) => + this.isSystemSetting(row), + ), }; } return null;