From 93284cdb3922cabe56214aa30b1554c7bf9302f9 Mon Sep 17 00:00:00 2001 From: librelad Date: Sun, 24 May 2026 19:54:11 +0100 Subject: [PATCH] =?UTF-8?q?feat(uninstall):=20add=20'init.sh=20uninstall'?= =?UTF-8?q?=20=E2=80=94=20full,=20guarded=20teardown?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A single 'sudo bash init.sh uninstall' that permanently removes the whole LibrePortal footprint, behind a typed 'DELETE LIBREPORTAL' confirmation: - stops + removes the task-processor service - best-effort graceful container removal, then tears down the rootless docker setup + the install user's session (linger/terminate/pkill) - removes the out-of-/docker footprint (/usr/local/lib/libreportal + /usr/local/bin/libreportal, /etc/sudoers.d, the systemd unit, the sysctl drop-ins, restic/kopia/ufw-docker, /root/init.sh) - rm -rf /docker - removes the libreportal + dockerinstall users + subuid/subgid ranges Runs as root (the entrypoint root-check enforces it — and the scoped sudoers can no longer self-remove anyway); self-contained (only init.sh's inline helpers, so it works as it deletes /docker); ordered so containers/ daemon stop before the users are removed. Leaves docker/compose/apt deps and SSH config in place (no lockout). Mirrors FOOTPRINT.md. Co-Authored-By: Claude Opus 4.7 Signed-off-by: librelad --- init.sh | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 83 insertions(+), 1 deletion(-) diff --git a/init.sh b/init.sh index b0fa564..2991dc2 100755 --- a/init.sh +++ b/init.sh @@ -176,7 +176,7 @@ fi # is EXECUTED directly (install time) — start.sh sources init.sh for its function # definitions at runtime (Model A, as the manager), and this block must not run # then (it would print a spurious "Auto-detected ..." and rewrite the config). -if [[ "${BASH_SOURCE[0]}" == "$0" && -z "$param7" ]]; then +if [[ "${BASH_SOURCE[0]}" == "$0" && "$param1" != "uninstall" && -z "$param7" ]]; then # A reinstall that doesn't re-pass the git args must not silently # downgrade an existing git install to local (that disables the updater # and blanks the saved creds). Honor a git URL already saved from a @@ -1270,6 +1270,86 @@ completeInitMessage() fi } +# FULL uninstall: permanently remove everything LibrePortal placed on the host. +# Runs as root (the entrypoint root-check below enforces it). Order matters — +# containers run AS the docker-install user and the rootless daemon is that user's +# systemd --user service, so stop those BEFORE removing the users. Self-contained: +# uses only init.sh's inline helpers, so it still works as it deletes /docker. +# Keep in sync with FOOTPRINT.md. +runFullUninstall() +{ + local mgr="${sudo_user_name:-libreportal}" + local iuser + iuser=$(grep -h '^CFG_DOCKER_INSTALL_USER=' /docker/configs/general/general_docker_install 2>/dev/null | head -1 | cut -d= -f2 | awk '{print $1}') + iuser="${iuser:-dockerinstall}" + + isHeader "LibrePortal — FULL Uninstall" + isError "This PERMANENTLY removes EVERYTHING — there is no undo:" + echo " - all containers + images + the rootless docker setup" + echo " - /docker (ALL app data, configs, database)" + echo " - the '$mgr' and '$iuser' users + their home directories" + echo " - /usr/local/lib/libreportal/ + the /usr/local/bin/libreportal command" + echo " - /etc/sudoers.d/$mgr, the systemd service, the sysctl drop-ins" + echo " - the restic / kopia / ufw-docker binaries LibrePortal installed" + echo "" + isNotice "LEFT IN PLACE: the docker engine, docker-compose, apt-installed deps," + isNotice "and your SSH config (so you can't get locked out)." + echo "" + isQuestion "Type exactly DELETE LIBREPORTAL to confirm:" + local confirm; read -r confirm + if [[ "$confirm" != "DELETE LIBREPORTAL" ]]; then + isNotice "Aborted — nothing was removed." + return 0 + fi + + isHeader "Tearing down LibrePortal" + + # 1. Stop + remove the task-processor service. + systemctl disable --now libreportal.service >/dev/null 2>&1 + rm -f /etc/systemd/system/libreportal.service + systemctl daemon-reload >/dev/null 2>&1 + isSuccessful "Stopped + removed the task-processor service" + + # 2. Best-effort graceful container removal, then tear down the rootless + # user's session (stops the rootless dockerd + any survivors). + local uid; uid=$(id -u "$iuser" 2>/dev/null) + if [[ -n "$uid" ]]; then + sudo -u "$iuser" env XDG_RUNTIME_DIR="/run/user/$uid" \ + DOCKER_HOST="unix:///run/user/$uid/docker.sock" \ + bash -c 'docker ps -aq | xargs -r docker rm -f' >/dev/null 2>&1 || true + sudo -u "$iuser" env XDG_RUNTIME_DIR="/run/user/$uid" \ + dockerd-rootless-setuptool.sh uninstall >/dev/null 2>&1 || true + loginctl disable-linger "$iuser" >/dev/null 2>&1 || true + loginctl terminate-user "$iuser" >/dev/null 2>&1 || true + pkill -9 -u "$iuser" >/dev/null 2>&1 || true + isSuccessful "Stopped containers + rootless docker for '$iuser'" + fi + + # 3. Remove the out-of-/docker footprint (see FOOTPRINT.md). + rm -f /usr/local/bin/libreportal + rm -rf /usr/local/lib/libreportal + rm -f "/etc/sudoers.d/$mgr" + rm -f /etc/sysctl.d/99-libreportal-hardening.conf /etc/sysctl.d/99-libreportal-rootless.conf + sysctl --system >/dev/null 2>&1 + rm -f /usr/local/bin/restic /usr/local/bin/kopia /usr/local/bin/ufw-docker + rm -f /root/init.sh + isSuccessful "Removed the system-integration footprint" + + # 4. Remove all app data. + rm -rf /docker + isSuccessful "Removed /docker" + + # 5. Remove the LibrePortal users + their subuid/subgid ranges. + pkill -9 -u "$mgr" >/dev/null 2>&1 || true + userdel -r "$mgr" >/dev/null 2>&1 || true + userdel -r "$iuser" >/dev/null 2>&1 || true + sed -i "/^${mgr}:/d;/^${iuser}:/d" /etc/subuid /etc/subgid 2>/dev/null || true + isSuccessful "Removed users '$mgr' + '$iuser'" + + isHeader "LibrePortal uninstalled" + isNotice "Left in place: docker engine, docker-compose, apt deps, SSH config." +} + # Only run the installer entrypoint (root check + init flow) when init.sh is # EXECUTED directly. When it's SOURCED — start.sh loads init.sh for its function # defs at runtime, and under Model A start.sh runs as the manager, not root — the @@ -1310,5 +1390,7 @@ else initLibrePortalCommand initUpdateConfigs completeInitMessage + elif [[ "$param1" == "uninstall" ]]; then + runFullUninstall fi fi