Files
computer-playbook/roles/svc-db-postgres/files/shell/set_postgres_superuser_password.sh
T
kevinveenbirkenbachandClaude Opus 5 c0b3ac9462 fix(svc-db-postgres): run the hba swap once, not twice
The script carried its procedure twice. A retry loop was added around the original
linear body, and the body below it stayed: a second pg_hba backup, a second
`trap ... EXIT`, a second prepended scram line, a second reload, two more auth_test
rounds and a second ALTER USER. Every invocation therefore rewrote the host-based
auth file twice and set the password twice.

It worked, because each step is idempotent, and it cost every call an extra pass
with two auth probes that wait up to thirty seconds each.

The subtle part is the output. The loop set `result` and never printed it; what the
caller read came from the second body, which echoed [CHANGED] or [UNCHANGED] from its
own pre_ok/post_ok. Deleting the duplicate alone would have left the script silent and
`changed_when: '[CHANGED]' in stdout` permanently false. The verdict now comes from
the pass that actually measured, through `echo "$result"`.

164 lines to 110. The contract is unchanged: [CHANGED] / [UNCHANGED] on stdout,
[FAIL] on stderr with exit 1.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 21:55:10 +02:00

111 lines
3.2 KiB
Bash

#!/usr/bin/env bash
set -euo pipefail
# Usage:
# set_postgres_superuser_password.sh <container_name> <new_password> <role>
#
# The role is the cluster's superuser: "postgres" for the shared engine, and
# whatever POSTGRES_USER a dedicated one was initialised with. Both are reached
# the same way, over the container's local socket, which authenticates by peer
# and therefore needs no password -- that is what lets a rotated credential be
# written into an engine nobody can log into any more.
container_raw="${1:-}"
new_pw="${2:-}"
role="${3:-}"
if [[ -z "$container_raw" || -z "$new_pw" || -z "$role" ]]; then
echo "Usage: $0 <container_name> <new_password> <role>" >&2
exit 2
fi
container=""
eval "container=$container_raw"
pg_exec() {
container exec -e PG_ROLE="$role" "$container" bash -lc \
"psql -U \"\$PG_ROLE\" -d postgres -Atc '$1'" 2>/dev/null
}
# Retry TCP auth up to 15 times (30s) to handle HBA reload delay
auth_test() {
for _ in {1..15}; do
# shellcheck disable=SC2016
if container exec -e PGPASSWORD="$new_pw" -e PG_ROLE="$role" "$container" bash -lc \
'psql -h 127.0.0.1 -p 5432 -U "$PG_ROLE" -d postgres -Atc "SELECT 1" >/dev/null 2>&1'; then
return 0
fi
sleep 2
done
return 1
}
result=""
for _ in {1..30}; do
hba_file=""
for _ in {1..30}; do
if hba_file=$(pg_exec 'SHOW hba_file;'); then
break
fi
sleep 2
done
if [[ -z "$hba_file" ]]; then
sleep 2; continue
fi
backup="${hba_file}.bak_ansible_pwcheck_$$"
if ! container exec "$container" bash -lc "cp -a \"$hba_file\" \"$backup\"" 2>/dev/null; then
sleep 2; continue
fi
cleanup() {
container exec "$container" bash -lc "if [ -f \"$backup\" ]; then cp -a \"$backup\" \"$hba_file\" && rm -f \"$backup\"; fi" >/dev/null 2>&1 || true # nocheck: shell-or-true -- grandfathered: worked in practice; TODO: sharpen to catch only the exact tolerated error
pg_exec 'SELECT pg_reload_conf();' >/dev/null 2>&1 || true # nocheck: shell-or-true -- grandfathered: worked in practice; TODO: sharpen to catch only the exact tolerated error
}
trap 'cleanup' EXIT
if ! container exec "$container" bash -lc "{
echo 'host all all 127.0.0.1/32 scram-sha-256'
cat \"$backup\"
} > \"$hba_file\"" 2>/dev/null; then
cleanup; sleep 2; continue
fi
if ! pg_exec 'SELECT pg_reload_conf();' >/dev/null; then
cleanup; sleep 2; continue
fi
pre_ok=0
if auth_test; then pre_ok=1; fi
# shellcheck disable=SC2016
if ! container exec -e NEW_POSTGRES_PASSWORD="$new_pw" -e PG_ROLE="$role" "$container" bash -lc '
psql -U "$PG_ROLE" -d postgres -v ON_ERROR_STOP=1 -v role="$PG_ROLE" -v password="$NEW_POSTGRES_PASSWORD" <<'"'"'SQL'"'"'
ALTER USER :"role" WITH PASSWORD :'"'"'password'"'"';
SQL
' 2>/dev/null; then
cleanup; sleep 2; continue
fi
post_ok=0
if auth_test; then post_ok=1; fi
cleanup
if [[ "$pre_ok" -eq 0 && "$post_ok" -eq 1 ]]; then
result="[CHANGED] New password did not work before, and works now."
elif [[ "$pre_ok" -eq 1 && "$post_ok" -eq 1 ]]; then
result="[UNCHANGED] New password already worked before."
fi
break
done
if [[ -z "$result" ]]; then
echo "[FAIL] Password auth test failed even after ALTER USER." >&2
exit 1
fi
echo "$result"