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>
181 lines
8.9 KiB
Bash
Executable File
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
|