Rename Castle -> Wild PC across the repo
Repo-side rename only (Phases 1-3 of the migration plan); the live box (~/.castle, systemd units, /data/castle, domains) is a separate cutover. - Slug `castle` -> `wildpc`: CLI command, module names (wildpc_core/cli/api), dist names, entry point `wildpc = wildpc_cli.main:main`. - Identifiers: CastleConfig/NATSClient/DirError/MDNS -> Wildpc*. - Env/constants: CASTLE_* -> WILDPC_*; ~/.castle -> ~/.wildpc, castle.yaml -> wildpc.yaml, /data/castle -> /data/wildpc. - Systemd UNIT_PREFIX castle- -> wildpc-; own programs castle-api/gateway/etc. - Display prose "Castle" -> "Wild PC" in docs, agent-guide files, README, frontend. - Package dirs and bootstrap yaml renamed via git mv; lockfiles regenerated; redundant nested uv.lock files dropped (workspace root lock is authoritative). Tests: core 273, cli 47, wildpc-api 120 all pass. Frontend type-checks + builds. Fixed a stale test fixture (secret_env_path kind arg) broken pre-rename.
This commit is contained in:
196
docs/design.md
196
docs/design.md
@@ -1,12 +1,12 @@
|
||||
# Castle Design
|
||||
# Wild PC Design
|
||||
|
||||
Castle is a personal software platform. It manages independent services,
|
||||
Wild PC is a personal software platform. It manages independent services,
|
||||
tools, and frontends on a Linux machine using standard Unix primitives —
|
||||
systemd for process supervision, Caddy for HTTP routing, the filesystem
|
||||
for storage, and env vars for configuration. The `castle` CLI and API
|
||||
for storage, and env vars for configuration. The `wildpc` CLI and API
|
||||
provide a registry and coordination layer on top.
|
||||
|
||||
The long-term goal: multiple Castle nodes (machines) that discover each
|
||||
The long-term goal: multiple Wild PC nodes (machines) that discover each
|
||||
other and coordinate, forming a personal infrastructure mesh. Each node
|
||||
is self-sufficient. The mesh is optional.
|
||||
|
||||
@@ -15,9 +15,9 @@ is self-sufficient. The mesh is optional.
|
||||
1. **Unix-native.** Use the OS. systemd, journald, filesystem, signals,
|
||||
env vars, DNS. Don't reimplement what Linux already provides.
|
||||
|
||||
2. **Independence.** Components never depend on Castle. They accept
|
||||
2. **Independence.** Components never depend on Wild PC. They accept
|
||||
standard configuration (ports, data dirs, URLs) via env vars. A
|
||||
Castle service is just a well-behaved Unix daemon that happens to
|
||||
Wild PC service is just a well-behaved Unix daemon that happens to
|
||||
be registered in a manifest.
|
||||
|
||||
3. **Stack and kind.** Each program has an optional *stack* (development
|
||||
@@ -29,12 +29,12 @@ is self-sufficient. The mesh is optional.
|
||||
|
||||
4. **Language-agnostic above the build line.** Below the build line,
|
||||
every language is different (uv, pnpm, cargo, go). Above it,
|
||||
everything is just processes, ports, files, and signals. Castle
|
||||
everything is just processes, ports, files, and signals. Wild PC
|
||||
operates above the line.
|
||||
|
||||
5. **Separate source from runtime.** The repo is for development. The
|
||||
runtime lives in standard Unix locations (`$CASTLE_HOME`, default
|
||||
`~/.castle/`, plus systemd units). Nothing running should point into the
|
||||
runtime lives in standard Unix locations (`$WILDPC_HOME`, default
|
||||
`~/.wildpc/`, plus systemd units). Nothing running should point into the
|
||||
source tree.
|
||||
|
||||
6. **AI-manageable.** The CLI and API exist so that AI assistants can
|
||||
@@ -70,7 +70,7 @@ responds to SIGTERM.
|
||||
|
||||
### Build Layer
|
||||
|
||||
Transforms source code into runnable artifacts. Castle does not abstract
|
||||
Transforms source code into runnable artifacts. Wild PC does not abstract
|
||||
over language toolchains — it just records the build commands and their
|
||||
outputs.
|
||||
|
||||
@@ -81,11 +81,11 @@ outputs.
|
||||
| Rust | cargo | Binary |
|
||||
| Go | go build | Binary |
|
||||
|
||||
Castle's `build` spec is intentionally minimal: a list of shell commands
|
||||
and a list of output paths. This works for any language without Castle
|
||||
Wild PC's `build` spec is intentionally minimal: a list of shell commands
|
||||
and a list of output paths. This works for any language without Wild PC
|
||||
needing to understand the toolchain.
|
||||
|
||||
For interpreted languages (Python, Node), Castle also needs to know the
|
||||
For interpreted languages (Python, Node), Wild PC also needs to know the
|
||||
runtime wrapper — how to invoke the artifact. For a `manager: systemd`
|
||||
deployment this is the nested `run:` block's **launcher** variants:
|
||||
|
||||
@@ -99,7 +99,7 @@ deployment this is the nested `run:` block's **launcher** variants:
|
||||
`manager: path` installs a CLI, `manager: none` is an external reference.)
|
||||
|
||||
Compiled languages (Rust, Go) use the `command` launcher — once built, they're
|
||||
just binaries. No Castle-specific launcher needed.
|
||||
just binaries. No Wild PC-specific launcher needed.
|
||||
|
||||
### Runtime Layer
|
||||
|
||||
@@ -120,12 +120,12 @@ Manages running processes using standard Linux infrastructure.
|
||||
- TLS termination
|
||||
|
||||
**Filesystem** handles storage:
|
||||
- Service data: `$CASTLE_DATA_DIR/<name>/` (default `/data/castle/`, on a
|
||||
- Service data: `$WILDPC_DATA_DIR/<name>/` (default `/data/wildpc/`, on a
|
||||
dedicated volume)
|
||||
- Secrets: `$CASTLE_HOME/secrets/` (default `~/.castle/secrets/`)
|
||||
- Generated config: `$CASTLE_HOME/artifacts/specs/` (Caddyfile, registry.yaml)
|
||||
- Secrets: `$WILDPC_HOME/secrets/` (default `~/.wildpc/secrets/`)
|
||||
- Generated config: `$WILDPC_HOME/artifacts/specs/` (Caddyfile, registry.yaml)
|
||||
|
||||
Castle generates systemd unit files and Caddyfile entries from the
|
||||
Wild PC generates systemd unit files and Caddyfile entries from the
|
||||
registry. It doesn't run a daemon itself — it configures OS-level
|
||||
infrastructure and gets out of the way.
|
||||
|
||||
@@ -136,7 +136,7 @@ than stage a copy, Caddy serves their built assets **in place** from the repo
|
||||
|
||||
### Registry Layer
|
||||
|
||||
The registry is the central concept in Castle. It tracks what programs
|
||||
The registry is the central concept in Wild PC. It tracks what programs
|
||||
exist, what they can do, and how they're configured. But it's not a
|
||||
single thing — it's three distinct concepts:
|
||||
|
||||
@@ -146,7 +146,7 @@ information, version-controlled in the repo. It answers: "what programs
|
||||
could exist?"
|
||||
|
||||
**2. Node config** — what's *deployed on this machine*, with what concrete
|
||||
ports, data paths, and env vars. This is per-machine. Two Castle nodes
|
||||
ports, data paths, and env vars. This is per-machine. Two Wild PC nodes
|
||||
might run different subsets of programs with different parameters. It
|
||||
answers: "what's running here, and how?"
|
||||
|
||||
@@ -156,7 +156,7 @@ answers: "is it working?"
|
||||
|
||||
#### Source vs. runtime split
|
||||
|
||||
These map to the config root (`castle.yaml` globals plus per-resource files
|
||||
These map to the config root (`wildpc.yaml` globals plus per-resource files
|
||||
under `programs/` and `deployments/`), version-controlled in the repo:
|
||||
|
||||
```yaml
|
||||
@@ -198,19 +198,19 @@ reference) captures whether it's an always-on daemon, a scheduled task, a CLI on
|
||||
PATH, a served frontend, or an external reference.
|
||||
|
||||
A deployment can reference a program via `program:` for description fallthrough
|
||||
and source code linking. It can also exist independently (e.g., `castle-gateway`
|
||||
and source code linking. It can also exist independently (e.g., `wildpc-gateway`
|
||||
runs Caddy — not our software).
|
||||
|
||||
A service's env is exactly its `defaults.env` — castle injects nothing
|
||||
A service's env is exactly its `defaults.env` — wildpc injects nothing
|
||||
implicitly. Values may use `${port}`/`${data_dir}`/`${name}`/`${secret:…}`
|
||||
placeholders, which deploy resolves into the registry's flat `env`.
|
||||
|
||||
**`$CASTLE_HOME/artifacts/specs/registry.yaml`** (per-node, not in the repo, generated by `castle apply`) — Node config:
|
||||
**`$WILDPC_HOME/artifacts/specs/registry.yaml`** (per-node, not in the repo, generated by `wildpc apply`) — Node config:
|
||||
|
||||
```yaml
|
||||
node:
|
||||
hostname: tower
|
||||
castle_root: /data/repos/castle
|
||||
wildpc_root: /data/repos/wildpc
|
||||
gateway_port: 9000
|
||||
deployed:
|
||||
central-context:
|
||||
@@ -218,7 +218,7 @@ deployed:
|
||||
launcher: python
|
||||
run_cmd: [/home/user/.local/bin/central-context]
|
||||
env:
|
||||
CENTRAL_CONTEXT_DATA_DIR: /home/user/.castle/data/central-context
|
||||
CENTRAL_CONTEXT_DATA_DIR: /home/user/.wildpc/data/central-context
|
||||
CENTRAL_CONTEXT_PORT: "9001"
|
||||
kind: service
|
||||
stack: python-fastapi
|
||||
@@ -229,7 +229,7 @@ deployed:
|
||||
```
|
||||
|
||||
The node config says what's deployed *here* and with what concrete
|
||||
values. `castle apply` reads the spec from the repo, resolves the
|
||||
values. `wildpc apply` reads the spec from the repo, resolves the
|
||||
`defaults.env` placeholders and secrets, resolves binary paths,
|
||||
and writes the registry. Systemd units and Caddyfile are then generated
|
||||
from the registry — never from the spec directly.
|
||||
@@ -244,31 +244,31 @@ This separation means:
|
||||
|
||||
Three interfaces expose the registry:
|
||||
|
||||
- **CLI** (`castle`) — For AI agents and terminal users. Structured
|
||||
- **CLI** (`wildpc`) — For AI agents and terminal users. Structured
|
||||
output via `--json`. Commands for listing, inspecting, creating,
|
||||
and managing programs.
|
||||
- **API** (`castle-api`) — For programmatic access over HTTP. Used by
|
||||
- **API** (`wildpc-api`) — For programmatic access over HTTP. Used by
|
||||
the dashboard, other nodes, and remote agents.
|
||||
- **Dashboard** (`castle`) — For human discoverability. Visual
|
||||
- **Dashboard** (`wildpc`) — For human discoverability. Visual
|
||||
overview of what's running, health status, logs.
|
||||
|
||||
### Coordination Layer
|
||||
|
||||
Coordination handles discovery and communication — both between
|
||||
programs on a single node and across multiple Castle nodes.
|
||||
programs on a single node and across multiple Wild PC nodes.
|
||||
|
||||
**Intra-node coordination:**
|
||||
- Programs find each other through the gateway or direct port access via env
|
||||
vars. A gateway route maps a host address (`<name>.<domain>`) to a target of one
|
||||
kind: **proxy** (a local service port), **remote** (a service on another
|
||||
node), or **static** (a built frontend's `dist/`, served as files). The same
|
||||
computed route list drives the Caddyfile, `castle gateway status`, and the
|
||||
computed route list drives the Caddyfile, `wildpc gateway status`, and the
|
||||
dashboard, so they always agree.
|
||||
- The registry (CLI/API) provides discoverability.
|
||||
- No service mesh or message broker required for basic operation.
|
||||
|
||||
**Inter-node coordination:**
|
||||
- Each Castle node runs the API, which exposes its program registry.
|
||||
- Each Wild PC node runs the API, which exposes its program registry.
|
||||
- Nodes discover each other via MQTT retained messages and mDNS/DNS-SD
|
||||
(python-zeroconf) for LAN environments.
|
||||
- The gateway on each node can proxy to services on other nodes via
|
||||
@@ -277,21 +277,21 @@ programs on a single node and across multiple Castle nodes.
|
||||
they're talking to.
|
||||
- MQTT provides pub/sub messaging for events, status, and coordination
|
||||
across nodes.
|
||||
- All mesh features are opt-in: `CASTLE_API_MQTT_ENABLED=true` and
|
||||
`CASTLE_API_MDNS_ENABLED=true`. Single-node works without them.
|
||||
- All mesh features are opt-in: `WILDPC_API_MQTT_ENABLED=true` and
|
||||
`WILDPC_API_MDNS_ENABLED=true`. Single-node works without them.
|
||||
|
||||
**MQTT topics:**
|
||||
- `castle/{hostname}/registry` — retained JSON, full NodeRegistry.
|
||||
Published on connect and after `castle apply`.
|
||||
- `castle/{hostname}/status` — `"online"` (retained) / `"offline"` (LWT).
|
||||
- `wildpc/{hostname}/registry` — retained JSON, full NodeRegistry.
|
||||
Published on connect and after `wildpc apply`.
|
||||
- `wildpc/{hostname}/status` — `"online"` (retained) / `"offline"` (LWT).
|
||||
LWT ensures nodes are marked offline if they disconnect unexpectedly.
|
||||
|
||||
**MeshStateManager** (`castle_api.mesh`) holds remote NodeRegistry
|
||||
**MeshStateManager** (`wildpc_api.mesh`) holds remote NodeRegistry
|
||||
instances in memory, indexed by hostname. 5-minute staleness TTL.
|
||||
Updated by the MQTT client on incoming messages. Read by API endpoints
|
||||
to serve cross-node data.
|
||||
|
||||
**mDNS** (`castle_api.mdns`) advertises `_castle._tcp` and browses
|
||||
**mDNS** (`wildpc_api.mdns`) advertises `_wildpc._tcp` and browses
|
||||
for peers and `_mqtt._tcp` broker. Uses python-zeroconf. Properties
|
||||
include hostname, gateway_port, api_port.
|
||||
|
||||
@@ -302,27 +302,27 @@ Local paths always take precedence.
|
||||
**Why MQTT over custom gossip:**
|
||||
- Standard protocol, every language has a client library.
|
||||
- Retained messages give new nodes an immediate view of the network.
|
||||
- Topic-based routing maps naturally to `castle/{node}/{program}`.
|
||||
- Topic-based routing maps naturally to `wildpc/{node}/{program}`.
|
||||
- Works across networks (not just LAN like mDNS).
|
||||
- Mosquitto is a single binary, simple to run as a Castle program.
|
||||
- Mosquitto is a single binary, simple to run as a Wild PC program.
|
||||
|
||||
**Why mDNS/DNS-SD as a complement:**
|
||||
- Zero-config LAN discovery via python-zeroconf.
|
||||
- Each node advertises `_castle._tcp` — standard tooling works
|
||||
- Each node advertises `_wildpc._tcp` — standard tooling works
|
||||
(`avahi-browse`, `dns-sd`).
|
||||
- Good for bootstrapping: find the MQTT broker without hardcoding
|
||||
its address.
|
||||
|
||||
### Dashboard
|
||||
|
||||
The web dashboard (`castle`) is a React SPA served by Caddy in place from
|
||||
The web dashboard (`wildpc`) is a React SPA served by Caddy in place from
|
||||
its repo build output (`<source>/dist/`) at the root `/`. It talks to
|
||||
`castle-api` via the gateway proxy at `/api`.
|
||||
`wildpc-api` via the gateway proxy at `/api`.
|
||||
|
||||
**Layout:**
|
||||
|
||||
```
|
||||
Castle
|
||||
Wild PC
|
||||
Personal software platform
|
||||
|
||||
[tower] [devbox (3)] ← NodeBar (hidden in single-node)
|
||||
@@ -331,7 +331,7 @@ Personal software platform
|
||||
│ Gateway · tower · port 9000 · 4 routes [Reload] [Caddyfile] │
|
||||
│ │
|
||||
│ Path Component Port Node Health│
|
||||
│ /api castle-api 9020 tower ● up │
|
||||
│ /api wildpc-api 9020 tower ● up │
|
||||
│ /central-context central-context 9001 tower ● up │
|
||||
│ /notifications notification-bridge 9002 tower ● up │
|
||||
│ /devbox-api devbox-api 9020 devbox ● up │
|
||||
@@ -343,7 +343,7 @@ Personal software platform
|
||||
|
||||
Daemons · Long-running processes that expose ports
|
||||
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
||||
│ castle-api │ │ central-ctx │ │ notif-bridge │
|
||||
│ wildpc-api │ │ central-ctx │ │ notif-bridge │
|
||||
│ ● up 5ms │ │ ● up 12ms │ │ ● up 8ms │
|
||||
│ :9020 │ │ :9001 │ │ :9002 │
|
||||
└──────────────┘ └──────────────┘ └──────────────┘
|
||||
@@ -352,7 +352,7 @@ Components · Software catalog
|
||||
Name Stack Kind Schedule Status
|
||||
pdf2md Python / CLI tool — installed
|
||||
protonmail Python / CLI tool */5 * * * * installed
|
||||
castle React / Vite static — —
|
||||
wildpc React / Vite static — —
|
||||
backup-collect Python / CLI job 0 2 * * * —
|
||||
```
|
||||
|
||||
@@ -381,7 +381,7 @@ Components · Software catalog
|
||||
|
||||
## Component Contract
|
||||
|
||||
Every Castle program, regardless of language, must satisfy a minimal
|
||||
Every Wild PC program, regardless of language, must satisfy a minimal
|
||||
contract. This is what makes the system uniform above the build line.
|
||||
|
||||
### Services (long-running daemons)
|
||||
@@ -394,8 +394,8 @@ contract. This is what makes the system uniform above the build line.
|
||||
| Data storage | Read `*_DATA_DIR` env var, write there |
|
||||
| Logging | stdout for output, stderr for errors |
|
||||
| Graceful shutdown | Handle SIGTERM, exit cleanly |
|
||||
| Secrets | Read from env vars (Castle resolves `${secret:NAME}`) |
|
||||
| No Castle dependency | Must run standalone with just env vars set |
|
||||
| Secrets | Read from env vars (Wild PC resolves `${secret:NAME}`) |
|
||||
| No Wild PC dependency | Must run standalone with just env vars set |
|
||||
|
||||
### Tools (CLI utilities)
|
||||
|
||||
@@ -419,73 +419,73 @@ Same contract as tools, plus:
|
||||
## Component Lifecycle
|
||||
|
||||
The path from source to managed process is two moves — **develop** (against the
|
||||
program) and **converge** (`castle apply`):
|
||||
program) and **converge** (`wildpc apply`):
|
||||
|
||||
```
|
||||
source → [dev verbs: build/test/…] → config (programs/ + deployments/) → [castle apply] → running
|
||||
source → [dev verbs: build/test/…] → config (programs/ + deployments/) → [wildpc apply] → running
|
||||
```
|
||||
|
||||
- **Develop** — language-specific dev verbs (`build`, `test`, …) that Castle
|
||||
- **Develop** — language-specific dev verbs (`build`, `test`, …) that Wild PC
|
||||
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 +
|
||||
- **Converge** — `wildpc 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.
|
||||
start step, and no per-kind verb. `wildpc apply --plan` shows the diff first.
|
||||
|
||||
Desired on/off is `enabled` on the deployment; the only way to durably stop
|
||||
something is `enabled: false` + apply. `castle restart` is the one imperative
|
||||
something is `enabled: false` + apply. `wildpc restart` is the one imperative
|
||||
bounce that re-actualizes current state without changing it.
|
||||
|
||||
## Runtime Filesystem Layout
|
||||
|
||||
Two roots, each overridable by an env var: `$CASTLE_HOME` (config, artifacts,
|
||||
secrets; default `~/.castle`) and `$CASTLE_DATA_DIR` (bulk program data; default
|
||||
`/data/castle`, on a dedicated volume). Program source lives separately under
|
||||
`/data/repos/<name>/` (`$CASTLE_REPOS_DIR`).
|
||||
Two roots, each overridable by an env var: `$WILDPC_HOME` (config, artifacts,
|
||||
secrets; default `~/.wildpc`) and `$WILDPC_DATA_DIR` (bulk program data; default
|
||||
`/data/wildpc`, on a dedicated volume). Program source lives separately under
|
||||
`/data/repos/<name>/` (`$WILDPC_REPOS_DIR`).
|
||||
|
||||
```
|
||||
$CASTLE_HOME/ ← Config & artifacts (default ~/.castle)
|
||||
├── castle.yaml ← Global settings (gateway, repo, agents)
|
||||
$WILDPC_HOME/ ← Config & artifacts (default ~/.wildpc)
|
||||
├── wildpc.yaml ← Global settings (gateway, repo, agents)
|
||||
├── programs/ deployments/ ← One YAML file per program / deployment
|
||||
├── infra.conf ← Infrastructure install choices
|
||||
├── artifacts/specs/ ← Generated by `castle apply`
|
||||
├── artifacts/specs/ ← Generated by `wildpc apply`
|
||||
│ ├── Caddyfile
|
||||
│ └── registry.yaml ← Node config (what's deployed here)
|
||||
└── secrets/ ← Secret files (NAME → value)
|
||||
|
||||
/data/repos/<name>/ ← Program source (your programs; absolute source:)
|
||||
$CASTLE_DATA_DIR/<name>/ ← Persistent service data (default /data/castle)
|
||||
~/.config/systemd/user/ ← Systemd units + timers (castle-*.service/.timer)
|
||||
$WILDPC_DATA_DIR/<name>/ ← Persistent service data (default /data/wildpc)
|
||||
~/.config/systemd/user/ ← Systemd units + timers (wildpc-*.service/.timer)
|
||||
```
|
||||
|
||||
Compiled-language tools (Rust, Go — planned) install their binaries to the
|
||||
standard `~/.local/bin/`. Source is referenced only by dev verbs and while a
|
||||
deployment is materialized; everything the runtime touches lives under
|
||||
`$CASTLE_HOME`, `$CASTLE_DATA_DIR`, or standard systemd paths.
|
||||
`$WILDPC_HOME`, `$WILDPC_DATA_DIR`, or standard systemd paths.
|
||||
|
||||
## OTP as Design Guide
|
||||
|
||||
Castle's architecture parallels Erlang/OTP, mapped onto Unix:
|
||||
Wild PC's architecture parallels Erlang/OTP, mapped onto Unix:
|
||||
|
||||
| OTP Concept | Castle Equivalent |
|
||||
| OTP Concept | Wild PC Equivalent |
|
||||
|-------------|------------------|
|
||||
| Application | Component (independent, self-contained) |
|
||||
| Application resource file | Component spec in `castle.yaml` |
|
||||
| Release config (sys.config) | Node config in `$CASTLE_HOME/artifacts/specs/registry.yaml` |
|
||||
| Release assembly | `castle apply` (spec + node config → runtime) |
|
||||
| Application resource file | Component spec in `wildpc.yaml` |
|
||||
| Release config (sys.config) | Node config in `$WILDPC_HOME/artifacts/specs/registry.yaml` |
|
||||
| Release assembly | `wildpc apply` (spec + node config → runtime) |
|
||||
| Supervisor | systemd (restart policies, ordering) |
|
||||
| Process | Running service/worker/job |
|
||||
| Application env | Env vars |
|
||||
| Node | A machine running Castle |
|
||||
| Node | A machine running Wild PC |
|
||||
| epmd | mDNS / MQTT discovery |
|
||||
| Distribution | Inter-node coordination via MQTT + gateway proxying |
|
||||
| "Let it crash" | `restart: on-failure` in systemd |
|
||||
| Global registry | Merged node registries via MQTT retained messages |
|
||||
|
||||
The mapping is conceptual, not literal. Castle doesn't implement OTP
|
||||
The mapping is conceptual, not literal. Wild PC doesn't implement OTP
|
||||
semantics — it uses OTP's *thinking* to guide which Unix primitives
|
||||
to compose and how.
|
||||
|
||||
@@ -501,41 +501,41 @@ Key OTP ideas that apply:
|
||||
can remap these across nodes.
|
||||
- **Spec vs. config.** In OTP, an application defines its structure
|
||||
(the `.app` file) and a release provides the deployment config
|
||||
(`sys.config`). Castle mirrors this: the program spec defines
|
||||
(`sys.config`). Wild PC mirrors this: the program spec defines
|
||||
structure, the node config provides deployment values.
|
||||
|
||||
## Current State
|
||||
|
||||
What exists today:
|
||||
|
||||
- **CLI** — `castle` command, installed via `uv tool install --editable cli/`
|
||||
- **Three packages** — `castle-core` (models, config, generators),
|
||||
`castle-cli` (commands), `castle-api` (HTTP API)
|
||||
- **Source/runtime split** — `castle.yaml` (spec) → `castle apply` →
|
||||
`$CASTLE_HOME/artifacts/specs/registry.yaml` (node config). Systemd units and
|
||||
- **CLI** — `wildpc` command, installed via `uv tool install --editable cli/`
|
||||
- **Three packages** — `wildpc-core` (models, config, generators),
|
||||
`wildpc-cli` (commands), `wildpc-api` (HTTP API)
|
||||
- **Source/runtime split** — `wildpc.yaml` (spec) → `wildpc apply` →
|
||||
`$WILDPC_HOME/artifacts/specs/registry.yaml` (node config). Systemd units and
|
||||
Caddyfile generated from registry with fully resolved paths. No repo references
|
||||
in runtime artifacts.
|
||||
- **Explicit env with placeholders** — a deployment's env is exactly its
|
||||
`defaults.env`; `castle apply` resolves `${port}`/`${data_dir}`/`${name}`/
|
||||
`defaults.env`; `wildpc apply` resolves `${port}`/`${data_dir}`/`${name}`/
|
||||
`${secret:…}` into concrete values. No hidden convention injection.
|
||||
- **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 programs)
|
||||
- **Dashboard** — `castle` React/Vite frontend, static assets
|
||||
- **API** — `wildpc-api` on port 9020, reads from registry (optional
|
||||
wildpc.yaml fallback for non-deployed programs)
|
||||
- **Dashboard** — `wildpc` React/Vite frontend, static assets
|
||||
served in place from its repo build output (`<source>/dist/`)
|
||||
- **Services** — central-context (content storage), notification-bridge
|
||||
(desktop notification forwarder)
|
||||
- **Jobs** — protonmail (email sync every 5 min), backup-collect (nightly),
|
||||
backup-data (nightly restic backup)
|
||||
- **Tools** — ~15 CLI utilities (pdf2md, docx2md, search, gpt, etc.)
|
||||
- **Manifest** — `castle.yaml` with typed Pydantic models
|
||||
- **Manifest** — `wildpc.yaml` with typed Pydantic models
|
||||
|
||||
- **Mesh infrastructure** — MQTT client (paho-mqtt), mDNS discovery
|
||||
(python-zeroconf), MeshStateManager, all wired into API lifespan.
|
||||
Opt-in via `CASTLE_API_MQTT_ENABLED` / `CASTLE_API_MDNS_ENABLED`.
|
||||
- **MQTT broker** — Mosquitto running as `castle-mqtt` Docker container
|
||||
Opt-in via `WILDPC_API_MQTT_ENABLED` / `WILDPC_API_MDNS_ENABLED`.
|
||||
- **MQTT broker** — Mosquitto running as `wildpc-mqtt` Docker container
|
||||
on port 1883, managed by systemd. Config and data in
|
||||
`$CASTLE_DATA_DIR/castle-mqtt/`.
|
||||
`$WILDPC_DATA_DIR/wildpc-mqtt/`.
|
||||
- **Node API** — `GET /mesh/status`, `GET /nodes`, `GET /nodes/{hostname}`.
|
||||
`GET /deployments?include_remote=true` for cross-node program listing.
|
||||
- **Gateway panel** — Dedicated UI showing route table, health per route,
|
||||
@@ -551,10 +551,10 @@ What doesn't exist yet:
|
||||
|
||||
- **Multi-language support** — Rust and Go programs (the abstractions
|
||||
support them via the `command` launcher, but no examples exist yet)
|
||||
- **Build automation** — Castle records build specs but doesn't
|
||||
- **Build automation** — Wild PC records build specs but doesn't
|
||||
orchestrate builds (each project builds independently)
|
||||
- **Multi-machine testing** — Mesh infrastructure is built and running
|
||||
on one node, but not yet tested with a second Castle node
|
||||
on one node, but not yet tested with a second Wild PC node
|
||||
|
||||
## Technology Map
|
||||
|
||||
@@ -562,18 +562,18 @@ 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_HOME/artifacts/specs/registry.yaml` | Active |
|
||||
| CLI | castle (Python, uv) | Active |
|
||||
| API | castle-api (FastAPI) | Active |
|
||||
| Dashboard | castle (React, Vite, shadcn/ui) | Active |
|
||||
| Component specs | wildpc.yaml + Pydantic models | Active |
|
||||
| Node config | `$WILDPC_HOME/artifacts/specs/registry.yaml` | Active |
|
||||
| CLI | wildpc (Python, uv) | Active |
|
||||
| API | wildpc-api (FastAPI) | Active |
|
||||
| Dashboard | wildpc (React, Vite, shadcn/ui) | Active |
|
||||
| Python packaging | uv | Active |
|
||||
| Node packaging | pnpm | Active |
|
||||
| Linting | ruff (Python), ESLint (TS) | Active |
|
||||
| Type checking | pyright (Python), tsc (TS) | Active |
|
||||
| Testing | pytest (Python), Vitest (TS) | Active |
|
||||
| Secrets | `~/.castle/secrets/` file-based | Active |
|
||||
| Data storage | Filesystem (`$CASTLE_DATA_DIR/`, default `/data/castle/`) | Active |
|
||||
| Secrets | `~/.wildpc/secrets/` file-based | Active |
|
||||
| Data storage | Filesystem (`$WILDPC_DATA_DIR/`, default `/data/wildpc/`) | Active |
|
||||
| Messaging | MQTT (paho-mqtt client, Mosquitto broker) | Active (opt-in) |
|
||||
| Node discovery | mDNS (python-zeroconf) + MQTT | Active (opt-in) |
|
||||
| Rust packaging | cargo | Planned |
|
||||
|
||||
Reference in New Issue
Block a user