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:
202
docs/registry.md
202
docs/registry.md
@@ -1,14 +1,14 @@
|
||||
# Registry
|
||||
|
||||
How castle tracks, configures, and manages programs and their deployments.
|
||||
This is the central reference for `castle.yaml` structure and the registry
|
||||
How wildpc tracks, configures, and manages programs and their deployments.
|
||||
This is the central reference for `wildpc.yaml` structure and the registry
|
||||
architecture.
|
||||
|
||||
## Vocabulary (canonical)
|
||||
|
||||
Use these terms consistently across code, CLI, API, and docs.
|
||||
|
||||
- **program** — any project castle manages, regardless of what it does. The
|
||||
- **program** — any project wildpc manages, regardless of what it does. The
|
||||
software catalog (`programs/`). Every program has an optional **stack**.
|
||||
*("component" was the old name for program — don't use it.)*
|
||||
- **stack** — a creation-time toolchain + scaffold template (`python-cli`,
|
||||
@@ -37,11 +37,11 @@ gateway). A single `deployments/<name>.yaml` file carries the whole thing.
|
||||
|
||||
## Configuration Directory Layout
|
||||
|
||||
Castle splits its configuration across a root directory (`~/.castle/` or your config root) instead of a single file:
|
||||
Wild PC splits its configuration across a root directory (`~/.wildpc/` or your config root) instead of a single file:
|
||||
|
||||
```
|
||||
~/.castle/
|
||||
├── castle.yaml # Global settings (gateway, repo, etc.)
|
||||
~/.wildpc/
|
||||
├── wildpc.yaml # Global settings (gateway, repo, etc.)
|
||||
├── programs/ # Program configuration files (one file per program)
|
||||
│ └── my-tool.yaml
|
||||
└── deployments/ # Deployment configuration files (one file per deployment)
|
||||
@@ -51,35 +51,35 @@ Castle splits its configuration across a root directory (`~/.castle/` or your co
|
||||
└── my-app.yaml # manager: caddy → kind: static
|
||||
```
|
||||
|
||||
### castle.yaml (Globals)
|
||||
### wildpc.yaml (Globals)
|
||||
|
||||
The core `castle.yaml` contains configuration settings that apply globally to your Castle platform instance:
|
||||
The core `wildpc.yaml` contains configuration settings that apply globally to your Wild PC platform instance:
|
||||
|
||||
```yaml
|
||||
gateway:
|
||||
port: 9000
|
||||
repo: /data/repos/castle
|
||||
data_dir: /data/castle # optional — where program/service data lives
|
||||
repo: /data/repos/wildpc
|
||||
data_dir: /data/wildpc # optional — where program/service data lives
|
||||
repos_dir: /data/repos # optional — default home for new program source repos
|
||||
```
|
||||
|
||||
**`data_dir` / `repos_dir` — the configurable roots.** Both are optional and omitted
|
||||
by default (the built-ins `/data/castle` and `/data/repos` apply). Each resolves with
|
||||
precedence **env var > `castle.yaml` > built-in default**:
|
||||
by default (the built-ins `/data/wildpc` and `/data/repos` apply). Each resolves with
|
||||
precedence **env var > `wildpc.yaml` > built-in default**:
|
||||
|
||||
| root | env override | castle.yaml key | default |
|
||||
| root | env override | wildpc.yaml key | default |
|
||||
|------|--------------|-----------------|---------|
|
||||
| program data (`${data_dir}` base) | `CASTLE_DATA_DIR` | `data_dir:` | `/data/castle` |
|
||||
| new-repo home (`castle create`/`add`/`clone`) | `CASTLE_REPOS_DIR` | `repos_dir:` | `/data/repos` |
|
||||
| program data (`${data_dir}` base) | `WILDPC_DATA_DIR` | `data_dir:` | `/data/wildpc` |
|
||||
| new-repo home (`wildpc create`/`add`/`clone`) | `WILDPC_REPOS_DIR` | `repos_dir:` | `/data/repos` |
|
||||
|
||||
Put the value in `castle.yaml`, not an env var. The `castle` CLI and the `castle-api`
|
||||
Put the value in `wildpc.yaml`, not an env var. The `wildpc` CLI and the `wildpc-api`
|
||||
service each resolve config independently in their own process; a per-shell env var is
|
||||
seen by only one of them, so the two silently diverge (and `apply` crashes if the
|
||||
resolved dir — e.g. a non-existent `/data/castle` — can't be created). Persisting the
|
||||
choice in `castle.yaml` is the single source of truth both read. `install.sh` writes
|
||||
these keys when you install with `CASTLE_DATA_DIR`/`CASTLE_REPOS_DIR` set; `castle
|
||||
resolved dir — e.g. a non-existent `/data/wildpc` — can't be created). Persisting the
|
||||
choice in `wildpc.yaml` is the single source of truth both read. `install.sh` writes
|
||||
these keys when you install with `WILDPC_DATA_DIR`/`WILDPC_REPOS_DIR` set; `wildpc
|
||||
doctor` flags a data dir that isn't writable, or an env var that's overriding the file.
|
||||
(`CASTLE_HOME`, the dir that *contains* castle.yaml, stays env-or-default `~/.castle` —
|
||||
(`WILDPC_HOME`, the dir that *contains* wildpc.yaml, stays env-or-default `~/.wildpc` —
|
||||
it can't be defined inside the file it locates.)
|
||||
|
||||
### Resource Configuration Files (`programs/`, `deployments/`)
|
||||
@@ -139,7 +139,7 @@ root: dist
|
||||
| **deployments** | `deployments/*.yaml` | How a program is realized on this node | service, job, tool, static, 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). The **kind** is derived from `manager` (+
|
||||
`schedule`), never stored.
|
||||
|
||||
@@ -148,28 +148,28 @@ runs Caddy — not our software). The **kind** is derived from `manager` (+
|
||||
Programs define **what software exists** — identity, source, builds. How a
|
||||
program is *used* is not a program property: it's decided by its deployment's
|
||||
`manager` and surfaces as the derived **kind** (service/job/tool/static/reference).
|
||||
A program with no deployment is just source castle knows how to develop.
|
||||
A program with no deployment is just source wildpc knows how to develop.
|
||||
|
||||
### `source` — Where the source lives
|
||||
|
||||
```yaml
|
||||
source: /data/repos/my-tool # your programs, under $CASTLE_REPOS_DIR
|
||||
source: repo:castle-api # castle's own programs, inside the git repo
|
||||
source: /data/repos/my-tool # your programs, under $WILDPC_REPOS_DIR
|
||||
source: repo:wildpc-api # wildpc's own programs, inside the git repo
|
||||
```
|
||||
|
||||
The `source` path is resolved one of three ways (`core/src/castle_core/config.py`):
|
||||
The `source` path is resolved one of three ways (`core/src/wildpc_core/config.py`):
|
||||
|
||||
| `source:` value | Resolves to | Used for |
|
||||
|-----------------|-------------|----------|
|
||||
| `/data/repos/my-tool` *(absolute)* | as-is | Your own programs (the default) |
|
||||
| `repo:castle-api` | `<repo>/castle-api` (via the top-level `repo:` field) | Castle's built-in programs |
|
||||
| `code/my-tool` *(relative)* | `$CASTLE_HOME/code/my-tool` | Legacy — pre-`/data/repos` layout |
|
||||
| `repo:wildpc-api` | `<repo>/wildpc-api` (via the top-level `repo:` field) | Wild PC's built-in programs |
|
||||
| `code/my-tool` *(relative)* | `$WILDPC_HOME/code/my-tool` | Legacy — pre-`/data/repos` layout |
|
||||
|
||||
Programs you create or adopt live under **`$CASTLE_REPOS_DIR`** (default
|
||||
`/data/repos`, override with `CASTLE_REPOS_DIR`) and are recorded with an
|
||||
**absolute** `source:`. Castle's own programs (CLI, core, castle-api, app) live
|
||||
Programs you create or adopt live under **`$WILDPC_REPOS_DIR`** (default
|
||||
`/data/repos`, override with `WILDPC_REPOS_DIR`) and are recorded with an
|
||||
**absolute** `source:`. Wild PC's own programs (CLI, core, wildpc-api, app) live
|
||||
in the git repo and use the `repo:` prefix. A relative `source:` still resolves
|
||||
against `$CASTLE_HOME` for back-compat, but new programs no longer use it.
|
||||
against `$WILDPC_HOME` for back-compat, but new programs no longer use it.
|
||||
|
||||
### `stack` — Development toolchain (optional)
|
||||
|
||||
@@ -196,7 +196,7 @@ the stack default; an absent verb falls back to the stack handler (if any), else
|
||||
the verb is unavailable. `build` is declared via `build:` (it also carries
|
||||
`outputs:`); every other verb via `commands:`. This is what lets a wired-in repo
|
||||
with no stack be linted/tested/run. Verb resolution lives in
|
||||
`core/src/castle_core/stacks.py` (`run_action`, `available_actions`).
|
||||
`core/src/wildpc_core/stacks.py` (`run_action`, `available_actions`).
|
||||
|
||||
### `repo` / `ref` — Wiring in an existing repo
|
||||
|
||||
@@ -205,9 +205,9 @@ repo: https://github.com/me/widget.git
|
||||
ref: v2.1.0 # optional branch/tag/commit
|
||||
```
|
||||
|
||||
`repo` records a git URL so `castle program clone` can provision the source on a fresh
|
||||
`repo` records a git URL so `wildpc program clone` can provision the source on a fresh
|
||||
machine. When `source:` points at an existing working copy, that takes
|
||||
precedence. Use `castle program add <path|url>` to register an existing repo as a program.
|
||||
precedence. Use `wildpc program add <path|url>` to register an existing repo as a program.
|
||||
|
||||
### `system_dependencies` — Required system packages
|
||||
|
||||
@@ -216,7 +216,7 @@ system_dependencies: [pandoc, poppler-utils]
|
||||
```
|
||||
|
||||
System packages that must be installed for the program to work. Displayed
|
||||
in `castle tool list` / `castle tool info` and the dashboard.
|
||||
in `wildpc tool list` / `wildpc tool info` and the dashboard.
|
||||
|
||||
### `version` — Program version
|
||||
|
||||
@@ -246,7 +246,7 @@ declares a **`manager`** — who makes it available and supervises its lifecycle
|
||||
### `manager` — Who realizes it (the discriminant)
|
||||
|
||||
A deployment is a *managed materialization* of a program. Its **`manager`** is
|
||||
the stored discriminant — the single axis `castle apply` and status dispatch on
|
||||
the stored discriminant — the single axis `wildpc apply` and status dispatch on
|
||||
(it's what makes activation polymorphic: one verb, kind-specific mechanism):
|
||||
|
||||
| Manager | Makes available as | Launch mechanism | how `apply` activates | Kind |
|
||||
@@ -298,7 +298,7 @@ A `compose` launcher supervises a **whole multi-container stack as one systemd
|
||||
unit** — `ExecStart` runs `docker compose … up` attached (`Type=simple`) and a
|
||||
generated `ExecStop` runs `… down` so networks/anonymous volumes are reclaimed on
|
||||
stop. Unlike the single-container `container` launcher, compose owns the stack's own
|
||||
networking, startup ordering, and per-service health — Castle delegates rather
|
||||
networking, startup ordering, and per-service health — Wild PC delegates rather
|
||||
than reinventing orchestration. Secrets/env reach compose through the unit's
|
||||
`Environment=`/`EnvironmentFile=` (from `defaults.env`), which compose interpolates
|
||||
from the process environment. This is what runs the shared **Supabase substrate**
|
||||
@@ -309,7 +309,7 @@ manager: systemd
|
||||
run:
|
||||
launcher: compose
|
||||
file: docker-compose.yml # resolved under the program source
|
||||
# project_name: castle-my-stack # optional; defaults to castle-<name>
|
||||
# project_name: wildpc-my-stack # optional; defaults to wildpc-<name>
|
||||
```
|
||||
|
||||
### `root` — Static frontend (caddy only)
|
||||
@@ -354,9 +354,9 @@ proxy: true # expose at <service-name>.<gateway.domain>
|
||||
`public: true` additionally projects a proxied service to the public internet via a
|
||||
Cloudflare tunnel, at **`<service-name>.<gateway.public_domain>`** (a separate zone,
|
||||
so internal subdomain names stay out of public DNS). Defaults to `false` — public is
|
||||
explicit — and **requires `proxy: true`**. `castle apply` generates the cloudflared
|
||||
explicit — and **requires `proxy: true`**. `wildpc apply` generates the cloudflared
|
||||
ingress from the set of public services. Needs `gateway.public_domain` +
|
||||
`gateway.tunnel_id` set and the `castle-tunnel` service running; see
|
||||
`gateway.tunnel_id` set and the `wildpc-tunnel` service running; see
|
||||
@docs/tunnel-setup.md for the one-time setup.
|
||||
|
||||
```yaml
|
||||
@@ -401,15 +401,15 @@ prerequisites (tokens + LAN DNS for the apex): @docs/tunnel-setup.md.
|
||||
|
||||
"Serving a frontend" and "proxying a service" are the same thing — a subdomain
|
||||
route — differing only in whether the target is files on disk or a live process.
|
||||
The table is shown by `castle gateway status`, the dashboard Gateway panel, and
|
||||
The table is shown by `wildpc gateway status`, the dashboard Gateway panel, and
|
||||
`GET /gateway`; the Caddyfile is generated from it.
|
||||
|
||||
**The dashboard and its API.** `castle` (the dashboard frontend) and `castle-api`
|
||||
are just two such subdomains (`castle.<domain>`, `castle-api.<domain>`); the
|
||||
dashboard calls the API **cross-origin** (castle-api allows CORS `*`). The bare
|
||||
**The dashboard and its API.** `wildpc` (the dashboard frontend) and `wildpc-api`
|
||||
are just two such subdomains (`wildpc.<domain>`, `wildpc-api.<domain>`); the
|
||||
dashboard calls the API **cross-origin** (wildpc-api allows CORS `*`). The bare
|
||||
gateway port (`:9000`) redirects to the dashboard subdomain. On a node with **no
|
||||
domain** (`gateway.tls: off`), there are no subdomains, so `:9000` serves just the
|
||||
control plane — the dashboard at `/` plus a `/api` reverse-proxy to castle-api —
|
||||
control plane — the dashboard at `/` plus a `/api` reverse-proxy to wildpc-api —
|
||||
and other services stay port-only.
|
||||
|
||||
#### Host routes need DNS, and the gateway is HTTP-only
|
||||
@@ -456,7 +456,7 @@ the floor once: `net.ipv4.ip_unprivileged_port_start=80` (persist in
|
||||
|
||||
A private-CA approach would force every client device to trust a custom root —
|
||||
which some platforms (e.g. Android browsers, and Firefox, which uses its own
|
||||
store) make painful — so Castle doesn't offer one. `acme` mode avoids it entirely:
|
||||
store) make painful — so Wild PC doesn't offer one. `acme` mode avoids it entirely:
|
||||
Caddy obtains a
|
||||
**real Let's Encrypt wildcard cert** (`*.<domain>`) via a **DNS-01** challenge, so
|
||||
every browser trusts it with **zero CA install** — while the services stay
|
||||
@@ -496,25 +496,25 @@ is published at `<name>.<domain>`. Services stay domain-agnostic (switching
|
||||
`gateway.domain` needs no service edits). One `*.<domain>` site means a single cert
|
||||
covers every route — adding a service needs no new cert.
|
||||
|
||||
Setup (the parts castle can't do for you):
|
||||
Setup (the parts wildpc can't do for you):
|
||||
|
||||
- **DNS-plugin Caddy.** Stock Caddy has no DNS modules; build one with the
|
||||
provider plugin: `./install.sh --with-dns-plugin=cloudflare` (uses `xcaddy`,
|
||||
installs to `/usr/local/bin/caddy`, which the gateway picks up on next deploy).
|
||||
- **Provider token.** Store a scoped API token as the `CLOUDFLARE_API_TOKEN`
|
||||
secret (Cloudflare scope: **Zone → DNS → Edit**), and map it into the gateway
|
||||
service env — add to `deployments/castle-gateway.yaml`:
|
||||
service env — add to `deployments/wildpc-gateway.yaml`:
|
||||
```yaml
|
||||
defaults:
|
||||
env:
|
||||
CLOUDFLARE_API_TOKEN: ${secret:CLOUDFLARE_API_TOKEN}
|
||||
```
|
||||
`castle apply` warns if the domain, this env var, or the secret is missing.
|
||||
`wildpc apply` warns if the domain, this env var, or the secret is missing.
|
||||
- **LAN DNS.** Add a wildcard on your LAN's DNS server (usually the router)
|
||||
pointing `*.<domain>` at the gateway's private IP — `address=/<domain>/<gateway-ip>`
|
||||
(dnsmasq) or the equivalent A record. The public zone gets no A records, so
|
||||
services aren't externally reachable.
|
||||
- **Staging first.** Set `CASTLE_ACME_STAGING=1` to use Let's Encrypt's staging CA
|
||||
- **Staging first.** Set `WILDPC_ACME_STAGING=1` to use Let's Encrypt's staging CA
|
||||
(its rate limits are generous) while verifying issuance, then unset it and
|
||||
redeploy to get a browser-trusted production cert. Verify with
|
||||
`openssl s_client -connect <ip>:443 -servername claw.<domain> | openssl x509 -noout -issuer`.
|
||||
@@ -535,8 +535,8 @@ manage:
|
||||
systemd: {}
|
||||
```
|
||||
|
||||
Marks the deployment systemd-managed, so `castle apply` generates and reconciles
|
||||
its unit (and `castle service logs` tails it). An empty `{}` uses defaults
|
||||
Marks the deployment systemd-managed, so `wildpc apply` generates and reconciles
|
||||
its unit (and `wildpc service logs` tails it). An empty `{}` uses defaults
|
||||
(enable=true, restart=on-failure, restart_sec=2).
|
||||
|
||||
Full options:
|
||||
@@ -547,7 +547,7 @@ manage:
|
||||
restart: always # on-failure | always | no
|
||||
restart_sec: 2
|
||||
no_new_privileges: true
|
||||
after: [network.target, castle-other.service]
|
||||
after: [network.target, wildpc-other.service]
|
||||
wanted_by: [default.target]
|
||||
exec_reload: "caddy reload ..."
|
||||
```
|
||||
@@ -555,7 +555,7 @@ manage:
|
||||
### `defaults` — Environment
|
||||
|
||||
`defaults.env` is the **single, explicit source** of the env a service/job runs
|
||||
with — what you write here is exactly what lands in the systemd unit. Castle
|
||||
with — what you write here is exactly what lands in the systemd unit. Wild PC
|
||||
does **not** inject hidden convention vars; whatever env var your program reads
|
||||
for its port, data dir, etc., you map here.
|
||||
|
||||
@@ -564,25 +564,25 @@ expose: { http: { internal: { port: 9001 }, health_path: /health } }
|
||||
defaults:
|
||||
env:
|
||||
MY_SERVICE_PORT: ${port} # the program's own port var ← expose.port
|
||||
MY_SERVICE_DATA_DIR: ${data_dir} # = $CASTLE_DATA_DIR/<name>
|
||||
MY_SERVICE_DATA_DIR: ${data_dir} # = $WILDPC_DATA_DIR/<name>
|
||||
CENTRAL_CONTEXT_URL: http://localhost:9001
|
||||
API_KEY: ${secret:MY_API_KEY}
|
||||
```
|
||||
|
||||
Values may contain placeholders that castle resolves at deploy:
|
||||
Values may contain placeholders that wildpc resolves at deploy:
|
||||
|
||||
| Placeholder | Expands to |
|
||||
|-------------|------------|
|
||||
| `${port}` | the service's `expose.http.internal.port` (so it can't drift) |
|
||||
| `${data_dir}` | `$CASTLE_DATA_DIR/<program-or-name>` (the dedicated data volume) |
|
||||
| `${data_dir}` | `$WILDPC_DATA_DIR/<program-or-name>` (the dedicated data volume) |
|
||||
| `${name}` | the deployment name |
|
||||
| `${public_url}` | the service's gateway-facing base URL — `https://<name>.<domain>` when exposed under `tls: acme`, else the node-local `http://localhost:<port>`. The origin an app allowlists (CORS/WebSocket/secure-context); tracks `gateway.domain`, so a domain change needs no app edit. |
|
||||
| `${secret:NAME}` | the contents of `~/.castle/secrets/NAME` |
|
||||
| `${secret:NAME}` | the contents of `~/.wildpc/secrets/NAME` |
|
||||
|
||||
Hardcode the values instead if you prefer; the placeholders just save you from
|
||||
repeating castle's computed paths/ports. `castle program create` scaffolds the
|
||||
repeating wildpc's computed paths/ports. `wildpc program create` scaffolds the
|
||||
`${port}`/`${data_dir}` lines for new services. Never store secrets in
|
||||
castle.yaml — use `${secret:…}`.
|
||||
wildpc.yaml — use `${secret:…}`.
|
||||
|
||||
## Job fields
|
||||
|
||||
@@ -598,37 +598,37 @@ schedule: "*/5 * * * *"
|
||||
timezone: America/Los_Angeles # default
|
||||
```
|
||||
|
||||
Castle generates a systemd `.timer` file alongside the `.service` unit.
|
||||
Wild PC generates a systemd `.timer` file alongside the `.service` unit.
|
||||
|
||||
## How programs get into `/data/repos/`
|
||||
|
||||
Every program's source lives under `$CASTLE_REPOS_DIR` (default `/data/repos/<name>/`).
|
||||
Every program's source lives under `$WILDPC_REPOS_DIR` (default `/data/repos/<name>/`).
|
||||
It can arrive there a few ways:
|
||||
|
||||
1. **Scaffold a new one** with `castle program create` — writes the project into
|
||||
`/data/repos/<name>/` and registers it in `castle.yaml` with an absolute
|
||||
1. **Scaffold a new one** with `wildpc program create` — writes the project into
|
||||
`/data/repos/<name>/` and registers it in `wildpc.yaml` with an absolute
|
||||
`source: /data/repos/<name>`.
|
||||
2. **Adopt an existing repo** — `castle program add <path|git-url>` registers it
|
||||
in place (or records its `repo:` URL for `castle program clone`).
|
||||
2. **Adopt an existing repo** — `wildpc program add <path|git-url>` registers it
|
||||
in place (or records its `repo:` URL for `wildpc program clone`).
|
||||
3. **Drop files in directly** — a `/data/repos/<name>/` directory is just a
|
||||
working tree; it doesn't have to be under version control to be run.
|
||||
|
||||
`/data/repos/` holds independent repos — each program directory manages its own
|
||||
version control (or none); some are standalone git clones, others 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>`.
|
||||
Wild PC's own programs (CLI, core, wildpc-api, app) are the exception: they live
|
||||
inside the wildpc git repo and are referenced with `source: repo:<name>`.
|
||||
|
||||
## Registering a new program
|
||||
|
||||
### Via `castle program create` (recommended)
|
||||
### Via `wildpc program create` (recommended)
|
||||
|
||||
```bash
|
||||
# Service — scaffolds into /data/repos/, assigns port, registers in castle.yaml
|
||||
castle program create my-service --stack python-fastapi --description "Does something"
|
||||
# Service — scaffolds into /data/repos/, assigns port, registers in wildpc.yaml
|
||||
wildpc program create my-service --stack python-fastapi --description "Does something"
|
||||
|
||||
# Tool — scaffolds into /data/repos/
|
||||
castle program create my-tool --stack python-cli --description "Does something"
|
||||
wildpc program create my-tool --stack python-cli --description "Does something"
|
||||
```
|
||||
|
||||
### Manually
|
||||
@@ -672,18 +672,18 @@ manage:
|
||||
|
||||
## Lifecycle
|
||||
|
||||
One flow for every kind — scaffold, implement, **`castle apply`**. Activation is
|
||||
One flow for every kind — scaffold, implement, **`wildpc apply`**. Activation is
|
||||
polymorphic over the `manager`, so the *verb* never changes; only what apply *does*
|
||||
does:
|
||||
|
||||
```bash
|
||||
castle program create my-thing --stack python-fastapi # scaffold source + deployment
|
||||
wildpc program create my-thing --stack python-fastapi # scaffold source + deployment
|
||||
cd /data/repos/my-thing && uv sync # implement
|
||||
castle program test my-thing # dev verbs (build/test/lint/…)
|
||||
castle apply my-thing # converge: render + activate
|
||||
wildpc program test my-thing # dev verbs (build/test/lint/…)
|
||||
wildpc apply my-thing # converge: render + activate
|
||||
```
|
||||
|
||||
| kind (manager) | what `castle apply` does |
|
||||
| kind (manager) | what `wildpc apply` does |
|
||||
|----------------|--------------------------|
|
||||
| **service** (systemd) | render the `.service` unit + gateway route, `enable --now` |
|
||||
| **job** (systemd + schedule) | render a `.service` (Type=oneshot) **and** a `.timer` |
|
||||
@@ -693,47 +693,47 @@ castle apply my-thing # converge: render + act
|
||||
Manage a running deployment:
|
||||
|
||||
```bash
|
||||
castle logs my-thing -f # Tail logs
|
||||
castle program run my-thing # Run in foreground (for debugging)
|
||||
castle restart my-thing # Imperative bounce — re-actualize current state
|
||||
wildpc logs my-thing -f # Tail logs
|
||||
wildpc program run my-thing # Run in foreground (for debugging)
|
||||
wildpc restart my-thing # Imperative bounce — re-actualize current state
|
||||
```
|
||||
|
||||
To durably turn something off, set `enabled: false` in its deployment and
|
||||
`castle apply` — there is no start/stop/enable/disable/install verb.
|
||||
`wildpc apply` — there is no start/stop/enable/disable/install verb.
|
||||
|
||||
## Infrastructure paths
|
||||
|
||||
Castle uses **two** independent roots, each overridable by an environment
|
||||
Wild PC 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
|
||||
- **`WILDPC_HOME`** — config, code, artifacts, and secrets. Default `~/.wildpc`.
|
||||
- **`WILDPC_DATA_DIR`** — program/service data I/O (potentially large; lives on a
|
||||
dedicated volume). Default `/data/wildpc`. Decoupled from `WILDPC_HOME` on
|
||||
purpose so bulk data doesn't sit in the home directory.
|
||||
|
||||
| What | Where |
|
||||
|------|-------|
|
||||
| Castle home | `$CASTLE_HOME` (default `~/.castle`) |
|
||||
| Config | `$CASTLE_HOME/castle.yaml` + `programs/` + `deployments/` |
|
||||
| Program source (yours) | `/data/repos/<name>/` (`$CASTLE_REPOS_DIR`; absolute `source:`) |
|
||||
| Program source (castle's) | `<repo>/<name>` (via `source: repo:<name>`) |
|
||||
| Secrets | `$CASTLE_HOME/secrets/<NAME>` |
|
||||
| Generated Caddyfile | `$CASTLE_HOME/artifacts/specs/Caddyfile` |
|
||||
| Wild PC home | `$WILDPC_HOME` (default `~/.wildpc`) |
|
||||
| Config | `$WILDPC_HOME/wildpc.yaml` + `programs/` + `deployments/` |
|
||||
| Program source (yours) | `/data/repos/<name>/` (`$WILDPC_REPOS_DIR`; absolute `source:`) |
|
||||
| Program source (wildpc's) | `<repo>/<name>` (via `source: repo:<name>`) |
|
||||
| Secrets | `$WILDPC_HOME/secrets/<NAME>` |
|
||||
| Generated Caddyfile | `$WILDPC_HOME/artifacts/specs/Caddyfile` |
|
||||
| Built frontends | served in place from `<source>/<dist>/` (no copy) |
|
||||
| **Service data** | **`$CASTLE_DATA_DIR/<name>/` (default `/data/castle/<name>/`)** |
|
||||
| Systemd units | `~/.config/systemd/user/castle-*.service` |
|
||||
| Systemd timers | `~/.config/systemd/user/castle-*.timer` |
|
||||
| **Service data** | **`$WILDPC_DATA_DIR/<name>/` (default `/data/wildpc/<name>/`)** |
|
||||
| Systemd units | `~/.config/systemd/user/wildpc-*.service` |
|
||||
| Systemd timers | `~/.config/systemd/user/wildpc-*.timer` |
|
||||
|
||||
Defined in `core/src/castle_core/config.py`: `CASTLE_HOME` (with derived
|
||||
Defined in `core/src/wildpc_core/config.py`: `WILDPC_HOME` (with derived
|
||||
`CODE_DIR`, `SECRETS_DIR`, `SPECS_DIR`, `CONTENT_DIR`) and the independent
|
||||
`DATA_DIR` (`CASTLE_DATA_DIR`). A service reaches its data path by mapping
|
||||
`${data_dir}` (= `$CASTLE_DATA_DIR/<name>`) to the env var its program reads, in
|
||||
`DATA_DIR` (`WILDPC_DATA_DIR`). A service reaches its data path by mapping
|
||||
`${data_dir}` (= `$WILDPC_DATA_DIR/<name>`) to the env var its program reads, in
|
||||
`defaults.env`. 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:
|
||||
The Pydantic models live in `core/src/wildpc_core/manifest.py`. Key classes:
|
||||
|
||||
- `ProgramSpec` — software catalog entry (source, stack, build, system_dependencies)
|
||||
- `DeploymentSpec` — a deployment, a discriminated union on `manager`:
|
||||
@@ -745,8 +745,8 @@ The Pydantic models live in `core/src/castle_core/manifest.py`. Key classes:
|
||||
- `ExposeSpec`, `ProxySpec`, `ManageSpec`, `BuildSpec`
|
||||
- `CaddySpec`, `SystemdSpec`, `HttpExposeSpec`, `HttpInternal`
|
||||
|
||||
Config loading: `core/src/castle_core/config.py` — `load_config()` parses the
|
||||
config root into `CastleConfig` with typed `programs` and `deployments` dicts.
|
||||
Config loading: `core/src/wildpc_core/config.py` — `load_config()` parses the
|
||||
config root into `WildpcConfig` with typed `programs` and `deployments` dicts.
|
||||
|
||||
Infrastructure generators: `core/src/castle_core/generators/` — systemd unit/timer
|
||||
Infrastructure generators: `core/src/wildpc_core/generators/` — systemd unit/timer
|
||||
generation (`systemd.py`) and Caddyfile generation (`caddyfile.py`).
|
||||
|
||||
Reference in New Issue
Block a user