From 6543fd77d8f1d79733703ce633282d0fd5450a67 Mon Sep 17 00:00:00 2001 From: claude-bot Date: Sat, 13 Jun 2026 14:34:35 +0200 Subject: [PATCH] =?UTF-8?q?fix(nexus-db):=20address=20codex=20review=20?= =?UTF-8?q?=E2=80=94=20early=20encoding=20guard,=20robust=20helpers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - assert encoding BEFORE role/password mutation on re-run, so an old SQL_ASCII DB aborts with no side effects (codex finding 1) - ensure_utf8_locale_active honours its locale argument consistently in match, locale.gen line and export (codex finding 2) - assert_db_encoding_utf8 uses argv-clean runuser psql with :'db' literal binding instead of nested su -c shell; docs keep su - postgres -c (codex finding 3) --- install/nexus-db-install.sh | 15 ++++++++++----- lib/install.func | 22 ++++++++++++++++------ 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/install/nexus-db-install.sh b/install/nexus-db-install.sh index 1f57a44..f5dafad 100755 --- a/install/nexus-db-install.sh +++ b/install/nexus-db-install.sh @@ -101,6 +101,13 @@ systemctl restart postgresql # ── role + database + extension (idempotent, password kept on re-run) ───────── run_psql() { runuser -u postgres -- psql -v ON_ERROR_STOP=1 -qAt "$@"; } +# Re-run safety (issue #8): if the database already exists, verify its +# encoding BEFORE touching roles/passwords. An old SQL_ASCII database must +# abort the run with NO side effects — not after rotating credentials. +if [[ "$(run_psql -c "SELECT 1 FROM pg_database WHERE datname='$DB_NAME'")" == "1" ]]; then + assert_db_encoding_utf8 "$DB_NAME" +fi + if [[ "$(run_psql -c "SELECT 1 FROM pg_roles WHERE rolname='$DB_USER'")" != "1" ]]; then msg_info "Creating role $DB_USER + database $DB_NAME..." DB_PASS="$(openssl rand -base64 32 | tr -d '/+=' | head -c 32)" @@ -126,16 +133,14 @@ if [[ "$(run_psql -c "SELECT 1 FROM pg_database WHERE datname='$DB_NAME'")" != " run_psql -c "CREATE DATABASE $DB_NAME OWNER $DB_USER ENCODING 'UTF8' LC_COLLATE 'en_US.UTF-8' LC_CTYPE 'en_US.UTF-8' TEMPLATE template0" # Only the owner may connect — no PUBLIC access. run_psql -c "REVOKE CONNECT ON DATABASE $DB_NAME FROM PUBLIC" + # Verify what we just created (the pre-existing case was already checked + # before the role block, issue #8). + assert_db_encoding_utf8 "$DB_NAME" msg_ok "Database $DB_NAME created (UTF8, owner $DB_USER, PUBLIC revoked)" else msg_warn "Database $DB_NAME already exists, skipping creation" fi -# Encoding guard (issue #8): catch both a freshly mis-created DB and a -# pre-existing SQL_ASCII database from an old provisioning. Abort before the -# app ever connects — a wrong encoding is DB damage, not a warning. -assert_db_encoding_utf8 "$DB_NAME" - # pgvector: CREATE EXTENSION needs superuser; installed now (per ADR-0002: # "Extension ab Tag 1 installiert, ungenutzt bis Phase 2"). run_psql -d "$DB_NAME" -c "CREATE EXTENSION IF NOT EXISTS vector" >/dev/null diff --git a/lib/install.func b/lib/install.func index d1bf1a1..a8fd0fc 100644 --- a/lib/install.func +++ b/lib/install.func @@ -63,14 +63,21 @@ apt_cleanup() { # for THIS process before the server package runs its automatic initdb, then # fail loudly if it is not actually available (generating alone is not enough # — the locale must be active when initdb runs). +# Generates+activates a UTF-8 locale (default en_US.UTF-8); the argument +# honours other UTF-8 locales consistently (match, locale.gen line and the +# exported value all derive from it). Matching normalises case and dashes so +# the canonical `en_US.UTF-8` matches `locale -a`'s `en_US.utf8`. ensure_utf8_locale_active() { local loc="${1:-en_US.UTF-8}" + local norm; norm="$(printf '%s' "$loc" | tr 'A-Z' 'a-z' | tr -d '-')" + _locale_present() { locale -a 2>/dev/null | tr 'A-Z' 'a-z' | tr -d '-' | grep -qx "$norm"; } msg_info "Ensuring $loc is generated and active (DB encoding is frozen at initdb)..." - if ! locale -a 2>/dev/null | tr 'A-Z' 'a-z' | grep -q '^en_us\.utf-\?8$'; then - sed -i 's/^# *en_US\.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen + if ! _locale_present; then + # Uncomment the matching `# UTF-8` line, then generate. + sed -i "s/^# *${loc} UTF-8/${loc} UTF-8/" /etc/locale.gen locale-gen >/dev/null fi - if ! locale -a 2>/dev/null | tr 'A-Z' 'a-z' | grep -q '^en_us\.utf-\?8$'; then + if ! _locale_present; then msg_err "Locale $loc not available after locale-gen — refusing to continue (initdb would create a SQL_ASCII cluster)" return 1 fi @@ -82,11 +89,14 @@ ensure_utf8_locale_active() { # Post-install guard: a database MUST be UTF8. Encoding is irreversible, so a # wrong value is database damage — abort with a clear, actionable message -# instead of shipping a broken cluster. Reads via `su - postgres -c` (minimal -# LXCs have no sudo). +# instead of shipping a broken cluster. Uses argv-clean `runuser ... psql` +# with a quoted :'db' literal binding (robust regardless of caller); the +# credentials/README docs use `su - postgres -c` for hand maintenance (these +# minimal LXCs have no sudo). assert_db_encoding_utf8() { local db="$1" enc - enc="$(su - postgres -c "psql -X -qAt -c \"SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname='$db'\"")" + enc="$(runuser -u postgres -- psql -X -qAt -v db="$db" \ + -c "SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname = :'db'")" if [[ "$enc" != "UTF8" ]]; then msg_err "Database '$db' has encoding '${enc:-}', expected UTF8." msg_err "Encoding is frozen at creation time — this is DB damage, not cosmetic."