#!/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 # The control plane is owned by the MANAGER user in BOTH modes. root:root was # never the intended model — it only ever showed up as an artifact of # un-de-sudo'd commands (sudo creating files as root instead of libreportal). # Robust resolution — these globals aren't always populated in the CLI/switch # context, which previously made ops silently no-op (relative paths / empty # user). Fall back to absolute defaults and the live config file; never empty. local owner="${sudo_user_name:-libreportal}" local ddir="${docker_dir:-/docker}" local cdir="${containers_dir:-$ddir/containers/}" local cfgdir="${configs_dir:-$ddir/configs/}" local logdir="${logs_dir:-$ddir/logs/}" local scrdir="${script_dir:-$ddir/install}" local dbpath="$ddir/${db_file:-database.db}" # Read the rootless docker install user AUTHORITATIVELY from config — NOT the # lowercase $docker_install_user global, which check_install_type.sh sets to # the MANAGER user in rooted mode, so during a rooted->rootless switch it's # stale (=manager) and would mis-own the WebUI dir. local appusr appusr=$(grep -h '^CFG_DOCKER_INSTALL_USER=' "$cfgdir/general/general_docker_install" 2>/dev/null | head -1 | cut -d= -f2 | awk '{print $1}') appusr="${appusr:-${CFG_DOCKER_INSTALL_USER:-dockerinstall}}" [[ -d "$ddir" ]] || return 0 # Swap ONLY the owner on our own control-plane files; never reset mode bits. # The only two bits we *add* (never remove) are structural: o+x on /docker so # the docker user can traverse to its container dirs, and o+r on the DB so the # WebUI container can read it. runSystem chown "$owner:$owner" "$ddir" runSystem chmod o+x "$ddir" local p for p in "$cfgdir" "$logdir" "$scrdir" "$dbpath"; do [[ -e "$p" ]] && runSystem chown -R "$owner:$owner" "$p" done [[ -f "$dbpath" ]] && runSystem chmod o+r "$dbpath" # LibrePortal's OWN WebUI container dir is regenerable, so flip it to the # mode's container owner so the WebUI survives a switch (safe to recurse — # one UID, no per-app uid to clobber). Third-party app data is left untouched. # rooted -> the manager (owns everything under /docker in rooted) # rootless -> the docker install user (owns /docker/containers/**) local webui_dir="${cdir}libreportal" if [[ -d "$webui_dir" ]]; then local app_owner="$owner" [[ "$mode" == "rootless" ]] && app_owner="$appusr" runSystem chown -R "$app_owner:$app_owner" "$webui_dir" isSuccessful "Reconciled WebUI dir ($webui_dir) -> $app_owner" else isNotice "reconcileDockerOwnership: WebUI dir '$webui_dir' not found — skipped" fi isSuccessful "Reconciled ownership for $mode — control plane: $owner, app install user: $appusr" } fixFolderPermissions() { local silent_flag="$1" local app_name="$2" local result=$(runSystem chmod +x "$docker_dir" > /dev/null 2>&1) if [ "$silent_flag" == "loud" ]; then checkSuccess "Updating $docker_dir with execute permissions." fi local result=$(runSystem chmod +x "$containers_dir" > /dev/null 2>&1) if [ "$silent_flag" == "loud" ]; then checkSuccess "Updating $containers_dir with execute permissions." fi local result=$(runSystem find "$script_dir" "$ssl_dir" "$ssh_dir" "$backup_dir" "$restore_dir" "$migrate_dir" -maxdepth 2 -type d -exec chmod +x {} \;) if [ "$silent_flag" == "loud" ]; then checkSuccess "Adding execute permissions for $docker_install_user user" fi # Install user related local result=$(runSystem chown $docker_install_user:$docker_install_user "$containers_dir" > /dev/null 2>&1) if [ "$silent_flag" == "loud" ]; then checkSuccess "Updating $containers_dir with $docker_install_user ownership" fi }