LibrePortal/scripts/docker/command/run_privileged.sh
librelad 10af56b9c4 refactor(desudo): rooted ops run as the manager user, not sudo->root
Maintainer confirmed the intended model: the manager user (libreportal, in the
docker group) owns /docker in BOTH modes and runs things directly; root:root was
always an accident of un-de-sudo'd sudo. Rework the helpers accordingly:

- add runAsManager (run as the manager: plain when already it at runtime, else
  sudo -u at install time) so files end up manager-owned, never root-owned.
- runFileOp/runFileWrite: rooted -> runAsManager (was sudo->root); rootless
  unchanged (docker install user owns containers/).
- runInstallOp/runInstallWrite: always runAsManager (control plane is manager-
  owned in both modes).
- runSystem unchanged (genuine root: apt/systemctl/ufw/sysctl).
All ~40 converted call sites inherit this via the helpers. reconcile's WebUI dir
now -> manager in rooted / docker install user in rootless.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-24 01:40:58 +01:00

86 lines
3.3 KiB
Bash

#!/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
}
# 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 "$@"
}