Add supabase stack + general compose runner

Adds "a stack whose default is a substrate": a shared self-hosted Supabase
backend plus a `--stack supabase` that scaffolds per-app projects (migrations +
edge functions + static UI) which deploy against it. Apps own their code and
stay repo-durable; only their rows/blobs live on the shared substrate.

- compose runner: new RunCompose supervises a multi-container stack as one
  systemd unit (ExecStart=`compose up`, generated ExecStop=`compose down`);
  secrets via EnvironmentFile. Reusable beyond Supabase. Deployment.stop_cmd
  carries the teardown command through the registry.
- supabase stack: CLI --stack choice, STACK_DEFAULTS→frontend with
  build.outputs=[public], _scaffold_supabase() (migrations/RLS/functions/
  static UI/app manifest), and SupabaseHandler with a forward-only idempotent
  migration runner (plan_migrations + psql build) and deno dev-verbs.
- docs: docs/stacks/supabase.md, registered in CLAUDE.md; compose runner
  documented in registry.md.
- tests: compose run/stop cmd, systemd ExecStop, compose host-route TLS,
  migration planner, supabase verb resolution, create --stack supabase.
This commit is contained in:
2026-06-30 18:18:47 -07:00
parent fc5802d823
commit b726e79c23
19 changed files with 822 additions and 10 deletions

View File

@@ -246,6 +246,25 @@ class TestCaddyfileTlsInternal:
assert "tls internal" in caddyfile
assert "reverse_proxy localhost:18789" in caddyfile
def test_compose_substrate_host_route_is_tls_site(self) -> None:
"""The Supabase substrate (compose runner + host route) becomes its own
HTTPS site under tls:internal — routing is runner-agnostic."""
registry = _make_registry(
gateway_tls="internal",
deployed={
"supabase": Deployment(
runner="compose",
run_cmd=["docker", "compose", "-p", "castle-supabase", "up"],
port=8000,
proxy_host="supabase.lan",
),
},
)
caddyfile = generate_caddyfile_from_registry(registry)
assert "supabase.lan {" in caddyfile
assert "tls internal" in caddyfile
assert "reverse_proxy localhost:8000" in caddyfile
def test_no_auto_https_off_in_tls_mode(self) -> None:
# auto_https off would suppress the internal-CA certs we now want.
caddyfile = generate_caddyfile_from_registry(self._host_registry("internal"))