From 48cb5d9380b21141c2a57a09448674c14a148079 Mon Sep 17 00:00:00 2001 From: librelad Date: Sat, 1 Aug 2026 11:15:29 +0100 Subject: [PATCH] fix(backup): name preserved env vars instead of relying on sudo -E MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sudo-rs — the default sudo from Ubuntu 25.10, so on 26.04 — does not implement bare -E. It does not reject it either: it warns to stderr ("preserving the entire environment is not supported, '-E' is ignored") and runs the command with the environment DROPPED, leaving the exit status untouched. Callers capture stderr, so the warning is invisible and the backup engines simply never receive RESTIC_PASSWORD / BORG_PASSPHRASE / KOPIA_PASSWORD and cannot open the repository. Name the nine vars explicitly via --preserve-env=, which sudo-rs and classic sudo (>=1.8.21, so Debian 10's 1.8.27) both honour, so this needs no version gate. The list is cross-checked against every RESTIC_/BORG_/KOPIA_ var the engine env scripts export. The list lives in variables.sh with a literal fallback in runBackupOp, because init.sh sources run_privileged.sh directly during install without ever loading variables.sh — an unguarded empty list would silently reproduce the same dropped-credential bug. restoreFirstRunDiscover now goes through runBackupOp rather than issuing its own sudo. It was the only backup-engine call bypassing that funnel, which is why it missed this fix by construction; routing it back also gives it the -H that keeps restic's cache under the backup user's HOME. Co-Authored-By: Claude Opus 5 --- scripts/docker/command/run_privileged.sh | 19 ++++++++++++++----- scripts/restore/restore_first_run.sh | 5 ++++- variables.sh | 9 +++++++++ 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/scripts/docker/command/run_privileged.sh b/scripts/docker/command/run_privileged.sh index 668ad9f..14c2be5 100644 --- a/scripts/docker/command/run_privileged.sh +++ b/scripts/docker/command/run_privileged.sh @@ -99,17 +99,26 @@ runCfgOp() { } # 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. +# ($docker_install_user), with the repo password and BORG_/RESTIC_/KOPIA_ env +# vars carried across the privilege drop. 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. +# The vars are named explicitly instead of using bare `-E`: sudo-rs (the default +# from Ubuntu 25.10, so on 26.04) doesn't implement -E — it prints "preserving +# the entire environment is not supported, '-E' is ignored" to stderr and then +# runs the command with the environment DROPPED. Exit status is unaffected and +# callers capture stderr, so that failure is invisible; the engine just can't +# open the repository. --preserve-env= is honoured by both sudo-rs and +# classic sudo (>=1.8.21, so Debian 10's 1.8.27 included). +# The literal fallback mirrors $backup_env_preserve in variables.sh, which isn't +# loaded when init.sh sources this file directly during install. # -H resets HOME to the target user's so restic finds (or creates) its cache # under /home/$docker_install_user/.cache/restic instead of inheriting the # manager's HOME (which dockerinstall can't write into, surfacing as # "unable to open cache: mkdir /home/libreportal/.cache/restic: permission denied" # on every backup). runBackupOp() { - sudo -E -H -u "$docker_install_user" "$@" + sudo --preserve-env="${backup_env_preserve:-BORG_PASSPHRASE,BORG_REPO,BORG_RSH,KOPIA_CHECK_FOR_UPDATES,KOPIA_CONFIG_PATH,KOPIA_PASSWORD,RESTIC_PASSWORD,RESTIC_REPOSITORY,RESTIC_SFTP_COMMAND}" -H -u "$docker_install_user" "$@" } # Run one of the ROOT-OWNED LibrePortal helpers installed (root:root 0755) under diff --git a/scripts/restore/restore_first_run.sh b/scripts/restore/restore_first_run.sh index 382be59..485fd55 100644 --- a/scripts/restore/restore_first_run.sh +++ b/scripts/restore/restore_first_run.sh @@ -10,7 +10,10 @@ restoreFirstRunDiscover() fi resticEnvExport "$idx" || return 1 - sudo -E -u "$docker_install_user" restic snapshots --tag engine=libreportal --json --no-lock 2>/dev/null + # Via runBackupOp rather than its own sudo: this was the one backup-engine + # call bypassing that funnel, so it silently missed the -E fix for sudo-rs + # (and the -H that puts restic's cache under the backup user's HOME). + runBackupOp restic snapshots --tag engine=libreportal --json --no-lock 2>/dev/null local rc=$? resticEnvUnset return $rc diff --git a/variables.sh b/variables.sh index b3ac320..fc6e061 100755 --- a/variables.sh +++ b/variables.sh @@ -49,6 +49,15 @@ sysctl="/etc/sysctl.d/99-libreportal-rootless.conf" # aren't loaded. Ubuntu 24.04/26.04 don't autoload them on a fresh box, so we # both modprobe them now and persist them here for subsequent boots. modules_load="/etc/modules-load.d/libreportal-rootless.conf" + +# Env vars that must survive the privilege drop into $docker_install_user for the +# backup engines to open their repository (repo URI + passphrase + ssh transport). +# Named explicitly rather than relying on `sudo -E`: sudo-rs, the default on +# Ubuntu 25.10+ (incl. 26.04), does not implement bare -E — it warns and runs the +# command with the environment dropped. Keep in sync with the fallback list in +# scripts/docker/command/run_privileged.sh (init.sh sources that file directly, +# without this one). +backup_env_preserve="BORG_PASSPHRASE,BORG_REPO,BORG_RSH,KOPIA_CHECK_FOR_UPDATES,KOPIA_CONFIG_PATH,KOPIA_PASSWORD,RESTIC_PASSWORD,RESTIC_REPOSITORY,RESTIC_SFTP_COMMAND" docker_log_file=libreportal.log backup_log_file=backup.log db_file=database.db