@@ -63,13 +63,28 @@ func Uninstall(full bool) error {
6363
6464 // --- Kubernetes (load-bearing) -----------------------------------------
6565 // kubeadm reset can hang on a wedged container runtime / etcd, so bound it.
66+ // Only reset if kubeadm is actually installed: an absent kubeadm means there
67+ // is nothing to reset (the host is already clean), NOT a failure. Without this
68+ // guard a re-run on a half-uninstalled box wedges forever — `timeout` can't
69+ // exec the missing kubeadm (exit 127), which marked this load-bearing step
70+ // failed and made `runos uninstall` report a partial uninstall on every retry.
6671 roslog .Print ("Removing Kubernetes... " )
67- critical ("kubeadm reset" , "timeout 120 kubeadm reset -f" )
68- // Remove cluster + etcd data (load-bearing: leftover etcd data is the worst
69- // thing to silently keep on a "uninstalled" node).
70- critical ("wipe /etc/kubernetes" , "rm -rf /etc/kubernetes" )
71- critical ("wipe /var/lib/kubelet" , "rm -rf /var/lib/kubelet" )
72- critical ("wipe /var/lib/etcd" , "rm -rf /var/lib/etcd" )
72+ critical ("kubeadm reset" , "if command -v kubeadm >/dev/null 2>&1; then timeout 120 kubeadm reset -f; fi" )
73+ // Stop kubelet + the container runtime before wiping their data dirs so nothing
74+ // holds them open. kubeadm reset does this when present, but it may be absent on a
75+ // half-uninstalled box (the guard above skips it), so do it explicitly. Best-effort.
76+ step ("timeout 30 systemctl stop kubelet || true" )
77+ step ("timeout 30 systemctl stop containerd || true" )
78+ // Remove cluster + etcd data (load-bearing: leftover etcd data is the worst thing
79+ // to silently keep on an "uninstalled" node). Each wipe ASSERTS the target is
80+ // actually gone (`[ ! -e ... ]`): a bare `rm -rf` exits non-zero on a busy mount or
81+ // immutable file, and as a load-bearing step that would wedge the uninstall as a
82+ // permanent "partial uninstall" on every retry. /var/lib/kubelet can hold live
83+ // pod-volume mounts (SA-token / secret / emptyDir tmpfs), so lazy-unmount
84+ // everything under it (deepest first) before removing, or `rm` fails "device busy".
85+ critical ("wipe /etc/kubernetes" , "rm -rf /etc/kubernetes; [ ! -e /etc/kubernetes ]" )
86+ critical ("wipe /var/lib/kubelet" , "awk '$2 ~ \" ^/var/lib/kubelet\" {print $2}' /proc/mounts | sort -r | while read -r m; do umount -lf \" $m\" 2>/dev/null || true; done; rm -rf /var/lib/kubelet; [ ! -e /var/lib/kubelet ]" )
87+ critical ("wipe /var/lib/etcd" , "rm -rf /var/lib/etcd; [ ! -e /var/lib/etcd ]" )
7388 step ("rm -rf ~/.kube || true" )
7489 // CNI configurations (best-effort)
7590 step ("rm -rf /etc/cni || true" )
@@ -127,10 +142,17 @@ func Uninstall(full bool) error {
127142 roslog .Print ("Removing packages... " )
128143 // Unhold the held Kubernetes packages so they can be purged (best-effort).
129144 step ("apt-mark unhold kubelet kubeadm kubectl || true" )
130- // Remove ALL RunOS-installed packages in a SINGLE non-interactive apt-get
131- // (was five separate, slow, lock-contending invocations — the long delay).
132- // Bounded by the dpkg-lock timeout above plus an overall `timeout`.
133- critical ("purge packages" , "DEBIAN_FRONTEND=noninteractive timeout 300 " + aptGet + " remove --purge dnsmasq kubeadm kubectl kubelet kubernetes-cni containerd wireguard wireguard-tools haproxy" )
145+ // Purge ALL RunOS-installed packages in a SINGLE non-interactive apt-get (was
146+ // five separate, slow, lock-contending invocations — the long delay). Bounded
147+ // by the dpkg-lock timeout above plus an overall `timeout`.
148+ //
149+ // Purge only the subset dpkg still tracks (installed or residual-config). Once
150+ // the k8s apt repo is removed — a best-effort step just below, which a PRIOR
151+ // partial uninstall may already have run — `apt-get remove kubeadm ...` fails
152+ // with "Unable to locate package" (exit 100) for the now-unknown names, which
153+ // wedged the uninstall on every retry. dpkg-query lists the present names; if
154+ // none remain there is nothing to purge and the step is a clean no-op.
155+ critical ("purge packages" , "pkgs=$(dpkg-query -W -f='${Package}\\ n' dnsmasq kubeadm kubectl kubelet kubernetes-cni containerd wireguard wireguard-tools haproxy 2>/dev/null); if [ -n \" $pkgs\" ]; then DEBIAN_FRONTEND=noninteractive timeout 300 " + aptGet + " remove --purge $pkgs; else echo 'no RunOS packages present to purge'; fi" )
134156 step ("DEBIAN_FRONTEND=noninteractive timeout 120 " + aptGet + " autoremove || true" )
135157 step ("apt-get clean || true" )
136158 // Remove Kubernetes apt repo (best-effort)
0 commit comments