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:
@@ -251,14 +251,16 @@ def _build_deployed_service(
|
||||
_ensure_python_tool(config, svc.program, messages)
|
||||
|
||||
# Build run_cmd (container runners get --env-file for the secrets).
|
||||
source_dir = _program_source_dir(config, svc.program)
|
||||
run_cmd = _build_run_cmd(
|
||||
name,
|
||||
run,
|
||||
env,
|
||||
messages,
|
||||
_program_source_dir(config, svc.program),
|
||||
source_dir,
|
||||
secret_env_file=secret_env_file,
|
||||
)
|
||||
stop_cmd = _build_stop_cmd(name, run, source_dir)
|
||||
|
||||
# Proxy: a path prefix (handle_path on the gateway) and/or a hostname (a
|
||||
# dedicated host site block, so a root-based app serves unchanged).
|
||||
@@ -284,6 +286,7 @@ def _build_deployed_service(
|
||||
return Deployment(
|
||||
runner=run.runner,
|
||||
run_cmd=run_cmd,
|
||||
stop_cmd=stop_cmd,
|
||||
env=env,
|
||||
secret_env_keys=sorted(secret_env),
|
||||
description=_resolve_description(config, svc),
|
||||
@@ -473,6 +476,13 @@ def _build_run_cmd(
|
||||
if run.args: # type: ignore[union-attr]
|
||||
cmd.extend(run.args) # type: ignore[union-attr]
|
||||
return cmd
|
||||
case "compose":
|
||||
# A whole docker-compose stack supervised as one unit. `up` runs
|
||||
# attached (no -d) so systemd Type=simple owns the lifecycle; teardown
|
||||
# is a generated ExecStop (`down`, see _build_stop_cmd). Secrets/env
|
||||
# reach compose via the unit's Environment=/EnvironmentFile= (compose
|
||||
# interpolates from the process env), not argv — so nothing here.
|
||||
return [*_compose_base(name, run, source_dir), "up"]
|
||||
case "node":
|
||||
# Like the python runner bakes `--project <source>` into `uv run`, the
|
||||
# node runner bakes `--dir <source>` so the package manager runs the
|
||||
@@ -493,6 +503,33 @@ def _build_run_cmd(
|
||||
raise ValueError(f"Unsupported runner: {run.runner}") # type: ignore[union-attr]
|
||||
|
||||
|
||||
def _compose_base(name: str, run: object, source_dir: Path | None) -> list[str]:
|
||||
"""The shared ``docker compose -p <project> -f <file>`` prefix for a stack.
|
||||
|
||||
The compose file is resolved against the program source (like the node runner
|
||||
uses ``--dir``) so the unit — which carries no WorkingDirectory — finds it. The
|
||||
project name defaults to the unit name so a stack's containers/networks are
|
||||
namespaced and collision-free.
|
||||
"""
|
||||
runtime = shutil.which("docker") or shutil.which("podman") or "docker"
|
||||
project = run.project_name or f"castle-{name}" # type: ignore[union-attr]
|
||||
compose_file = Path(run.file) # type: ignore[union-attr]
|
||||
if not compose_file.is_absolute() and source_dir is not None:
|
||||
compose_file = source_dir / compose_file
|
||||
return [runtime, "compose", "-p", project, "-f", str(compose_file)]
|
||||
|
||||
|
||||
def _build_stop_cmd(name: str, run: object, source_dir: Path | None) -> list[str]:
|
||||
"""The ExecStop teardown command for a runner, or [] if a plain SIGTERM suffices.
|
||||
|
||||
Compose stacks need an explicit ``down`` so networks/anonymous volumes are
|
||||
reclaimed rather than left dangling when the unit stops.
|
||||
"""
|
||||
if run.runner == "compose": # type: ignore[union-attr]
|
||||
return [*_compose_base(name, run, source_dir), "down"]
|
||||
return []
|
||||
|
||||
|
||||
def _format_deployed(name: str, deployed: Deployment) -> str:
|
||||
"""Format deployment summary for a component."""
|
||||
parts = [name]
|
||||
|
||||
Reference in New Issue
Block a user