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

@@ -41,7 +41,7 @@ from castle_core.generators.systemd import (
unit_env_file,
unit_name,
)
from castle_core.manifest import JobSpec, ServiceSpec
from castle_core.manifest import JobSpec, ServiceSpec, manager_for
from castle_core.registry import (
REGISTRY_PATH,
Deployment,
@@ -336,9 +336,10 @@ def _build_deployed_service(
# /data/castle/protonmail). Falls back to the service name.
config_key = svc.program or name
# `remote` and `static` are caddy-managed (or unmanaged) — no local process, no
# systemd unit. The gateway is their runtime.
managed = run.runner not in ("remote", "static")
# Only systemd-managed runners get a unit. caddy (static), path (tools), and
# none (remote) have no local process — the manager mapping is the single
# source of truth, so there's no runner special-casing here.
managed = manager_for(run.runner) == "systemd"
if svc.manage and svc.manage.systemd and not svc.manage.systemd.enable:
managed = False