457 Commits

Author SHA1 Message Date
librelad
853b489caa refactor(gluetun): move the network-routing feature into gluetun's folder
If it's gluetun code, it lives with gluetun. Both functions in
scripts/config/tags/processors/tags_processor_network_mode.sh manipulate gluetun
markers / gluetun's compose, so move them into containers/gluetun/scripts/
gluetun_network.sh and rename to the per-app-hook convention:

  tagsProcessorNetworkMode             -> appNetworkApplyMode_gluetun
  tagsProcessorGluetunForwardedPorts   -> appNetworkRegisterPorts_gluetun

Central call sites are now provider-agnostic — no "gluetun" literal anywhere:

- docker_config_setup_data.sh: an app routing via CFG_<APP>_NETWORK=<provider>
  triggers `appNetworkApplyMode_<provider>` + `appNetworkRegisterPorts_<provider>`
  via declare -F, so any future gateway provider plugs in with no engine edits.
- uninstall_app.sh: loops every `appNetworkRegisterPorts_*` hook (each self-skips
  when its provider isn't installed), so removing a routed app refreshes the
  right provider with no provider name in central code.

Delete tags_processor_network_mode.sh; regenerate arrays. Verified with stubs:
default mode no-ops, gluetun-routed app fires both hooks, gluetun itself is
skipped, unknown provider is silently no-op, uninstall loop calls registerPorts.

Drive-by cleanup: 9 stale "${X_scripts[@]}" array references in app_files.sh /
cli_files.sh (gluetun + headscale from this session's moves, plus 7 pre-existing:
command/ssl/swapfile/ufw/ufwd/user — all from older refactors that left them
behind). Each expanded to nothing at runtime (harmless), but they're dead
misleading refs. Cleaned both files; every remaining array ref now points to a
real files_*.sh.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-26 10:43:49 +01:00
librelad
3117203913 Merge claude/1 2026-05-26 01:29:37 +01:00
librelad
3be119af13 refactor(checks): data-driven app requirements (collapse per-service case arms)
The 5 service arms in appInstallCheckRequirements (traefik/gluetun/authelia/
headscale/prometheus) were identical _appReqServiceInstalled calls. Collapse them
into one generic default: any requirement naming a real container is a service
prerequisite — so a new service requirement now needs NO code here, just list it
in the app's CFG_<APP>_REQUIRES. domain + mail stay as their own special types; a
requirement that isn't a known app is still treated as a typo and ignored (safety
net preserved). Flavor messages kept via a small optional reason map
(_appReqServiceMsg); unknown-to-the-map services get a clean generic message.

Stays central (it's the requirements engine, not per-app logic) but is now
extensible without edits. Verified with stubs: met→rc0, absent service→flavor or
generic msg, brand-new container service→generic (zero code), typo→ignored.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-26 01:29:37 +01:00
librelad
8f02e3ff2e Merge claude/1 2026-05-26 01:25:34 +01:00
librelad
406ebf3bb9 docs(webui): fix stale comment naming (webuiGenerateGluetunProviders -> appWebuiRefresh_gluetun)
Caught in the final review — config-options.js referenced the pre-rename function
name. Comment-only fix.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-26 01:25:34 +01:00
librelad
a3b1db3251 Merge claude/1 2026-05-26 01:21:08 +01:00
librelad
7f797273dd refactor(wireguard): inline the host-conflict guard, drop central allowed_install
dockerCheckAllowedInstall was a one-app `case` whose only active caller was the
wireguard app itself — so inline its check (abort if a host WireGuard exists at
/etc/wireguard/params, which would collide on the wg kernel module + UDP 51820)
directly into containers/wireguard/wireguard.sh and delete
scripts/docker/app/checks/allowed_install.sh.

The protection is unchanged; wireguard is now fully self-contained and the last
app name leaves central install code. Regenerated arrays. (The only remaining
dockerCheckAllowedInstall references are in scripts/unused/ — retired apps,
never sourced.)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-26 01:21:08 +01:00
librelad
3f0f22cedb Merge claude/1 2026-05-26 01:07:14 +01:00
librelad
196b8e1dc8 refactor(traefik): per-app middleware hooks + moneyapp placeholder icon
Last app-specific bits out of central infra (from the per-app audit):

- traefik middleware: replace the hardcoded onlyoffice/owncloud exclude-list +
  onlyoffice-headers special-case (in traefik_middlewares.sh AND
  traefik_port_middlewares.sh) with two per-app hooks an app ships in
  containers/<app>/scripts/<app>_traefik.sh:
    appTraefikSkipsDefaultMiddleware_<app>  (marker: opt out of default@file)
    appTraefikExtraMiddlewares_<app>        (echo extra middleware entries)
  onlyoffice defines both; owncloud defines the skip marker. Two narrow hooks
  (not one clever one) so behavior — incl. the different onlyoffice-headers
  ordering between the two files — is preserved exactly. Verified with stubs:
  identical middleware strings across normal/onlyoffice/owncloud × authelia/wl.

- moneyapp: add a placeholder icon (geometric banknote SVG, 512x512) so it no
  longer falls back to default.svg in the WebUI.

Central traefik/compose code is now app-agnostic.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-26 01:07:14 +01:00
librelad
eb7060f450 Merge claude/1 2026-05-26 00:56:00 +01:00
librelad
34bd6d7936 feat(backup): kopia + borg system-config adapters (engine parity)
Mirror the restic system-config adapters for the other two engines, each in that
engine's own convention, so system backup/restore/status/retention work on any
location regardless of engine:

- kopia: BackupSystemToLocation (--tags system:config), SystemSnapshotsJson
  (filter tag system:config), RestoreSystemLatest, ForgetSystem (per-source policy
  on $configs_dir + maintenance).
- borg: BackupSystemToLocation (archive system-<host>-<ts>, comment system=config;
  no app is named "system" so the namespace can't collide), SystemSnapshotsJson
  (--glob-archives system-<host>-*), RestoreSystemLatest, ForgetSystem (prune the
  system-<host>-* glob).

No dispatcher change needed — engineBackupSystem/SystemSnapshotsJson/
RestoreSystemLatest/ForgetSystem already resolve <engine><fn> per location. All
three engines now define the full set; syntax clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-26 00:56:00 +01:00
librelad
49aa5f01f3 Merge claude/1 2026-05-26 00:48:18 +01:00
librelad
038d1c0729 fix(backup): system config in scheduled backups + retention (review findings)
Final-review gaps in the system-config backup:

1. Scheduled (cron) backups skipped it — backupScheduleEnabledApps only queued
   per-app backups, so the daily schedule never refreshed the system config (and
   thus the backup-location creds could go stale). Now it queues a
   `libreportal backup system` task (or runs inline on terminal-only installs),
   and skips the reproducible libreportal app for consistency with backupAllApps.

2. No retention on system snapshots — they bypass backupAppStart's per-app forget,
   so they accumulated unbounded. Add resticForgetSystem (tag system=config,
   respects append-only + the same keep-* policy) + engineForgetSystem dispatcher;
   backupSystemConfig now applies retention across all locations after snapshotting.

Verified with stubs: backupSystemConfig snapshots AND prunes on every location;
engineForgetSystem pairs with resticForgetSystem; scheduled createTaskFile call
matches the existing 3-arg signature.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-26 00:48:18 +01:00
librelad
95882ea7e6 Merge claude/1 2026-05-26 00:43:25 +01:00
librelad
9f37f7655d polish(webui): spacing + icon for the System config backup card; doc the status
- Add .backup-system-card { margin-top: 20px } — the card stands alone below the
  two-column cards row (which has no bottom margin), so it was butting against it.
- Add a server-stack icon to the card header (matches the nebula stroke-icon style).
- DEVELOPMENT.md: document the dashboard "System config" card + its last-backup
  status (tag system=config → `system` in the dashboard JSON), the CLI/auto paths,
  and that the libreportal app is excluded from the per-app grid.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-26 00:43:24 +01:00
librelad
1547d047c2 Merge claude/1 2026-05-26 00:38:39 +01:00
librelad
3283b3f7a3 feat(webui): track system-config backup status on the dashboard
Make the system config a tracked backup, not just action buttons:

- engine: resticSystemSnapshotsJson (tag system=config) + engineSystemSnapshotsJson
  dispatcher — query the system snapshots the way per-app status is queried.
- webui_backup_dashboard.sh: emit a "system": { latest_snapshot, latest_time }
  object (latest system snapshot on the primary location), and exclude the
  libreportal WebUI app from the per-app grid (it's intentionally not backed up, so
  it no longer shows a perpetual "No backup yet" tile).
- backup dashboard card: a status line (dot + "Last backed up <relative>" / "No
  backup yet"), populated in renderDashboard from d.system — mirrors the app tiles.

Verified: shell + JS parse; dashboard content assembles to valid JSON with the
system key; engine query defined + dispatched; frontend reads d.system into the
#backup-system-status element.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-26 00:38:39 +01:00
librelad
914185d42d Merge claude/1 2026-05-26 00:31:23 +01:00
librelad
c2c10103b8 feat(webui): surface system-config backup/restore on the backup dashboard
Add a "System config" card to the backup dashboard with two actions wired through
the task processor (same path as "Backup all apps"):

- "Back up now"  -> libreportal backup system
- "Restore…"     -> libreportal restore system  (confirm dialog explains it lands
  in a staging folder and never overwrites live config)

Card copy explains why it matters (the backup-location creds otherwise live only on
the box). Click handlers + runBackupSystem/confirmRestoreSystem added; JS parses,
data-actions match handlers, commands match the CLI subcommands.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-26 00:31:23 +01:00
librelad
88f6ce7820 Merge claude/1 2026-05-26 00:27:25 +01:00
librelad
839cf3561a feat(cli): backup system / restore system subcommands
Expose the system-config backup on demand (not just within 'backup all'):

- `libreportal backup system`      -> backupSystemConfig (snapshot the system
  config — settings, WebUI creds, backup-location creds — to all enabled locations)
- `libreportal restore system [loc_idx]` -> backupRestoreSystemConfig (restore the
  latest system snapshot into a staging dir; never overwrites live config)

Distinct from the existing 'restore migrate system' (which restores all *apps*
from another host). Help text updated for both. Routing verified with stubs.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-26 00:27:25 +01:00
librelad
f2dc3f27d9 Merge claude/1 2026-05-26 00:20:31 +01:00
librelad
fe770ae699 feat(backup): system-config snapshot + skip the reproducible WebUI; reserved-name docs
(a) Docs: reserve tools/ scripts/ resources/ as LibrePortal folder names (apps must
not bind-mount to them); document resources/ as the home for nest-able data AND for
.sh payloads that execute on load (vs scripts/ for sourced functions); document the
backup model (what's captured vs reproducible).

(b) System-config backup so a bare-metal restore is self-sufficient — this is why
the system root is its own tree. New scripts/backup/system/backup_system.sh:
- backupSystemConfig snapshots <system>/configs (global settings, WebUI creds, and
  the BACKUP-LOCATION creds — otherwise the keys to reach your own backups live only
  on the box) to every enabled location. Lightweight static-dir snapshot — it does
  NOT go through backupAppStart (no containers to quiesce / DBs to dump).
- restic adapter resticBackupSystemToLocation (tag system=config) + dispatcher
  engineBackupSystem; restore via resticRestoreSystemLatest / engineRestoreSystemLatest
  + backupRestoreSystemConfig (restores to a STAGING dir — never auto-overwrites
  live config).
- backupAllApps runs it after the app loop.

WebUI exclusion: backupAllApps skips the 'libreportal' app — its frontend + generated
JSON regenerate, and its only state (the login) is in the system config now captured
above. Nothing in its data dir warrants a snapshot.

Verified with stubs: app loop skips libreportal + invokes the system backup; the
system backup dispatches to both locations; backup/restore function names pair with
the dispatcher. NOTE: restic-only (the sole live engine adapter); end-to-end repo
round-trip still needs a live box before being relied on.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-26 00:20:31 +01:00
librelad
5ae9a3ae38 Merge claude/1 2026-05-26 00:00:55 +01:00
librelad
3e10dc99b4 refactor(headscale): flatten + move headscale into its app folder
Move the whole central scripts/headscale/ tree into containers/headscale/, the
last app-specific dir living centrally:

- 11 sourced function files (incl. the former local/ remote/ subdirs) flattened
  into containers/headscale/scripts/ — flat because the container scan is
  maxdepth 3, so one subfolder level is the limit; basenames already encode the
  local/remote distinction.
- tailscale.sh is a CONTAINER PAYLOAD (ends in a bare `install_tailscale` call,
  runs apt/curl) — it must never be sourced into the manager, so it goes to
  containers/headscale/resources/ (pruned by the scan), NOT scripts/. Verified
  install_tailscale does not leak into the runtime after sourcing.
- Fix tailscaleInstallToContainer to copy the payload from its new resources/
  path (it previously referenced ${install_scripts_dir}tailscale.sh, which never
  matched the file's actual location) and drop the dead commented docker-cp line.
- Remove the now-moot headscale special-case from generate_arrays.sh; regenerate
  (files_headscale.sh drops — headscale is fully container-scanned now).

All 11 functions source + define cleanly; callers resolve by name regardless of
location.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-26 00:00:55 +01:00
librelad
727734dbfa Merge claude/1 2026-05-25 23:52:53 +01:00
librelad
d2595c3ef6 refactor(apps): per-app compose-tag hooks (remove the central App-Specific ladder)
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>
2026-05-25 23:52:53 +01:00
librelad
69641dafd9 Merge claude/1 2026-05-25 23:48:47 +01:00
librelad
8670a02c00 refactor(gluetun): collapse to one function name for the refresh hook
Drop the appWebuiRefresh_gluetun -> webuiGenerateGluetunProviders wrapper; rename
the function itself to appWebuiRefresh_gluetun and point the installer + the
gluetun_refresh_providers tool at it. One name, no indirection.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-25 23:48:47 +01:00
librelad
e24927ee6f Merge claude/1 2026-05-25 23:44:42 +01:00
librelad
3e6bb565e0 refactor(apps): modularize the gluetun providers generator via a per-app refresh hook
Move scripts/webui/data/generators/apps/webui_gluetun_providers.sh ->
containers/gluetun/scripts/gluetun_providers.sh and replace the gluetun-specific
gated call in webui_updater.sh with a generic per-app loop: an installed app may
define appWebuiRefresh_<app> (in its scripts/) for data it wants refreshed on
every WebUI update. gluetun provides appWebuiRefresh_gluetun (a thin wrapper over
webuiGenerateGluetunProviders).

- No gluetun-specific code remains in central WebUI code — it's a true drop-in.
- Install gate preserved + generalized: the loop iterates the manager-owned
  install templates (listable) and tests each app's live compose directly (works
  without list perm on the container-user data dir), so non-users never pay for it.
- webuiGenerateGluetunProviders keeps its name (still called by the installer and
  the gluetun_refresh_providers tool); now sourced via the container scan.
- Regenerate arrays (generator drops out of files_webui).

Loop verified with stubs: only installed apps with a defined hook fire; apps
without a hook are skipped; nothing fires when nothing's installed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-25 23:44:42 +01:00
librelad
d4778126e4 Merge claude/1 2026-05-25 23:38:19 +01:00
librelad
98e1a0a05d refactor(apps): per-app post-install hooks + move gluetun/crowdsec logic into their apps
Replace the central app-name if-ladder in app_update_specifics.sh with a generic
dispatcher: each app ships containers/<app>/scripts/<app>_update_specifics.sh
defining appUpdateSpecifics_<app> (live-sourced by the container scan, dispatched
by `declare -F` — same pattern as tools). A hook may set shouldrestart=true. Apps
with no specifics ship no hook.

- Move the adguard/pihole (DNS updater), dashy (conf refresh), focalboard (nobody
  ownership + restart), and libreportal (webui regen) branches to per-app hooks.
- Move scripts/gluetun/gluetun_route_apps.sh -> containers/gluetun/scripts/
  (scripts/gluetun/ removed).
- Move scripts/install/install_crowdsec.sh -> containers/crowdsec/scripts/
  crowdsec_install_host.sh; fix the path note in crowdsec.sh.
- Regenerate arrays (moved files drop out; the per-app files are container-scanned,
  not arrayed).

Dispatch verified with stubs: adguard/pihole/dashy/focalboard/libreportal behave
identically to the old ladder (incl. shouldrestart propagation), apps without a
hook are a clean no-op. The CLI itself had no per-app branches — app-specific CLI
is already the (now fully modular) tools system.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-25 23:38:19 +01:00
librelad
e944e4b92c Merge claude/1 2026-05-25 23:27:44 +01:00
librelad
8cdf5fb294 revert(footprint): drop the libreportal.service rename
The rename was justified partly by an anticipated second `libreportal-regen`
unit — which we then decided not to create (the poll rides the existing task
processor). What's left is cosmetic, and it isn't worth a footprint_version bump
(which forces a root re-install on every existing box) plus the dual-name
migration cruft.

Reverting also means the rename was the ONLY footprint change in the regen work,
so the whole regen system now ships as a plain manager-owned code deploy — no
root re-install needed. footprint_version stays 2.

Kept only the accurate FOOTPRINT.md note that the service also drives the poll.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-25 23:27:44 +01:00
librelad
2e17a5557c Merge claude/1 2026-05-25 23:23:18 +01:00
librelad
bd1f9455ce refactor(footprint): rename libreportal.service -> libreportal-taskprocessor.service
The single systemd unit is the task processor (and now also drives the periodic
regen poll), so name it for what it does instead of the ambiguous bare
"libreportal.service" — clearer now that the runtime has more than one concern.

- svc helper: SERVICE_NAME=libreportal-taskprocessor.service; _drop_legacy()
  stops/removes the pre-rename unit on install (idempotent migration) so an
  upgraded box never runs two processors.
- init.sh: read baked roots from the new unit (fall back to the old name);
  uninstall removes both names; bump footprint_version 2 -> 3 (root-owned unit
  changed, so a manager-run update flags "root re-install needed").
- check_webui_systemd: accept either name during the transition.
- docs/FOOTPRINT.md: new unit name + uninstall command.

No sudoers change — it allows /usr/bin/systemctl generically, not a named unit.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-25 23:23:18 +01:00
librelad
7cf0bcf678 Merge claude/1 2026-05-25 23:20:02 +01:00
librelad
899e04bcd3 feat(regen): unified regeneration front door + self-heal poll
Add `lpRegen` (scripts/webui/webui_regen.sh) — one entry point that rebuilds the
file-derived artifacts whose sources changed, so callers don't have to know which
generator owns what. Self-heal is a cheap `find -newer` mtime compare (no watcher
/ daemon): a stage runs only when a source is newer than its artifact, or --force.

- `libreportal regen [all|webui|arrays] [--force]` CLI command (new category).
- Task processor idle tick runs a throttled `regen webui` poll, so an app dropped
  in out-of-band (drag-drop / marketplace) appears on its own — no manual command,
  no inotify (works on the relocatable/external-drive roots where inotify can't).
- make_release.sh guards against shipping stale source arrays (regenerate; abort
  if the committed tree was out of date), killing the "forgot generate_arrays" bug
  class at the build boundary.
- Document the front door in DEVELOPMENT.md.

webui scope rebuilds from containers/<app>/{*.config,tools/*.tools.json}; arrays
scope from scripts/** (a dev/build concern — a no-op on a normal install). Gate
logic verified in a sandbox (clean/config-newer/tools-newer/force/missing).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-25 23:20:02 +01:00
librelad
9279910e84 Merge claude/1 2026-05-25 22:45:33 +01:00
librelad
898068a390 refactor(apps): make app tools + helpers fully self-contained per app
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>
2026-05-25 22:45:33 +01:00
librelad
7204be3aff Merge claude/1 2026-05-25 22:33:58 +01:00
librelad
2d5fdd5326 docs(dev): document the self-contained per-app tools convention
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-25 22:33:58 +01:00
librelad
49361c3874 Merge claude/1 2026-05-25 22:30:49 +01:00
librelad
3bc91eef55 refactor(tools): modular per-app tools convention (containers/<app>/tools/) + migrate adguard
Establish the self-contained tools convention and prove it on a core app:
- discovery now reads containers/<app>/tools/<app>.tools.json (the tools/ subfolder);
  tool functions live at containers/<app>/tools/*.sh, auto-sourced by the container
  scan (depth 3) — no scripts/app/ entry, no array regen.
- adguard migrated: its 2 Tools-tab actions (reset_password, apply_dns_updater) moved
  to containers/adguard/tools/ + tools/adguard.tools.json, and dropped from the
  central webui_tools.sh heredoc. adguard_auth.sh stays in scripts/app/ — it's a logic
  helper, NOT a tool (the key distinction: only DECLARED tools move).

Central + per-app styles coexist (pihole etc. still central), so the remaining apps
can migrate one at a time with nothing breaking. Verified: heredoc valid sans adguard,
per-app merge re-adds adguard's 2 tools, scripts array dropped the moved fns.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-25 22:30:49 +01:00
librelad
7213322cf3 Merge claude/2 2026-05-25 22:14:58 +01:00
librelad
3adc960c00 chore: untrack stale site/ build artifacts swept in by mistake
The previous commit accidentally tracked site/dist/** and the generated
site/src/_data/*.json — leftover Eleventy build output from the legacy site/
location (the active site now lives in containers/weblibreportal). They are
generator artifacts, not source. Untrack them (kept on disk) and gitignore so
they can't be swept in again.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-25 22:14:58 +01:00
librelad
4fe1dea847 Merge claude/1 2026-05-25 22:13:54 +01:00
librelad
7bed2de2d2 feat(tools): auto-discover per-app <app>.tools.json (drop-in tool registration)
webui_tools.sh now merges any containers/<app>/<app>.tools.json into apps-tools.json
(jq, sets .apps[<app>]) on top of the central heredoc. So a dropped-in app — e.g.
from LibrePortal-Infra — registers its own Tools-tab actions WITHOUT editing this
file. Combined with the container scan already sourcing containers/<app>/*.sh live,
an app can now be fully self-contained (install fn + tool fns in <app>.sh + tool
declarations in <app>.tools.json) → true copy-on-top deploy, no array regen, no
central edits. Core apps in the heredoc are unaffected; invalid tools files are
skipped with a notice. Verified the merge (drop-in registers, core preserved).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-25 22:13:54 +01:00
librelad
1efb5cf772 Merge claude/2 2026-05-25 22:12:50 +01:00