feat(desudo): root-owned ownership helper (no blanket sudo chown needed)
Under Model A the runtime runs as the manager, so establishing the /docker ownership model needs root. Granting the manager a blanket 'sudo chown'/'sudo chmod' in the scoped sudoers would be root-equivalent (chown /etc/sudoers, ...). Introduce a self-contained, root-owned helper that performs only a FIXED set of reconciles on FIXED LibrePortal paths, with owners derived from config + a baked manager name (never the caller) and a strictly-validated app-name argument. - scripts/system/libreportal-ownership: the helper (actions: reconcile, traversal, containers-top, app-perms, webui, taskdir, app-data-nobody) - run_privileged: runOwnership wrapper (sudo the installed helper; run the bundled copy directly when already root mid-install) - init.sh: installOwnershipHelper bakes the manager name and installs it root:root 0755 to /usr/local/sbin (manager can't modify it) - libreportal_folders/app_folder/app_update_specifics/task processor: delegate the ownership chowns to runOwnership instead of runSystem chown This removes chown/chmod-on-/docker from the runtime sudo surface, a prerequisite for a non-root-equivalent scoped sudoers. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: librelad <librelad@digitalangels.vip>
This commit is contained in:
parent
85fd086281
commit
46622cd2f9
28
init.sh
28
init.sh
@ -705,6 +705,34 @@ initUsers()
|
|||||||
isError "Refusing to install an invalid sudoers drop-in for $sudo_user_name."
|
isError "Refusing to install an invalid sudoers drop-in for $sudo_user_name."
|
||||||
fi
|
fi
|
||||||
rm -f "$sudoers_tmp"
|
rm -f "$sudoers_tmp"
|
||||||
|
|
||||||
|
initOwnershipHelper
|
||||||
|
}
|
||||||
|
|
||||||
|
# Install the root-owned ownership-reconcile helper. Under Model A the runtime
|
||||||
|
# runs AS the manager, so establishing the /docker ownership model needs root —
|
||||||
|
# but granting the manager a blanket `sudo chown`/`sudo chmod` would be
|
||||||
|
# root-equivalent. This helper does a FIXED set of reconciles on FIXED paths; it
|
||||||
|
# lives root:root 0755 where the manager can't edit it, so the scoped sudoers can
|
||||||
|
# allow it wholesale. The manager name is baked in here (manager can't change it).
|
||||||
|
initOwnershipHelper()
|
||||||
|
{
|
||||||
|
local helper_src="$script_dir/scripts/system/libreportal-ownership"
|
||||||
|
local helper_dst="/usr/local/sbin/libreportal-ownership"
|
||||||
|
if [[ ! -f "$helper_src" ]]; then
|
||||||
|
isError "Ownership helper source missing ($helper_src) — skipping install."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
local helper_tmp
|
||||||
|
helper_tmp=$(mktemp)
|
||||||
|
sed "s/__MANAGER__/${sudo_user_name}/g" "$helper_src" > "$helper_tmp"
|
||||||
|
if bash -n "$helper_tmp" 2>/dev/null; then
|
||||||
|
sudo install -m 0755 -o root -g root "$helper_tmp" "$helper_dst"
|
||||||
|
isSuccessful "Installed root-owned ownership helper ($helper_dst)."
|
||||||
|
else
|
||||||
|
isError "Refusing to install a malformed ownership helper."
|
||||||
|
fi
|
||||||
|
rm -f "$helper_tmp"
|
||||||
}
|
}
|
||||||
|
|
||||||
initFolders()
|
initFolders()
|
||||||
|
|||||||
@ -33,7 +33,7 @@ appUpdateSpecifics()
|
|||||||
# under its mounted data dir; fixPermissionsBeforeStart hands the dir to
|
# under its mounted data dir; fixPermissionsBeforeStart hands the dir to
|
||||||
# the install user, so give it to 65534 here or the server can't open
|
# the install user, so give it to 65534 here or the server can't open
|
||||||
# the database. Restart so it picks the dir up.
|
# the database. Restart so it picks the dir up.
|
||||||
runSystem chown -R 65534:65534 "$containers_dir$app_name/data";
|
runOwnership app-data-nobody "$app_name";
|
||||||
shouldrestart="true";
|
shouldrestart="true";
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@ -143,12 +143,10 @@ setupTaskDir() {
|
|||||||
# it. Create-if-absent to keep a stable inode for flock across restarts.
|
# it. Create-if-absent to keep a stable inode for flock across restarts.
|
||||||
[[ -e "$LOCK_FILE" ]] || runFileOp install -m 666 /dev/null "$LOCK_FILE" 2>/dev/null
|
[[ -e "$LOCK_FILE" ]] || runFileOp install -m 666 /dev/null "$LOCK_FILE" 2>/dev/null
|
||||||
runFileOp chmod 666 "$LOCK_FILE" 2>/dev/null
|
runFileOp chmod 666 "$LOCK_FILE" 2>/dev/null
|
||||||
# Establish ownership with runSystem (root): the unprivileged dir owner can't
|
# Establish ownership via the root-owned helper: the unprivileged dir owner
|
||||||
# reclaim files an earlier run left root/manager-owned (e.g. a root-owned
|
# can't reclaim files an earlier run left root/manager-owned (e.g. a root-owned
|
||||||
# task_processor.log), which would then block the daemon's log appends.
|
# task_processor.log), which would then block the daemon's log appends.
|
||||||
if [[ -n "$docker_install_user" ]]; then
|
runOwnership taskdir 2>/dev/null
|
||||||
runSystem chown -R "$docker_install_user":"$docker_install_user" "$TASK_DIR" 2>/dev/null
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Open the FIFO in read-write mode on fd 3. With <>, Linux returns from open()
|
# Open the FIFO in read-write mode on fd 3. With <>, Linux returns from open()
|
||||||
|
|||||||
@ -86,6 +86,28 @@ runBackupOp() {
|
|||||||
sudo -E -u "$docker_install_user" "$@"
|
sudo -E -u "$docker_install_user" "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Trigger a fixed ownership reconcile through the ROOT-OWNED helper installed at
|
||||||
|
# /usr/local/sbin/libreportal-ownership. This is how the manager-run runtime
|
||||||
|
# (Model A) establishes the ownership model — manager owns the control plane, the
|
||||||
|
# docker install user owns the containers — without the scoped sudoers having to
|
||||||
|
# grant a blanket `sudo chown`/`sudo chmod` (which would be root-equivalent: chown
|
||||||
|
# /etc/sudoers and so on). The helper validates its own (fixed-path) operations,
|
||||||
|
# so the sudoers can allow it wholesale.
|
||||||
|
# action ∈ {reconcile [mode]|traversal|containers-top|app-perms|webui|taskdir|
|
||||||
|
# app-data-nobody <app>}
|
||||||
|
# At install time (already root) the helper may not be installed yet, so run the
|
||||||
|
# bundled copy directly — no sudo, no escalation, since we are root already.
|
||||||
|
runOwnership() {
|
||||||
|
local helper="/usr/local/sbin/libreportal-ownership"
|
||||||
|
if [[ -x "$helper" ]]; then
|
||||||
|
sudo "$helper" "$@"
|
||||||
|
elif [[ $EUID -eq 0 ]]; then
|
||||||
|
bash "${script_dir:-/docker/install}/scripts/system/libreportal-ownership" "$@"
|
||||||
|
else
|
||||||
|
sudo "$helper" "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# Genuine system-administration command (ufw/systemctl/apt/sysctl/useradd, /etc
|
# Genuine system-administration command (ufw/systemctl/apt/sysctl/useradd, /etc
|
||||||
# edits). Needs real root in both modes; funnelled through one place so it can
|
# edits). Needs real root in both modes; funnelled through one place so it can
|
||||||
# later be confined to a scoped sudoers allowlist.
|
# later be confined to a scoped sudoers allowlist.
|
||||||
|
|||||||
@ -1,81 +1,14 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
fixAppFolderPermissions()
|
# Per-app structural permissions + ownership of the LibrePortal-managed files
|
||||||
|
# (config + compose) for every installed app. The actual chown/chmod run in the
|
||||||
|
# root-owned ownership helper (runOwnership) so the manager-run runtime needs no
|
||||||
|
# blanket sudo; the helper walks /docker/containers itself.
|
||||||
|
fixAppFolderPermissions()
|
||||||
{
|
{
|
||||||
local silent_flag="$1"
|
local silent_flag="$1"
|
||||||
|
runOwnership app-perms
|
||||||
# Collect all app names in an array
|
if [ "$silent_flag" == "loud" ]; then
|
||||||
local app_names=()
|
checkSuccess "Updating app folder permissions."
|
||||||
for app_dir in "$containers_dir"/*/; do
|
fi
|
||||||
if [ -d "$app_dir" ]; then
|
|
||||||
local app_name=$(basename "$app_dir")
|
|
||||||
app_names+=("$app_name")
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
for app_name in "${app_names[@]}"; do
|
|
||||||
if [[ $app_name != "" ]]; then
|
|
||||||
|
|
||||||
# Updating $containers_dir with execute permissions
|
|
||||||
if [ -d "$containers_dir" ]; then
|
|
||||||
local result=$(runSystem chmod +x "$containers_dir" > /dev/null 2>&1)
|
|
||||||
if [ "$silent_flag" == "loud" ]; then
|
|
||||||
checkSuccess "Updating $containers_dir with execute permissions."
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
if [ "$silent_flag" == "loud" ]; then
|
|
||||||
isNotice "$containers_dir does not exist."
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Updating $containers_dir$app_name with execute permissions
|
|
||||||
if [ -d "$containers_dir$app_name" ]; then
|
|
||||||
local result=$(runSystem chmod +x "$containers_dir$app_name" > /dev/null 2>&1)
|
|
||||||
if [ "$silent_flag" == "loud" ]; then
|
|
||||||
checkSuccess "Updating $containers_dir$app_name with execute permissions."
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
if [ "$silent_flag" == "loud" ]; then
|
|
||||||
isNotice "$containers_dir$app_name does not exist."
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Updating $app_name with read permissions
|
|
||||||
if [ -d "$containers_dir$app_name" ]; then
|
|
||||||
local result=$(runSystem chmod o+r "$containers_dir$app_name")
|
|
||||||
if [ "$silent_flag" == "loud" ]; then
|
|
||||||
checkSuccess "Updating $app_name with read permissions"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
if [ "$silent_flag" == "loud" ]; then
|
|
||||||
isNotice "$containers_dir$app_name does not exist."
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Updating compose file(s) for LibrePortal access
|
|
||||||
if [ -d "$containers_dir$app_name" ]; then
|
|
||||||
local result=$(runSystem find "$containers_dir$app_name" -type f -name '*docker-compose*' -exec chmod o+r {} \;)
|
|
||||||
if [ "$silent_flag" == "loud" ]; then
|
|
||||||
isNotice "Updating compose file(s) for LibrePortal access"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
if [ "$silent_flag" == "loud" ]; then
|
|
||||||
isNotice "$containers_dir$app_name does not exist."
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Fix LibrePortal specific file permissions
|
|
||||||
local files=("migrate.txt" "$app_name.config" "docker-compose.yml" "docker-compose.$app_name.yml")
|
|
||||||
for file in "${files[@]}"; do
|
|
||||||
local file_path="$containers_dir$app_name/$file"
|
|
||||||
# Check if the file exists
|
|
||||||
if [ -e "$file_path" ]; then
|
|
||||||
local result=$(runSystem chown $docker_install_user:$docker_install_user "$file_path")
|
|
||||||
if [ "$silent_flag" == "loud" ]; then
|
|
||||||
checkSuccess "Updating $file with $docker_install_user ownership"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -41,35 +41,10 @@ reconcileDockerOwnership()
|
|||||||
{
|
{
|
||||||
local mode="${1:-$CFG_DOCKER_INSTALL_TYPE}"
|
local mode="${1:-$CFG_DOCKER_INSTALL_TYPE}"
|
||||||
[[ -d "$docker_dir" ]] || return 0
|
[[ -d "$docker_dir" ]] || return 0
|
||||||
|
# All the chown/chmod live in the root-owned helper (see runOwnership) so the
|
||||||
# Robust resolution — these globals aren't always populated in the CLI/switch
|
# scoped sudoers needn't grant blanket `sudo chown`. The helper reads the mode
|
||||||
# context, which previously made ops silently no-op (relative paths / empty
|
# + owners from config itself; this just triggers it.
|
||||||
# user). Fall back to absolute defaults; never empty.
|
runOwnership reconcile "$mode"
|
||||||
local owner="${sudo_user_name:-libreportal}"
|
|
||||||
local ddir="${docker_dir:-/docker}"
|
|
||||||
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}"
|
|
||||||
|
|
||||||
[[ -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"
|
|
||||||
|
|
||||||
# Structural container dirs owned by the mode's container owner.
|
|
||||||
reconcileContainersTopOwnership "$mode"
|
|
||||||
reconcileWebuiDirOwnership "$mode"
|
|
||||||
|
|
||||||
isSuccessful "Reconciled ownership for $mode"
|
isSuccessful "Reconciled ownership for $mode"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,13 +54,7 @@ reconcileDockerOwnership()
|
|||||||
# reconcile. Runs as root (runSystem stays root in both modes).
|
# reconcile. Runs as root (runSystem stays root in both modes).
|
||||||
reconcileContainersTopOwnership()
|
reconcileContainersTopOwnership()
|
||||||
{
|
{
|
||||||
local mode="${1:-$CFG_DOCKER_INSTALL_TYPE}"
|
runOwnership containers-top
|
||||||
local cdir="${containers_dir:-${docker_dir:-/docker}/containers/}"
|
|
||||||
[[ -d "$cdir" ]] || return 0
|
|
||||||
local owner
|
|
||||||
owner="$(dockerContainerOwner "$mode")"
|
|
||||||
runSystem chown "$owner:$owner" "$cdir"
|
|
||||||
runSystem chmod o+x "$cdir"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Chown LibrePortal's own (regenerable) WebUI container dir to the mode's
|
# Chown LibrePortal's own (regenerable) WebUI container dir to the mode's
|
||||||
@ -104,10 +73,8 @@ reconcileWebuiDirOwnership()
|
|||||||
isNotice "reconcileWebuiDirOwnership: WebUI dir '$webui_dir' not found — skipped"
|
isNotice "reconcileWebuiDirOwnership: WebUI dir '$webui_dir' not found — skipped"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
local owner
|
runOwnership webui
|
||||||
owner="$(dockerContainerOwner "$mode")"
|
isSuccessful "Reconciled WebUI dir ($webui_dir)"
|
||||||
runSystem chown -R "$owner:$owner" "$webui_dir"
|
|
||||||
isSuccessful "Reconciled WebUI dir ($webui_dir) -> $owner"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Traversal (+x) bits only. Ownership of the structural container dirs is handled
|
# Traversal (+x) bits only. Ownership of the structural container dirs is handled
|
||||||
@ -116,17 +83,11 @@ reconcileWebuiDirOwnership()
|
|||||||
fixFolderPermissions()
|
fixFolderPermissions()
|
||||||
{
|
{
|
||||||
local silent_flag="$1"
|
local silent_flag="$1"
|
||||||
local app_name="$2"
|
|
||||||
|
|
||||||
local result=$(runSystem chmod +x "$docker_dir" > /dev/null 2>&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
|
if [ "$silent_flag" == "loud" ]; then
|
||||||
checkSuccess "Updating $docker_dir with execute permissions."
|
checkSuccess "Updating folder traversal permissions."
|
||||||
fi
|
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
|
|
||||||
|
|
||||||
reconcileContainersTopOwnership
|
|
||||||
}
|
}
|
||||||
|
|||||||
164
scripts/system/libreportal-ownership
Normal file
164
scripts/system/libreportal-ownership
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# LibrePortal ownership-reconcile helper — the ONLY root-privileged file-ownership
|
||||||
|
# operation the manager is allowed to trigger via sudo.
|
||||||
|
#
|
||||||
|
# Why this exists: under Model A the runtime executes AS the manager (libreportal),
|
||||||
|
# so establishing the ownership model (manager owns the control plane, the docker
|
||||||
|
# install user owns the containers) needs root. Granting the manager a blanket
|
||||||
|
# `sudo chown`/`sudo chmod` in the scoped sudoers would be root-equivalent (chown
|
||||||
|
# /etc/sudoers, etc.). Instead this script — installed root:root 0755 to
|
||||||
|
# /usr/local/sbin by init.sh, so the manager cannot modify it — performs a FIXED
|
||||||
|
# set of reconciles on FIXED LibrePortal paths only. Owners are derived from
|
||||||
|
# config + the baked manager name, never from the caller; the single free argument
|
||||||
|
# (an app name) is strictly validated and must resolve to an existing dir under
|
||||||
|
# /docker/containers.
|
||||||
|
#
|
||||||
|
# Self-contained ON PURPOSE: it must NOT source any manager-owned code, or it
|
||||||
|
# would re-open the very escalation it exists to close. init.sh is the source of
|
||||||
|
# truth for the install; it bakes the manager name into the installed copy.
|
||||||
|
|
||||||
|
set -u
|
||||||
|
|
||||||
|
[[ $EUID -eq 0 ]] || { echo "libreportal-ownership: must run as root" >&2; exit 1; }
|
||||||
|
|
||||||
|
# Baked by init.sh at install (placeholder replaced); default if run unbaked.
|
||||||
|
MANAGER="__MANAGER__"
|
||||||
|
[[ "$MANAGER" == "__MANAGER__" || -z "$MANAGER" ]] && MANAGER="libreportal"
|
||||||
|
|
||||||
|
DOCKER_DIR="/docker"
|
||||||
|
CONFIGS_DIR="$DOCKER_DIR/configs"
|
||||||
|
LOGS_DIR="$DOCKER_DIR/logs"
|
||||||
|
INSTALL_DIR="$DOCKER_DIR/install"
|
||||||
|
CONTAINERS_DIR="$DOCKER_DIR/containers"
|
||||||
|
SSL_DIR="$DOCKER_DIR/ssl"
|
||||||
|
SSH_DIR="$DOCKER_DIR/ssh"
|
||||||
|
BACKUP_DIR="$DOCKER_DIR/backups"
|
||||||
|
RESTORE_DIR="$DOCKER_DIR/restore"
|
||||||
|
MIGRATE_DIR="$DOCKER_DIR/migrate"
|
||||||
|
DB_PATH="$DOCKER_DIR/database.db"
|
||||||
|
WEBUI_DIR="$CONTAINERS_DIR/libreportal"
|
||||||
|
TASK_DIR="$WEBUI_DIR/frontend/data/tasks"
|
||||||
|
DB_CFG="$CONFIGS_DIR/general/general_docker_install"
|
||||||
|
|
||||||
|
# Current docker mode, read authoritatively from config.
|
||||||
|
_mode() {
|
||||||
|
local m
|
||||||
|
m=$(grep -h '^CFG_DOCKER_INSTALL_TYPE=' "$DB_CFG" 2>/dev/null | head -1 | cut -d= -f2 | awk '{print $1}')
|
||||||
|
echo "${m:-rootless}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Who owns container data for a mode: rooted -> the manager; rootless -> the
|
||||||
|
# configured docker install user (must be a real account, else fall back).
|
||||||
|
_container_owner() {
|
||||||
|
local mode="$1" appusr=""
|
||||||
|
if [[ "$mode" == "rootless" ]]; then
|
||||||
|
appusr=$(grep -h '^CFG_DOCKER_INSTALL_USER=' "$DB_CFG" 2>/dev/null | head -1 | cut -d= -f2 | awk '{print $1}')
|
||||||
|
if [[ -n "$appusr" ]] && id -u "$appusr" >/dev/null 2>&1; then
|
||||||
|
echo "$appusr"; return
|
||||||
|
fi
|
||||||
|
echo "dockerinstall"
|
||||||
|
else
|
||||||
|
echo "$MANAGER"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validate + resolve an app name to its container dir (reject traversal/odd names).
|
||||||
|
_app_dir() {
|
||||||
|
local app="$1"
|
||||||
|
[[ "$app" =~ ^[A-Za-z0-9._-]+$ && "$app" != "." && "$app" != ".." ]] \
|
||||||
|
|| { echo "libreportal-ownership: invalid app name" >&2; return 1; }
|
||||||
|
local d="$CONTAINERS_DIR/$app"
|
||||||
|
[[ -d "$d" ]] || { echo "libreportal-ownership: no such app dir: $d" >&2; return 1; }
|
||||||
|
printf '%s' "$d"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Control plane -> manager; structural container dirs -> container owner. Owner
|
||||||
|
# only, never reset mode bits; only ADD o+x (traversal) and o+r (DB read).
|
||||||
|
reconcile() {
|
||||||
|
local mode="${1:-$(_mode)}"
|
||||||
|
[[ -d "$DOCKER_DIR" ]] || return 0
|
||||||
|
chown "$MANAGER:$MANAGER" "$DOCKER_DIR"
|
||||||
|
chmod o+x "$DOCKER_DIR"
|
||||||
|
local p
|
||||||
|
for p in "$CONFIGS_DIR" "$LOGS_DIR" "$INSTALL_DIR" "$DB_PATH"; do
|
||||||
|
[[ -e "$p" ]] && chown -R "$MANAGER:$MANAGER" "$p"
|
||||||
|
done
|
||||||
|
[[ -f "$DB_PATH" ]] && chmod o+r "$DB_PATH"
|
||||||
|
local cowner; cowner="$(_container_owner "$mode")"
|
||||||
|
if [[ -d "$CONTAINERS_DIR" ]]; then
|
||||||
|
chown "$cowner:$cowner" "$CONTAINERS_DIR"
|
||||||
|
chmod o+x "$CONTAINERS_DIR"
|
||||||
|
fi
|
||||||
|
[[ -d "$WEBUI_DIR" ]] && chown -R "$cowner:$cowner" "$WEBUI_DIR"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Traversal (+x) bits only, on the structural LibrePortal dirs.
|
||||||
|
traversal() {
|
||||||
|
[[ -d "$DOCKER_DIR" ]] && chmod +x "$DOCKER_DIR"
|
||||||
|
local d
|
||||||
|
for d in "$INSTALL_DIR" "$SSL_DIR" "$SSH_DIR" "$BACKUP_DIR" "$RESTORE_DIR" "$MIGRATE_DIR"; do
|
||||||
|
[[ -d "$d" ]] && find "$d" -maxdepth 2 -type d -exec chmod +x {} \;
|
||||||
|
done
|
||||||
|
local mode cowner; mode="$(_mode)"; cowner="$(_container_owner "$mode")"
|
||||||
|
if [[ -d "$CONTAINERS_DIR" ]]; then
|
||||||
|
chown "$cowner:$cowner" "$CONTAINERS_DIR"
|
||||||
|
chmod o+x "$CONTAINERS_DIR"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Per-app structural perms + ownership of the LibrePortal-managed files only.
|
||||||
|
app_perms() {
|
||||||
|
[[ -d "$CONTAINERS_DIR" ]] || return 0
|
||||||
|
local mode cowner; mode="$(_mode)"; cowner="$(_container_owner "$mode")"
|
||||||
|
chmod +x "$CONTAINERS_DIR" 2>/dev/null
|
||||||
|
local app_dir app f
|
||||||
|
for app_dir in "$CONTAINERS_DIR"/*/; do
|
||||||
|
[[ -d "$app_dir" ]] || continue
|
||||||
|
app="$(basename "$app_dir")"
|
||||||
|
chmod +x "$app_dir" 2>/dev/null
|
||||||
|
chmod o+r "$app_dir" 2>/dev/null
|
||||||
|
find "$app_dir" -type f -name '*docker-compose*' -exec chmod o+r {} \;
|
||||||
|
for f in migrate.txt "$app.config" docker-compose.yml "docker-compose.$app.yml"; do
|
||||||
|
[[ -e "$app_dir$f" ]] && chown "$cowner:$cowner" "$app_dir$f"
|
||||||
|
done
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# LibrePortal's own (regenerable) WebUI container dir -> container owner.
|
||||||
|
webui() {
|
||||||
|
local mode cowner; mode="$(_mode)"; cowner="$(_container_owner "$mode")"
|
||||||
|
[[ -d "$WEBUI_DIR" ]] && chown -R "$cowner:$cowner" "$WEBUI_DIR"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Structural containers/ top dir only -> container owner + traversable.
|
||||||
|
containers_top() {
|
||||||
|
local mode cowner; mode="$(_mode)"; cowner="$(_container_owner "$mode")"
|
||||||
|
if [[ -d "$CONTAINERS_DIR" ]]; then
|
||||||
|
chown "$cowner:$cowner" "$CONTAINERS_DIR"
|
||||||
|
chmod o+x "$CONTAINERS_DIR"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# The task IPC dir -> container owner (reclaims stale manager/root-owned files).
|
||||||
|
taskdir() {
|
||||||
|
local mode cowner; mode="$(_mode)"; cowner="$(_container_owner "$mode")"
|
||||||
|
[[ -d "$TASK_DIR" ]] && chown -R "$cowner:$cowner" "$TASK_DIR"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Some apps' data must be owned by nobody (65534) inside the container.
|
||||||
|
app_data_nobody() {
|
||||||
|
local d; d="$(_app_dir "${1:-}")" || return 1
|
||||||
|
[[ -d "$d/data" ]] && chown -R 65534:65534 "$d/data"
|
||||||
|
}
|
||||||
|
|
||||||
|
action="${1:-}"; shift 2>/dev/null || true
|
||||||
|
case "$action" in
|
||||||
|
reconcile) reconcile "${1:-}";;
|
||||||
|
traversal) traversal;;
|
||||||
|
containers-top) containers_top;;
|
||||||
|
app-perms) app_perms;;
|
||||||
|
webui) webui;;
|
||||||
|
taskdir) taskdir;;
|
||||||
|
app-data-nobody) app_data_nobody "${1:-}";;
|
||||||
|
*) echo "usage: libreportal-ownership {reconcile [mode]|traversal|containers-top|app-perms|webui|taskdir|app-data-nobody <app>}" >&2; exit 2;;
|
||||||
|
esac
|
||||||
Loading…
x
Reference in New Issue
Block a user