Files
vpsmgr/uninstall.sh
T
bluebluesodaandCommandCodeBot 7bc37567da fix(net): detect ext iface via IPv6 route / link fallback
The installer and IPv6 probe found the external interface only through
`ip route show default` (the IPv4 main table). On IPv6-only hosts and
policy-routed boxes (v4 default in a non-main table, e.g. WARP/wireguard
tunnels) that returns nothing, so check-ipv6-support.sh died at
"no default route / ext interface found" before probing anything and
the installer silently fell back to a wrong iface.

Fall back to the IPv6 default route, then the first non-virtual UP
interface (excluding lo/incusbr/virbr/docker/veth/warp/wg), in all five
places that detect the WAN iface: check-ipv6-support.sh, 00-check.sh,
00-ip-ask.sh, uninstall.sh and DetectExtIF.

Verified on a real IPv6-only host: probe now reports ext iface eth0 and
reaches Phase 2 instead of dying.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-08-20 08:12:43 +00:00

148 lines
6.7 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..."
for svc in vps vps-nft vps-ipv6 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 proxy (ndppd) and drop its generated config
systemctl disable --now ndppd.service >/dev/null 2>&1 || service ndppd 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..."
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 /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) and /etc/traefik are deliberately KEPT here:
# without --purge, a reinstall should adopt the existing users/domains/settings.
log " kept /etc/vpsmgr 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 and traefik config..."
rm -rf /etc/vpsmgr /etc/traefik
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
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