Files
wild-pc/README.md
Paul Payne d1076995a6 install.sh: create programs/ + deployments/ and seed a default castle.yaml
Moves the ~/.castle/{programs,deployments} dir creation and the minimal
castle.yaml (gateway.port: 9000) into install.sh's create_directories — seeded
only when absent, never clobbering an existing config. Drops the stale 'code/'
dir (program source lives in /data/repos now). README quick-start no longer
does the manual mkdir/printf; install.sh handles it.
2026-07-01 15:29:48 -07:00

262 lines
11 KiB
Markdown

# Castle
> Standing to author, run, govern, and maintain your own software.
A personal software platform. Castle manages independent services, tools, and
frontends — and launches your coding agents — from a single CLI, with a unified
gateway, systemd integration, and a web dashboard.
Historically, applications have usually been developed by third parties, packaged
for distribution, installed through app stores or package managers, and updated
through channels controlled by someone other than the user. That model still works
for large applications, but it is often too heavy for the small, personal,
situational software people increasingly want to create.
AI-assisted development changes the shape of this problem. Users can now create
useful software directly: small services, scripts, dashboards, agents, automations,
and configurations of existing tools. Many of these programs do not need a public
release process, an app store listing, or a conventional distribution channel. They
need a reliable place to run.
Castle provides that place.
Castle gives simple applications a consistent local environment for development,
registration, deployment, discovery, and operation. Programs remain independent, but
Castle provides the surrounding structure: process management, routing, metadata,
service lifecycle, logs, and a common interface for running and inspecting the
software in your domain.
In this sense, Castle is a **personal software estate**: a practical way to organize
the software you create and run yourself, without requiring every tool to become a
fully packaged product.
## How it works
Castle separates *what software exists* from *how it runs*:
- **Programs** — the catalog. A program is a source repo Castle knows how to work
with (dev verbs, build) and where it lives. One file per program under
`~/.castle/programs/<name>.yaml`.
- **Deployments** — how a program is realized on this node. One file per deployment
under `~/.castle/deployments/<name>.yaml`, discriminated by its **manager**:
| manager | what it is | derived **kind** |
|---------|------------|------------------|
| `systemd` | a supervised process — or, with a `schedule`, a timer | **service** / **job** |
| `caddy` | a static site served by the gateway from a build dir | **static** |
| `path` | a CLI installed on your PATH (`uv tool install`) | **tool** |
| `none` | an external service on another node (reference only) | **reference** |
For `systemd`, a nested `run.launcher` (`python` / `command` / `container` /
`compose` / `node`) says *how* the process starts. The **kind** is always
*derived* from the manager (+ `schedule`) — never stored.
A program can have several deployments — a CLI that is both a `tool` on PATH and a
scheduled `job` — so a program has no single kind of its own; it *has deployments*,
each with its own.
Standing everything up is the two honest steps `castle deploy` (regenerate systemd
units and gateway config from your config) then `castle start` (enable/start it).
There is no bundled "up".
## Stacks
Castle **stacks** are pre-configured development environments that provide starting
points for building Castle programs. A stack can define the language, framework,
dependencies, tools, conventions, and Castle integration needed for a particular
kind of application — `python-fastapi`, `python-cli`, `react-vite`, `supabase`.
Stacks are designed to work well with coding assistants. They give assistants a
consistent target when generating Castle programs, making it easier to produce
applications that are correctly structured, configured, and ready to run under
Castle. If your coding assistant understands Castle, it can help you create,
register, manage, and evolve custom applications more efficiently.
A stack seeds a new program's scaffold and default dev-verb commands, but it's
optional at runtime: a program stands on its own via its declared `commands:` and
`source:`, so you can adopt any existing repo with `castle program add` — no stack
required.
## Agents
Castle can launch your coding agents. Declare them in `castle.yaml` under `agents:`
— each entry is just a command Castle runs in a terminal:
```yaml
agents:
claude:
command: claude
description: Anthropic Claude Code
aider:
command: aider
args: ["--no-auto-commits"]
cwd: /data/repos/my-project
```
The dashboard has a terminal dock that launches any declared agent in a
pseudo-terminal (over a WebSocket) and manages live sessions (list, resume, kill).
Castle is **assistant-agnostic** — it only ever runs `command args` in a pty and
never parses the agent's output, so any interactive CLI works. With no `agents:`
block set, a sensible default set (`claude`, `opencode`, `amplifier`, …) is offered.
## Quick start
```bash
# Install the CLI (editable, onto your PATH)
uv tool install --editable cli/
# Bootstrap infrastructure + the ~/.castle tree and a default castle.yaml
./install.sh
castle list # what's registered
castle deploy && castle start # apply config to the runtime, then bring it up
open http://localhost:9000 # the dashboard
```
## Creating programs
`castle program create` scaffolds the source **and** its deployment from a stack:
```bash
# A service — FastAPI app + a systemd service deployment (health, unit, route)
castle program create my-api --stack python-fastapi --description "Does something"
castle program test my-api
castle deploy my-api && castle service enable my-api
# A tool — a CLI installed on your PATH
castle program create my-tool --stack python-cli --description "Does something"
castle tool install my-tool
# A static frontend — built once, served by the gateway
castle program create my-app --stack react-vite --description "Web interface"
castle program build my-app && castle deploy
# Adopt an existing repo (no stack needed — dev verbs detected or declared)
castle program add ~/projects/some-rust-tool
```
## CLI
Operations live under the resource they act on. `program` is the catalog;
`service`, `job`, and `tool` are **views** over the one deployment set; platform
lifecycle is top-level.
```
# Programs — the software catalog
castle program list|info|create|add|clone|delete|run|install|uninstall
castle program build|test|lint|type-check|check [name] # dev verbs
# Deployment lenses (service = systemd, job = systemd + schedule, tool = path)
castle service list|info|create|delete|deploy|enable|disable|start|stop|restart|logs
castle job …same verbs; create takes --schedule
castle tool list|info|install|uninstall # CLIs on your PATH
# Platform-wide
castle list [--kind K] [--stack S] [--json] # catalog + every deployment view
castle status # unified status
castle deploy [name] # apply config → units + Caddyfile
castle start | stop | restart # all deployments (+ gateway)
castle gateway start|stop|reload|status
```
`castle tool list --json` is the machine-readable tool catalog assistants use to
build context — it surfaces each tool's actual **executable** (which can differ from
the program name, e.g. `litellm-intent-router` installs `intent-router`), its
description, and whether it's installed.
## Configuration
The registry lives under `~/.castle/`: a global `castle.yaml` plus one file per
resource under `programs/` and `deployments/`.
```yaml
# ~/.castle/castle.yaml — globals
gateway:
port: 9000
repo: /data/repos/castle # resolves `source: repo:<name>` for castle's own programs
```
```yaml
# ~/.castle/programs/my-api.yaml — the catalog entry
description: Does something useful
source: /data/repos/my-api
stack: python-fastapi
```
```yaml
# ~/.castle/deployments/my-api.yaml — a systemd service (derived kind: service)
program: my-api
manager: systemd
run: { launcher: python, program: my-api }
expose: { http: { internal: { port: 9001 }, health_path: /health } }
proxy: true # served at my-api.<gateway.domain>
manage: { systemd: {} }
defaults:
env:
MY_API_PORT: ${port} # = expose.http.internal.port
MY_API_DATA_DIR: ${data_dir} # = $CASTLE_DATA_DIR/my-api
API_KEY: ${secret:MY_API_KEY}
```
`defaults.env` is the **single, explicit source** of a deployment's environment —
Castle injects nothing implicitly. The placeholders `${port}`, `${data_dir}`,
`${name}`, `${public_url}`, and `${secret:NAME}` map your program's own env var names
to Castle's computed values. Secrets live in `~/.castle/secrets/` (never in a repo).
## Gateway, DNS & TLS
Every gateway-exposed deployment gets its own subdomain — `<name>.<gateway.domain>`
— routed to it by the Caddy gateway (there are no path-prefix routes). Exposure is a
single checkbox: `proxy: true` on a service, while a static deployment is inherently
served. The dashboard is `castle.<domain>` and the API `castle-api.<domain>`; on a
node with no domain, `:9000` serves the dashboard plus a `/api` proxy.
`gateway.tls` is a per-node choice: `off` (plain HTTP on `:9000`) or `acme` (a real
Let's Encrypt wildcard `*.<domain>` via a DNS-01 challenge — publicly trusted, no CA
to install, while the services stay internal-only). Reaching a service from the
public internet is separately opt-in via a Cloudflare tunnel (`public: true`). See
[docs/dns-and-tls.md](docs/dns-and-tls.md).
## Layout
```
~/.castle/ # instance: config, artifacts, secrets
castle.yaml # globals (gateway, repo, agents)
programs/ deployments/ # one file per program / deployment
secrets/ # secret files (mode 700)
artifacts/specs/ # generated Caddyfile, registry.yaml
/data/repos/<name>/ # your program source (absolute source:)
/data/castle/<name>/ # per-deployment data volume
<repo>/ # castle itself: cli/ core/ castle-api/ app/ docs/
```
**Independence principle:** your programs never depend on Castle. They accept
configuration (data dir, port, URLs) via environment variables; only Castle's own
programs (CLI, API, gateway) know Castle internals.
## Dashboard & API
The **dashboard** (`app/`, served at `castle.<domain>` or `http://localhost:9000`)
lists programs and deployments, edits their config, drives lifecycle, shows the
gateway route table and logs, and hosts the agent terminal dock.
**`castle-api`** (port 9020, proxied at `castle-api.<domain>`) is the control plane:
`/deployments`, `/programs`, `/services`, `/jobs`, `/gateway`, `/status`, an SSE
`/stream`, config editing under `/config/…`, and the agent session endpoints under
`/agents`. The full endpoint reference is in [CLAUDE.md](CLAUDE.md).
## Mesh (opt-in)
Castle nodes can discover each other via MQTT + mDNS to form a personal
infrastructure mesh — the gateway can route to services on other nodes and the
dashboard shows discovered nodes and cross-node routes. It's all off by default;
single-node needs none of it. Enable on `castle-api` via `CASTLE_API_MQTT_ENABLED`
and `CASTLE_API_MDNS_ENABLED`.
## Docs
- [docs/registry.md](docs/registry.md) — the registry model, `castle.yaml`, deployment fields, lifecycle
- [docs/dns-and-tls.md](docs/dns-and-tls.md) — gateway routing, DNS, the `off` / `acme` TLS modes
- [docs/stacks/](docs/stacks/) — per-stack guides (python-fastapi, python-cli, react-vite, supabase)
- [AGENTS.md](AGENTS.md) — the canonical, assistant-agnostic operator guide (recipes, gateway, tunnel, mesh)