Clean up.

This commit is contained in:
2026-06-13 12:24:41 -07:00
parent 9fe95f6d1e
commit 74a902ee62
11 changed files with 217 additions and 91 deletions

View File

@@ -6,7 +6,8 @@ architecture.
## castle.yaml
The single source of truth. Lives at the repo root. Three top-level sections:
The single source of truth. Lives at `~/.castle/castle.yaml`. Three top-level
sections:
```yaml
gateway:
@@ -15,7 +16,7 @@ gateway:
programs:
my-tool:
description: Does something useful
source: programs/my-tool
source: code/my-tool
stack: python-cli
behavior: tool
system_dependencies: [pandoc]
@@ -76,10 +77,24 @@ Explicit declaration of how the program is used:
### `source` — Where the source lives
```yaml
source: programs/my-tool
source: code/my-tool # your programs, under ~/.castle/code/
source: repo:castle-api # castle's own programs, inside the git repo
```
Relative path from repo root to the project directory.
The `source` path is resolved one of three ways (`core/src/castle_core/config.py`):
| `source:` value | Resolves to | Used for |
|-----------------|-------------|----------|
| `code/my-tool` *(relative)* | `$CASTLE_HOME/code/my-tool` | Your own programs |
| `repo:castle-api` | `<repo>/castle-api` (via the top-level `repo:` field) | Castle's built-in programs |
| `/abs/path` | as-is | Anything explicitly absolute |
Relative sources resolve against the castle home (`$CASTLE_HOME`, default
`~/.castle` — see [Infrastructure paths](#infrastructure-paths)). Most programs
you create live under **`$CASTLE_HOME/code/`** and are recorded as
`source: code/<name>`. Castle's own programs (CLI, core, castle-api, app) live in
the git repo and use the `repo:` prefix. When `castle deploy` writes `castle.yaml`
back out, it rewrites absolute paths into these relative forms.
### `stack` — Development toolchain
@@ -215,28 +230,50 @@ Castle generates a systemd `.timer` file alongside the `.service` unit.
Jobs also support `run` (required), `manage`, and `defaults` — same
semantics as services.
## How programs get into `~/.castle/code/`
Every program's source lives in one place — `~/.castle/code/<name>/`. It can
arrive there a few ways:
1. **Scaffold a new one** with `castle create` — writes the project into
`~/.castle/code/<name>/` and registers it in `castle.yaml` with
`source: code/<name>`.
2. **Clone an existing project**`git clone <url> ~/.castle/code/<name>`,
then add a `programs:` entry pointing at `source: code/<name>`.
3. **Drop files in directly** — a `code/<name>/` directory is just a working
tree; it doesn't have to be under version control to be registered and run.
`~/.castle/` and `~/.castle/code/` are **not** themselves git repos, and there
are no submodules. Each program directory manages its own version control (or
none) independently — some are standalone git clones, others are just loose
files.
Castle's own programs (CLI, core, castle-api, app) are the exception: they live
inside the castle git repo and are referenced with `source: repo:<name>`.
## Registering a new program
### Via `castle create` (recommended)
```bash
# Service — scaffolds project, assigns port, registers in castle.yaml
# Service — scaffolds into ~/.castle/code/, assigns port, registers in castle.yaml
castle create my-service --stack python-fastapi --description "Does something"
# Tool — scaffolds under programs/
# Tool — scaffolds into ~/.castle/code/
castle create my-tool --stack python-cli --description "Does something"
```
### Manually
Add entries to the appropriate sections of `castle.yaml`:
Clone or create the project under `~/.castle/code/`, then add entries to the
appropriate sections of `castle.yaml`:
```yaml
# Tool — only needs a program entry
programs:
my-tool:
description: Does something useful
source: programs/my-tool
source: code/my-tool
stack: python-cli
behavior: tool
@@ -244,7 +281,7 @@ programs:
programs:
my-service:
description: Does something useful
source: programs/my-service
source: code/my-service
stack: python-fastapi
behavior: daemon
@@ -270,7 +307,7 @@ services:
```bash
castle create my-service --stack python-fastapi # 1. Scaffold + register
cd programs/my-service && uv sync # 2. Install deps
cd ~/.castle/code/my-service && uv sync # 2. Install deps
# ... implement ...
castle test my-service # 3. Run tests
castle service enable my-service # 4. Generate systemd unit, start
@@ -290,10 +327,10 @@ castle service disable my-service # Stop and remove systemd unit
```bash
castle create my-tool --stack python-cli # 1. Scaffold + register
cd programs/my-tool && uv sync # 2. Install deps
cd ~/.castle/code/my-tool && uv sync # 2. Install deps
# ... implement ...
castle test my-tool # 3. Run tests
uv tool install --editable programs/my-tool/ # 4. Install to PATH
uv tool install --editable ~/.castle/code/my-tool/ # 4. Install to PATH
```
### Job lifecycle
@@ -317,15 +354,33 @@ and a `.timer` file.
## Infrastructure paths
Castle uses **two** independent roots, each overridable by an environment
variable (both expand `~` and resolve relative paths):
- **`CASTLE_HOME`** — config, code, artifacts, and secrets. Default `~/.castle`.
- **`CASTLE_DATA_DIR`** — program/service data I/O (potentially large; lives on a
dedicated volume). Default `/data/castle`. Decoupled from `CASTLE_HOME` on
purpose so bulk data doesn't sit in the home directory.
| What | Where |
|------|-------|
| Registry | `castle.yaml` (repo root) |
| Service data | `/data/castle/<name>/` |
| Secrets | `~/.castle/secrets/<NAME>` |
| Generated Caddyfile | `~/.castle/generated/Caddyfile` |
| Castle home | `$CASTLE_HOME` (default `~/.castle`) |
| Registry | `$CASTLE_HOME/castle.yaml` |
| Program source (yours) | `$CASTLE_HOME/code/<name>/` |
| Program source (castle's) | `<repo>/<name>` (via `source: repo:<name>`) |
| Secrets | `$CASTLE_HOME/secrets/<NAME>` |
| Generated Caddyfile | `$CASTLE_HOME/artifacts/specs/Caddyfile` |
| Built frontends | `$CASTLE_HOME/artifacts/content/<name>/` |
| **Service data** | **`$CASTLE_DATA_DIR/<name>/` (default `/data/castle/<name>/`)** |
| Systemd units | `~/.config/systemd/user/castle-*.service` |
| Systemd timers | `~/.config/systemd/user/castle-*.timer` |
Defined in `core/src/castle_core/config.py`: `CASTLE_HOME` (with derived
`CODE_DIR`, `SECRETS_DIR`, `SPECS_DIR`, `CONTENT_DIR`) and the independent
`DATA_DIR` (`CASTLE_DATA_DIR`). `castle deploy` passes each service its data
path via the generated `<PREFIX>_DATA_DIR` env var. Systemd unit/timer paths are
fixed by systemd's user-unit convention.
## Manifest models
The Pydantic models live in `core/src/castle_core/manifest.py`. Key classes: