feat: unified gateway route table (static · proxy · remote)

The gateway does two things — reverse-proxy services and serve static
frontends — but the dashboard/API route table only ever showed the proxy
routes, so 'serving a frontend' and 'proxying a service' looked like unrelated
features and castle-app/power-graph-app were invisible in the route view.

Now there's one concept: a route maps an address (path '/foo' or host 'foo.lan')
to a target of one kind — static (a built dist served by file_server), proxy
(a local service port), or remote (a service on another node).

- core: caddyfile.py gains compute_routes() — the single source of truth for
  the route list; generate_caddyfile_from_registry renders it (output byte-for-
  byte identical, verified against the live Caddyfile). Adds a GatewayRoute
  dataclass.
- api: GatewayRoute model → {address, kind, target, name, node}; GET /gateway
  builds from compute_routes (incl. config for static frontends + mesh for
  remote), so the table matches what Caddy actually does.
- app: Gateway panel shows Address · Kind · Target for every route (static
  frontends + host routes now appear); program detail shows 'Reachable at
  /foo/ · served (static)' for static frontends.
- cli: 'castle gateway status' prints the full route table.
- docs: registry.md/design.md/CLAUDE.md describe routes as one concept with
  three target kinds.

core 94 / cli 24 / api 52 green; ruff + app build clean; Caddyfile unchanged.
This commit is contained in:
2026-06-14 17:48:35 -07:00
parent 7314b5cddb
commit c40f84104d
11 changed files with 304 additions and 194 deletions

View File

@@ -128,8 +128,12 @@ code, artifacts, secrets; default `~/.castle`) and `CASTLE_DATA_DIR` (program
data I/O on a dedicated volume; default `/data/castle`). Paths below use
`$CASTLE_HOME` and `$CASTLE_DATA_DIR` accordingly.
- **Gateway**: Caddy reverse proxy at port 9000, config generated from `castle.yaml`
into `$CASTLE_HOME/artifacts/specs/Caddyfile`. Dashboard served at root.
- **Gateway**: Caddy at port 9000 — both a reverse proxy (to local/remote
services) and a static file server (for built frontends, served in place from
`<source>/<dist>`). Config generated from `castle.yaml` into
`$CASTLE_HOME/artifacts/specs/Caddyfile`. A route maps an address (path or
host) to a target of kind static/proxy/remote; `castle gateway status` lists
them. Dashboard (castle-app) served at root.
- **Systemd**: User units generated under `~/.config/systemd/user/castle-*.service`.
Use drop-in overrides (`*.service.d/*.conf`) for extra env vars that `castle deploy`
shouldn't overwrite (e.g., `CASTLE_API_MQTT_ENABLED`).
@@ -165,7 +169,7 @@ Config:
- `POST /apply` — Apply registry changes; `POST /deploy` — Deploy to runtime
Gateway:
- `GET /gateway` — Gateway info with route table and hostname
- `GET /gateway` — Gateway info + full route table (every route tagged kind=static|proxy|remote, with its address and target)
- `GET /gateway/caddyfile` — Generated Caddyfile content
- `POST /gateway/reload` — Regenerate Caddyfile and reload Caddy