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.
292 lines
13 KiB
Markdown
292 lines
13 KiB
Markdown
# Wild PC
|
|
|
|
> Standing to author, run, govern, and maintain your own software.
|
|
|
|
A personal software platform. Wild PC 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.
|
|
|
|
Wild PC provides that place.
|
|
|
|
Wild PC gives simple applications a consistent local environment for development,
|
|
registration, deployment, discovery, and operation. Programs remain independent, but
|
|
Wild PC 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, Wild PC 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
|
|
|
|
Wild PC separates *what software exists* from *how it runs*:
|
|
|
|
- **Programs** — the catalog. A program is a source repo Wild PC knows how to work
|
|
with (dev verbs, build) and where it lives. One file per program under
|
|
`~/.wildpc/programs/<name>.yaml`.
|
|
- **Deployments** — how a program is realized on this node. One file per deployment
|
|
under `~/.wildpc/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 one honest step: `wildpc apply` renders systemd units and
|
|
gateway config from your config, then reconciles the runtime to match — activating
|
|
what's enabled, restarting what changed, deactivating what's disabled. Edit config,
|
|
`wildpc apply`; `wildpc apply --plan` shows the diff first.
|
|
|
|
## Stacks
|
|
|
|
Wild PC **stacks** are pre-configured development environments that provide starting
|
|
points for building Wild PC programs. A stack can define the language, framework,
|
|
dependencies, tools, conventions, and Wild PC 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 Wild PC programs, making it easier to produce
|
|
applications that are correctly structured, configured, and ready to run under
|
|
Wild PC. If your coding assistant understands Wild PC, 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 `wildpc program add` — no stack
|
|
required.
|
|
|
|
## Agents
|
|
|
|
Wild PC can launch your coding agents. Declare them in `wildpc.yaml` under `agents:`
|
|
— each entry is just a command Wild PC 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).
|
|
Wild PC 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
|
|
|
|
**Prerequisites:** a Debian/Ubuntu-family Linux with `apt` and `sudo`, `systemd`
|
|
(user services), and `git`. `install.sh` sets up everything else — Docker, Caddy,
|
|
`uv`, and the `wildpc` CLI itself.
|
|
|
|
```bash
|
|
git clone <this-repo> ~/wildpc && cd ~/wildpc
|
|
|
|
# One command: installs uv + the wildpc CLI, sets up infra (Docker, Caddy, MQTT,
|
|
# Postgres), creates ~/.wildpc, registers Wild PC's own control plane, builds the UI.
|
|
./install.sh
|
|
|
|
wildpc apply # converge the runtime to config (units, routes, run)
|
|
wildpc doctor # verify — every check should be green
|
|
open http://localhost:9000 # the dashboard
|
|
```
|
|
|
|
`wildpc doctor` is your friend at every step: it inspects setup *and* runtime and,
|
|
for anything not green, prints the exact next command. Run it any time something
|
|
looks off — after an install, a deploy, or a config change.
|
|
|
|
### Exposure: from localhost to your own HTTPS domain
|
|
|
|
Localhost is the first rung; you climb only as far as you need. Each rung is a small
|
|
config change plus `wildpc apply`, and `wildpc doctor` tells you what a rung still
|
|
needs.
|
|
|
|
| Rung | You get | What it takes |
|
|
|------|---------|---------------|
|
|
| **localhost** *(default)* | dashboard + services on `:9000` / `host:port` | nothing — this is the quick start above |
|
|
| **LAN HTTPS** | real `https://<name>.<your-domain>` on every device on your network, publicly-trusted cert, services stay internal | own a domain; set `gateway.tls: acme` + `domain:`; one wildcard DNS record on your router; a Cloudflare token. → [docs/dns-and-tls.md](docs/dns-and-tls.md) |
|
|
| **Public** | a chosen service reachable from the internet | `public: true` on the service + a Cloudflare tunnel. → [docs/tunnel-setup.md](docs/tunnel-setup.md) |
|
|
|
|
The jump to LAN HTTPS is the involved one (DNS + a token + binding `:443`). Set
|
|
`gateway.tls: acme` and run `wildpc doctor` — it enumerates exactly the pieces that
|
|
are still missing, each with its fix.
|
|
|
|
## Creating programs
|
|
|
|
`wildpc program create` scaffolds the source **and** its deployment from a stack:
|
|
|
|
```bash
|
|
# A service — FastAPI app + a systemd service deployment (health, unit, route)
|
|
wildpc program create my-api --stack python-fastapi --description "Does something"
|
|
wildpc program test my-api
|
|
wildpc apply my-api # render unit + route, then start it
|
|
|
|
# A tool — a CLI installed on your PATH
|
|
wildpc program create my-tool --stack python-cli --description "Does something"
|
|
wildpc apply my-tool # installs its path deployment on PATH
|
|
|
|
# A static frontend — built once, served by the gateway
|
|
wildpc program create my-app --stack react-vite --description "Web interface"
|
|
wildpc program build my-app && wildpc apply
|
|
|
|
# Adopt an existing repo (no stack needed — dev verbs detected or declared)
|
|
wildpc 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. Lifecycle
|
|
is convergence — `wildpc apply` — not a pile of per-kind verbs.
|
|
|
|
```
|
|
# Programs — the software catalog
|
|
wildpc program list|info|create|add|clone|delete|run
|
|
wildpc program build|test|lint|type-check|check [name] # dev verbs
|
|
|
|
# Deployment lenses (service = systemd, job = systemd + schedule, tool = path)
|
|
wildpc service list|info|create|delete|restart|logs
|
|
wildpc job …same verbs; create takes --schedule
|
|
wildpc tool list|info # CLIs on your PATH
|
|
|
|
# Platform-wide
|
|
wildpc apply [name] [--plan] # converge runtime to config — the workhorse
|
|
wildpc list [--kind K] [--stack S] [--json] # catalog + every deployment view
|
|
wildpc status # unified runtime status
|
|
wildpc doctor # diagnose setup + health, with fix hints
|
|
wildpc restart [name] # imperative bounce (one or all)
|
|
wildpc gateway # status + route table (inspection)
|
|
```
|
|
|
|
To turn a deployment off, set `enabled: false` in its config and `wildpc apply` —
|
|
there's no start/stop/enable/install verb; the manager decides the mechanism
|
|
(systemd unit, PATH install, gateway route), the verb is always `apply`.
|
|
|
|
`wildpc 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 `~/.wildpc/`: a global `wildpc.yaml` plus one file per
|
|
resource under `programs/` and `deployments/`.
|
|
|
|
```yaml
|
|
# ~/.wildpc/wildpc.yaml — globals
|
|
gateway:
|
|
port: 9000
|
|
repo: /data/repos/wildpc # resolves `source: repo:<name>` for wildpc's own programs
|
|
```
|
|
|
|
```yaml
|
|
# ~/.wildpc/programs/my-api.yaml — the catalog entry
|
|
description: Does something useful
|
|
source: /data/repos/my-api
|
|
stack: python-fastapi
|
|
```
|
|
|
|
```yaml
|
|
# ~/.wildpc/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} # = $WILDPC_DATA_DIR/my-api
|
|
API_KEY: ${secret:MY_API_KEY}
|
|
```
|
|
|
|
`defaults.env` is the **single, explicit source** of a deployment's environment —
|
|
Wild PC injects nothing implicitly. The placeholders `${port}`, `${data_dir}`,
|
|
`${name}`, `${public_url}`, and `${secret:NAME}` map your program's own env var names
|
|
to Wild PC's computed values. Secrets live in `~/.wildpc/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 `wildpc.<domain>` and the API `wildpc-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
|
|
|
|
```
|
|
~/.wildpc/ # instance: config, artifacts, secrets
|
|
wildpc.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/wildpc/<name>/ # per-deployment data volume
|
|
<repo>/ # wildpc itself: cli/ core/ wildpc-api/ app/ docs/
|
|
```
|
|
|
|
**Independence principle:** your programs never depend on Wild PC. They accept
|
|
configuration (data dir, port, URLs) via environment variables; only Wild PC's own
|
|
programs (CLI, API, gateway) know Wild PC internals.
|
|
|
|
## Dashboard & API
|
|
|
|
The **dashboard** (`app/`, served at `wildpc.<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.
|
|
|
|
**`wildpc-api`** (port 9020, proxied at `wildpc-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)
|
|
|
|
Wild PC 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 `wildpc-api` via `WILDPC_API_MQTT_ENABLED`
|
|
and `WILDPC_API_MDNS_ENABLED`.
|
|
|
|
## Docs
|
|
|
|
- [docs/registry.md](docs/registry.md) — the registry model, `wildpc.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)
|