feat(uninstall): --skip-docker-images keeps the docker layer for fast reinstall

A full uninstall tears down the rootless daemon and removes the
docker-install user's home, which destroys the WebUI image AND the build
cache — so every reinstall's `docker build` runs from scratch (slow,
re-pulls the base image + reinstalls deps). On a slow local box that
dominates the iteration loop.

--skip-docker-images on `init.sh ... uninstall` preserves the rootless
docker layer: it still removes stale containers, the control plane,
manager user, footprint and /docker, but keeps the daemon running, the
docker-install user + home (image/layer cache), and the rootless sysctl
drop-in. The following reinstall then finds rootless already set up and
rebuilds the WebUI image from cache — fast. No effect on install.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
This commit is contained in:
librelad 2026-05-24 22:07:56 +01:00
parent a42f2c6618
commit 50d11a7728

66
init.sh
View File

@ -10,11 +10,17 @@
# --unattended Run in unattended mode (skip confirmations) # --unattended Run in unattended mode (skip confirmations)
# --skip-os-update Skip operating system update # --skip-os-update Skip operating system update
# --skip-prereqs Skip installing prerequisite apps # --skip-prereqs Skip installing prerequisite apps
# --skip-docker-images On UNINSTALL: keep the rootless docker daemon, the
# docker-install user, and the image/build cache instead
# of tearing them down — so a following reinstall rebuilds
# the WebUI image from cache (fast) instead of from
# scratch. (No effect on install.)
# #
# Examples: # Examples:
# ./init.sh --random-password --local init # ./init.sh --random-password --local init
# ./init.sh --random-password --local --unattended init # ./init.sh --random-password --local --unattended init
# ./init.sh --random-password --local --skip-os-update --skip-prereqs init # ./init.sh --random-password --local --skip-os-update --skip-prereqs init
# ./init.sh --unattended --skip-docker-images uninstall # keep docker layer
# ./init.sh init mypassword myuser mytoken https://github.com/user/repo.git # ./init.sh init mypassword myuser mytoken https://github.com/user/repo.git
# #
@ -90,6 +96,7 @@ init_local_install=false
init_unattended_mode=false init_unattended_mode=false
init_skip_os_update=false init_skip_os_update=false
init_skip_prereqs=false init_skip_prereqs=false
init_skip_docker_images=false
install_param="init" install_param="init"
sudo_user_name=libreportal sudo_user_name=libreportal
@ -144,6 +151,13 @@ for ((i=1; i<=$#; i++)); do
init_skip_prereqs=true init_skip_prereqs=true
((init_shift_count++)) ((init_shift_count++))
;; ;;
--skip-docker-images)
# On uninstall: preserve the rootless docker layer (daemon +
# docker-install user + image/build cache) so the next reinstall's
# `docker build` is cache-fast. Honored in runFullUninstall.
init_skip_docker_images=true
((init_shift_count++))
;;
esac esac
done done
@ -1305,6 +1319,13 @@ runFullUninstall()
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=$(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}" iuser="${iuser:-dockerinstall}"
# --skip-docker-images: keep the rootless docker layer (the daemon, the
# "$iuser" user, the image/build cache + the rootless sysctl drop-in) instead
# of tearing it down, so a following reinstall's `docker build` is cache-fast
# instead of from-scratch. Everything else (control plane, manager, footprint,
# /docker) is still removed.
local keep_docker="${init_skip_docker_images:-false}"
isHeader "LibrePortal — FULL Uninstall" isHeader "LibrePortal — FULL Uninstall"
isError "This PERMANENTLY removes EVERYTHING — there is no undo:" isError "This PERMANENTLY removes EVERYTHING — there is no undo:"
echo " - all containers + images + the rootless docker setup" echo " - all containers + images + the rootless docker setup"
@ -1317,6 +1338,10 @@ runFullUninstall()
isNotice "LEFT IN PLACE: the docker engine, docker-compose, apt-installed deps," isNotice "LEFT IN PLACE: the docker engine, docker-compose, apt-installed deps,"
isNotice "and your SSH config (so you can't get locked out)." isNotice "and your SSH config (so you can't get locked out)."
echo "" echo ""
if [[ "$keep_docker" == "true" ]]; then
isNotice "--skip-docker-images: KEEPING the rootless docker daemon, the '$iuser' user, and the image/build cache (for a faster reinstall)."
echo ""
fi
if [[ "$init_unattended_mode" == true ]]; then if [[ "$init_unattended_mode" == true ]]; then
isNotice "Unattended mode — proceeding without the DELETE LIBREPORTAL prompt." isNotice "Unattended mode — proceeding without the DELETE LIBREPORTAL prompt."
else else
@ -1340,23 +1365,35 @@ runFullUninstall()
# user's session (stops the rootless dockerd + any survivors). # user's session (stops the rootless dockerd + any survivors).
local uid; uid=$(id -u "$iuser" 2>/dev/null) local uid; uid=$(id -u "$iuser" 2>/dev/null)
if [[ -n "$uid" ]]; then if [[ -n "$uid" ]]; then
# Remove stale containers either way — they bind-mount the about-to-be-
# wiped /docker. The images + build cache live in the user's home and are
# untouched by `docker rm`.
sudo -u "$iuser" env XDG_RUNTIME_DIR="/run/user/$uid" \ sudo -u "$iuser" env XDG_RUNTIME_DIR="/run/user/$uid" \
DOCKER_HOST="unix:///run/user/$uid/docker.sock" \ DOCKER_HOST="unix:///run/user/$uid/docker.sock" \
bash -c 'docker ps -aq | xargs -r docker rm -f' >/dev/null 2>&1 || true 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" \ if [[ "$keep_docker" == "true" ]]; then
dockerd-rootless-setuptool.sh uninstall >/dev/null 2>&1 || true isSuccessful "Removed containers; kept the rootless docker daemon + images for '$iuser'"
loginctl disable-linger "$iuser" >/dev/null 2>&1 || true else
loginctl terminate-user "$iuser" >/dev/null 2>&1 || true sudo -u "$iuser" env XDG_RUNTIME_DIR="/run/user/$uid" \
pkill -9 -u "$iuser" >/dev/null 2>&1 || true dockerd-rootless-setuptool.sh uninstall >/dev/null 2>&1 || true
isSuccessful "Stopped containers + rootless docker for '$iuser'" 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
fi fi
# 3. Remove the out-of-/docker footprint (see FOOTPRINT.md). # 3. Remove the out-of-/docker footprint (see FOOTPRINT.md).
rm -f /usr/local/bin/libreportal rm -f /usr/local/bin/libreportal
rm -rf /usr/local/lib/libreportal rm -rf /usr/local/lib/libreportal
rm -f "/etc/sudoers.d/$mgr" rm -f "/etc/sudoers.d/$mgr"
rm -f /etc/sysctl.d/99-libreportal-hardening.conf /etc/sysctl.d/99-libreportal-rootless.conf # Keep the sysctl drop-ins when preserving docker — removing + reloading them
sysctl --system >/dev/null 2>&1 # would reset ip_unprivileged_port_start etc. out from under the still-running
# rootless daemon. (The reinstall re-adds them idempotently.)
if [[ "$keep_docker" != "true" ]]; then
rm -f /etc/sysctl.d/99-libreportal-hardening.conf /etc/sysctl.d/99-libreportal-rootless.conf
sysctl --system >/dev/null 2>&1
fi
rm -f /usr/local/bin/restic /usr/local/bin/kopia /usr/local/bin/ufw-docker rm -f /usr/local/bin/restic /usr/local/bin/kopia /usr/local/bin/ufw-docker
rm -f /root/init.sh rm -f /root/init.sh
isSuccessful "Removed the system-integration footprint" isSuccessful "Removed the system-integration footprint"
@ -1370,15 +1407,22 @@ runFullUninstall()
# `userdel -r` leaves the home behind ("user currently used"); rm -rf the # `userdel -r` leaves the home behind ("user currently used"); rm -rf the
# home afterwards as a backstop. # home afterwards as a backstop.
local u local u
for u in "$mgr" "$iuser"; do local users_to_remove=("$mgr")
[[ "$keep_docker" == "true" ]] || users_to_remove+=("$iuser")
for u in "${users_to_remove[@]}"; do
loginctl disable-linger "$u" >/dev/null 2>&1 || true loginctl disable-linger "$u" >/dev/null 2>&1 || true
loginctl terminate-user "$u" >/dev/null 2>&1 || true loginctl terminate-user "$u" >/dev/null 2>&1 || true
pkill -9 -u "$u" >/dev/null 2>&1 || true pkill -9 -u "$u" >/dev/null 2>&1 || true
userdel -r "$u" >/dev/null 2>&1 || true userdel -r "$u" >/dev/null 2>&1 || true
[[ -n "$u" ]] && rm -rf "/home/$u" [[ -n "$u" ]] && rm -rf "/home/$u"
done done
sed -i "/^${mgr}:/d;/^${iuser}:/d" /etc/subuid /etc/subgid 2>/dev/null || true if [[ "$keep_docker" == "true" ]]; then
isSuccessful "Removed users '$mgr' + '$iuser' (+ home dirs)" sed -i "/^${mgr}:/d" /etc/subuid /etc/subgid 2>/dev/null || true
isSuccessful "Removed user '$mgr' (+ home); kept '$iuser' + its docker image store"
else
sed -i "/^${mgr}:/d;/^${iuser}:/d" /etc/subuid /etc/subgid 2>/dev/null || true
isSuccessful "Removed users '$mgr' + '$iuser' (+ home dirs)"
fi
isHeader "LibrePortal uninstalled" isHeader "LibrePortal uninstalled"
isNotice "Left in place: docker engine, docker-compose, apt deps, SSH config." isNotice "Left in place: docker engine, docker-compose, apt deps, SSH config."