Checkpoint: Cloudflare tunnel + public toggle, and static-runner frontends

Two features built this session, committed together as a checkpoint before the
deployment-model refactor (they share files, so kept as one rollback point):

- Public exposure via Cloudflare tunnel: a `public` toggle on services (requires
  proxy), gateway.public_domain/tunnel_id config, a cloudflared ingress generator
  (generators/tunnel.py) that maps <name>.<public_domain> to the gateway's
  internal host, deploy wiring, and docs/tunnel-setup.md.
- Frontends as `static`-runner services: a RunStatic runner served by Caddy
  (file_server), no systemd unit (like `remote`); the Caddyfile generator now
  derives static routes from services instead of a behavior==frontend branch.
This commit is contained in:
2026-07-01 06:44:23 -07:00
parent 2cafcbb372
commit 5f6afbf007
10 changed files with 465 additions and 40 deletions

View File

@@ -279,6 +279,21 @@ service is reachable only at its own `host:port`.
proxy: true # expose at <service-name>.<gateway.domain>
```
### `public` — Also expose to the public internet (opt-in)
`public: true` additionally projects a proxied service to the public internet via a
Cloudflare tunnel, at **`<service-name>.<gateway.public_domain>`** (a separate zone,
so internal subdomain names stay out of public DNS). Defaults to `false` — public is
explicit — and **requires `proxy: true`**. `castle deploy` generates the cloudflared
ingress from the set of public services. Needs `gateway.public_domain` +
`gateway.tunnel_id` set and the `castle-tunnel` service running; see
@docs/tunnel-setup.md for the one-time setup.
```yaml
proxy: true
public: true # also reachable at <service-name>.<gateway.public_domain>
```
The subdomain is always the service name — there's nothing to customize (rename the
service to change it). There are **no path-prefix routes**: a whole subdomain maps
to the backend root, so root-relative asset URLs and `window.location`-derived