diff --git a/cli/src/castle_cli/commands/deploy.py b/cli/src/castle_cli/commands/deploy.py index 40fd24f..6c65ded 100644 --- a/cli/src/castle_cli/commands/deploy.py +++ b/cli/src/castle_cli/commands/deploy.py @@ -122,7 +122,7 @@ def _build_deployed_service(config: CastleConfig, name: str, svc: ServiceSpec) - env: dict[str, str] = {} # Data dir convention (for managed services) - managed = True # Services are always managed by default + managed = run.runner != "remote" # Remote services have no local process if svc.manage and svc.manage.systemd and not svc.manage.systemd.enable: managed = False if managed: @@ -159,6 +159,9 @@ def _build_deployed_service(config: CastleConfig, name: str, svc: ServiceSpec) - if svc.component and svc.component in config.programs: stack = config.programs[svc.component].stack + # Remote services proxy to an external base_url + base_url = getattr(run, "base_url", None) + return DeployedComponent( runner=run.runner, run_cmd=run_cmd, @@ -169,6 +172,7 @@ def _build_deployed_service(config: CastleConfig, name: str, svc: ServiceSpec) - port=port, health_path=health_path, proxy_path=proxy_path, + base_url=base_url, managed=managed, ) @@ -322,6 +326,8 @@ def _build_run_cmd(run: object, env: dict[str, str]) -> list[str]: if run.args: cmd.extend(run.args) return cmd + case "remote": + return [] # No local process for remote services case _: raise ValueError(f"Unsupported runner: {run.runner}") diff --git a/core/src/castle_core/generators/caddyfile.py b/core/src/castle_core/generators/caddyfile.py index f74537d..de3075f 100644 --- a/core/src/castle_core/generators/caddyfile.py +++ b/core/src/castle_core/generators/caddyfile.py @@ -24,12 +24,16 @@ def generate_caddyfile_from_registry( local_paths: set[str] = set() for name, deployed in registry.deployed.items(): - if not deployed.proxy_path or not deployed.port: + if not deployed.proxy_path: + continue + # Need either a local port or a remote base_url to proxy to + if not deployed.port and not deployed.base_url: continue local_paths.add(deployed.proxy_path) + target = deployed.base_url or f"localhost:{deployed.port}" lines.append(f" handle_path {deployed.proxy_path}/* {{") - lines.append(f" reverse_proxy localhost:{deployed.port}") + lines.append(f" reverse_proxy {target}") lines.append(" }") lines.append("") diff --git a/core/src/castle_core/registry.py b/core/src/castle_core/registry.py index 16c1fa3..7178ff0 100644 --- a/core/src/castle_core/registry.py +++ b/core/src/castle_core/registry.py @@ -40,6 +40,7 @@ class DeployedComponent: port: int | None = None health_path: str | None = None proxy_path: str | None = None + base_url: str | None = None schedule: str | None = None managed: bool = False @@ -82,7 +83,15 @@ def load_registry(path: Path | None = None) -> NodeRegistry: behavior = comp_data.get("behavior") if behavior is None: category = comp_data.get("category", "service") - behavior = "daemon" if category == "service" else "tool" if category in ("job", "tool") else "frontend" if category == "frontend" else category + behavior = ( + "daemon" + if category == "service" + else "tool" + if category in ("job", "tool") + else "frontend" + if category == "frontend" + else category + ) deployed[name] = DeployedComponent( runner=comp_data.get("runner", "command"), run_cmd=comp_data.get("run_cmd", []), @@ -93,6 +102,7 @@ def load_registry(path: Path | None = None) -> NodeRegistry: port=comp_data.get("port"), health_path=comp_data.get("health_path"), proxy_path=comp_data.get("proxy_path"), + base_url=comp_data.get("base_url"), schedule=comp_data.get("schedule"), managed=comp_data.get("managed", False), ) @@ -136,6 +146,8 @@ def save_registry(registry: NodeRegistry, path: Path | None = None) -> None: entry["health_path"] = comp.health_path if comp.proxy_path: entry["proxy_path"] = comp.proxy_path + if comp.base_url: + entry["base_url"] = comp.base_url if comp.schedule: entry["schedule"] = comp.schedule if comp.managed: