9 Commits

Author SHA1 Message Date
librelad
27ad517626 feat(backup): per-app strategy override (advanced, context-aware)
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>
2026-05-23 15:34:17 +01:00
librelad
69f7289b4a feat(backup): declare server databases + fail safe to stop on dump failure
- 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>
2026-05-23 15:12:55 +01:00
librelad
d9f2feef05 feat(backup): consistent live database backups with auto strategy
Adds a logical-dump path so apps with a database can be backed up with zero
downtime and full consistency, instead of stopping the container.

- backup_db.sh: dump each declared DB live (mysqldump --single-transaction /
  pg_dump / sqlite3 .backup), exclude the raw data dir from the snapshot, and
  replay the dump on restore (pre-start rehydrate for sqlite, post-start load
  for server engines).
- Databases are declared via a 'libreportal.backup.db' compose label so the
  metadata travels with the app in the snapshot.
- New 'auto' strategy (now the default): live where a DB is dumpable or the app
  is marked live-safe, stop-snapshot-start otherwise. Explicit stop/pause/live
  remain as overrides.
- restic/borg/kopia adapters honour an exclude list on the live path.
- Manifest records the resolved per-app strategy and dumped databases.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-23 15:09:14 +01:00
librelad
d682178a08 feat(backup): configurable Default Backup Location; simplify Path Mode label
Automatic path mode hardcoded /docker/backups/<id>, baked into the Path Mode
dropdown label. Add a CFG_BACKUP_DEFAULT_PATH option in the Backup Engine
config ("Default Backup Location", default /docker/backups) and have
backupLocationResolvedPath build the auto path from it (<base>/<id>, trailing
slash tolerated). Defaults to the old path, so existing auto locations are
unchanged.

Path Mode's option is now just "Automatic" (no inline path); its tooltip
points at the Default Backup Location config option instead.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-23 14:51:43 +01:00
librelad
24abe412e0 feat(backup): move Engine into the location editor's Advanced tab
The backup engine is an implementation detail — LibrePortal picks a sensible
default and handles it — so it doesn't belong next to Name/Type on the
Connection tab. Add ENGINE to LOC_ADVANCED_SUFFIXES and mark it **ADVANCED**
in the location.config template + seed so it's metadata-driven.

Since the engine select now lives in the Advanced tab while SSH-auth and
path-mode stay on Connection, refreshInlineTypeFields re-applies the dynamic
behaviors (engine filtering, SSH/path visibility) against the shared
.task-details scope rather than a single panel.

Also fixed the live per-location engine label (restic -> Restic) which now
surfaces in the dropdown via the generator-emitted options.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-23 14:39:48 +01:00
librelad
3684ccaf68 feat(config): emit per-location field metadata into configs.json
The config generator only scanned flat per-category files, so the dynamic
CFG_BACKUP_LOC_N_* keys carried no titles/descriptions/options — the Locations
editor had to hardcode that metadata in backup-page.js. Add a pass that
descends into configs/backup/locations/<n>/location.config and emits each key
(value/title/description/options) into the config map, plus an "advanced"
flag parsed from a **ADVANCED** token in the field comment (stripped from the
user-facing description).

These keys use subcategory "backup_locations", which isn't in any category's
subcategory_order, so the generic /config page ignores them — only the custom
Locations editor consumes them. URI, SSH port, and append-only are marked
advanced. Verified: configs.json stays valid JSON and /config subcategories
are unchanged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-23 13:38:03 +01:00
librelad
4e0b057277 feat(backup): capitalize Restic and surface the default engine in location dropdowns
- Display the restic engine as "Restic" to match BorgBackup/Kopia. The
  lowercase name lived in scripts/backup/engines/restic.json (drives the
  location-row engine pill, per-location engine select, and engine modal),
  the hardcoded per-location dropdown options, the engine-list fallback, and
  the config-option metadata. All set to "Restic".
- In each location's Engine dropdown, float the system-default engine
  (CFG_BACKUP_ENGINE) to the top and tag it "(default)", mirroring the
  retention-preset pattern.

Repo config metadata is the install template (add-only reconciliation), so
the live /docker/configs/backup/backup_engine label was updated in place too
for the global Configuration-tab dropdown on this install.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-23 13:16:33 +01:00
librelad
4ce0340ef8 refactor(backup): replace per-app cron stagger with task-queue scheduler
Application backups were driven by one crontab entry per app, each offset by
id * CFG_BACKUP_CRONTAB_APP_INTERVAL minutes. That minute offset is written
straight into cron's 0-59 minute field, so past ~20 apps it overflowed into
an invalid entry that silently never fired, and the fixed spacing could not
serialize backups that ran longer than the gap.

Replace it with a single daily entry (`libreportal backup scheduled`) that
enqueues a backup task per enabled app. The existing systemd task processor
drains them serially — no minute overflow, real serialization, and backups
are now visible/cancellable in the Tasks UI. Per-app enable is read from
CFG_<APP>_BACKUP at schedule time instead of being mirrored into crontab.

Removes the stagger machinery (timing/setup/check/remove scripts), the
now-unused cron_jobs table + insert, and the CFG_BACKUP_CRONTAB_APP_INTERVAL
config knob and its WebUI field.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-22 14:34:35 +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