Files
vpsmgr/uninstall.sh
T
bluebluesodaandCommandCodeBot 4c93ae4be3 feat(haproxy): replace Traefik with a pinned HAProxy domain proxy
The panel now fronts user domains with HAProxy 3.4 LTS (official performance
packages) instead of Traefik: about 100 MB of RSS and 200 MB of disk saved for
a feature set that never used more than Host routing on :80 and SNI passthrough
on :443.

- hpx: publish the proxy configuration as immutable GENERATIONS. Every domain
  mutation renders the whole set from the DB, validates it with the pinned
  `haproxy -c`, and only then repoints the `current` symlink, so a rejected
  candidate leaves the running proxy untouched and a stale domain can never
  survive into a later generation. Routing is carried by two map files
  (Host -> backend, SNI -> backend): per-domain frontend rules cannot work from
  a second file because HAProxy rejects duplicate section names since 3.3 and
  attaches bare directives to the section opened last.
- mgr: republish and reload (SIGUSR2, i.e. `systemctl reload`) after every
  domain change, so a change takes effect without dropping established HTTP or
  TLS connections. Rollback semantics (DB first, then publish) are unchanged.
- cfg,db: `net.traefik` is renamed to `net.haproxy` with identical values. A
  pre-rename config.yaml key and the legacy VPSMGR_TRAEFIK env var are still
  read (new name wins), and schema migration v18 moves the settings mirror from
  `traefik` to `haproxy`, keeping a newer value when both rows exist.
- 30-haproxy.sh: install the pinned branch from the vendor repository and
  migrate a Traefik host in one run — install and validate HAProxy first, then
  remove Traefik's binary, unit, service account and dynamic rules (only its
  config file is kept, inert). No fallback keeps both proxies alive.
- upgrade-haproxy.sh: follow the pinned branch to its newest patch, with a
  configuration check and a graceful reload; `install.sh --update` runs it.
- tests: generation publish/rollback, map rendering, config-key and settings
  migrations, plus real-binary tests (skipped unless
  VPSMGR_TEST_HAPROXY_BIN is set) that run the generated configuration through
  `haproxy -c` and start it in a network namespace.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-09-16 12:59:13 +00:00

181 lines
8.9 KiB
Bash
Executable File

#!/usr/bin/env bash
# uninstall.sh — remove vpsmgr. Without --purge the manager's config/db/certs
# and the traefik config are KEPT so a plain reinstall adopts the previous
# users/domains/settings. --purge also removes those, plus containers/storage.
set -uo pipefail
PURGE=0
if [[ "${1:-}" == "--purge" ]]; then PURGE=1; fi
log(){ echo "[un] $*"; }
log "stopping services..."
# 'traefik' is listed for historical installs: a host that never ran the
# HAProxy-era installer still has that unit, and uninstall must converge from
# any version.
for svc in vps vps-nft vps-ipv6 haproxy traefik; do
systemctl disable --now "$svc.service" >/dev/null 2>&1 || true
done
systemctl daemon-reload >/dev/null 2>&1 || true
# --- IPv6 pass-through cleanup (before removing config, which holds the prefix) ---
V6SUBNET=""
if [[ -f /etc/vpsmgr/config.yaml ]]; then
V6SUBNET=$(grep -E '^\s+ipv6_subnet:' /etc/vpsmgr/config.yaml 2>/dev/null | awk -F': ' '{print $2}' | tr -d '"')
fi
if [[ -n "$V6SUBNET" ]]; then
log "cleaning IPv6 pass-through ($V6SUBNET)..."
# stop and disable the NDP proxies (ndppd / npd6) and drop generated config
systemctl disable --now ndppd.service >/dev/null 2>&1 || service ndppd stop >/dev/null 2>&1 || true
systemctl disable --now npd6.service >/dev/null 2>&1 || service npd6 stop >/dev/null 2>&1 || true
rm -f /etc/vpsmgr/ndppd.conf
rm -f /etc/ndppd.conf # root-owned symlink -> /etc/vpsmgr/ndppd.conf (created at install)
# remove proxy_ndp entries on the ext iface for the prefix
# ext iface: prefer the IPv4 default route, then the IPv6 default route
# (covers IPv6-only hosts / policy-routed boxes), then the first non-virtual
# UP link — the same fallback chain the installer uses.
EXT_IF=$(ip route show default 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="dev"){print $(i+1); exit}}')
if [[ -z "$EXT_IF" ]]; then
EXT_IF=$(ip -6 route show default 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="dev"){print $(i+1); exit}}')
fi
if [[ -z "$EXT_IF" ]]; then
EXT_IF=$(ip -o link show up 2>/dev/null | grep -v -E 'lo|incusbr|virbr|docker|veth|warp|wg' | awk -F': ' '{print $2; exit}')
fi
if [[ -n "$EXT_IF" ]]; then
ip -6 neigh show proxy dev "$EXT_IF" 2>/dev/null | awk '{print $1}' | while read -r a; do
case "$a" in
${V6SUBNET%%/*}*) ip -6 neigh del proxy "$a" dev "$EXT_IF" 2>/dev/null || true ;;
esac
done
sysctl -w net.ipv6.conf."$EXT_IF".proxy_ndp=0 >/dev/null 2>&1 || true
fi
# remove per-container /128 routes via incusbr0 for the prefix
ip -6 route show dev incusbr0 2>/dev/null | awk '{print $1}' | while read -r a; do
case "$a" in
${V6SUBNET%%/*}*) ip -6 route del "$a" dev incusbr0 2>/dev/null || true ;;
esac
done
# restore incusbr0 IPv6 to disabled (matches vpsmgr default)
if command -v incus >/dev/null 2>&1 && incus network show incusbr0 >/dev/null 2>&1; then
incus network set incusbr0 ipv6.address none 2>/dev/null || true
incus network set incusbr0 ipv6.nat false 2>/dev/null || true
incus network set incusbr0 ipv6.routing false 2>/dev/null || true
incus network set incusbr0 ipv6.dhcp.stateful false 2>/dev/null || true
fi
# live sysctls back to defaults
sysctl -w net.ipv6.conf.all.forwarding=0 net.ipv6.conf.default.forwarding=0 >/dev/null 2>&1 || true
fi
log "removing files..."
# Both proxy binaries are removed: /usr/local/bin/traefik for a host that never
# ran the HAProxy-era installer, and the HAProxy package itself is purged in
# the --purge branch below (see the apt handling there).
rm -f /usr/local/bin/vps /usr/local/bin/traefik
rm -f /etc/systemd/system/vps.service /etc/systemd/system/vps-nft.service /etc/systemd/system/vps-ipv6.service
rm -f /etc/systemd/system/haproxy.service /etc/systemd/system/traefik.service
# Restore the host-wide io_uring clamp to the kernel default before dropping
# 99-vpsmgr.conf (which sets it to 1 at install time).
sysctl -w kernel.io_uring_disabled=0 >/dev/null 2>&1 || true
rm -f /etc/sysctl.d/99-vpsmgr.conf
nft delete table inet vpsmgr 2>/dev/null || true
# /etc/vpsmgr (config/db/certs), /etc/haproxy (the published proxy generations
# and the static entry config) and /etc/traefik (a pre-HAProxy install's config)
# are deliberately KEPT here: without --purge a reinstall should adopt the
# existing users/domains/settings without republishing everything by hand.
# The HAProxy PACKAGE is kept too on a plain uninstall, so a reinstall needs no
# network round trip; --purge removes it below.
log " kept /etc/vpsmgr, /etc/haproxy and /etc/traefik (reinstall will adopt them)"
# Remove the panel's privilege surface even on a plain uninstall (review
# P2-10): a leftover /etc/sudoers.d/vps grants the vps user root-level nft /
# ip / systemctl rights forever, and the two service accounts linger. A
# reinstall recreates user + whitelist, so adoption is unaffected — only the
# residual root-equivalent access is gone.
log "removing vpsmgr users and sudoers whitelist..."
rm -f /etc/sudoers.d/vps
if id -u vps >/dev/null 2>&1; then
userdel vps >/dev/null 2>&1 || true
fi
# userdel leaves the same-named primary group behind; a stale group makes a
# reinstall's `useradd vps` fail (exit 9: it cannot create the group again),
# so remove it together with the user.
if getent group vps >/dev/null 2>&1; then
groupdel vps >/dev/null 2>&1 || true
fi
if id -u traefik >/dev/null 2>&1; then
userdel traefik >/dev/null 2>&1 || true
fi
if getent group traefik >/dev/null 2>&1; then
groupdel traefik >/dev/null 2>&1 || true
fi
if [[ $PURGE -eq 1 ]]; then
log "purging vpsmgr config/db/certs, the HAProxy layout and any traefik config..."
rm -rf /etc/vpsmgr /etc/haproxy /etc/traefik
# The HAProxy package is removed here (not on a plain uninstall): purging the
# apt source and key as well leaves no vendor repository behind.
if dpkg -s haproxy-awslc >/dev/null 2>&1; then
DEBIAN_FRONTEND=noninteractive apt-get purge -y -qq haproxy-awslc >/dev/null 2>&1 || true
fi
rm -f /etc/apt/sources.list.d/haproxy.list /usr/share/keyrings/HAPROXY-key-community.asc
apt-get update -qq >/dev/null 2>&1 || true
if command -v incus >/dev/null 2>&1; then
log "purging Incus instances..."
for c in $(incus list --format=csv -c n 2>/dev/null); do
log " deleting container $c"
incus delete --force "$c" >/dev/null 2>&1 || true
done
log "purging vpsmgr images..."
while IFS=, read -r alias fingerprint; do
[[ "$alias" == vpsmgr/* || "$alias" == vpsmgr-* ]] || continue
incus image alias delete "$alias" >/dev/null 2>&1 || true
incus image delete "$fingerprint" >/dev/null 2>&1 || true
done < <(incus image alias list --format=csv -c af 2>/dev/null)
log "removing vpsmgr profile devices and network..."
incus profile device remove default root eth0 >/dev/null 2>&1 || true
incus network delete incusbr0 >/dev/null 2>&1 || true
log "removing storage pool..."
incus storage delete vpsmgr >/dev/null 2>&1 || true
fi
if command -v zpool >/dev/null 2>&1 && zpool list vpsmgr >/dev/null 2>&1; then
zpool destroy -f vpsmgr >/dev/null 2>&1 || true
fi
rm -rf /var/lib/vpsmgr
log "stopping and removing Incus (Zabbly package) and its state..."
for svc in incus-user.socket incus.socket incus-startup.service incus-lxcfs.service incus.service; do
systemctl disable --now "$svc" >/dev/null 2>&1 || true
done
mapfile -t INCUS_PACKAGES < <(dpkg-query -W -f='${binary:Package}\n' 'incus*' 2>/dev/null)
if (( ${#INCUS_PACKAGES[@]} > 0 )); then
DEBIAN_FRONTEND=noninteractive apt-get purge -y -qq "${INCUS_PACKAGES[@]}" >/dev/null 2>&1 || true
fi
# Installs predating the container-only switch pulled in VM-only
# dependencies (QEMU/SPICE/Ceph/ISO tooling) via the full `incus` package.
# Purge them too for a complete removal — but only the ones nothing else on
# the host still needs. Each package is checked against its installed
# reverse dependencies first, so a blanket `apt autoremove` (which can take
# unrelated desktop/media libraries with it) is never run.
VM_DEPS=(genisoimage libnuma1 libpixman-1-0 libpng16-16t64 librados2 librbd1 libseccomp2 libspice-server1 libusb-1.0-0 libusbredirparser1t64)
for pkg in "${VM_DEPS[@]}"; do
dpkg -s "$pkg" >/dev/null 2>&1 || continue
# leaf check: no installed package may still depend on it
if ! apt-cache rdepends --installed "$pkg" 2>/dev/null | awk '/^Reverse Depends:/{f=1; next} f && NF' | grep -q .; then
DEBIAN_FRONTEND=noninteractive apt-get purge -y -qq "$pkg" >/dev/null 2>&1 || true
fi
done
rm -rf /etc/incus /var/lib/incus /var/cache/incus /var/log/incus /var/lib/incus-lxcfs
nft delete table inet incus 2>/dev/null || true
nft delete table bridge incus 2>/dev/null || true
rm -f /etc/apt/sources.list.d/zabbly-incus-lts-7.0.sources /etc/apt/keyrings/zabbly.asc
apt-get update -qq >/dev/null 2>&1 || true
fi
if [[ $PURGE -eq 1 ]]; then
log "done. use ./install.sh to reinstall (config/db removed)."
else
log "done. use ./install.sh to reinstall (config/db kept)."
fi