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