5 Commits

Author SHA1 Message Date
librelad
a28eed0729 fix(services): route per-service restart through the task system + CLI
The Services tab restart button POSTed to a backend endpoint that (a)
checked the app's compose path from INSIDE the webui container, where
the host's containers root isn't mounted — so every restart failed with
'Compose file not found' — and (b) queued a raw 'docker compose restart'
that the host task processor would run as the manager user, which can't
talk to the rootless daemon anyway. Errors surfaced via a bare alert().

Per-service restart now follows the exact shape of the whole-app verbs:

- CLI: 'libreportal app restart <app> [service]' — the optional service
  arg makes dockerRestartApp restart just that compose service, via
  dockerCommandRun (right user in rootless mode) from the app dir on the
  host, where the compose file actually lives. Service names validated
  against compose-legal characters before touching a shell line.
- WebUI: the button dispatches a 'service_restart' task action through
  the task router (mutations-via-tasks), runs in the background with the
  standard task toast + link — no page switch — and failures use the
  notification system instead of alert(). Because the task runs host-
  side, restarting the WebUI's own libreportal-service now works too.
- Backend: the mutating restart endpoint and its now-unused helpers are
  removed; service-routes.js is read-only surface (status + log tails).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-06-12 23:26:40 +01:00
librelad
3f582120ba feat(cli): route all long-running app + update commands through tasks
Extends the install-routing spike (e5273a4) to every long-running CLI
command, so CLI and WebUI now share one execution path everywhere:

  app install      ← already done
  app uninstall
  app start / stop / restart / up / down / reload
  app backup
  app restore
  update apply
  backup app create   (matches `app backup` — same end target)

Each handler now has the same shape:
  if [[ "$LIBREPORTAL_TASK_EXEC" == "1" ]]; then
      <inline call>            # processor's recursive invocation
  else
      cliTaskRun "<cmd>" <type> <app>   # user invocation: enqueue + follow
  fi

Processor change — crontab_task_processor.sh:
  Adds `export LIBREPORTAL_TASK_EXEC=1` next to LIBREPORTAL_NONINTERACTIVE.
  Universal bypass: every task command the processor runs (CLI-queued OR
  pre-existing WebUI-queued like `libreportal app install adguard`)
  inherits the env var, so the inline branch fires and we never
  re-enqueue. This also lets us drop the env-var prefix the install spike
  was baking into the command string (e5273a4) — cleaner task files +
  one place to think about the bypass.

`backup app schedule` (the cron-driven path that already enqueues via
createTaskFile in backup_app_schedule.sh) is left alone — different
entry point, different runtime context, already correctly task-routed.

Why route the fast ones too (start/stop/restart/up/down):
  Consistency beats the ~1s task-roundtrip latency for a CLI button.
  Locking now serialises a CLI `app stop foo` against a WebUI restart of
  the same app; the audit trail covers every state change. Cheap to
  revert any individually if the latency turns out to bother someone.

Validated live earlier with `libreportal app install dashy` — task file
written, processor dispatched, follower streamed install live, exit 0
propagated. Same machinery now powers the other 9 handlers.

Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-27 14:38:14 +01:00
librelad
e5273a482d feat(cli): route app install through the task processor + live follower
Spike — closes the gap where the CLI install bypassed the very task system
the WebUI uses. Now both surfaces hit the same path:

  user types `libreportal app install dashy`
    → CLI enqueues a task file in $TASK_DIR (identical shape to the
      WebUI's createTaskFile)
    → pokes $TASK_DIR/.queue.fifo so the processor dispatches in <100ms
      instead of waiting up to IDLE_POLL_SECS
    → CLI tails the task log + polls .status, exits with the task's
      exit_code on terminal state
    → Ctrl-C detaches the follower without killing the task — the
      WebUI's tasks panel keeps showing it

Bypass: the recursive command in the task file is prefixed
`LIBREPORTAL_TASK_EXEC=1 libreportal app install <name>`. The install
branch in cli_app_commands.sh honours that env var by running inline,
which is what the processor's eval invocation hits. No processor
changes — the bypass travels with the task.

Wins:
  - one log file per install, shared by CLI + WebUI (audit trail + replay)
  - locking serialises CLI + WebUI installs (no more two-frontend race)
  - WebUI's "current task" indicator now reflects CLI work too
  - free `--detach` for fire-and-forget queueing

New: scripts/cli/task/cli_task_run.sh
  cliTaskRun <cmd> [type] [app] [--detach]
    Enqueues + follows; --detach prints the task id and exits 0.
  cliTaskFollow <task_id>
    `tail -F` the log + jq-poll the status; returns the task's exit_code.
    Designed to be reused for `libreportal task log <id>` reattach later.

Trade-off: ~200-500ms latency before the first byte (write task file,
processor wakes, opens log, follower starts tailing). Negligible for
install/update/backup — fast commands (list/status/config get) still
run inline. The current branch only changes `app install`; uninstall +
update + backup can be moved on the same pattern once this lands clean.

Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-27 14:29:30 +01:00
librelad
52e0227bb6 chore(cleanup): retire appGenerate — dead-on-arrival app-skeleton wizard
`libreportal app generate <name>` (and the menu's "g. Generate App" entry)
was broken three independent ways and incompatible with the per-app
architecture the project actually uses now:

  1. Copies from $install_containers_dir/template/ which doesn't exist —
     the only template/ in the tree was in scripts/unused/OLD_CONTAINERS/
     and was never installed into the live tree. cp -r would just fail.

  2. Every sed call used BSD/macOS syntax `sed -i '' -e …`. On Linux
     (every distro this targets) the empty '' becomes a positional file
     argument, so the substitutions never ran. 8 calls, all broken.

  3. Even if it had run, the produced skeleton would have been a
     pre-modular-tools / pre-per-port-subdomain app shape: no tools/,
     no scripts/ subdir, HOST_NAME=test in the .config. Every active
     containers/<app>/ today carries the modular layout the rest of the
     framework expects.

Plus the recent cleanups (the prompt loop fix in 9ffc8e4, the per-port
subdomain refactor in 2e4f420) had been peeling pieces off it without
the root question — does the function still belong? — getting asked.

Delete the whole surface:
  - scripts/app/app_generate.sh (157 lines, the function body)
  - scripts/unused/OLD_CONTAINERS/template/ (the never-installed source
    files appGenerate would have copied — stale enough to still carry
    HOST_NAME=test, CFG_<X>_HOST_NAME, and 248 lines of compose template)
  - menu entry "g. Generate App" + its dispatch in menu_main.sh
  - "generate" case branch in cli_app_commands.sh
  - `libreportal app generate` line in cli_app_header.sh
  - The corresponding entries auto-drop from files_app.sh +
    function_manifest.sh via regen.

New apps are added the way the catalog already grew — by hand-crafting
containers/<app>/{<app>.sh, <app>.config, docker-compose.yml,
tools/<app>.tools.json, scripts/<app>_*.sh}. Copying an existing app's
folder + renaming is the closest thing to a "generator" and it's a one-
command operation.

Net: -556 lines, no behaviour lost (the function never worked).

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