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=<list>, 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 <noreply@anthropic.com>
dockerDeleteData (uninstall) and the wipe-before-restore step in
restoreAppStart both did `runFileOp rm -rf $containers_dir$app_name`,
which runs as $CFG_DOCKER_INSTALL_USER (dockerinstall, uid 1002 on
rootless). That user owns app-template files but CANNOT remove
container sub-UID dirs created by the daemon's userns mapping —
postgres data at uid 232070, nextcloud html at uid 33, etc. The rm
therefore silently failed with
rm: cannot remove '/libreportal-containers/invidious/postgresdata':
Permission denied
while still reporting "<app> successfully uninstalled" — leaving the
sub-UID directory tree on disk to confuse the next install and leak
storage.
Fix: route the wipe through a new `app-data-remove` action in the
root-owned libreportal-ownership helper. Root can rm sub-UID files
unconditionally. The helper validates the app name (alphanumeric +
. _ -, no traversal), refuses the WebUI's own slot (libreportal), and
is idempotent when the dir is already gone.
Two callers updated:
- scripts/docker/app/uninstall/delete_data.sh
- scripts/restore/restore_app_start.sh
The helper itself ships root-owned at /usr/local/lib/libreportal/, so a
fresh install or release upgrade is needed to pick up the new action.
Bumped init.sh footprint_version 2 → 3 so the runtime updater
prompts a root re-install on the next release.
Signed-off-by: librelad <librelad@digitalangels.vip>
Convert the backup/restore data-plane sudo calls (mkdir/chown/rm/sqlite3/tar/
gzip|tee) to runFileOp/runFileWrite. Rooted behaviour is identical (helper runs
sudo); rootless will run them as the unprivileged install user. Pilot subsystem
for the wider de-sudo. verify.sh's /tmp scratch ops left as-is (different
ownership domain, handled separately).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
Reads files the backup user can't see from the host (container-owned, e.g.
Nextcloud's www-data data dir) by streaming them out THROUGH the container
(docker exec tar) — no host root, no host read perms, works rooted + rootless.
Extracts to staging as plain files so restic keeps full dedup + per-file
restore (not a piped tar blob); the live path is excluded from the snapshot.
Restore streams the staging copy back through a throwaway in-namespace
container that recreates the tree with the app's uid:gid.
Declared via a libreportal.backup.files compose label; Nextcloud (html, 33:33)
is the first to use it. Live capture failure falls back to stop-snapshot-start.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
Adds a logical-dump path so apps with a database can be backed up with zero
downtime and full consistency, instead of stopping the container.
- backup_db.sh: dump each declared DB live (mysqldump --single-transaction /
pg_dump / sqlite3 .backup), exclude the raw data dir from the snapshot, and
replay the dump on restore (pre-start rehydrate for sqlite, post-start load
for server engines).
- Databases are declared via a 'libreportal.backup.db' compose label so the
metadata travels with the app in the snapshot.
- New 'auto' strategy (now the default): live where a DB is dumpable or the app
is marked live-safe, stop-snapshot-start otherwise. Explicit stop/pause/live
remain as overrides.
- restic/borg/kopia adapters honour an exclude list on the live path.
- Manifest records the resolved per-app strategy and dumped databases.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
A free, open, self-hosted app platform (GNU AGPLv3): one-click app deploys,
Traefik reverse proxy with automatic SSL, rootless Docker support, gluetun
VPN routing, and a web dashboard to manage it all.
Free & open forever to self-host; optional paid hosted services fund it.
See PROMISE.md.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>