5 Commits

Author SHA1 Message Date
librelad
536b5b8e10 restore: stop the first-run bulk restore from silently dropping most of the host
An end-to-end run restored 4 of 13 apps and reported
"First-run restore complete — 4 apps restored" as a success.

Two truncations, and fixing the first had hidden the second:

  * the CLI dispatcher calls handlers with no arguments, so "$@"/shift inside
    one operate on an empty list. Fixed earlier with LP_CLI_ARGS.
  * LP_CLI_ARGS was built from start.sh's "$@" — but the root wrapper invokes
    start.sh with exactly nine hardcoded positional slots. So the array could
    never hold more than nine entries, and `${LP_CLI_ARGS[@]:5}` yielded at
    most four app names.

The wrapper now forwards the real argv after those nine slots (they stay
untouched: every dispatcher reads them, and unset ones must keep arriving as
the literal "empty"), and start.sh reads it back as "${@:10}". Verified: a
preflight given six apps checks six, where five was the previous ceiling.
footprint_version 7 -> 8, since the wrapper is root-owned and baked at install.

Two further fixes so a truncation cannot pass as success again:

  * restoreFirstRunBulk with no app list is now a whole-host restore — it
    discovers the host's apps and re-applies the preflight. The installer's
    report runs in its own process, so without this an app the user was told
    would be skipped got restored anyway. init.sh now passes no list, so a
    whole-host restore builds nothing that can be truncated.
  * it counts what actually landed and returns non-zero naming the failures,
    instead of reporting the length of the list it was handed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 12:20:59 +01:00
librelad
6c89e430e1 feat(restore): preflight report — phase 3
`restore preflight <loc_idx> <host>` reads every app's manifest out of its
own snapshot (engineDumpFile pulls a single file without restoring) and
checks it against this machine before anything is written:

  * an app this version no longer ships is skipped — restoring one
    produces a directory that can never start, and looks like success
    until someone opens it
  * an app that will not fit is skipped individually, because filling the
    disk part-way through takes the apps that already landed with it
  * a manifest naming a storage location this machine lacks falls back to
    the default, and says which app moved where

The installer's restore path runs it and asks once before continuing.

Two bugs found by running it against the live repository rather than
reading it:

The CLI dispatcher calls handlers with NO arguments, so `shift 4; "$@"`
inside one operates on an empty list. `restore first-run bulk` has always
had this — a bulk restore silently received zero apps. Fixed at the entry
point: start.sh now captures LP_CLI_ARGS from "$@", and both call sites
use it.

And the wrapper fills unset argv slots with the literal string "empty"
(${5:-empty} … ${9:-empty}), so a trailing slot arrives as a five-
character app name rather than a blank. Filtering on -n alone let five
phantom apps through and reported each as "no longer shipped". Both call
sites now drop the sentinel. That also caps any explicit list at five, so
preflight discovers the host's apps itself when given none.

Verified against the live repository: 13 apps discovered and checked.
Sizes read "?" there because those snapshots predate manifests carrying
size_bytes — the intended graceful fallback, not a refusal.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 09:05:49 +01:00
librelad
32b2840d73 refactor(migrate)!: rewrite kernel — discover/preflight/apply with JSON progress
Phase 0 of the migration-system refresh. Replaces the 77-line
scripts/migrate/ with a properly-shaped kernel that Phase 1 (WebUI) and
Phase 3 (direct peer SSH) can both build on.

New module layout (6 files):
  migrate_progress.sh   — migrateEmit JSON-per-line helper; opt-in via
                          MIGRATE_JSON_PROGRESS=1, writes to fd 3 if open
                          (clean WebUI streaming channel) else stdout.
  migrate_discover.sh   — migrateDiscoverHosts / migrateDiscoverApps /
                          migrateDiscoverAppDetail (JSON {snapshots, latest_*}).
                          Old migrateDiscoverAppsForHost kept as back-compat.
  migrate_preflight.sh  — migratePreflight emits one JSON object with
                          snapshot{id,date}, destination{installed,running,
                          disk_free_kb}, collision{occurs,default_action,
                          pre_backup_default}, url_rewrite{default_action,
                          per_app_opt_out}, warnings[], errors[].
                          Exit 0 on usable preflight, 1 on hard error.
  migrate_url_rewrite.sh— Host-bound CFG_<APP>_* fields (URL/HOST/DOMAIN/
                          DOMAIN_PREFIX/HOSTNAME/PUBLIC_URL) get rewritten
                          from the destination's install-template after
                          restore — so a moved app stops claiming the
                          source's hostnames. Per-app opt-out via
                          CFG_<APP>_MIGRATE_URL_REWRITE=false. All other
                          fields (DB passwords, API keys, prefs) carry
                          over from the source unchanged.
  migrate_pre_backup.sh — migratePreBackupDestination takes a snapshot of
                          the destination's existing <app> (tagged
                          pre-migrate=<UTC timestamp>) before the wipe.
                          Default ON; opt-out with --no-pre-backup. Safety
                          net for the always-replace collision policy.
  migrate_apply.sh      — migrateApplyApp / migrateApplySystem. Parses
                          --no-pre-backup / --keep-urls / --json-progress
                          opts, runs preflight → pre-backup → restoreAppStart
                          (existing flow) → URL rewrite → re-deploy compose.
                          migrateApp / migrateSystem kept as shims so the
                          old CLI surface still works.

CLI dispatcher (cli_restore_commands.sh + cli_restore_header.sh):
  Existing 'restore migrate app/system/discover' calls all still work.
  New verbs:
    restore migrate list <host> [loc_idx]
    restore migrate preflight <host> <app> [loc_idx]   ← JSON, for the WebUI

Design choices baked in (per the spec):
  - Always-replace collision (no multi-install of an app), safety net is the
    on-by-default pre-migrate backup.
  - URL rewrite by host-bound suffix list, not per-field allowlist — works
    out-of-the-box for new apps without extra config.
  - migrateEmit fd-3 contract is what Phase 1's WebUI will stream; falls
    back to stdout in interactive CLI so dev/debug just works.
  - Transport-agnostic: nothing in this kernel knows whether the backup
    location is local/SSH/S3/Connect — engineSnapshotsJson + engineBackupApp
    do that, so Connect (the future blind-relay) plugs in as 'just another
    location kind' with zero kernel changes.

Smoke-tested: all 13 public functions register; JSON emit produces correct
escaping (quoted strings vs bare numerics) and respects MIGRATE_JSON_PROGRESS.

Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-26 17:22:54 +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
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