9 Commits

Author SHA1 Message Date
librelad
19c76f0a3f feat(backup): CLI + data plumbing for per-location SSH keys
Expose the existing location_ssh.sh key store through the backup CLI:
'backup location ssh-key-set|ssh-key-generate|ssh-key-public|ssh-key-delete <idx>'
(the WebUI runs these as tasks). The locations generator now emits
ssh_key_exists + ssh_public_key (public key only — the private key never
leaves the per-location ssh.key file), so the editor can show the key state.
Also fix the stale SSH_AUTH label (~/.ssh/id_rsa -> managed per-location key).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-23 16:11:31 +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
ba6b30c425 refactor(config): rename backup subcategory file Advanced -> Engine
Drive the section title from the filename instead of the JS display-override
added earlier: rename configs/backup/backup_advanced -> backup_engine, update
the category SUBCATEGORY_ORDER and the file's header comment, and revert the
formatSubcategoryName override. The CFG_BACKUP_* keys are unchanged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-23 00:06:52 +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