checkApplicationsConfigFilesMissingVariables does a `find $containers_dir
-maxdepth 2 -type f -name '*.config'` to enumerate every app's live
config. runFileOp drops privileges to $CFG_DOCKER_INSTALL_USER
(dockerinstall), which is intentionally the *manager* for the rootless
data plane — but doesn't own the per-app container sub-UID dirs (e.g.
invidious/postgresdata uid 232070, nextcloud/html uid 33).
At maxdepth 2 find doesn't actually need to descend into those dirs to
satisfy the name filter, but it tries to anyway and emits chatter like
find: '.../invidious/postgresdata': Permission denied
every time the function runs (config-reconcile path on install / app
start / restart). Cosmetic only — the actual .config files are at the
right depth and ARE found — but it shows up in the live CLI output
during installs.
2>/dev/null on the find. The function's purpose is purely to enumerate
LibrePortal-managed .config files; sub-UID data dirs are by design
unreachable to the manager and there's no signal in that error.
Signed-off-by: librelad <librelad@digitalangels.vip>
Reverts the 2>/dev/null band-aids and fixes the root cause. The
manager-run install boot scans app configs under /docker/containers AS
the container user (runFileOp). But init.sh's initFolders creates that
dir manager-owned, and the handover to the container user happened later
(start_preinstall), AFTER the boot scans — so the scans ran as the
container user against a dir it didn't own yet: "find:
'/docker/containers/': Permission denied" (cosmetic; the dir is empty
that early, but it's the wrong ownership at the wrong time).
Add initContainerLayer() to init.sh's root phase (after initGIT +
initUpdateConfigs, before the manager-run handoff): rootless-only, it
creates the docker-install user if missing and chowns /docker/containers
to it (751). The later rootless setup is now idempotent — it finds the
user existing and just (re)asserts its password + daemon config (moved
updateDockerInstallPassword out of the create-only branch). Rooted is
unaffected (containers stay manager-owned, which the manager reads).
Result: by the time the boot scans run, /docker/containers is owned by
the user doing the scanning — no permission error, nothing suppressed.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
Early in an install the docker-type config isn't loaded yet, so runFileOp
falls back to the manager, which can't list the container-owned (751)
/docker/containers/ dir. Two best-effort scans then leaked
"find: '/docker/containers/': Permission denied" to the install output
(x3 per run): scan_files.sh's app_configs scan and the application config
reconcile. No app configs exist that early on a fresh install, so the
empty result is correct — just suppress the find stderr (the -print0
output still flows). Cosmetic only; doesn't change what's enumerated once
the config is loaded.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
The apps SQLite DB ($docker_dir/$db_file) is owned by the manager user, so
read/write it AS the manager via runInstallOp instead of sudo (root). 48 call
sites across 28 scripts. In rooted this drops root->manager (correct owner);
in rootless it's the manager too (using runFileOp/dockerinstall here was the
'unable to open database' bug). The broken 'command -v sudo sqlite3' check
lines are left untouched (separate pre-existing issue).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
Application backups were driven by one crontab entry per app, each offset by
id * CFG_BACKUP_CRONTAB_APP_INTERVAL minutes. That minute offset is written
straight into cron's 0-59 minute field, so past ~20 apps it overflowed into
an invalid entry that silently never fired, and the fixed spacing could not
serialize backups that ran longer than the gap.
Replace it with a single daily entry (`libreportal backup scheduled`) that
enqueues a backup task per enabled app. The existing systemd task processor
drains them serially — no minute overflow, real serialization, and backups
are now visible/cancellable in the Tasks UI. Per-app enable is read from
CFG_<APP>_BACKUP at schedule time instead of being mirrored into crontab.
Removes the stagger machinery (timing/setup/check/remove scripts), the
now-unused cron_jobs table + insert, and the CFG_BACKUP_CRONTAB_APP_INTERVAL
config knob and its WebUI field.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
Replaces the slow, interactive per-variable scan with a deterministic
reconcile: each live config is rebuilt from its (freshly-cloned) template —
keeping the user's existing values, adding new template keys
(CFG_REQUIREMENT_CONFIGS_AUTO_UPDATE), and dropping keys the template no
longer defines (new CFG_REQUIREMENT_CONFIGS_AUTO_DELETE, default true).
Structure/order/comments follow the template; non-interactive; atomic with a
.bak; refuses to act on a missing/empty template so a broken clone can't wipe
a config. Applies to both general and per-app configs.
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>