fix(infra): own pg_ident mappings in an app-specific moments.conf
Some checks failed
deploy / Build prerendered web (push) Successful in 6m59s
deploy / Build api + worker (static musl) (push) Failing after 11m22s
deploy / Deploy web to oolon (push) Successful in 28s
deploy / Deploy moments-api to nikola (push) Has been skipped
deploy / Deploy moments-worker to frootmig (push) Has been skipped
refresh / Rebuild prerendered web (push) Successful in 6m49s
refresh / Deploy refreshed web to oolon (push) Successful in 19s
Some checks failed
deploy / Build prerendered web (push) Successful in 6m59s
deploy / Build api + worker (static musl) (push) Failing after 11m22s
deploy / Deploy web to oolon (push) Successful in 28s
deploy / Deploy moments-api to nikola (push) Has been skipped
deploy / Deploy moments-worker to frootmig (push) Has been skipped
refresh / Rebuild prerendered web (push) Successful in 6m49s
refresh / Deploy refreshed web to oolon (push) Successful in 19s
Write the cert_cn -> role mappings to pg_ident.conf.d/moments.conf instead of per-host <cert_cn>.conf files, so provisioning for other apps that own drop-ins for the same hosts can never clobber or drop moments access. db-perms.sh now also migrates any moments_ro/rw lines found in legacy host-named files into moments.conf verbatim (deleting legacy files left empty), corrects the standby hostname to frankie.hanzalova.internal, and warns-and-continues on an unreachable pg host instead of aborting the run. Closes #3 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dcsi987fK5ftZEP6w3HXEd
This commit is contained in:
@@ -1,11 +1,16 @@
|
|||||||
# moments — pg_ident.conf.d drop-in template
|
# moments — pg_ident.conf.d drop-in template
|
||||||
# Rendered by script/deploy.sh per host based on manifest.yml.
|
# Maintained by script/db-perms.sh (idempotent; run from a workstation).
|
||||||
# Install path: /var/lib/pgsql/18/data/pg_ident.conf.d/{{HOST_FQDN}}.conf
|
# 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
|
# Apply with: sudo systemctl reload postgresql-18 — on BOTH magrathea
|
||||||
# (primary) and frankie (standby) so failover doesn't lock the app out.
|
# (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
|
# One line per (cert_cn host, role) mapping. A host that runs both
|
||||||
# and moments-worker will have two lines (one for moments_ro, one for
|
# moments-api and moments-worker will have two lines (one for moments_ro,
|
||||||
# moments_rw).
|
# one for moments_rw).
|
||||||
|
|
||||||
cert_cn {{HOST_FQDN}} {{DB_ROLE}}
|
cert_cn {{HOST_FQDN}} {{DB_ROLE}}
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
#
|
#
|
||||||
# Idempotently add cert_cn → role mappings to pg_ident.conf.d on the moments
|
# Idempotently maintain the moments cert_cn → role mappings in an app-owned
|
||||||
# postgres primary and standby, then reload postgres so the changes take
|
# pg_ident.conf.d/moments.conf on the moments postgres primary and standby,
|
||||||
# effect. Re-running is a no-op (no duplicate lines, no spurious reload).
|
# 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
|
# Run from a workstation with ssh access to both pg hosts. This script ssh's
|
||||||
# out; do NOT run it on magrathea/frankie directly.
|
# out; do NOT run it on magrathea/frankie directly.
|
||||||
@@ -14,32 +20,63 @@ worker_host=frootmig.kosherinata.internal
|
|||||||
|
|
||||||
pg_hosts=(
|
pg_hosts=(
|
||||||
magrathea.kosherinata.internal
|
magrathea.kosherinata.internal
|
||||||
frankie.kosherinata.internal
|
frankie.hanzalova.internal
|
||||||
)
|
)
|
||||||
|
|
||||||
# Each (cert_cn host, role) pair becomes one cert_cn line in
|
# Each (cert_cn host, role) pair becomes one cert_cn line in
|
||||||
# pg_ident.conf.d/<cert_cn host>.conf on every pg host listed above.
|
# pg_ident.conf.d/moments.conf on every pg host listed above.
|
||||||
mapping_pairs=(
|
mapping_pairs=(
|
||||||
"$api_host" moments_ro
|
"$api_host" moments_ro
|
||||||
"$worker_host" moments_rw
|
"$worker_host" moments_rw
|
||||||
)
|
)
|
||||||
|
|
||||||
ident_dir=/var/lib/pgsql/18/data/pg_ident.conf.d
|
ident_dir=/var/lib/pgsql/18/data/pg_ident.conf.d
|
||||||
|
app_file=moments.conf
|
||||||
|
|
||||||
|
failed=0
|
||||||
for pg_host in "${pg_hosts[@]}"; do
|
for pg_host in "${pg_hosts[@]}"; do
|
||||||
printf '==> %s\n' "$pg_host"
|
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
|
set -euo pipefail
|
||||||
ident_dir="$1"; shift
|
ident_dir="$1"; shift
|
||||||
|
file="${ident_dir}/$1"; shift
|
||||||
|
|
||||||
changed=0
|
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
|
while [[ $# -gt 0 ]]; do
|
||||||
cert_cn_host="$1"
|
cert_cn_host="$1"
|
||||||
role="$2"
|
role="$2"
|
||||||
shift 2
|
shift 2
|
||||||
|
|
||||||
line="cert_cn ${cert_cn_host} ${role}"
|
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
|
# 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
|
# without dropping privs. tee --append runs as postgres so a newly-created
|
||||||
@@ -60,4 +97,9 @@ else
|
|||||||
echo " no changes; reload skipped"
|
echo " no changes; reload skipped"
|
||||||
fi
|
fi
|
||||||
REMOTE_EOF
|
REMOTE_EOF
|
||||||
|
then
|
||||||
|
echo " WARNING: ${pg_host} unreachable or failed — re-run when it is back"
|
||||||
|
failed=1
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
|
exit "$failed"
|
||||||
|
|||||||
Reference in New Issue
Block a user