payneio 2069e30353 refactor: Remove dedicated /tools endpoints, use /programs?behavior=tool instead
Eliminates inconsistency where tools had their own filtered view but daemons and frontends didn't.

Changes:
- API: Removed tools router and ToolSummary/ToolDetail models. Added optional behavior query parameter to GET /programs for filtering by program type (tool, daemon, frontend).
- Frontend: Updated hooks to pass behavior param to usePrograms instead of separate useTools. Updated components to use ProgramSummary type. Removed useToolDetail hook.
- Docs: Updated API documentation to reflect program behavior filtering.
2026-04-27 21:06:57 -07:00
2026-04-27 20:48:35 -07:00

Castle

A personal software platform. Castle manages independent services, tools, and frontends from a single CLI, with a unified gateway, systemd integration, and a web dashboard.

Quick Start

# Install the castle CLI
cd cli && uv tool install --editable . && cd ..

# Run the installer (sets up Docker, Caddy, MQTT, Postgres, Neo4j, directory tree)
./install.sh

# Initialize castle.yaml (the registry that tracks everything)
cat > ~/.castle/castle.yaml << 'EOF'
gateway:
  port: 9000

programs: {}
services: {}
jobs: {}
EOF

# Sync all projects (git submodules + dependencies)
castle sync

# See what's here
castle list

# Deploy and start everything
castle deploy
castle services start

# Visit the dashboard
open http://localhost:9000

Creating Components

# Service — FastAPI app with health endpoint, systemd unit, gateway route
castle create my-api --stack python-fastapi --description "Does something useful"
castle test my-api
castle deploy my-api
castle services start

# Standalone tool — CLI tool with argparse, stdin/stdout, Unix pipes
castle create my-tool --stack python-cli --description "Does something"

# Frontend — React/Vite app, built and served through the gateway
castle create my-app --stack react-vite --description "Web interface"
castle build my-app
castle deploy my-app

CLI Reference

castle list [--behavior B] [--stack S] [--json]  List all programs, services, and jobs
castle info NAME [--json]                        Show program details
castle create NAME --stack STACK                 Scaffold a new project
castle build [NAME]                   Build projects (one or all)
castle test [NAME]                    Run tests (one or all)
castle lint [NAME]                    Run linter (one or all)
castle deploy [NAME]                  Deploy to ~/.castle/ (spec -> runtime)
castle run NAME                       Run a service in the foreground
castle sync                           Sync submodules + install deps
castle logs NAME [-f] [-n 50]         View service/job logs
castle gateway start|stop|reload      Manage Caddy reverse proxy
castle service enable|disable NAME    Manage a systemd service
castle service status                 Show all service statuses
castle services start|stop            Start/stop everything
castle tool list                      List all tools
castle tool info NAME                 Show tool details

Registry

castle.yaml lives at ~/.castle/castle.yaml and is the single source of truth. It has four top-level sections:

  • programs: — Software catalog (source, stack, behavior, build config)
  • services: — Long-running daemons (run, expose, proxy, systemd)
  • jobs: — Scheduled tasks (run, cron schedule, systemd timer)
  • units: — Compact shorthand that expands into programs + services/jobs

Services and jobs can reference a program via component: for description fallthrough.

gateway:
  port: 9000
repo: /path/to/castle

programs:
  central-context:
    description: Content storage API
    behavior: daemon
    source: code/central-context
    stack: python-fastapi

services:
  central-context:
    component: central-context
    run:
      runner: python
      program: central-context
    expose:
      http:
        internal: { port: 9001 }
        health_path: /health
    proxy:
      caddy: { path_prefix: /central-context }
    manage:
      systemd: {}

jobs:
  backup-collect:
    component: backup-collect
    run:
      runner: command
      argv: [backup-collect]
    schedule: "0 2 * * *"
    manage:
      systemd: {}

Convention-based env vars (<PREFIX>_DATA_DIR, <PREFIX>_PORT) are generated automatically by castle deploy. Only non-convention values need defaults.env.

The optional repo: field enables source: repo:<path> references that resolve relative to the git repo rather than ~/.castle/.

Architecture

~/.castle/
  castle.yaml          <- program registry (single source of truth)
  code/                <- component source directories
  data/                <- per-service data directories
  secrets/             <- secret files (700 permissions)
  artifacts/
    specs/             <- generated Caddyfile, registry.yaml, systemd units
    content/           <- built frontend assets

<repo>/
  cli/                 <- castle CLI
  core/                <- castle-core library (models, config, generators)
  castle-api/          <- Castle API (dashboard backend)
  app/                 <- Castle web app (React/Vite frontend)
  docs/                <- architecture docs
  install.sh           <- infrastructure bootstrapper

Independence principle: Services never depend on castle. They accept configuration (data dir, port, URLs) via environment variables. Only castle infrastructure (CLI, API, gateway) knows about castle internals.

Gateway: Caddy reverse proxy at port 9000. Services are proxied under one address (localhost:9000/central-context/* -> localhost:9001/*). The web app is served at the root.

Systemd: The CLI generates user units under ~/.config/systemd/user/castle-*.service. Scheduled jobs get .timer files alongside.

Data: Service data lives in ~/.castle/data/<service-name>/. Secrets live in ~/.castle/secrets/.

Mesh Coordination

Castle nodes can discover each other via MQTT and mDNS, forming a personal infrastructure mesh. All mesh features are opt-in — single-node works without them.

# Enable on castle-api (via systemd drop-in or env vars)
CASTLE_API_MQTT_ENABLED=true     # Connect to MQTT broker
CASTLE_API_MQTT_HOST=localhost    # Broker address (default)
CASTLE_API_MQTT_PORT=1883         # Broker port (default)
CASTLE_API_MDNS_ENABLED=true     # Advertise/discover via mDNS

When enabled, the API publishes the node's registry to castle/{hostname}/registry (retained) and subscribes to other nodes. The gateway can proxy to services on remote nodes. The dashboard shows discovered nodes, cross-node routes, and mesh connection status.

API

castle-api runs on port 9020 and is proxied at /api through the gateway.

Endpoint Description
GET /health Health check
GET /stream SSE stream (health, service-action, mesh events)
Programs
GET /programs List all programs (?behavior=tool|daemon|frontend to filter)
GET /programs/{name} Program detail
POST /programs/{name}/{action} Run a lifecycle action (build, test, lint, install, etc.)
GET /components Unified view across nodes (?include_remote=true for cross-node)
GET /components/{name} Component detail
Services
GET /services List all services with status
GET /services/{name} Service detail
POST /services/{name}/start Start a service
POST /services/{name}/stop Stop a service
POST /services/{name}/restart Restart a service
GET /services/{name}/unit View generated systemd unit
Jobs
GET /jobs List all jobs
GET /jobs/{name} Job detail
Gateway
GET /gateway Gateway info with route table
GET /gateway/caddyfile Generated Caddyfile content
POST /gateway/reload Regenerate Caddyfile and reload Caddy
GET /status Live health for all services
Config
GET /config Read castle.yaml
PUT /config Write castle.yaml
PUT /config/programs/{name} Update a program entry
PUT /config/services/{name} Update a service entry
PUT /config/jobs/{name} Update a job entry
POST /config/apply Apply config changes (deploy + reload)
Secrets
GET /secrets List secrets
GET /secrets/{name} Read a secret
PUT /secrets/{name} Write a secret
DELETE /secrets/{name} Delete a secret
Logs
GET /logs/{name} View service/job logs
Mesh
GET /mesh/status Mesh connection state (MQTT, mDNS, peers)
GET /nodes All known nodes (local + remote)
GET /nodes/{hostname} Node detail with deployed components
Description
Personal software environment.
Readme 2.3 MiB
Languages
Python 69.3%
TypeScript 28.5%
Shell 2%