diff --git a/asset/postgres/ident.conf.tmpl b/asset/postgres/ident.conf.tmpl index 4e5e36b..f604789 100644 --- a/asset/postgres/ident.conf.tmpl +++ b/asset/postgres/ident.conf.tmpl @@ -1,11 +1,16 @@ # moments — pg_ident.conf.d drop-in template -# Rendered by script/deploy.sh per host based on manifest.yml. -# Install path: /var/lib/pgsql/18/data/pg_ident.conf.d/{{HOST_FQDN}}.conf +# Maintained by script/db-perms.sh (idempotent; run from a workstation). +# Install path: /var/lib/pgsql/18/data/pg_ident.conf.d/moments.conf +# +# The file is named for the app (moments.conf), NOT the cert_cn host, so +# provisioning for other apps — which may own drop-ins for the same hosts — +# can never clobber or interleave with moments access. +# # Apply with: sudo systemctl reload postgresql-18 — on BOTH magrathea # (primary) and frankie (standby) so failover doesn't lock the app out. # -# One line per (host, role) mapping. A host that runs both moments-api -# and moments-worker will have two lines (one for moments_ro, one for -# moments_rw). +# One line per (cert_cn host, role) mapping. A host that runs both +# moments-api and moments-worker will have two lines (one for moments_ro, +# one for moments_rw). cert_cn {{HOST_FQDN}} {{DB_ROLE}} diff --git a/script/db-perms.sh b/script/db-perms.sh index dd05c87..eb0ef68 100755 --- a/script/db-perms.sh +++ b/script/db-perms.sh @@ -1,8 +1,14 @@ #!/usr/bin/env bash # -# Idempotently add cert_cn → role mappings to pg_ident.conf.d on the moments -# postgres primary and standby, then reload postgres so the changes take -# effect. Re-running is a no-op (no duplicate lines, no spurious reload). +# Idempotently maintain the moments cert_cn → role mappings in an app-owned +# pg_ident.conf.d/moments.conf on the moments postgres primary and standby, +# then reload postgres so the changes take effect. Re-running is a no-op +# (no duplicate lines, no spurious reload). +# +# The file is named for the app, not the cert_cn host, so provisioning for +# other apps (which may own files for the same hosts) can never clobber or +# interleave with moments access. Any moments_* lines found in legacy +# host-named files are migrated here and removed from the legacy file. # # Run from a workstation with ssh access to both pg hosts. This script ssh's # out; do NOT run it on magrathea/frankie directly. @@ -14,32 +20,63 @@ worker_host=frootmig.kosherinata.internal pg_hosts=( magrathea.kosherinata.internal - frankie.kosherinata.internal + frankie.hanzalova.internal ) # Each (cert_cn host, role) pair becomes one cert_cn line in -# pg_ident.conf.d/.conf on every pg host listed above. +# pg_ident.conf.d/moments.conf on every pg host listed above. mapping_pairs=( "$api_host" moments_ro "$worker_host" moments_rw ) ident_dir=/var/lib/pgsql/18/data/pg_ident.conf.d +app_file=moments.conf +failed=0 for pg_host in "${pg_hosts[@]}"; do printf '==> %s\n' "$pg_host" - ssh -o BatchMode=yes "$pg_host" "sudo bash -s -- ${ident_dir@Q} ${mapping_pairs[@]@Q}" <<'REMOTE_EOF' + if ! ssh -o BatchMode=yes -o ConnectTimeout=5 "$pg_host" \ + "sudo bash -s -- ${ident_dir@Q} ${app_file@Q} ${mapping_pairs[@]@Q}" <<'REMOTE_EOF' set -euo pipefail ident_dir="$1"; shift +file="${ident_dir}/$1"; shift changed=0 + +# Migrate any moments_* lines out of legacy host-named files into the +# app-owned file, so other apps' provisioning (which may own files for the +# same hosts) can never clobber or drop moments access. Lines are preserved +# verbatim (this covers mappings beyond mapping_pairs, e.g. a workstation's). +# A legacy file left empty is removed. +moments_re='^cert_cn[[:space:]]+[^[:space:]]+[[:space:]]+moments_(ro|rw)[[:space:]]*$' +for legacy in "$ident_dir"/*.conf; do + [[ "$legacy" == "$file" ]] && continue + grep --extended-regexp --quiet "$moments_re" "$legacy" || continue + while read -r line; do + if [[ -f "$file" ]] && grep --fixed-strings --line-regexp --quiet -- "$line" "$file"; then + printf ' already in %s, dropping from %s: %s\n' "${file##*/}" "${legacy##*/}" "$line" + else + printf '%s\n' "$line" | sudo -u postgres tee --append "$file" >/dev/null + printf ' migrated from %s: %s\n' "${legacy##*/}" "$line" + fi + done < <(grep --extended-regexp "$moments_re" "$legacy") + remaining="$(grep --extended-regexp --invert-match "$moments_re" "$legacy" || true)" + if [[ -n "$remaining" ]]; then + printf '%s\n' "$remaining" | sudo -u postgres tee "$legacy" >/dev/null + else + rm "$legacy" + printf ' removed empty legacy file %s\n' "${legacy##*/}" + fi + changed=1 +done + while [[ $# -gt 0 ]]; do cert_cn_host="$1" role="$2" shift 2 line="cert_cn ${cert_cn_host} ${role}" - file="${ident_dir}/${cert_cn_host}.conf" # The heredoc runs as root via sudo bash, so [[ -f ]] and grep are fine # without dropping privs. tee --append runs as postgres so a newly-created @@ -60,4 +97,9 @@ else echo " no changes; reload skipped" fi REMOTE_EOF + then + echo " WARNING: ${pg_host} unreachable or failed — re-run when it is back" + failed=1 + fi done +exit "$failed"