fix(nexus-db): enforce UTF8 encoding at install time (issue #8)
CI / Shell-Lint (bash -n, source-check, Validierungs-Tests) (pull_request) Successful in 1s

A PostgreSQL cluster/database freezes its encoding at initdb / CREATE
DATABASE time; a C (non-UTF-8) locale yields a SQL_ASCII cluster, which
makes psycopg3 return bytes and crashes SQLAlchemy. Harden the installer
and add a reusable pattern for future DB installers:

- ensure_utf8_locale_active: generate AND activate en_US.UTF-8 for the
  install process before the server package runs initdb; abort if the
  locale is not actually available
- create the database explicitly with TEMPLATE template0 ENCODING 'UTF8'
  LC_COLLATE/LC_CTYPE 'en_US.UTF-8' instead of inheriting the cluster
  default
- assert_db_encoding_utf8: post-install guard, abort with an actionable
  message if pg_encoding_to_char is not UTF8 (catches old SQL_ASCII DBs
  on re-run too)
- credentials/README docs use su - postgres -c (minimal LXCs have no sudo)
This commit is contained in:
2026-06-13 14:30:40 +02:00
parent 553b923445
commit bd4293f53e
3 changed files with 68 additions and 5 deletions
+44
View File
@@ -54,6 +54,50 @@ apt_cleanup() {
apt-get autoclean -qq >/dev/null || true
}
# ── database installers: UTF-8 locale before initdb ──────────────────────────
# Pattern for every DB installer. A PostgreSQL cluster/database freezes its
# encoding at initdb / CREATE DATABASE time and it cannot be changed later —
# a C (non-UTF-8) locale yields a SQL_ASCII cluster. psycopg3 then returns
# text as bytes and SQLAlchemy crashes on server-version detection; the app
# reports "db: unreachable". So: GENERATE the UTF-8 locale AND make it active
# 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).
ensure_utf8_locale_active() {
local loc="${1:-en_US.UTF-8}"
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
locale-gen >/dev/null
fi
if ! locale -a 2>/dev/null | tr 'A-Z' 'a-z' | grep -q '^en_us\.utf-\?8$'; then
msg_err "Locale $loc not available after locale-gen — refusing to continue (initdb would create a SQL_ASCII cluster)"
return 1
fi
# Activate for the current process so any automatic initdb during the
# server package install inherits a UTF-8 locale, not the bare-template C.
export LANG="$loc" LC_ALL="$loc"
msg_ok "Locale active for initdb: LANG=$LANG"
}
# 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).
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'\"")"
if [[ "$enc" != "UTF8" ]]; then
msg_err "Database '$db' has encoding '${enc:-<not found>}', expected UTF8."
msg_err "Encoding is frozen at creation time — this is DB damage, not cosmetic."
msg_err "Fix: regenerate the locale (locale-gen en_US.UTF-8) and recreate the DB"
msg_err " with: CREATE DATABASE $db ... TEMPLATE template0 ENCODING 'UTF8'"
msg_err " LC_COLLATE 'en_US.UTF-8' LC_CTYPE 'en_US.UTF-8';"
return 1
fi
msg_ok "Encoding check: database '$db' is UTF8"
}
# ── ssh ──────────────────────────────────────────────────────────────────────
# SSH-Root-Login gemäß Host-Prompt (prompt_lxc_config setzt SSH_ROOT_LOGIN,
# bootstrap_install_script reicht es als Env durch; Default: yes).