#!/bin/bash # postinst for edgeguard-api — creates system user, filesystem layout, # initialises PostgreSQL (role + db + migrations), enables systemd # units. Each step idempotent; safe to re-run on every upgrade. set -e export LC_ALL=C export LANG=C EG_USER="edgeguard" EG_HOME="/var/lib/edgeguard" case "$1" in configure) # ── System user ────────────────────────────────────────────── if ! getent passwd "$EG_USER" >/dev/null; then adduser --system --group --home "$EG_HOME" \ --shell /usr/sbin/nologin --no-create-home \ --gecos "EdgeGuard daemon" "$EG_USER" fi # ── Directories ────────────────────────────────────────────── for d in /etc/edgeguard /var/lib/edgeguard /var/log/edgeguard \ /etc/edgeguard/haproxy /etc/edgeguard/squid \ /etc/edgeguard/wireguard /etc/edgeguard/unbound \ /etc/edgeguard/nftables.d /etc/edgeguard/tls \ /var/lib/edgeguard/acme; do install -d -m 0750 -o "$EG_USER" -g "$EG_USER" "$d" done # ACME-Account-Dir 0700 — hält den lego-Account-Schlüssel, # gehört nur edgeguard. install -d -m 0700 -o "$EG_USER" -g "$EG_USER" /var/lib/edgeguard/acme-account # ── sudoers: HAProxy reload + (later) systemd-networkd reload # Damit edgeguard-api nach einer SSL- oder Netzwerk-Mutation # selbst reloaden kann ohne root zu sein. NOPASSWD ist auf # genau dieses Kommando beschränkt. cat > /etc/sudoers.d/edgeguard <<'SUDOERS' edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl reload haproxy.service edgeguard ALL=(root) NOPASSWD: /bin/systemctl reload haproxy.service edgeguard ALL=(root) NOPASSWD: /usr/sbin/nft -f /etc/edgeguard/nftables.d/ruleset.nft edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl start wg-quick@*.service edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl restart wg-quick@*.service edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl stop wg-quick@*.service edgeguard ALL=(root) NOPASSWD: /bin/systemctl start wg-quick@*.service edgeguard ALL=(root) NOPASSWD: /bin/systemctl restart wg-quick@*.service edgeguard ALL=(root) NOPASSWD: /bin/systemctl stop wg-quick@*.service edgeguard ALL=(root) NOPASSWD: /usr/bin/wg show all dump edgeguard ALL=(root) NOPASSWD: /usr/bin/wg show * SUDOERS chmod 0440 /etc/sudoers.d/edgeguard # ── Self-signed default cert so HAProxy starts cleanly ─────── # HAProxy `bind :443 ssl crt /etc/edgeguard/tls/` needs at least # one PEM in the directory to come up. Operator runs certbot # later; until then, browsers see an unverified cert which is # the expected first-boot UX. DEFAULT_PEM="/etc/edgeguard/tls/_default.pem" if [ ! -f "$DEFAULT_PEM" ]; then HOSTNAME_FQDN="$(hostname -f 2>/dev/null || hostname)" TMP_KEY="$(mktemp)" TMP_CRT="$(mktemp)" openssl req -x509 -nodes -newkey rsa:2048 \ -keyout "$TMP_KEY" -out "$TMP_CRT" \ -days 3650 \ -subj "/CN=$HOSTNAME_FQDN" \ -addext "subjectAltName = DNS:$HOSTNAME_FQDN,DNS:localhost" \ >/dev/null 2>&1 cat "$TMP_CRT" "$TMP_KEY" > "$DEFAULT_PEM" chown "$EG_USER:$EG_USER" "$DEFAULT_PEM" chmod 0640 "$DEFAULT_PEM" rm -f "$TMP_KEY" "$TMP_CRT" fi # ── Pre-flight: validate embedded migration set ────────────── # Catches duplicate version prefixes BEFORE we touch the DB, # so a broken upgrade can't half-apply migrations and leave # the cluster wedged (mail-gateway 2026-05-08 incident). if ! /usr/bin/edgeguard-ctl migrate check; then echo "postinst: embedded migrations failed validation — aborting" >&2 exit 1 fi # ── PostgreSQL: ensure role + database exist ───────────────── # Requires postgresql-16 (or -17) running locally — guaranteed # by Depends. Idempotent — re-runs on upgrade are no-ops. if ! /usr/bin/edgeguard-ctl initdb; then echo "postinst: edgeguard-ctl initdb failed — aborting" >&2 exit 1 fi # ── Apply pending schema migrations ────────────────────────── if ! sudo -n -u "$EG_USER" /usr/bin/edgeguard-ctl migrate up; then echo "postinst: edgeguard-ctl migrate up failed — aborting" >&2 exit 1 fi # ── Render initial service configs ─────────────────────────── # Writes /etc/edgeguard/haproxy/haproxy.cfg + nftables.d/ # ruleset.nft from the (just-migrated, empty) PG state. # # haproxy bekommt --no-reload (drop-in unten zeigt erst danach # auf unsere cfg; wir restarten explizit); nftables muss aber # aktiv reloadet werden, sonst läuft das Kernel-Set bei Template- # Änderungen (z.B. neue anti-lockout-Ports) hinterher. if ! sudo -n -u "$EG_USER" /usr/bin/edgeguard-ctl render-config --only=haproxy --no-reload; then echo "postinst: edgeguard-ctl render-config (haproxy) failed — aborting" >&2 exit 1 fi if ! sudo -n -u "$EG_USER" /usr/bin/edgeguard-ctl render-config --only=nftables; then echo "postinst: edgeguard-ctl render-config (nftables) failed — aborting" >&2 exit 1 fi # ── HAProxy systemd drop-in: read EdgeGuard config ─────────── # Keeps the distro /etc/haproxy/haproxy.cfg untouched (it's a # conffile of the haproxy package). Drop-in is reversible by # removing the file + daemon-reload. install -d /etc/systemd/system/haproxy.service.d if [ -f /etc/edgeguard/systemd/haproxy-edgeguard.conf ]; then install -m 0644 /etc/edgeguard/systemd/haproxy-edgeguard.conf \ /etc/systemd/system/haproxy.service.d/edgeguard.conf fi # ── systemd: pick up new units + restart haproxy with our cfg systemctl daemon-reload systemctl restart haproxy.service || true systemctl enable --now edgeguard-api.service edgeguard-scheduler.service || true ;; abort-upgrade|abort-remove|abort-deconfigure) ;; *) echo "postinst called with unknown argument \`$1'" >&2 exit 1 ;; esac #DEBHELPER# exit 0