#!/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). local 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" # 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 — # it's all one UID, no per-app uid to clobber). Third-party app data # elsewhere under containers/ is left untouched. # rooted -> the manager (it owns everything under /docker in rooted) # rootless -> the docker install user (owns /docker/containers/**) local webui_dir="${containers_dir}libreportal" if [[ -d "$webui_dir" ]]; then local app_owner="$owner" [[ "$mode" == "rootless" ]] && app_owner="$docker_install_user" runSystem chown -R "$app_owner:$app_owner" "$webui_dir" fi isSuccessful "Reconciled LibrePortal control-plane ownership for $mode ($owner)" } 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 }