6 Commits

Author SHA1 Message Date
librelad
8b5e02c760 refactor(storage): resolve every app directory through appDir
The main sweep — ~260 call sites across ~100 files move from string
concatenation on a single root to appDir/storageAppDirs/storageAppConfigs.
On a single-root install the resolved paths are identical, so this is a
no-op until a location is registered.

Enumerators were the interesting half. `for d in "$containers_dir"/*/`
appears in the menus, the registry/artifact scanners and the DNS setup —
and a shell glob cannot list a rootless 751 tree at all, which is the
same bug config_find_file.sh already documents in a comment. Routing them
through storageAppDirs (which enumerates as the owning user) fixes that
alongside the multi-root work.

Three places needed judgement rather than substitution:

db_app_scan.sh deletes database rows and port allocations for apps whose
folder is missing, and reaps "empty" app dirs. With a storage location
unmounted, every app on it looks exactly like that. Each of those
branches now gates on appStorageAvailable first — an app on an unplugged
drive is skipped with a notice, never deleted.

instance_create.sh rewrites cloned hooks so an instance touches its own
directory instead of the base app's. Its sed matched ${containers_dir}<type>,
which this sweep just replaced with $(appDir <type>) — so it would have
silently stopped redirecting, and an instance would have written to the
original's files (the adguard auth adapter case its own comment warns
about). Now matches both appDir forms, verified against bare, quoted,
unrelated-app, legacy and prose cases.

peer_shell/peer_pull streamed and extracted relative to the primary root.
Both now use the app's own root, and peer_shell keeps a single-root
fallback since it runs as a restricted SSH shell with no LibrePortal env.

Also fixes a pre-existing bug found on the way: webui_app_config.sh
tested "$containers_dir/frontend/data/last_update", one level short of the
real tree under the libreportal app dir, so the WebUI refresh trigger
after a config update has never once fired.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 04:09:51 +01:00
librelad
053a620e22 fix(reliability): split local result=$(cmd) so $? survives for checkSuccess
'local result=$(cmd)' resets $? to 0 (the local builtin's own exit), so the
following checkSuccess always saw success regardless of cmd's real exit — the
mechanism that masked the de-sudo write failures. Split declaration from
assignment ('local result; result=$(cmd)') across all 235 active-code sites
(84 files) so the command's exit reaches checkSuccess. No behaviour change
beyond $? now being accurate (no set -e in runtime code; multi-line
assignments transform safely).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-31 03:09:25 +01:00
librelad
9af2465ffe feat(desudo): socket + systemd-svc helpers; route traefik/db chowns + svc
Move the last runtime-critical root file-primitive subsystems behind
root-owned helpers so the type switcher + task service work under a scoped
sudoers:

- scripts/system/libreportal-socket: {rootless|rooted} {on|off} chmod of
  the docker sockets (paths computed from config, not caller-supplied;
  exit 3 = absent so the *_found flags come from its exit code)
- scripts/system/libreportal-svc: GENERATES + installs the systemd unit
  from config (mode/uid/baked manager) — never accepts unit content from
  the caller (arbitrary unit = root). Idempotent install/enable/restart.
- ownership helper: add db-own + app-file <app> <relpath> actions
- run_privileged: runSocket / runSvc
- set_socket_permissions -> runSocket; webui_install_systemd -> runSvc
  (+ crontab cleanup runs as the manager directly, no sudo -u self)
- before_start: db chown -> runOwnership db-own; traefik cert/yml ->
  runOwnership app-file (retires updateFileOwnership/changeRootOwnedFile)
- init.sh installs all five helpers

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-24 18:28:56 +01:00
librelad
92c0261ca4 refactor(de-sudo): config-plane + permission helpers off raw sudo
config_scan_variables + config_check_missing operate on the manager-owned
configs_dir -> runInstallOp (test/cat/cmp/cp/mkdir). Container-path chmods in
before_start (traefik) + config.sh -> runFileOp. Fix the 'sudo sudo chown'
double in root_file.sh -> runSystem chown (ownership establishment).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-24 17:03:19 +01:00
librelad
8e0d662a16 refactor(perms): one source of truth for container ownership
The install/start paths and the switch reconcile managed /docker ownership
separately, so a fresh install produced different ownership than a post-switch
state — the root cause of the rootless 'touch: Permission denied' storm.

Consolidate onto the reconcile model:
- dockerContainerOwner(): single definition of the mode's container owner
  (rooted -> manager, rootless -> config-authoritative docker install user).
- reconcileContainersTopOwnership(): owns + makes traversable the structural
  containers/ top dir; now also run by the switch reconcile (previously only
  the install pass set it, so a rootless->rooted switch left it stale).
- reconcileWebuiDirOwnership(): now uses dockerContainerOwner.
- reconcileDockerOwnership(): calls both helpers.
- fixFolderPermissions(): slimmed to the +x traversal bits; its ad-hoc
  containers/ chown is now the shared helper.
- fixPermissionsBeforeStart(): drop changeRootOwnedFilesAndFolders (a
  pre-de-sudo band-aid that only fixed root-owned files and ran contrary to
  the don't-touch-third-party-data rule); reconcile the WebUI dir via the
  shared helper instead. Delete the now-unused root_files_folders.sh and
  regenerate the source arrays.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-24 13:46:12 +01:00
librelad
875a60f90f LibrePortal v0.1.0 — initial release
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>
2026-05-21 20:37:54 +01:00