#!/bin/bash # The user that owns container data for the current Docker mode: # rooted -> the manager ($sudo_user_name) # rootless -> the docker install user # For rootless read it AUTHORITATIVELY from config — the $docker_install_user # global is set to the manager in rooted mode, so it goes stale across a switch. # Single source of truth for "who owns container files", used by the install # permission pass and the switch reconcile alike. dockerContainerOwner() { local mode="${1:-$CFG_DOCKER_INSTALL_TYPE}" if [[ "$mode" == "rootless" ]]; then local cfgdir="${configs_dir:-${docker_dir:-/docker}/configs/}" 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}') echo "${appusr:-${CFG_DOCKER_INSTALL_USER:-dockerinstall}}" else echo "${sudo_user_name:-libreportal}" fi } # Reconcile the LibrePortal control plane + structural container dirs for the # current Docker mode, so the CLI and the de-sudo helpers (runFileOp/ # runInstallOp) keep working after a rooted<->rootless switch. The control plane # is owned by the MANAGER user in BOTH modes (root:root was never the intended # model — it only ever appeared as an artifact of un-de-sudo'd commands); the # structural container dirs are owned by the mode's container owner (see # dockerContainerOwner). # # Scope is DELIBERATELY narrow — the de-sudo-critical, LibrePortal-owned files # only: configs/, logs/, install scripts, the apps DB, the /docker top level # (kept o+x for traversal), plus the structural containers/ top dir and the # regenerable WebUI dir. It does NOT touch third-party /docker/containers// # data (per-app container UIDs, uid-mapped through subuids in rootless), nor # backups/, nor ssl/ssh (key material) — a blanket chown there would break # permission-strict apps 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 # All the chown/chmod live in the root-owned helper (see runOwnership) so the # scoped sudoers needn't grant blanket `sudo chown`. The helper reads the mode # + owners from config itself; this just triggers it. runOwnership reconcile "$mode" isSuccessful "Reconciled ownership for $mode" } # Own the containers/ top dir (structural, NOT per-app data) as the mode's # container owner and keep it traversable, so the docker user can create + own # its per-app dirs under it. Shared by the install permission pass and the switch # reconcile. Runs as root (runSystem stays root in both modes). reconcileContainersTopOwnership() { runOwnership containers-top } # Chown LibrePortal's own (regenerable) WebUI container dir to the mode's # container owner, so both the WebUI container and the host-side runFileOp # generators — which run as that user — can write it. Recurses (one regenerable # UID, no per-app uid to clobber). Must run as root. Shared by the switch # reconcile and the fresh-install WebUI setup so a fresh install gets the same # ownership a switch does — otherwise rootless generators hit "Permission # denied" on a manager-owned frontend/data tree. # # Also restores the configs/webui bind-mount access (webui-bind): those system-tree # files are read by the container through the container-owner GROUP, but any rewrite # by the non-root manager (e.g. the credential randomizer's sed of webui_logins) # resets their group to the manager's own — after which the rootless container can # no longer read them and exits on boot. Folding it in here makes this the single # "ready the WebUI for its container" pass: run it after any config write and right # before the container (re)starts. reconcileWebuiDirOwnership() { local mode="${1:-$CFG_DOCKER_INSTALL_TYPE}" local cdir="${containers_dir:-${docker_dir:-/docker}/containers/}" local webui_dir="${cdir}libreportal" if [[ ! -d "$webui_dir" ]]; then isNotice "reconcileWebuiDirOwnership: WebUI dir '$webui_dir' not found — skipped" return 0 fi runOwnership webui runOwnership webui-bind isSuccessful "Reconciled WebUI dir ($webui_dir)" } # Traversal (+x) bits only. Ownership of the structural container dirs is handled # by the shared reconcile helpers above (single source of truth), so install and # switch converge on the same state. fixFolderPermissions() { local silent_flag="$1" # +x traversal bits on the structural dirs + the containers/ top owner — all # in the root-owned helper (traversal already covers containers-top). runOwnership traversal if [ "$silent_flag" == "loud" ]; then checkSuccess "Updating folder traversal permissions." fi }