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

@@ -16,6 +16,7 @@ from castle_core.manifest import (
HttpInternal,
ProgramSpec,
RunPython,
RunStatic,
ServiceSpec,
)
from castle_core.registry import Deployment, NodeConfig, NodeRegistry
@@ -105,17 +106,16 @@ class TestAcmeMode:
def test_static_frontend_is_a_file_server_subdomain(
self, monkeypatch: pytest.MonkeyPatch
) -> None:
# Static frontends come from config.programs, so hand the generator a config.
# A static frontend is a `runner: static` service serving <source>/<root>.
import castle_core.config as config_mod
cfg = _config(
services={},
programs={
"castle": ProgramSpec(
behavior="frontend", source="/data/repos/castle/app",
build=BuildSpec(outputs=["dist"]),
services={
"castle": ServiceSpec(
program="castle", run=RunStatic(runner="static", root="dist")
)
},
programs={"castle": ProgramSpec(source="/data/repos/castle/app")},
)
monkeypatch.setattr(config_mod, "load_config", lambda *a, **k: cfg)
cf = generate_caddyfile_from_registry(_acme({}))