Files
proxmox-scripts/lib/install.func
T
claude-bot bd4293f53e
CI / Shell-Lint (bash -n, source-check, Validierungs-Tests) (pull_request) Successful in 1s
fix(nexus-db): enforce UTF8 encoding at install time (issue #8)
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)
2026-06-13 14:30:40 +02:00

170 lines
7.8 KiB
Bash

#!/usr/bin/env bash
# lib/install.func — shared in-container helpers for install/*-install.sh
#
# Sourced via:
# source <(curl -fsSL https://gitea.luki-net.org/luki-net/proxmox-scripts/raw/branch/main/lib/install.func)
# ── colors / logging ─────────────────────────────────────────────────────────
if [[ -z "${_COLORS_LOADED:-}" ]]; then
RED="\033[0;31m"; GREEN="\033[0;32m"; YELLOW="\033[1;33m"; BLUE="\033[0;34m"; NC="\033[0m"
_COLORS_LOADED=1
fi
msg_info() { echo -e "${BLUE}[i]${NC} $*"; }
msg_ok() { echo -e "${GREEN}[✓]${NC} $*"; }
msg_warn() { echo -e "${YELLOW}[!]${NC} $*"; }
msg_err() { echo -e "${RED}[✗]${NC} $*" >&2; }
# ── apt ──────────────────────────────────────────────────────────────────────
# Always installs: ca-certificates curl openssl tzdata gnupg locales
# Additional packages can be passed as args.
setup_base_apt() {
export DEBIAN_FRONTEND=noninteractive
# C.UTF-8 ist in glibc eingebaut und damit schon VOR dem locales-Paket
# verfügbar — deckt den ersten apt/dpkg-Lauf ab (keine perl-Warnungen
# "Setting locale failed" mehr, LXC-Templates kommen ohne Locale).
export LANG=C.UTF-8 LC_ALL=C.UTF-8
msg_info "Updating apt index..."
apt-get update -qq
if [[ $# -gt 0 ]]; then
msg_info "Installing base packages + $*..."
else
msg_info "Installing base packages..."
fi
apt-get install -y -qq \
ca-certificates curl openssl tzdata gnupg locales \
"$@" \
>/dev/null
setup_locales
msg_ok "apt setup complete"
}
# en_US.UTF-8 generieren und systemweit als Default setzen; C.UTF-8 braucht
# keine Generierung (glibc-built-in). Idempotent: sed greift nur auf die
# auskommentierte Zeile, locale-gen/update-locale sind re-run-sicher.
setup_locales() {
msg_info "Generating locales (en_US.UTF-8; C.UTF-8 built-in)..."
sed -i 's/^# *en_US\.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen
locale-gen >/dev/null
update-locale LANG=en_US.UTF-8
msg_ok "Default locale: en_US.UTF-8"
}
apt_cleanup() {
apt-get autoremove -y -qq >/dev/null || true
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).
# yes → PermitRootLogin yes (Passwort-Login mit dem generierten Root-Passwort)
# no → PermitRootLogin prohibit-password (Debian-Default, nur SSH-Key)
# Umsetzung als Drop-in, damit Paket-Updates von sshd_config nicht kollidieren.
configure_ssh_root_login() {
local choice="${SSH_ROOT_LOGIN:-yes}" value
case "$choice" in
yes) value="yes" ;;
no) value="prohibit-password" ;;
*) msg_err "SSH_ROOT_LOGIN must be yes|no, got: '$choice'"; return 1 ;;
esac
if [[ ! -d /etc/ssh/sshd_config.d ]]; then
if [[ "$choice" == "no" ]]; then
msg_warn "openssh-server not installed — nothing to configure (root login stays off)"
return 0
fi
msg_info "Installing openssh-server..."
apt-get install -y -qq openssh-server >/dev/null
fi
msg_info "Configuring SSH root login: PermitRootLogin $value"
printf 'PermitRootLogin %s\n' "$value" >/etc/ssh/sshd_config.d/zz-root-login.conf
systemctl reload ssh 2>/dev/null || systemctl restart ssh 2>/dev/null \
|| msg_warn "ssh.service not active yet — config applies on first start"
msg_ok "SSH root login: $choice"
}
# ── users / dirs ─────────────────────────────────────────────────────────────
create_system_user() {
local user="$1" home="$2"
if id -u "$user" >/dev/null 2>&1; then
msg_warn "User $user already exists, skipping"
return 0
fi
msg_info "Creating system user $user (home: $home)"
useradd --system --create-home --home-dir "$home" --shell /usr/sbin/nologin "$user"
}
# ── systemd ──────────────────────────────────────────────────────────────────
# write_systemd_unit NAME CONTENT
# Writes /etc/systemd/system/NAME.service, daemon-reloads, enables + starts.
write_systemd_unit() {
local name="$1" content="$2"
msg_info "Writing systemd unit: $name.service"
printf '%s\n' "$content" >"/etc/systemd/system/$name.service"
systemctl daemon-reload
systemctl enable --now "$name.service"
msg_ok "$name.service enabled and started"
}
# ── http wait ────────────────────────────────────────────────────────────────
# wait_for_http URL [TIMEOUT_SECONDS]
wait_for_http() {
local url="$1" timeout="${2:-30}"
msg_info "Waiting for $url..."
local i
for ((i=0; i<timeout; i++)); do
if curl -fsS "$url" >/dev/null 2>&1; then
msg_ok "$url responding"
return 0
fi
sleep 1
done
msg_warn "$url did not respond within ${timeout}s"
return 1
}