432 Commits

Author SHA1 Message Date
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
librelad
16eda07b3d fix(webui): make SSH Access page full-width like config/admin pages
The SSH Access page was boxed to max-width 860px and centered, unlike the
Overview and System admin pages (.admin-page) which span the full content
width. Drop the cap and match .admin-page padding so /admin/tools/ssh-access
looks like the rest of the Admin area.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-25 22:12:50 +01:00
librelad
f2acb36a35 Merge claude/1 2026-05-25 21:15:38 +01:00
librelad
ef67ab9b71 refactor(infra): move hosting apps out to LibrePortal-Infra
getlibreportal (downloads host) + weblibreportal (website) — including the website
Eleventy source and the publish tool functions — now live in the separate
LibrePortal-Infra repo (Webstar/LibrePortal-Infra). They're the project's own
outward-facing hosting, not something users install, so the base stays clean.

Removed from base: containers/{getlibreportal,weblibreportal}, the
scripts/app/containers/<app>/<app>_publish.sh tool functions, and their entries in
webui_tools.sh; regenerated the sourced-file arrays; dropped the dead .gitignore
docroot lines. scripts/release/make_release.sh stays here (it builds the base
release). docs/DEVELOPMENT.md now points publishing at LibrePortal-Infra.

LibrePortal-Infra overlays onto an install and picks up releases/catalogue from the
base tree — see its README.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-25 21:15:38 +01:00
librelad
f0f8416c93 Merge claude/1 2026-05-25 21:02:53 +01:00
librelad
8800f524d4 feat(tools): WebUI/CLI publish tool for getlibreportal + weblibreportal
Surface the publish step through the existing Tools system (apps-tools.json -> Tools
tab + 'libreportal app tool <app> publish'), so the docroot can be (re)built from
the WebUI instead of a manual cd + script.

- webui_tools.sh: declare a 'publish' tool (no inputs) for getlibreportal + weblibreportal.
- scripts/app/containers/getlibreportal/getlibreportal_publish.sh (appGetlibreportalPublish):
  runs the host's publish.sh into the served data dir, as the container user (owns it).
- scripts/app/containers/weblibreportal/weblibreportal_publish.sh (appWeblibreportalPublish):
  builds Eleventy as the manager (owns the install tree), then syncs the result into
  the container-user-owned docroot — handling the build-vs-write owner split.
- Both guard for the build prerequisites (repo source / npm / dist) and fail with a
  clear message; regenerated the sourced-file arrays.

Honest status: scaffolding only — wiring verified (dispatch names match, files sourced,
JSON valid) but the end-to-end tool RUN is untested, and it's build-box-only (needs the
repo checkout + npm + a built dist/). These hosting apps are dev-only and headed for a
separate repo; this just sets the automation up so it's ready to iterate on.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-25 21:02:53 +01:00
librelad
0e3a3d76ed Merge claude/1 2026-05-25 20:44:38 +01:00
librelad
ef100acb3c refactor(hosting): website -> containers/weblibreportal; getlibreportal = downloads-only
Move the loose root-level site/ into a proper containers/weblibreportal app
(mirrors getlibreportal): the Eleventy source + nginx serving ./data via publish.sh
(npm run build -> docroot). Fix gen-data.mjs repoRoot (now ../../.. from
containers/weblibreportal/scripts) so it still finds containers/ for the catalogue.

Decouple the two hosts:
- weblibreportal  -> the website (libreportal.org)
- getlibreportal  -> downloads only (install.sh + signed release channels); its
  publish.sh no longer builds the site, and its config text updated to match.

Both are dev-only project hosting and will move to a separate repo later; for now
they live under containers/ as normal apps. ignores updated for their built
docroots; dropped the dead 'site export-ignore'.

Verified: gen-data builds the catalogue from the new location (33 apps), and
weblibreportal/publish.sh produces a docroot with index.html.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-25 20:44:38 +01:00
librelad
ca01fd503f Merge claude/1 2026-05-25 20:02:47 +01:00
librelad
63b53c7751 feat(hosting): getlibreportal as a first-class LibrePortal app (phase E)
Redo the download/website host as a normal app under containers/ (dogfooded — the
project hosts its own downloads on LibrePortal), instead of the bespoke repo-root
thing. Modeled on speedtest: standard getlibreportal.{config,sh,svg} +
docker-compose.yml (tagged template) so it plugs into the app scan + install
dispatch (installGetlibreportal) like every other app. nginx serves ./data (the
app data dir) — no special /web.

- getlibreportal.config: features category, public (login=false — it's a download
  host), no backup (regenerable), healthcheck on.
- docker-compose.yml: nginx:alpine, ./data:ro docroot + ./nginx.conf, traefik tags.
- nginx.conf: install.sh + latest.json no-cache; tarball/.sha256/.minisig immutable.
- publish.sh: assembles the docroot (built site + install.sh + dist/<channel>) into
  a target data dir; run on a full repo checkout (site/ + dist/ are host-side).
- exec bits set on the run-directly scripts (make_release.sh, install.sh, publish.sh).
- .gitattributes: dropped the stray 'getlibreportal export-ignore' (the no-slash
  pattern would also have excluded containers/getlibreportal — the app must ship);
  data/ gitignored.

Verified: app discovered by the site catalog (32 apps), installGetlibreportal matches
the dispatch name, and the full release->publish flow yields a docroot with the
website + install.sh + the signed/checksummed stable channel. The actual app-install
run + DNS/TLS for get.libreportal.org are operational steps (need a real host).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-25 20:02:47 +01:00
librelad
b9daae36e6 Merge claude/1 2026-05-25 19:40:30 +01:00
librelad
5700f78c6b feat(release): minisign signature signing + verification
The sha256 only proves a download is intact; a compromised host could swap the
tarball + its checksum. Add minisign signatures, which prove authenticity (the host
can't forge them without the offline secret key). Ships INACTIVE behind a REPLACE_ME
placeholder, so installs work until a real key is generated; then it's REQUIRED.

- make_release.sh: signs the tarball when LP_MINISIGN_SECKEY is set -> <tarball>.minisig.
- libreportal.pub: the public key (placeholder), ships in the tarball and is installed
  to the ROOT-OWNED footprint (/usr/local/lib/libreportal/libreportal.pub) by init.sh
  -> the manager can't swap it to accept forged updates. footprint_version -> 2.
- install.sh: LP_MINISIGN_PUBKEY constant; once non-placeholder, downloads + verifies
  the .minisig (minisign -P) and REFUSES on invalid/missing (auto-installs minisign if
  needed). --no-verify-signature is a dev-only escape hatch.
- fetch.sh (update path): verifies against the footprint .pub (minisign -p), refuses on
  invalid/missing.
- docs/DEVELOPMENT.md: keygen (minisign -G), paste pubkey into libreportal.pub +
  install.sh, keep the secret key offline, sign builds via LP_MINISIGN_SECKEY, bump
  footprint_version on key rotation.

Verified end-to-end with a real throwaway key: good signature accepted; tampered,
wrong-key, and missing-signature all refused; placeholder skips (sha256 still enforced).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-25 19:40:30 +01:00
librelad
d0162ccda1 Merge claude/1 2026-05-25 19:07:16 +01:00
librelad
3014965b66 feat(update): FOOTPRINT_VERSION drift detection — flag when a root re-install is needed
A manager-run 'update apply' refreshes code/apps/WebUI but CANNOT rewrite the
root-owned footprint (helpers/wrapper/uninstall/unit/sudoers) — that immutability
is the de-sudo boundary. Previously a release that changed those would silently
leave them stale. Make it explicit:

- init.sh: footprint_version=1 constant, baked at install into
  /usr/local/lib/libreportal/.footprint_version (root:root 0644) by initRootHelpers.
  Bump it whenever a root component changes.
- make_release.sh: publishes footprint_version in latest.json.
- fetch.sh: lpInstalledFootprintVersion (marker) + lpReleaseLatestFootprint (manifest).
- check_update.sh: 'update apply' REFUSES when the release's footprint_version
  exceeds the installed one, directing to a root re-install (which fetches +
  re-bakes everything atomically). No half-applied updates.
- webui_system_update.sh: badge sets footprint_update_needed + clears can_update so
  the WebUI won't offer a one-click apply for a footprint-bumping release.
- docs/DEVELOPMENT.md: the bump rule + the footprint exception explained.

Verified: manifest carries footprint_version; drift decision correct both ways
(no marker/older -> needs re-install; equal -> no drift).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-25 19:07:16 +01:00
librelad
d95560c48c Merge claude/1 2026-05-25 18:59:38 +01:00
librelad
f2c2b0485a refactor(uninstall): drop repo uninstall.sh; init.sh generates the command
Remove the redundant repo-root uninstall.sh (it duplicated libreportal-uninstall).
init.sh now GENERATES the libreportal-uninstall launcher into the fixed footprint
(/usr/local/lib/libreportal/uninstall.sh + the /usr/local/bin symlink) — same
pattern as the CLI wrapper, so the on-box command survives without a separate repo
file. The launcher just runs the engine's uninstall ($script_dir/init.sh baked in,
/root/init.sh fallback).

This resolves the install/uninstall asymmetry: a bootstrap (install.sh) exists only
because install faces a bare box with no code yet; uninstall always runs the engine
that's already installed, so it needs no bootstrap — just a generated door into
init.sh. Repo root install/uninstall surface is now init.sh + install.sh.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-25 18:59:38 +01:00
librelad
94d91ff551 Merge claude/1 2026-05-25 18:50:26 +01:00
librelad
8472fdf0ae feat(uninstall): location-agnostic 'libreportal-uninstall' command (+ de-hardcode docs)
The docs were telling users to run /libreportal-system/install/uninstall.sh — a
hardcoded data path, wrong for any custom --system-dir, contradicting the whole
relocatable design.

Fix it the way the CLI already works: install uninstall.sh to the FIXED footprint
(/usr/local/lib/libreportal/uninstall.sh) and symlink it onto $PATH as
'libreportal-uninstall' (initLibrePortalCommand). It self-resolves the real data
roots from the systemd unit, so the command is the same everywhere regardless of
where data lives. Teardown removes the new symlink; FOOTPRINT.md lists it.

Docs now say 'sudo libreportal-uninstall' — no data path. (Dev-from-clone still
uses ./uninstall.sh / ./init.sh uninstall.)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-25 18:50:26 +01:00
librelad
55dd62af56 Merge claude/1 2026-05-25 18:46:05 +01:00
librelad
cb298d41e6 docs(user): point uninstall at ./uninstall.sh (handles custom roots)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-25 18:46:05 +01:00
librelad
45626d0641 Merge claude/1 2026-05-25 18:45:16 +01:00
librelad
805d557fd7 fix(uninstall): resolve the real install roots + add ./uninstall.sh
Bug: runFullUninstall used the derived $docker_dir/$containers_dir/$backup_dir,
but a bare 'init.sh uninstall' on a CUSTOM-location install has no LP_*_DIR in
scope and no /docker marker — so it defaulted to /libreportal-* and would MISS the
real data (e.g. /mnt/ssd), leaving it behind.

Fix: libreportalReadBakedRoots reads the authoritative baked record from the
systemd unit (Environment=LP_SYSTEM_DIR/CONTAINERS_DIR/BACKUPS_DIR + User=<manager>)
and runFullUninstall re-derives from it before removing anything. Legacy units
(no LP_*_DIR) fall through to the derive defaults + /docker compat shim.

Add top-level uninstall.sh: a root-only convenience that finds the installed
init.sh (via the unit's system root, then common locations) and runs it —
'sudo ./uninstall.sh [--skip-docker-images]'. Verified the unit parsing extracts
custom roots/manager and the discovery picks the right init.sh (without running the
destructive teardown).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-25 18:45:16 +01:00
librelad
800c70fefd Merge claude/1 2026-05-25 18:40:03 +01:00
librelad
40aa6d9d1a docs: move dev/reference docs into docs/ + refresh FOOTPRINT for the 3-root layout
Tidy the repo root (README + LICENSE stay there per convention; everything else
moves):
- CONTRIBUTING.md, PROMISE.md, FOOTPRINT.md -> docs/ (alongside USER.md/DEVELOPMENT.md)
- update the references: README links, the website site.json raw URLs, init.sh's
  'see FOOTPRINT.md' comments -> docs/FOOTPRINT.md; drop the now-redundant
  CONTRIBUTING.md export-ignore (docs/ is already export-ignored).

Refresh FOOTPRINT.md: it claimed 'everything lives under /docker', which is no
longer true. Now describes the three relocatable roots (system/containers/backups)
and makes explicit that the roots + manager name are baked into the helpers/unit/
wrapper at install (the privilege boundary) while this out-of-root footprint stays
fixed by design. Uninstall sketch + sudoers/unit rows updated for the configurable
manager. CONTRIBUTING/PROMISE were already current — left as-is.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-25 18:40:03 +01:00
librelad
846936617e Merge claude/1 2026-05-25 18:27:59 +01:00
librelad
a48a241fbe docs: add docs/USER.md + docs/DEVELOPMENT.md (+ README pointer)
Two guides covering what wasn't written down:
- USER.md: install (the install.sh one-liner), placing the three roots on separate
  disks/external drives, channels, updating, backups (REQUIRE_MOUNT), uninstall.
- DEVELOPMENT.md: the install-mode/roots/users model + key files; running a dev copy
  (local/git); cutting stable/edge releases (bump VERSION -> make_release.sh ->
  dist/<channel>/{tarball,.sha256,latest.json} -> publish); testing a release
  locally via LP_RELEASE_BASE_URL + python3 -m http.server (incl. checksum-refusal);
  how release updates work; conventions.

README Quick start updated to the release flow + a docs pointer. docs/ is
export-ignored so it doesn't bloat release tarballs.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-25 18:27:59 +01:00