Manager abstraction + manager-dispatched lifecycle; frontends create static services

- manifest: `manager_for(runner)` — the single source of truth mapping a runner to
  its manager (systemd | caddy | path | none). deploy's `managed` now derives from it.
- lifecycle: is_active/activate/deactivate dispatch by manager instead of behavior
  (removed the stale `_is_static_frontend`, which broke once frontends became
  services). caddy → gateway reload; path → install/uninstall; none → no-op.
- cli/service: start/stop/restart and enable/disable route through the manager
  (systemctl only for systemd; static reloads the gateway; remote is a no-op);
  platform restart skips non-systemd runners.
- create: a frontend stack (react-vite/supabase) now emits a `runner: static`
  service, not a bare frontend program.
This commit is contained in:
2026-07-01 06:51:31 -07:00
parent 5f6afbf007
commit 57861e58c1
8 changed files with 206 additions and 78 deletions

View File

@@ -97,6 +97,30 @@ RunSpec = Annotated[
]
# A deployment's *manager* — who makes the program available and supervises it —
# is determined by its runner. This is the single source of truth; lifecycle,
# deploy, and status all dispatch on it rather than special-casing runners.
# systemd — a long-running process (or a timer, for jobs)
# caddy — a gateway route (file_server for static; reverse_proxy for a port)
# path — an installed CLI on PATH (via `uv tool install`)
# none — external; we only reference/route it (remote)
_RUNNER_MANAGER: dict[str, str] = {
"python": "systemd",
"command": "systemd",
"container": "systemd",
"compose": "systemd",
"node": "systemd",
"static": "caddy",
"path": "path",
"remote": "none",
}
def manager_for(runner: str) -> str:
"""The manager (`systemd`|`caddy`|`path`|`none`) that supervises a runner."""
return _RUNNER_MANAGER.get(runner, "systemd")
# ---------------------
# Systemd management
# ---------------------