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

@@ -31,8 +31,9 @@ is self-sufficient. The mesh is optional.
operates above the line.
5. **Separate source from runtime.** The repo is for development. The
runtime lives in standard Unix locations (`~/.castle/`, `/data/castle/`,
systemd units). Nothing running should point into the source tree.
runtime lives in standard Unix locations (`$CASTLE_HOME`, default
`~/.castle/`, plus systemd units). Nothing running should point into the
source tree.
6. **AI-manageable.** The CLI and API exist so that AI assistants can
discover, create, and manage components programmatically. Humans
@@ -113,9 +114,10 @@ Manages running processes using standard Linux infrastructure.
- TLS termination
**Filesystem** handles storage:
- Service data: `/data/castle/<name>/`
- Secrets: `~/.castle/secrets/`
- Generated config: `~/.castle/generated/`
- Service data: `$CASTLE_DATA_DIR/<name>/` (default `/data/castle/`, on a
dedicated volume)
- Secrets: `$CASTLE_HOME/secrets/` (default `~/.castle/secrets/`)
- Generated config: `$CASTLE_HOME/artifacts/specs/` (Caddyfile, registry.yaml)
Castle generates systemd unit files and Caddyfile entries from the
registry. It doesn't run a daemon itself — it configures OS-level
@@ -123,8 +125,8 @@ infrastructure and gets out of the way.
Critically, the runtime layer references only standard paths — never
the source tree. Systemd units point to installed binaries (on PATH
or in `~/.castle/bin/`), not to repo subdirectories. Caddy serves
from `~/.castle/static/`, not from build output directories in the repo.
or in `~/.local/bin/`), not to repo subdirectories. Caddy serves
from `$CASTLE_HOME/artifacts/content/`, not from build output directories in the repo.
### Registry Layer
@@ -156,7 +158,7 @@ These map to two files:
programs:
central-context:
description: Content storage API
source: components/central-context
source: code/central-context
services:
central-context:
@@ -197,7 +199,7 @@ Convention-based env vars (`<PREFIX>_PORT`, `<PREFIX>_DATA_DIR`) are
generated automatically during deploy. Only non-convention values need
`defaults.env`.
**`~/.castle/registry.yaml`** (per-node, not in the repo) — Node config:
**`$CASTLE_HOME/artifacts/specs/registry.yaml`** (per-node, not in the repo, generated by `castle deploy`) — Node config:
```yaml
node:
@@ -209,7 +211,7 @@ deployed:
runner: python
run_cmd: [/home/user/.local/bin/central-context]
env:
CENTRAL_CONTEXT_DATA_DIR: /data/castle/central-context
CENTRAL_CONTEXT_DATA_DIR: /home/user/.castle/data/central-context
CENTRAL_CONTEXT_PORT: "9001"
behavior: daemon
stack: python-fastapi
@@ -302,7 +304,7 @@ Local paths always take precedence.
### Dashboard
The web dashboard (`castle-app`) is a React SPA served by Caddy from
`~/.castle/static/castle-app/`. It talks to `castle-api` via the
`$CASTLE_HOME/artifacts/content/castle-app/`. It talks to `castle-api` via the
gateway proxy at `/api`.
**Layout:**
@@ -417,9 +419,10 @@ Each step is distinct:
execute them implicitly.
2. **Install** — Makes the artifact available on the system. For tools:
`uv tool install` or compiled binary placed in `~/.castle/bin/`. For
`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: built assets copied to `~/.castle/static/`.
location. For frontends: built assets copied to
`$CASTLE_HOME/artifacts/content/`.
3. **Deploy** — Materializes the runtime configuration. Reads the
component spec, merges with node config, generates systemd units
@@ -427,28 +430,32 @@ Each step is distinct:
the source tree. Enables and starts services.
For compiled languages (Rust, Go), build produces a standalone binary
and install is just placing it in `~/.castle/bin/`. For interpreted
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
What already exists and what the target looks like:
Two roots, each overridable by an env var: `$CASTLE_HOME` (config, code,
artifacts, secrets; default `~/.castle`) and `$CASTLE_DATA_DIR` (bulk program
data; default `/data/castle`, kept on a dedicated volume):
```
~/.castle/ ← Castle runtime home
├── registry.yaml ← Node config (what's deployed here)
├── generated/Generated Caddyfile
│ └── Caddyfile
├── secrets/ ← Secret files (NAME → value)
│ └── PROTONMAIL_API_KEY
├── bin/ ← Compiled binaries, shims
└── my-go-tool
└── static/ ← Built frontend assets
└── castle-app/
└── dist/
$CASTLE_HOME/ ← Config & artifacts (default ~/.castle)
├── castle.yaml ← Registry spec (programs, services, jobs)
├── infra.confInfrastructure install choices
├── code/ ← Program source (your programs)
│ └── <name>/
├── artifacts/
│ ├── specs/ ← Generated by `castle deploy`
│ ├── Caddyfile
│ │ └── registry.yaml ← Node config (what's deployed here)
└── content/ ← Built frontend assets
└── castle-app/ ← (index.html + assets, served at root)
└── secrets/ ← Secret files (NAME → value)
└── PROTONMAIL_API_KEY
/data/castle/ ← Persistent service data
$CASTLE_DATA_DIR/ ← Persistent service data (default /data/castle)
└── <name>/
~/.config/systemd/user/ ← Systemd units (standard location)
@@ -458,8 +465,11 @@ What already exists and what the target looks like:
└── ...
```
Compiled-language tools (Rust, Go — planned) install their binaries to the
standard `~/.local/bin/`, not under `$CASTLE_HOME`.
Source (the repo) is referenced only during build and install. Everything
the runtime touches lives in `~/.castle/`, `/data/castle/`, or standard
the runtime touches lives under `$CASTLE_HOME`, `$CASTLE_DATA_DIR`, or standard
systemd paths.
## OTP as Design Guide
@@ -470,7 +480,7 @@ Castle's architecture parallels Erlang/OTP, mapped onto Unix:
|-------------|------------------|
| Application | Component (independent, self-contained) |
| Application resource file | Component spec in `castle.yaml` |
| Release config (sys.config) | Node config in `~/.castle/registry.yaml` |
| Release config (sys.config) | Node config in `$CASTLE_HOME/artifacts/specs/registry.yaml` |
| Release assembly | `castle deploy` (spec + node config → runtime) |
| Supervisor | systemd (restart policies, ordering) |
| Process | Running service/worker/job |
@@ -508,17 +518,17 @@ What exists today:
- **Three packages** — `castle-core` (models, config, generators),
`castle-cli` (commands), `castle-api` (HTTP API)
- **Source/runtime split** — `castle.yaml` (spec) → `castle deploy`
`~/.castle/registry.yaml` (node config). Systemd units and Caddyfile
generated from registry with fully resolved paths. No repo references
`$CASTLE_HOME/artifacts/specs/registry.yaml` (node config). Systemd units and
Caddyfile generated from registry with fully resolved paths. No repo references
in runtime artifacts.
- **Convention-based env generation** — `castle deploy` auto-generates
`<PREFIX>_DATA_DIR=/data/castle/<name>` and `<PREFIX>_PORT` from
`<PREFIX>_DATA_DIR=$CASTLE_DATA_DIR/<name>` and `<PREFIX>_PORT` from
the manifest. Only non-convention values need `defaults.env`.
- **Gateway** — Caddy on port 9000, Caddyfile generated from registry
- **API** — `castle-api` on port 9020, reads from registry (optional
castle.yaml fallback for non-deployed components)
- **Dashboard** — `castle-app` React/Vite frontend, static assets
served from `~/.castle/static/castle-app/`
served from `$CASTLE_HOME/artifacts/content/castle-app/`
- **Services** — central-context (content storage), notification-bridge
(desktop notification forwarder)
- **Jobs** — protonmail (email sync every 5 min), backup-collect (nightly),
@@ -531,7 +541,7 @@ What exists today:
Opt-in via `CASTLE_API_MQTT_ENABLED` / `CASTLE_API_MDNS_ENABLED`.
- **MQTT broker** — Mosquitto running as `castle-mqtt` Docker container
on port 1883, managed by systemd. Config and data in
`/data/castle/castle-mqtt/`.
`$CASTLE_DATA_DIR/castle-mqtt/`.
- **Node API** — `GET /mesh/status`, `GET /nodes`, `GET /nodes/{hostname}`.
`GET /components?include_remote=true` for cross-node component listing.
- **Gateway panel** — Dedicated UI showing route table, health per route,
@@ -559,7 +569,7 @@ What doesn't exist yet:
| Process supervision | systemd (user units) | Active |
| HTTP routing | Caddy (port 9000) | Active |
| Component specs | castle.yaml + Pydantic models | Active |
| Node config | `~/.castle/registry.yaml` | Active |
| Node config | `$CASTLE_HOME/artifacts/specs/registry.yaml` | Active |
| CLI | castle (Python, uv) | Active |
| API | castle-api (FastAPI) | Active |
| Dashboard | castle-app (React, Vite, shadcn/ui) | Active |
@@ -569,7 +579,7 @@ What doesn't exist yet:
| Type checking | pyright (Python), tsc (TS) | Active |
| Testing | pytest (Python), Vitest (TS) | Active |
| Secrets | `~/.castle/secrets/` file-based | Active |
| Data storage | Filesystem (`/data/castle/`) | Active |
| Data storage | Filesystem (`$CASTLE_DATA_DIR/`, default `/data/castle/`) | Active |
| Messaging | MQTT (paho-mqtt client, Mosquitto broker) | Active (opt-in) |
| Node discovery | mDNS (python-zeroconf) + MQTT | Active (opt-in) |
| Rust packaging | cargo | Planned |