From d3f073a1073996e206dadc0b13ba4419895c3686 Mon Sep 17 00:00:00 2001 From: librelad Date: Sun, 24 May 2026 14:42:16 +0100 Subject: [PATCH] fix(rootless): task processor must load the de-sudo helpers itself MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit systemd launches the processor standalone, so it never sourced the LibrePortal function library — runFileOp/runFileWrite were 'command not found' at runtime, so it couldn't write its log, create its lock (flock died on a bad fd), or update task status. Every task stayed queued and looped forever, and the setup 'finalize' never ran. Source the privilege helpers (run_privileged.sh, docker_run_install.sh, check_install_type.sh) + read the docker-type config at startup so runFileOp knows rooted vs rootless. Also create the lock and per-task log via runFileOp (world-writable) so the manager-user processor can open/append them in the docker-install-owned task dir. Co-Authored-By: Claude Opus 4.7 Signed-off-by: librelad --- .../crontab/task/crontab_task_processor.sh | 43 +++++++++++++++---- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/scripts/crontab/task/crontab_task_processor.sh b/scripts/crontab/task/crontab_task_processor.sh index d93f598..71bf9bc 100755 --- a/scripts/crontab/task/crontab_task_processor.sh +++ b/scripts/crontab/task/crontab_task_processor.sh @@ -25,6 +25,27 @@ script_task_processor_flag="$1" # Source guard — DO NOT remove. mainLoop is an infinite loop. [[ "$script_task_processor_flag" != "start_script" ]] && return 0 2>/dev/null +# --- Load the privilege helpers + docker-type config ------------------------ +# systemd launches this script standalone, so the de-sudo helpers +# (runFileOp/runFileWrite) and the config they key off (rooted vs rootless) are +# NOT otherwise in scope. Without them every privileged write into the +# docker-install-owned task dir fails ("command not found") and tasks loop +# forever. Load them here — these files are pure function/var defs, safe to +# source, no side effects. +LP_SCRIPTS="${install_scripts_dir:-/docker/install/scripts/}" +LP_DOCKER_CFG="/docker/configs/general/general_docker_install" +[[ -f "$LP_DOCKER_CFG" ]] && \ + eval "$(grep -E '^CFG_DOCKER_INSTALL_(TYPE|USER)=' "$LP_DOCKER_CFG" | sed 's/[[:space:]]*#.*//')" +: "${sudo_user_name:=libreportal}" +: "${containers_dir:=/docker/containers/}" +: "${docker_dir:=/docker}" +for _lp_f in docker/command/run_privileged.sh \ + docker/command/docker_run_install.sh \ + checks/requirements/check_install_type.sh; do + [[ -f "${LP_SCRIPTS}${_lp_f}" ]] && source "${LP_SCRIPTS}${_lp_f}" +done +command -v resolveDockerInstallUser >/dev/null 2>&1 && resolveDockerInstallUser + # ============================================================================ # PATHS & CONSTANTS # ============================================================================ @@ -116,6 +137,12 @@ setupTaskDir() { fi runFileOp chmod 666 "$FIFO" 2>/dev/null runFileOp chmod 755 "$TASK_DIR" 2>/dev/null + # The processor (manager user) can't create files in the docker-install-owned + # task dir, so pre-create the lock AS the dir owner, world-writable, so the + # `exec 200>"$LOCK_FILE"` in acquireSingletonLock (run as the manager) can open + # 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 + runFileOp chmod 666 "$LOCK_FILE" 2>/dev/null if [[ -n "$docker_install_user" ]]; then runFileOp chown -R "$docker_install_user":"$docker_install_user" "$TASK_DIR" 2>/dev/null fi @@ -218,14 +245,14 @@ runTask() { # Previously this used `sudo truncate` + `sudo chmod 644` which left the file # root-owned and unwritable to the daemon, so the redirection failed and the # task immediately exited with rc=1. - local daemonUser; daemonUser=$(id -un) - if [[ -f "$logFile" ]]; then - runFileOp chown "$daemonUser":"$daemonUser" "$logFile" 2>/dev/null - runFileOp chmod 664 "$logFile" 2>/dev/null - : > "$logFile" 2>/dev/null || runFileOp truncate -s 0 "$logFile" 2>/dev/null - else - : > "$logFile" 2>/dev/null || runFileOp install -o "$daemonUser" -g "$daemonUser" -m 664 /dev/null "$logFile" - fi + # TASK_DIR is owned by the docker install user, so the manager-user processor + # can't create the log there directly. Create/truncate it AS the dir owner via + # runFileOp and leave it world-writable for the run so the eval's + # `>>"$logFile"` append (which runs as the manager, NOT under sudo) succeeds. + # Re-owned to the dir owner when the task finishes. + runFileOp install -m 666 /dev/null "$logFile" 2>/dev/null \ + || runFileOp truncate -s 0 "$logFile" 2>/dev/null + runFileOp chmod 666 "$logFile" 2>/dev/null export LIBREPORTAL_NONINTERACTIVE=1