#!/bin/bash # Mode-aware privileged operations. # # Ownership model (single source of truth — see reconcileDockerOwnership): # The MANAGER user ($sudo_user_name, e.g. libreportal) runs the CLI + host # scripts and is in the docker group, so it owns and operates the LibrePortal # control plane in BOTH modes. root:root is never the intended owner — it only # ever appeared as an artifact of un-de-sudo'd `sudo` commands. # rooted — the manager owns everything under /docker (it talks to the root # docker socket via the docker group); ops run AS the manager. # rootless — the manager owns the control plane; the docker install user owns # /docker/containers/** (the rootless daemon requires it). # Only genuine system administration (apt/systemctl/ufw/sysctl/useradd, /etc) # needs real root — that goes through runSystem. # Run a command AS the manager user (plain if we're already it — the runtime # case — otherwise sudo -u to it, e.g. at install time when we're root). This is # how we keep files manager-owned instead of accidentally root-owned. runAsManager() { local mgr="${sudo_user_name:-libreportal}" if [[ "$(id -un)" == "$mgr" ]]; then "$@" else sudo -u "$mgr" "$@" fi } # /docker data-plane command (mkdir/chown/rm/cp/mv/sed/sqlite3/docker/etc.) on # app/container files. # rooted -> as the manager user (owns /docker, in the docker group) # rootless -> as the docker install user (owns /docker/containers/**, and has # DOCKER_HOST set so `docker ...` hits the rootless socket) # For stdin-fed writes (`… | sudo tee file`) use runFileWrite below. runFileOp() { if [[ "$CFG_DOCKER_INSTALL_TYPE" == "rootless" ]]; then dockerCommandRunInstallUser --argv "$@" else runAsManager "$@" fi } # Write stdin to a /docker data-plane path (replaces `… | sudo tee path`). # Pass -a/--append as the first arg to append instead of truncate. # Usage: some_command | runFileWrite [-a] /path/to/file runFileWrite() { local append_flag=() if [[ "$1" == "-a" || "$1" == "--append" ]]; then append_flag=(-a) shift fi local dest="$1" if [[ "$CFG_DOCKER_INSTALL_TYPE" == "rootless" ]]; then dockerCommandRunInstallUser "tee ${append_flag[*]} '$dest' >/dev/null" else runAsManager tee "${append_flag[@]}" "$dest" >/dev/null fi } # Op on a MANAGER-owned path — the LibrePortal clone/templates AND the /docker # control plane (apps DB, configs/, logs/, scripts). Owned by the manager in # BOTH modes, so it always runs as the manager. runInstallOp() { runAsManager "$@" } # Write stdin to a MANAGER-owned path (apps DB sidecars, configs/, logs/ — e.g. # the /docker/logs log-append idiom). Manager-owned in both modes. # Pass -a/--append as the first arg to append. runInstallWrite() { local append_flag=() if [[ "$1" == "-a" || "$1" == "--append" ]]; then append_flag=(-a) shift fi local dest="$1" runAsManager tee "${append_flag[@]}" "$dest" >/dev/null } # Backup-engine command (borg/restic/kopia) run AS the dedicated backup user # ($docker_install_user), with the environment preserved (-E) so the repo # password and BORG_/RESTIC_/KOPIA_ env vars reach the tool. Never root — the # scoped sudoers lets the manager drop to this user. Single funnel so the # backup subsystem's privilege drop has one audit point. runBackupOp() { sudo -E -u "$docker_install_user" "$@" } # Genuine system-administration command (ufw/systemctl/apt/sysctl/useradd, /etc # edits). Needs real root in both modes; funnelled through one place so it can # later be confined to a scoped sudoers allowlist. runSystem() { sudo "$@" }