Rename the dashboard program castle-app -> castle

The dashboard is now served at castle.<domain> (was castle-app.<domain>). Since
the subdomain is the program name, this is just a program rename plus the one
special-case constant.

- Generator: _DASHBOARD = "castle" (the :9000 redirect target and off-mode root).
- Program file renamed (runtime): programs/castle-app.yaml -> programs/castle.yaml
  (source unchanged: repo:app).
- Dashboard UI: ProgramDetail shows a frontend's subdomain (via subdomainUrl),
  dropping the old castle-app-at-root special-case; comment refs updated.
- Docs: registry.md/design.md/react-vite.md updated to the subdomain model and the
  `castle` name (react-vite.md also corrected — frontends serve at their subdomain
  root with VITE_BASE=/, and builds are manual via `castle program build`, not
  deploy).
This commit is contained in:
2026-06-30 21:04:18 -07:00
parent 15dc8ef6f7
commit 4ff6af8f4d
8 changed files with 40 additions and 40 deletions

View File

@@ -242,7 +242,7 @@ Three interfaces expose the registry:
and managing programs.
- **API** (`castle-api`) — For programmatic access over HTTP. Used by
the dashboard, other nodes, and remote agents.
- **Dashboard** (`castle-app`) — For human discoverability. Visual
- **Dashboard** (`castle`) — For human discoverability. Visual
overview of what's running, health status, logs.
### Coordination Layer
@@ -307,7 +307,7 @@ Local paths always take precedence.
### Dashboard
The web dashboard (`castle-app`) is a React SPA served by Caddy in place from
The web dashboard (`castle`) 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`.
@@ -344,7 +344,7 @@ Components · Software catalog
Name Stack Behavior Schedule Status
pdf2md Python / CLI tool — installed
protonmail Python / CLI tool */5 * * * * installed
castle-app React / Vite frontend — —
castle React / Vite frontend — —
backup-collect Python / CLI tool 0 2 * * * —
```
@@ -455,7 +455,7 @@ $CASTLE_HOME/ ← Config & artifacts (default ~/.castle)
│ │ ├── Caddyfile
│ │ └── registry.yaml ← Node config (what's deployed here)
│ └── content/ ← Built frontend assets
│ └── castle-app/ ← (index.html + assets, served at root)
│ └── castle/ ← (index.html + assets, served at root)
└── secrets/ ← Secret files (NAME → value)
└── PROTONMAIL_API_KEY
@@ -531,7 +531,7 @@ What exists today:
- **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-app` React/Vite frontend, static assets
- **Dashboard** — `castle` 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)
@@ -576,7 +576,7 @@ What doesn't exist yet:
| 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 |
| Dashboard | castle (React, Vite, shadcn/ui) | Active |
| Python packaging | uv | Active |
| Node packaging | pnpm | Active |
| Linting | ruff (Python), ESLint (TS) | Active |

View File

@@ -301,13 +301,13 @@ route — differing only in whether the target is files on disk or a live proces
The table is shown by `castle gateway status`, the dashboard Gateway panel, and
`GET /gateway`; the Caddyfile is generated from it.
**The dashboard and its API.** `castle-app` (the dashboard frontend) and
`castle-api` are just two such subdomains (`castle-app.<domain>`,
`castle-api.<domain>`); the dashboard calls the API **cross-origin** (castle-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 — and other services stay port-only.
**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
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 —
and other services stay port-only.
#### Host routes need DNS, and the gateway is HTTP-only

View File

@@ -69,11 +69,10 @@ export default defineConfig({
})
```
> **Serving behind the gateway.** A static frontend mounts at `/<name>/` (the
> root `castle-app` at `/`). Castle's `react-vite` build passes that prefix as
> `VITE_BASE`, so a frontend that reads it (above) works at its subpath with no
> manual `base`. Frontends that don't read `VITE_BASE` must hand-set `base` to
> match, or they'll request assets from the wrong path.
> **Serving behind the gateway.** A static frontend is served at its own subdomain
> — `<name>.<gateway.domain>` — rooted at `/`, so `VITE_BASE` is always `/`. Castle's
> `react-vite` build passes `VITE_BASE=/`, and a `vite.config` that reads it (above)
> works unchanged. (Frontends are no longer mounted under a `/<name>/` path.)
## Project layout
@@ -159,7 +158,7 @@ services:
http:
internal: { port: 5173 }
proxy:
caddy: { path_prefix: /app }
caddy: {} # expose the dev server at my-frontend.<gateway.domain>
```
See @docs/registry.md for the full registry reference.
@@ -170,18 +169,21 @@ For production, the static build output is served by Caddy rather than a Node
process. You do **not** write this block by hand — `castle deploy` generates it.
The flow:
1. `castle deploy` runs the program's `build.commands` (so `dist/` is current).
1. You build the frontend with `castle program build <name>` (deploy does **not**
build — it only points Caddy at `dist/`).
2. The Caddyfile generator emits a route per `behavior: frontend` program that
has `build.outputs`, rooted **in place** at `<source>/<build.outputs[0]>`
no copy. A static frontend mounts at `/<name>/`; `castle-app` is
special-cased to serve at the root `/`.
no copy. Each frontend is served at its own subdomain `<name>.<gateway.domain>`
(the dashboard, `castle`, is also the target of the `:9000` redirect).
The build is run with `VITE_BASE` set to that serve prefix, so a `vite.config`
that reads it (see [Vite config](#vite-config)) emits asset URLs that resolve at
the subpath. The generated block (in `~/.castle/artifacts/specs/Caddyfile`):
The build is run with `VITE_BASE=/`, so a `vite.config` that reads it (see
[Vite config](#vite-config)) emits root-relative asset URLs. The generated block
(in `~/.castle/artifacts/specs/Caddyfile`) is a host matcher inside the
`*.<domain>` site:
```caddyfile
handle_path /my-frontend/* {
@host_my_frontend host my-frontend.example.com
handle @host_my_frontend {
root * /data/repos/my-frontend/dist
try_files {path} /index.html
file_server
@@ -190,8 +192,8 @@ handle_path /my-frontend/* {
The `try_files {path} /index.html` directive is essential for SPA routing —
it falls back to `index.html` for any path that doesn't match a static file,
letting React Router handle client-side routes. The serving prefix is derived
from the program name, not hand-configured.
letting React Router handle client-side routes. The subdomain is the program name,
not hand-configured.
## API integration