docs: simplify lifecycle sections for the converge model
The apply/converge + gateway refactors let the docs collapse a lot:
- design.md §Component Lifecycle: the build→install→deploy three-step
("enables and starts services") becomes two moves — develop, then
`castle apply` (activation polymorphic over the manager). Filesystem
layout de-duped and corrected (program source is /data/repos/<name>,
not the long-removed ~/.castle/code).
- registry.md §Lifecycle: three near-identical per-kind flows (service/
tool/job) collapse into one flow + a "what apply does per manager"
table. Manager table's "start/stop" column → "how apply activates".
Fixed stale paths-table rows (code/<name>) and a stale `service run`.
- developing-castle.md endpoint list: `POST /apply` is the one lifecycle
endpoint; dropped retired `/deploy`; service actions are restart-only;
added `/config/deployments/{name}/enabled`.
Net −37 lines; no retired-verb or stale-path references remain in docs.
This commit is contained in:
@@ -416,71 +416,53 @@ Same contract as tools, plus:
|
|||||||
|
|
||||||
## Component Lifecycle
|
## Component Lifecycle
|
||||||
|
|
||||||
The path from source to managed process:
|
The path from source to managed process is two moves — **develop** (against the
|
||||||
|
program) and **converge** (`castle apply`):
|
||||||
|
|
||||||
```
|
```
|
||||||
source → [build] → artifact → [install] → available → [deploy] → managed
|
source → [dev verbs: build/test/…] → config (programs/ + deployments/) → [castle apply] → running
|
||||||
```
|
```
|
||||||
|
|
||||||
Each step is distinct:
|
- **Develop** — language-specific dev verbs (`build`, `test`, …) that Castle
|
||||||
|
records but never runs implicitly. Frontends build in place under the repo
|
||||||
|
(`<source>/<dist>/`), served from there — no copy step.
|
||||||
|
- **Converge** — `castle apply` reads the config, generates systemd units +
|
||||||
|
Caddyfile entries, then reconciles reality to the desired state: activate what's
|
||||||
|
enabled, restart what changed, deactivate what's disabled. Activation is
|
||||||
|
*polymorphic over the manager* — systemd `enable --now`, `uv tool install` for a
|
||||||
|
tool on PATH, a gateway route for a static — so there is no separate install or
|
||||||
|
start step, and no per-kind verb. `castle apply --plan` shows the diff first.
|
||||||
|
|
||||||
1. **Build** — Language-specific. Produces an artifact (binary, venv
|
Desired on/off is `enabled` on the deployment; the only way to durably stop
|
||||||
entry point, static bundle). Castle records the commands but doesn't
|
something is `enabled: false` + apply. `castle restart` is the one imperative
|
||||||
execute them implicitly.
|
bounce that re-actualizes current state without changing it.
|
||||||
|
|
||||||
2. **Install** — Makes the artifact available on the system. For tools:
|
|
||||||
`uv tool install` or compiled binary placed in `~/.local/bin/`. For
|
|
||||||
services: same — the binary or entry point is on PATH or in a known
|
|
||||||
location. For frontends: assets built in place under the repo
|
|
||||||
(`<source>/<dist>/`), served from there — no copy.
|
|
||||||
|
|
||||||
3. **Deploy** — Materializes the runtime configuration. Reads the
|
|
||||||
program spec, merges with node config, generates systemd units
|
|
||||||
and Caddyfile entries that reference *installed* artifacts — never
|
|
||||||
the source tree. Enables and starts services.
|
|
||||||
|
|
||||||
For compiled languages (Rust, Go), build produces a standalone binary
|
|
||||||
and install is just placing it in `~/.local/bin/`. For interpreted
|
|
||||||
languages (Python, Node), the runtime wrapper (uv, node) handles
|
|
||||||
finding the installed artifact.
|
|
||||||
|
|
||||||
## Runtime Filesystem Layout
|
## Runtime Filesystem Layout
|
||||||
|
|
||||||
Two roots, each overridable by an env var: `$CASTLE_HOME` (config, code,
|
Two roots, each overridable by an env var: `$CASTLE_HOME` (config, artifacts,
|
||||||
artifacts, secrets; default `~/.castle`) and `$CASTLE_DATA_DIR` (bulk program
|
secrets; default `~/.castle`) and `$CASTLE_DATA_DIR` (bulk program data; default
|
||||||
data; default `/data/castle`, kept on a dedicated volume):
|
`/data/castle`, on a dedicated volume). Program source lives separately under
|
||||||
|
`/data/repos/<name>/` (`$CASTLE_REPOS_DIR`).
|
||||||
|
|
||||||
```
|
```
|
||||||
$CASTLE_HOME/ ← Config & artifacts (default ~/.castle)
|
$CASTLE_HOME/ ← Config & artifacts (default ~/.castle)
|
||||||
├── castle.yaml ← Global settings; resources under programs/ + deployments/
|
├── castle.yaml ← Global settings (gateway, repo, agents)
|
||||||
|
├── programs/ deployments/ ← One YAML file per program / deployment
|
||||||
├── infra.conf ← Infrastructure install choices
|
├── infra.conf ← Infrastructure install choices
|
||||||
├── code/ ← Program source (your programs)
|
├── artifacts/specs/ ← Generated by `castle apply`
|
||||||
│ └── <name>/
|
│ ├── Caddyfile
|
||||||
├── artifacts/
|
│ └── registry.yaml ← Node config (what's deployed here)
|
||||||
│ ├── specs/ ← Generated by `castle apply`
|
|
||||||
│ │ ├── Caddyfile
|
|
||||||
│ │ └── registry.yaml ← Node config (what's deployed here)
|
|
||||||
│ └── content/ ← Built frontend assets
|
|
||||||
│ └── castle/ ← (index.html + assets, served at root)
|
|
||||||
└── secrets/ ← Secret files (NAME → value)
|
└── secrets/ ← Secret files (NAME → value)
|
||||||
└── PROTONMAIL_API_KEY
|
|
||||||
|
|
||||||
$CASTLE_DATA_DIR/ ← Persistent service data (default /data/castle)
|
/data/repos/<name>/ ← Program source (your programs; absolute source:)
|
||||||
└── <name>/
|
$CASTLE_DATA_DIR/<name>/ ← Persistent service data (default /data/castle)
|
||||||
|
~/.config/systemd/user/ ← Systemd units + timers (castle-*.service/.timer)
|
||||||
~/.config/systemd/user/ ← Systemd units (standard location)
|
|
||||||
├── castle-central-context.service
|
|
||||||
├── castle-protonmail.service
|
|
||||||
├── castle-protonmail.timer
|
|
||||||
└── ...
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Compiled-language tools (Rust, Go — planned) install their binaries to the
|
Compiled-language tools (Rust, Go — planned) install their binaries to the
|
||||||
standard `~/.local/bin/`, not under `$CASTLE_HOME`.
|
standard `~/.local/bin/`. Source is referenced only by dev verbs and while a
|
||||||
|
deployment is materialized; everything the runtime touches lives under
|
||||||
Source (the repo) is referenced only during build and install. Everything
|
`$CASTLE_HOME`, `$CASTLE_DATA_DIR`, or standard systemd paths.
|
||||||
the runtime touches lives under `$CASTLE_HOME`, `$CASTLE_DATA_DIR`, or standard
|
|
||||||
systemd paths.
|
|
||||||
|
|
||||||
## OTP as Design Guide
|
## OTP as Design Guide
|
||||||
|
|
||||||
|
|||||||
@@ -47,15 +47,17 @@ for tools/libraries.
|
|||||||
## castle-api endpoints (port 9020)
|
## castle-api endpoints (port 9020)
|
||||||
|
|
||||||
- Core: `GET /health`, `GET /stream` (SSE: health, service-action, mesh)
|
- Core: `GET /health`, `GET /stream` (SSE: health, service-action, mesh)
|
||||||
|
- Converge: `POST /apply` (`{name?, plan?}`) — the one lifecycle endpoint
|
||||||
- Deployments: `GET /deployments[/{name}]`, `GET /status`
|
- Deployments: `GET /deployments[/{name}]`, `GET /status`
|
||||||
- Catalog + editing: `GET /programs[/{name}]`, `POST /programs/{name}/{action}`,
|
- Catalog + editing: `GET /programs[/{name}]`, `POST /programs/{name}/{action}`
|
||||||
`PUT|DELETE /programs/{name}`; likewise `/services`, `/jobs`
|
(dev verbs only), `PUT|DELETE /programs/{name}`; likewise `/services`, `/jobs`
|
||||||
- Config: `GET|PUT /`, `POST /apply`, `POST /deploy`
|
- Config: `GET|PUT /`, `POST /config/apply`, `PUT /config/deployments/{name}/enabled`
|
||||||
- Gateway: `GET /gateway`, `GET /gateway/caddyfile` (reload is convergence: `POST /apply`)
|
- Gateway: `GET /gateway`, `GET /gateway/caddyfile`, `PUT /gateway/config` (making
|
||||||
|
routes live is convergence: `POST /apply`)
|
||||||
- Mesh: `GET /mesh/status`, `GET /nodes[/{hostname}]`
|
- Mesh: `GET /mesh/status`, `GET /nodes[/{hostname}]`
|
||||||
- Agents (dashboard terminal UX): `GET /agents`, `GET /agents/sessions`,
|
- Agents (dashboard terminal UX): `GET /agents`, `GET /agents/sessions`,
|
||||||
`GET /agents/history`, `DELETE /agents/sessions/{id}`, `WS /agents/{name}/session`
|
`GET /agents/history`, `DELETE /agents/sessions/{id}`, `WS /agents/{name}/session`
|
||||||
- Service actions: `POST /services/{name}/{action}`, `GET /services/{name}/unit`
|
- Service actions: `POST /services/{name}/restart`, `GET /services/{name}/unit`
|
||||||
|
|
||||||
## Infrastructure internals
|
## Infrastructure internals
|
||||||
|
|
||||||
|
|||||||
@@ -225,14 +225,14 @@ declares a **`manager`** — who makes it available and supervises its lifecycle
|
|||||||
### `manager` — Who realizes it (the discriminant)
|
### `manager` — Who realizes it (the discriminant)
|
||||||
|
|
||||||
A deployment is a *managed materialization* of a program. Its **`manager`** is
|
A deployment is a *managed materialization* of a program. Its **`manager`** is
|
||||||
the stored discriminant — the single axis lifecycle, deploy, and status all
|
the stored discriminant — the single axis `castle apply` and status dispatch on
|
||||||
dispatch on:
|
(it's what makes activation polymorphic: one verb, kind-specific mechanism):
|
||||||
|
|
||||||
| Manager | Makes available as | Launch mechanism | start/stop | Kind |
|
| Manager | Makes available as | Launch mechanism | how `apply` activates | Kind |
|
||||||
|---------|--------------------|------------------|------------|------|
|
|---------|--------------------|------------------|-----------------------|------|
|
||||||
| **systemd** | a running process (or a `.timer` for jobs) | nested `run: { launcher: … }` | `systemctl` | service / job |
|
| **systemd** | a running process (or a `.timer` for jobs) | nested `run: { launcher: … }` | `systemctl enable --now` | service / job |
|
||||||
| **caddy** | a gateway static file_server route | *(none — files on disk; `root:`)* | add/remove route + reload | static |
|
| **caddy** | a gateway static file_server route | *(none — files on disk; `root:`)* | wire the route + reload | static |
|
||||||
| **path** | an installed CLI on `PATH` | *(none — `uv tool install`)* | `uv tool install` / `uninstall` | tool |
|
| **path** | an installed CLI on `PATH` | *(none — `uv tool install`)* | `uv tool install` | tool |
|
||||||
| **none** | an external reference | *(none; `base_url:`/`health_url:`)* | *(nothing — not ours)* | reference |
|
| **none** | an external reference | *(none; `base_url:`/`health_url:`)* | *(nothing — not ours)* | reference |
|
||||||
|
|
||||||
The **kind** (service/job/tool/static/reference) is **derived** from `manager` (+
|
The **kind** (service/job/tool/static/reference) is **derived** from `manager` (+
|
||||||
@@ -630,55 +630,34 @@ manage:
|
|||||||
|
|
||||||
## Lifecycle
|
## Lifecycle
|
||||||
|
|
||||||
### Service lifecycle
|
One flow for every kind — scaffold, implement, **`castle apply`**. Activation is
|
||||||
|
polymorphic over the `manager`, so the *verb* never changes; only what apply *does*
|
||||||
|
does:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
castle program create my-service --stack python-fastapi # 1. Scaffold + register
|
castle program create my-thing --stack python-fastapi # scaffold source + deployment
|
||||||
cd /data/repos/my-service && uv sync # 2. Install deps
|
cd /data/repos/my-thing && uv sync # implement
|
||||||
# ... implement ...
|
castle program test my-thing # dev verbs (build/test/lint/…)
|
||||||
castle program test my-service # 3. Run tests
|
castle apply my-thing # converge: render + activate
|
||||||
castle apply my-service # 4. Render unit + routes and reconcile (start it)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
`castle apply` renders the systemd unit + Caddy route and starts the service
|
| kind (manager) | what `castle apply` does |
|
||||||
(which then runs on boot and restarts on failure). Manage with:
|
|----------------|--------------------------|
|
||||||
|
| **service** (systemd) | render the `.service` unit + gateway route, `enable --now` |
|
||||||
|
| **job** (systemd + schedule) | render a `.service` (Type=oneshot) **and** a `.timer` |
|
||||||
|
| **tool** (path) | `uv tool install` — put the executable on PATH |
|
||||||
|
| **static** (caddy) | wire the gateway `file_server` route to `<source>/<root>` |
|
||||||
|
|
||||||
|
Manage a running deployment:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
castle logs my-service -f # Tail logs
|
castle logs my-thing -f # Tail logs
|
||||||
castle service run my-service # Run in foreground (for debugging)
|
castle program run my-thing # Run in foreground (for debugging)
|
||||||
castle service restart my-service # Imperative bounce
|
castle restart my-thing # Imperative bounce — re-actualize current state
|
||||||
# To stop it durably: set `enabled: false` in its deployment, then `castle apply`
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Tool lifecycle
|
To durably turn something off, set `enabled: false` in its deployment and
|
||||||
|
`castle apply` — there is no start/stop/enable/disable/install verb.
|
||||||
```bash
|
|
||||||
castle program create my-tool --stack python-cli # 1. Scaffold + register
|
|
||||||
cd /data/repos/my-tool && uv sync # 2. Install deps
|
|
||||||
# ... implement ...
|
|
||||||
castle program test my-tool # 3. Run tests
|
|
||||||
castle apply my-tool # 4. Install the path deployment on PATH
|
|
||||||
```
|
|
||||||
|
|
||||||
### Job lifecycle
|
|
||||||
|
|
||||||
Jobs are deployments with `manager: systemd` plus a `schedule` — a
|
|
||||||
`deployments/my-job.yaml` file with a nested `run:` launch block:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# deployments/my-job.yaml (manager: systemd + schedule → kind: job)
|
|
||||||
program: my-job
|
|
||||||
manager: systemd
|
|
||||||
run:
|
|
||||||
launcher: command
|
|
||||||
argv: ["my-job"]
|
|
||||||
schedule: "0 2 * * *"
|
|
||||||
manage:
|
|
||||||
systemd: {}
|
|
||||||
```
|
|
||||||
|
|
||||||
`castle apply my-job` generates both a `.service` (Type=oneshot)
|
|
||||||
and a `.timer` file.
|
|
||||||
|
|
||||||
## Infrastructure paths
|
## Infrastructure paths
|
||||||
|
|
||||||
@@ -693,8 +672,8 @@ variable (both expand `~` and resolve relative paths):
|
|||||||
| What | Where |
|
| What | Where |
|
||||||
|------|-------|
|
|------|-------|
|
||||||
| Castle home | `$CASTLE_HOME` (default `~/.castle`) |
|
| Castle home | `$CASTLE_HOME` (default `~/.castle`) |
|
||||||
| Registry | `$CASTLE_HOME/castle.yaml` |
|
| Config | `$CASTLE_HOME/castle.yaml` + `programs/` + `deployments/` |
|
||||||
| Program source (yours) | `$CASTLE_HOME/code/<name>/` |
|
| Program source (yours) | `/data/repos/<name>/` (`$CASTLE_REPOS_DIR`; absolute `source:`) |
|
||||||
| Program source (castle's) | `<repo>/<name>` (via `source: repo:<name>`) |
|
| Program source (castle's) | `<repo>/<name>` (via `source: repo:<name>`) |
|
||||||
| Secrets | `$CASTLE_HOME/secrets/<NAME>` |
|
| Secrets | `$CASTLE_HOME/secrets/<NAME>` |
|
||||||
| Generated Caddyfile | `$CASTLE_HOME/artifacts/specs/Caddyfile` |
|
| Generated Caddyfile | `$CASTLE_HOME/artifacts/specs/Caddyfile` |
|
||||||
|
|||||||
Reference in New Issue
Block a user