The hardcoded uid:gid in libreportal.backup.files labels was brittle: matched the
default PUID in the compose, but a PUID change (or new image version) would drift
silently and the next restore would chown to a stale owner. Make it impossible to
drift by letting the engine learn the uid at capture time.
backup_files.sh:
- After a successful tar capture, run `stat -c '%u:%g'` inside the container and
write the result to a <host_subdir>.lp-owner sidecar in staging. The sidecar
rides in the snapshot alongside the captured tree.
- Restore reads it back when the descriptor doesn't pin uid:gid; falls back to
0:0 with a clear notice if missing.
- The 5-field form (with explicit uid:gid) is still supported as an override; it
wins and skips the sidecar write entirely.
Update all 4 current labels to the new 3-field form
"<container>:<container_path>:<host_subdir>" (nextcloud, bookstack, gitea,
owncloud). Engine handles both formats during the transition.
Verified with stubs: 3-field capture writes the sidecar with the discovered
33:33; restore reads it back; 5-field override correctly skips the sidecar
write. backup_files.sh parses.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
Three closeouts in one pass:
1. DEVELOPMENT.md — consolidated hook-conventions table covering all 8 per-app
hook types (tools / update-specifics / compose-tags / webui-refresh / the two
traefik markers / the two network-provider hooks). One place to look instead
of inferring from the codebase.
2. Nextcloud APCu wired alongside Redis: appUpdateSpecifics_nextcloud now sets
memcache.local=\OC\Memcache\APCu too (was deferred from the fpm switch). APCu
= cheap in-process cache; the fpm-alpine image ships the extension. CLI mode
may emit a harmless "no memory cache" notice on `occ` runs — Nextcloud is
graceful, the FPM worker still uses APCu fine.
3. Container-side file-capture rollout to 3 confident cases:
- bookstack: lscr.io/linuxserver/bookstack with PUID=1000 → /config (1000:1000)
- gitea: gitea/gitea with USER_UID=1000 → /data (1000:1000)
- owncloud: owncloud/server (Apache/PHP) → /mnt/data (33:33, www-data)
Snapshots are now complete for these (the dir's excluded from the raw restic
pass and captured live through the container as a tar → libreportal-owned
staging, same proven pattern as Nextcloud). Less-evidenced candidates left
for live verification: linkding, mastodon, jellyfin, trilium, focalboard,
invidious, vaultwarden, headscale-service — each needs its in-container uid
confirmed before labeling (wrong uid won't break backup, but restore would
chown to the wrong owner).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
Drop Apache+mod_php for the actual performance win — nginx + PHP-FPM — without
the LinuxServer image cascade (custom auto-install, /custom-cont-init.d, abc-vs-
www-data rewrites in the auth adapter + every tool, HTTPS-by-default quirks).
The official fpm-alpine image keeps env-var auto-install and the www-data user,
so the auth adapter, all tools, and the compose-tags hook keep working unchanged.
- Compose: nextcloud-service is now fpm-alpine (still container_name=nextcloud-
service so docker exec ... nextcloud-service php occ in the auth adapter is
untouched). New nextcloud-web nginx sidecar serves :80 over the shared ./html
volume, terminating FastCGI to nextcloud-service:9000. Traefik labels + PORTS_
TAG_1 move to nextcloud-web (the HTTP face); backup.files stays on -service
(the file-owning brain). nextcloud-db + nextcloud-redis unchanged.
- resources/nginx.conf: Nextcloud's recommended nginx config, trimmed for
behind-Traefik (no TLS), large-upload + caldav/carddav/.well-known redirects.
- scripts/nextcloud_update_specifics.sh: NEW post-install hook —
appUpdateSpecifics_nextcloud waits for first-boot occ install to complete
(config.php + occ status=installed), then wires Redis as memcache.distributed
+ memcache.locking via occ config:system:set. Idempotent.
Auto-install is unchanged (official image's NEXTCLOUD_ADMIN_USER + MYSQL_* env
flow). Redis caching now actually USED by Nextcloud (previously the container
was up but config.php had no memcache config). Container-side backup capture
still the right answer for the perm boundary — image change doesn't affect it.
Verified statically: yaml structure, hook parses + dispatches + has the right
graceful-timeout fallback when occ isn't reachable. Live verification (sync
performance + actual Redis hit rate + traefik proxy of FastCGI) needs a fresh
install on a throwaway box.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
docker_config_setup_data.sh's "App Specific" if/elif ladder (pihole, nextcloud,
searxng, speedtest, vaultwarden, wireguard, gluetun) becomes a generic hook
dispatch: an app needing computed (non-CFG) compose tags ships
containers/<app>/scripts/<app>_compose_tags.sh defining appSetupComposeTags_<app>
(live-sourced by the container scan, called with the compose path; reads
host_setup/public_ip_v4/CFG_* from scope). Same declare -F pattern as the tool /
update-specifics / webui-refresh hooks.
- 7 per-app hook files added; central ladder replaced by the dispatch.
- The generic gluetun network-mode block stays (any app may route through gluetun);
tagsProcessorGluetunForwardedPorts stays central (hook + network-mode both use it).
- Regenerate arrays (hooks live under containers/, not arrayed).
Verified with stubs: each hook emits exactly the tags the old branch did
(pihole REV_SERVER, nextcloud trusted-domains, gluetun VPN set + forwarded ports,
etc.); apps without a hook are a clean no-op.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
Each app now carries everything under containers/<app>/: Tools-tab actions in
tools/ (declaration <app>.tools.json + function <app>_<tool_id>.sh) and logic
helpers in scripts/ (e.g. <app>_auth.sh). The container scan live-sources every
.sh under the app (maxdepth 3, prunes only resources/) and webui_tools.sh
auto-merges the .tools.json, so an app is a true drop-in — no central edit, no
array regen.
- Empty the central webui_tools.sh heredoc; all 34 tools across 11 apps now
come from per-app declarations (verified byte-identical to the old output).
- Retire the orphaned mattermost tool scripts to scripts/unused (there is no
containers/mattermost; its install fn already lived in unused).
- Update the dispatch comment/error path, the auth-adapter doc, and
DEVELOPMENT.md to the new convention.
- Regenerate static arrays (files_app.sh no longer lists app/containers/*).
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 CFG_<APP>_BACKUP_STRATEGY (default auto) so an app's backup strategy can
be overridden from its Advanced config tab, taking precedence over the global
default. Added to the 10 live-capable apps, so the dropdown's 'live' option only
appears where it actually works.
- backupResolveStrategy now checks the per-app override before the global value.
- backupAppLiveCapable / backupAppStrategyOptions expose capability + the valid
option set; predicate helpers hardened with explicit returns so they behave
identically with or without shell errexit.
- BACKUP_STRATEGY field mapping (select, advanced) renders the dropdown.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
- Add libreportal.backup.db labels to the MariaDB/Postgres apps (nextcloud,
owncloud, bookstack, mastodon, invidious) so they back up live + consistent.
- If a declared dump cannot be taken (DB down, wrong path), the backup falls
back to stop-snapshot-start for that run instead of snapshotting torn data —
a misconfiguration degrades to 'safe with downtime', never to 'unsafe'.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
The static per-app CFG_<APP>_HOST_NAME is gone. host_setup (the app's
canonical FQDN, feeding the legacy single DOMAINSUBNAME_DATA used by app env
vars, the app URL and trusted-domains) is now derived from the app's primary
Traefik port's subdomain: first recommended port, else first Traefik port;
@/root -> apex, set -> sub.domain, empty -> app-name. Removes HOST_NAME from
all app configs, the config-form field mapping (Hostname), the dead
headscale stub, and wireguard.sh (now uses host_setup). Completes the move to
dynamic per-port subdomain routing.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
Replace the static one-host-per-app model with per-port routers: each
Traefik-managed port carries a subdomain (12-col PORT format) and gets a
DOMAINSUBNAME_TAG_<n> host, so one container can serve unlimited hosts.
tagsProcessorPortSubdomains stamps per-port hosts (subdomain @/empty = apex,
multi-level allowed); tagsProcessorPortRouterBlocks comments out
# TRAEFIK_PORT_<n>_BEGIN/END blocks for non-Traefik ports so unfilled
placeholders never ship (mirrors GLUETUN_OFF). Convert all 27 router apps
(subdomains seeded from HOST_NAME; headscale admin. prefix -> subdomain).
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>