diff --git a/scripts/docker/type_switcher/swap_docker_type.sh b/scripts/docker/type_switcher/swap_docker_type.sh index 2a9ed13..d34d654 100755 --- a/scripts/docker/type_switcher/swap_docker_type.sh +++ b/scripts/docker/type_switcher/swap_docker_type.sh @@ -23,6 +23,13 @@ dockerSwitcherSwap() if [[ $CFG_DOCKER_INSTALL_TYPE != $docker_type ]]; then isHeader "Docker Root/Rootless Switcher" + # Switching modes re-maps container UIDs (rootless shifts them by the + # subuid base), so a stateful app's existing data won't line up in the + # new mode. LibrePortal's own control plane is reconciled automatically + # (reconcileDockerOwnership), but app data is not touched — the safe way + # to carry stateful apps across is backup-before / restore-after. + isNotice "Switching Docker mode re-maps container ownership. Back up any stateful apps (databases etc.) BEFORE switching and restore them AFTER — app data is intentionally left untouched here." + if [[ $CFG_DOCKER_INSTALL_TYPE == "rooted" ]]; then if [[ $flag != "cli" ]]; then isNotice "The current Docker Setup Type is currently : ${RED}$docker_type${NC}" @@ -53,6 +60,7 @@ dockerSwitcherSwap() fi dockerServiceStart root; dockerSwitcherUpdateContainersToDockerType; + reconcileDockerOwnership "$CFG_DOCKER_INSTALL_TYPE"; dockerStartAllApps; databaseOptionInsert "docker_type" $CFG_DOCKER_INSTALL_TYPE; fi @@ -84,6 +92,7 @@ dockerSwitcherSwap() fi dockerServiceStart rootless; dockerSwitcherUpdateContainersToDockerType; + reconcileDockerOwnership "$CFG_DOCKER_INSTALL_TYPE"; dockerStartAllApps; databaseOptionInsert "docker_type" $CFG_DOCKER_INSTALL_TYPE; fi diff --git a/scripts/function/permission/libreportal_folders.sh b/scripts/function/permission/libreportal_folders.sh index 800d597..64d4a5a 100755 --- a/scripts/function/permission/libreportal_folders.sh +++ b/scripts/function/permission/libreportal_folders.sh @@ -1,5 +1,49 @@ #!/bin/bash +# Reconcile ONLY the LibrePortal control plane ownership for the current Docker +# mode, so the CLI and the de-sudo helpers (runFileOp/runInstallOp) keep working +# after a rooted<->rootless switch: +# rooted -> root:root (CLI operates via sudo) +# rootless -> $sudo_user_name (the manager/runtime user owns its own files) +# +# Scope is DELIBERATELY narrow — the de-sudo-critical, LibrePortal-owned files +# only: configs/, logs/, install scripts, the apps DB, and the /docker top level +# (kept o+x so the docker user can still traverse to its container dirs). +# +# It does NOT touch /docker/containers/** (per-app data, written by per-app +# container UIDs — uid-mapped through subuids in rootless), nor backups/ (owned +# by the backup-engine user), nor ssl/ssh (key material). A blanket chown there +# would break permission-strict apps (Postgres/MySQL refuse a wrong-owned data +# dir; Grafana/Nextcloud run as fixed UIDs) AND can't survive the rootless +# subuid offset anyway. Moving a stateful app across modes is a backup->switch +# ->restore operation, not a chown. Must run as ROOT, apps stopped. Idempotent. +reconcileDockerOwnership() +{ + local mode="${1:-$CFG_DOCKER_INSTALL_TYPE}" + [[ -d "$docker_dir" ]] || return 0 + + local owner="root" + [[ "$mode" == "rootless" ]] && owner="$sudo_user_name" + + # Swap ONLY the owner on our own control-plane files; never reset mode bits + # (so nothing that validates its permissions gets surprised). The only two + # bits we *add* (never remove) are structural and on our own dirs, not app + # files: o+x on /docker so the docker user can still traverse to its + # container dirs, and o+r on the DB so the WebUI container can read it. + runSystem chown "$owner:$owner" "$docker_dir" + runSystem chmod o+x "$docker_dir" + + # LibrePortal-owned control plane (NOT containers/ backups/ ssl/ ssh) — owner + # only, modes preserved. + local p + for p in "$configs_dir" "$logs_dir" "$script_dir" "$docker_dir/$db_file"; do + [[ -e "$p" ]] && runSystem chown -R "$owner:$owner" "$p" + done + [[ -f "$docker_dir/$db_file" ]] && runSystem chmod o+r "$docker_dir/$db_file" + + isSuccessful "Reconciled LibrePortal control-plane ownership for $mode ($owner)" +} + fixFolderPermissions() { local silent_flag="$1"