The gateway is a deployment (castle-gateway) — its lifecycle is the same convergence as everything else, so the bespoke start/stop/reload verbs (which still routed through the retired enable/disable path) are gone. - CLI: `castle gateway` is now inspection only — bare or `status` shows the route table. Start/stop/reload it via `castle apply` (render routes + reload) or `castle restart castle-gateway`. Deletes the last users of `_service_enable`/`_service_disable`, so those are removed too. - API: retire `POST /gateway/reload` (making routes live is `POST /apply`); `PUT /gateway/config` message + docstring now say apply, not deploy. - UI: GatewayPanel's "Reload" button → "Apply" (useApply); drop the useGatewayReload hook; GatewaySettings points at `castle apply`. - docs: `castle gateway reload|status` → `castle gateway` (status lens). core 129 / cli 31 / api 59 pass; dashboard builds clean; live `castle gateway` shows routes with no start/stop/reload; /gateway/reload removed from the API.
3.3 KiB
3.3 KiB
Developing Castle
How to work on Castle's own code — the CLI, core library, control-plane API,
and dashboard. For using Castle to manage software (create/deploy/expose
programs), see the operator guide in AGENTS.md.
The Castle monorepo
Castle's own programs live in this git repo (source: repo:<name>) — distinct
from the programs you manage, which live under /data/repos/<name>/:
cli/— thecastleCLI (installed viauv tool install --editable cli/)core/—castle_core: manifest models, config loader, generatorscastle-api/— the FastAPI control-plane service (port 9020)app/— the dashboard frontend (React/Vite; program namecastle, served atcastle.<gateway.domain>)
The root pyproject.toml is the uv workspace (core, cli, castle-api).
Key files
core/src/castle_core/manifest.py— Pydantic models:ProgramSpec,DeploymentSpec(discriminated union onmanager),LaunchSpec,AgentSpec, …core/src/castle_core/config.py— config loader (castle.yaml+programs/+deployments/→CastleConfig), the two roots,source:resolutioncore/src/castle_core/generators/— systemd unit/timer + Caddyfile generationcli/src/castle_cli/— resource-first CLI commands;templates/scaffold.pycastle-api/src/castle_api/— routes, health polling, SSEstream.py, mesh, and the agent terminal UX (agents.py,pty_session.py,agent_sessions.py,agent_registry.py)ruff.toml/pyrightconfig.json— shared lint/type config
Commands (all projects use uv)
uv sync
uv run pytest tests/ -v
uv run ruff check . && uv run ruff format .
uv run pyright <paths>
Running uv sync from a member subdir trims the shared workspace venv — resync
from the repo root to restore all members. Code style: ruff (100-char lines),
pyright, pytest + pytest-asyncio; Python 3.13 for services, 3.11+
for tools/libraries.
castle-api endpoints (port 9020)
- Core:
GET /health,GET /stream(SSE: health, service-action, mesh) - Deployments:
GET /deployments[/{name}],GET /status - Catalog + editing:
GET /programs[/{name}],POST /programs/{name}/{action},PUT|DELETE /programs/{name}; likewise/services,/jobs - Config:
GET|PUT /,POST /apply,POST /deploy - Gateway:
GET /gateway,GET /gateway/caddyfile(reload is convergence:POST /apply) - Mesh:
GET /mesh/status,GET /nodes[/{hostname}] - Agents (dashboard terminal UX):
GET /agents,GET /agents/sessions,GET /agents/history,DELETE /agents/sessions/{id},WS /agents/{name}/session - Service actions:
POST /services/{name}/{action},GET /services/{name}/unit
Infrastructure internals
- Generated Caddyfile at
~/.castle/artifacts/specs/Caddyfile; a plugin Caddy at/usr/local/bin/caddywhentls: acme. - Systemd user units at
~/.config/systemd/user/castle-*.service(+.timer); the unit for programXiscastle-X.service. Use drop-in*.service.d/*.conffor extra envcastle applyshouldn't overwrite. - The
containerlauncher resolves docker viashutil.which("docker")(preferred over rootless podman on this box). - Service data at
$CASTLE_DATA_DIR/<name>/; secrets at~/.castle/secrets/(mode 700) — never in project directories.