handler: edgeguard-User darf systemd-run nicht direkt aufrufen ("Inter-
active authentication required"). sudo -n + sudoers-Whitelist auf
exakt die Unit-Form für edgeguard-upgrade.service.
UI: UpdateBanner-Komponente neu — Pattern wie mail-gateway/enconf:
Banner mit Force-Check-Button + Popconfirm. Beim Apply zeigt full-
screen-Overlay mit animiertem Orbit (zwei Ringe + Dots), Versions-
sprung, vier Step-Indicators (Download/Install/Restart/Verify) und
Live-Timer. Poll auf /system/health detektiert Version-Flip ODER
"sah down dann up" und window.reload nach 1.5s. Sicherheits-Timeout
2 min schickt sonst auch reload.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
292 lines
15 KiB
Bash
Executable File
292 lines
15 KiB
Bash
Executable File
#!/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
|
|
# haproxy admin.sock liegt mit mode 0660 root:haproxy. Damit
|
|
# edgeguard-api das Backend-Stat-CSV lesen kann (Dashboard),
|
|
# in die haproxy-group adden — best-effort, nicht failen wenn
|
|
# das Paket noch nicht installiert ist.
|
|
if getent group haproxy >/dev/null; then
|
|
usermod -a -G haproxy "$EG_USER" || true
|
|
fi
|
|
|
|
# ── Directories ──────────────────────────────────────────────
|
|
# /etc/edgeguard und Service-Subdirs müssen für die Service-User
|
|
# (squid, unbound, haproxy laufen NICHT als edgeguard) traversier-
|
|
# bzw lesbar sein. 0755 statt 0750 — kein Geheimnis ist hier
|
|
# gespeichert, alles sind Renderer-Outputs.
|
|
for d in /etc/edgeguard /etc/edgeguard/haproxy /etc/edgeguard/squid \
|
|
/etc/edgeguard/wireguard /etc/edgeguard/unbound \
|
|
/etc/edgeguard/nftables.d; do
|
|
install -d -m 0755 -o "$EG_USER" -g "$EG_USER" "$d"
|
|
done
|
|
# Sensitive Verzeichnisse bleiben 0750 (TLS-Keys, ACME-State).
|
|
for d in /etc/edgeguard/tls /var/lib/edgeguard /var/log/edgeguard \
|
|
/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/sbin/nft list tables
|
|
edgeguard ALL=(root) NOPASSWD: /usr/sbin/nft list table inet edgeguard
|
|
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 *
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl reload squid.service
|
|
edgeguard ALL=(root) NOPASSWD: /bin/systemctl reload squid.service
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl reload unbound.service
|
|
edgeguard ALL=(root) NOPASSWD: /bin/systemctl reload unbound.service
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl restart unbound.service
|
|
edgeguard ALL=(root) NOPASSWD: /bin/systemctl restart unbound.service
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl reload chrony.service
|
|
edgeguard ALL=(root) NOPASSWD: /bin/systemctl reload chrony.service
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl restart chrony.service
|
|
edgeguard ALL=(root) NOPASSWD: /bin/systemctl restart chrony.service
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/apt-get update -qq
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/apt-get update
|
|
# Self-Upgrade-Pfad (handlers/system.go → /system/upgrade). Whitelist
|
|
# nur die exakte Unit-Form, damit edgeguard NICHT beliebige systemd-
|
|
# Units anlegen darf.
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl reset-failed edgeguard-upgrade.service
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemd-run --unit=edgeguard-upgrade.service --description=EdgeGuard self-upgrade --collect bash /tmp/edgeguard-upgrade.sh
|
|
SUDOERS
|
|
|
|
# ── Distro-Conf-Includes für die per-Service Renderer ─────────
|
|
# Squid + Unbound lesen ihre Distro-Default-Conf, die wir per
|
|
# Symlink/Drop-in auf unsere managed conf zeigen lassen müssen.
|
|
# Renderer können das nicht selbst (kein Schreibrecht in /etc/squid
|
|
# bzw. /etc/unbound/unbound.conf.d/), daher hier einmalig.
|
|
# Plus: Squid + Unbound laufen als eigene User (squid/unbound),
|
|
# nicht edgeguard. Damit sie unsere Conf lesen können, müssen
|
|
# die Conf-Dirs world-readable sein (Configs ohne Secrets).
|
|
install -d -m 0755 -o "$EG_USER" -g "$EG_USER" /etc/edgeguard/squid /etc/edgeguard/unbound
|
|
# Squid: ersetzt die Distro-Datei durch Symlink (Backup .distro-bak)
|
|
if [ -f /etc/squid/squid.conf ] && [ ! -L /etc/squid/squid.conf ]; then
|
|
mv /etc/squid/squid.conf /etc/squid/squid.conf.distro-bak
|
|
fi
|
|
ln -sfn /etc/edgeguard/squid/squid.conf /etc/squid/squid.conf
|
|
# Unbound: Drop-in im conf.d-Verzeichnis. Wir schreiben direkt
|
|
# rein (statt /etc/edgeguard/unbound/...) weil das AppArmor-
|
|
# Profil unbound nur /etc/unbound erlaubt. Datei gehört dem
|
|
# edgeguard-User damit der Renderer sie überschreiben kann.
|
|
install -d /etc/unbound/unbound.conf.d
|
|
# Vorgänger-Symlink (aus früheren Versionen) wegräumen.
|
|
if [ -L /etc/unbound/unbound.conf.d/edgeguard.conf ]; then
|
|
rm /etc/unbound/unbound.conf.d/edgeguard.conf
|
|
fi
|
|
if [ ! -f /etc/unbound/unbound.conf.d/edgeguard.conf ]; then
|
|
: > /etc/unbound/unbound.conf.d/edgeguard.conf
|
|
fi
|
|
chown "$EG_USER":"$EG_USER" /etc/unbound/unbound.conf.d/edgeguard.conf
|
|
chmod 0644 /etc/unbound/unbound.conf.d/edgeguard.conf
|
|
|
|
# Chrony: gleicher Pattern wie Unbound — Drop-in im conf.d, der
|
|
# vom distro-default chrony.conf included wird. Datei gehört
|
|
# edgeguard damit der Renderer sie überschreiben kann.
|
|
install -d /etc/chrony/conf.d
|
|
if [ -L /etc/chrony/conf.d/edgeguard.conf ]; then
|
|
rm /etc/chrony/conf.d/edgeguard.conf
|
|
fi
|
|
if [ ! -f /etc/chrony/conf.d/edgeguard.conf ]; then
|
|
: > /etc/chrony/conf.d/edgeguard.conf
|
|
fi
|
|
chown "$EG_USER":"$EG_USER" /etc/chrony/conf.d/edgeguard.conf
|
|
chmod 0644 /etc/chrony/conf.d/edgeguard.conf
|
|
chmod 0440 /etc/sudoers.d/edgeguard
|
|
|
|
# ── Sysctl-Profil für Edge-Gateway (NAT + HAProxy + Forwarding) ──
|
|
# Voraussetzung für NAT/DNAT/Masquerade + sinnvolle Defaults
|
|
# für eine high-throughput Forwarding-Box. Edit nicht von Hand
|
|
# — Re-install vom Package überschreibt die Datei. Eigene
|
|
# Tweaks gehören in eine Datei mit höherer Nummer als 99.
|
|
rm -f /etc/sysctl.d/99-edgeguard-forward.conf # Vorgänger
|
|
cat > /etc/sysctl.d/99-edgeguard.conf <<'SYSCTL'
|
|
# ── Managed by edgeguard ────────────────────────────────────────────
|
|
# Lade-Reihenfolge: 99-* überschreibt distro-Defaults. Eigene
|
|
# Operator-Tweaks: /etc/sysctl.d/99-zzz-local.conf (lexikografisch
|
|
# später) — nicht in DIESE Datei!
|
|
|
|
# ─── Forwarding (NAT/DNAT/Masquerade) ───────────────────────────────
|
|
net.ipv4.ip_forward = 1
|
|
net.ipv6.conf.all.forwarding = 1
|
|
net.ipv4.conf.all.send_redirects = 0
|
|
net.ipv4.conf.default.send_redirects = 0
|
|
net.ipv4.conf.all.accept_redirects = 0
|
|
net.ipv6.conf.all.accept_redirects = 0
|
|
net.ipv4.conf.all.accept_source_route = 0
|
|
net.ipv6.conf.all.accept_source_route = 0
|
|
|
|
# ─── Reverse-Path-Filter (anti-spoof, loose-Modus für asymmetrisches
|
|
# Routing wie Multi-WAN / WireGuard split) ─────────────────────
|
|
net.ipv4.conf.all.rp_filter = 2
|
|
net.ipv4.conf.default.rp_filter = 2
|
|
|
|
# ─── Conntrack — Edge-Box trackt viele parallele Sessions ─────────
|
|
net.netfilter.nf_conntrack_max = 524288
|
|
net.netfilter.nf_conntrack_tcp_timeout_established = 86400
|
|
net.netfilter.nf_conntrack_tcp_timeout_time_wait = 30
|
|
net.netfilter.nf_conntrack_tcp_timeout_close_wait = 30
|
|
net.netfilter.nf_conntrack_buckets = 131072
|
|
|
|
# ─── TCP/IP-Stack-Tuning für HAProxy + viele Backends ─────────────
|
|
net.core.somaxconn = 65535
|
|
net.core.netdev_max_backlog = 16384
|
|
net.core.rmem_max = 16777216
|
|
net.core.wmem_max = 16777216
|
|
net.core.rmem_default = 262144
|
|
net.core.wmem_default = 262144
|
|
net.ipv4.tcp_rmem = 4096 87380 16777216
|
|
net.ipv4.tcp_wmem = 4096 65536 16777216
|
|
net.ipv4.tcp_max_syn_backlog = 65535
|
|
net.ipv4.tcp_fin_timeout = 15
|
|
net.ipv4.tcp_tw_reuse = 1
|
|
net.ipv4.tcp_keepalive_time = 300
|
|
net.ipv4.tcp_keepalive_probes = 5
|
|
net.ipv4.tcp_keepalive_intvl = 30
|
|
net.ipv4.tcp_mtu_probing = 1
|
|
net.ipv4.tcp_slow_start_after_idle = 0
|
|
net.ipv4.tcp_no_metrics_save = 1
|
|
net.ipv4.ip_local_port_range = 10240 65535
|
|
|
|
# ─── Modern congestion control + queueing (BBR + fq) ──────────────
|
|
# Wenn der Kernel BBR nicht hat, fällt Linux still auf cubic zurück.
|
|
net.ipv4.tcp_congestion_control = bbr
|
|
net.core.default_qdisc = fq
|
|
|
|
# ─── Anti-DoS / Hardening ─────────────────────────────────────────
|
|
net.ipv4.tcp_syncookies = 1
|
|
net.ipv4.icmp_echo_ignore_broadcasts = 1
|
|
net.ipv4.icmp_ignore_bogus_error_responses = 1
|
|
net.ipv4.conf.all.log_martians = 1
|
|
kernel.kptr_restrict = 2
|
|
kernel.dmesg_restrict = 1
|
|
|
|
# ─── Memory ───────────────────────────────────────────────────────
|
|
vm.swappiness = 10
|
|
vm.dirty_ratio = 20
|
|
vm.dirty_background_ratio = 5
|
|
SYSCTL
|
|
sysctl --system >/dev/null 2>&1 || true
|
|
|
|
# ── 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
|