install.sh: bootstrap Castle's own control plane
A fresh clone + ./install.sh set up the infra (Docker, Caddy, MQTT, Postgres) but left the registry empty — so `castle deploy && castle start` brought up nothing. install.sh now gets a person all the way to a running Castle: - ensure_uv + install_cli: install uv and the `castle` CLI (editable from ./cli) so the command exists on a fresh machine. - seed_control_plane: register castle-gateway, castle-api, and the castle dashboard from a new bootstrap/ seed dir, and add `repo:` to castle.yaml so `source: repo:<name>` resolves. Never clobbers existing entries. - build_dashboard: build app/dist/ so the gateway has a UI to serve (best-effort; warns if pnpm is absent). The gateway seed uses a `__SPECS_DIR__` placeholder (no machine-specific paths in the repo), substituted with this machine's specs dir at seed time. .gitignore negates bootstrap/**/castle.yaml so the seeds are tracked despite the user-registry ignore rule. Verified: seeded config loads, derives kinds service/service/static, and renders an off-mode Caddyfile serving the dashboard at :9000/ with /api/* proxied to castle-api.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,4 +5,5 @@ __pycache__
|
|||||||
.claude
|
.claude
|
||||||
node_modules
|
node_modules
|
||||||
castle.yaml
|
castle.yaml
|
||||||
|
!bootstrap/**/castle.yaml
|
||||||
.working
|
.working
|
||||||
|
|||||||
18
bootstrap/deployments/castle-api.yaml
Normal file
18
bootstrap/deployments/castle-api.yaml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
program: castle-api
|
||||||
|
description: Castle API
|
||||||
|
manager: systemd
|
||||||
|
run:
|
||||||
|
launcher: python
|
||||||
|
program: castle-api
|
||||||
|
expose:
|
||||||
|
http:
|
||||||
|
internal:
|
||||||
|
port: 9020
|
||||||
|
health_path: /health
|
||||||
|
proxy: true
|
||||||
|
manage:
|
||||||
|
systemd: {}
|
||||||
|
defaults:
|
||||||
|
env:
|
||||||
|
CASTLE_API_PORT: ${port}
|
||||||
|
CASTLE_API_DATA_DIR: ${data_dir}
|
||||||
22
bootstrap/deployments/castle-gateway.yaml
Normal file
22
bootstrap/deployments/castle-gateway.yaml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# The Caddy gateway. `__SPECS_DIR__` is substituted with $CASTLE_HOME/artifacts/specs
|
||||||
|
# at install time (install.sh seed_control_plane). For acme TLS, add a
|
||||||
|
# CLOUDFLARE_API_TOKEN to defaults.env later — see docs/dns-and-tls.md.
|
||||||
|
description: Caddy reverse proxy gateway
|
||||||
|
manager: systemd
|
||||||
|
run:
|
||||||
|
launcher: command
|
||||||
|
argv:
|
||||||
|
- caddy
|
||||||
|
- run
|
||||||
|
- --config
|
||||||
|
- __SPECS_DIR__/Caddyfile
|
||||||
|
- --adapter
|
||||||
|
- caddyfile
|
||||||
|
expose:
|
||||||
|
http:
|
||||||
|
internal:
|
||||||
|
port: 9000
|
||||||
|
health_path: /
|
||||||
|
manage:
|
||||||
|
systemd:
|
||||||
|
exec_reload: caddy reload --config __SPECS_DIR__/Caddyfile --adapter caddyfile
|
||||||
3
bootstrap/deployments/castle.yaml
Normal file
3
bootstrap/deployments/castle.yaml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
program: castle
|
||||||
|
description: Castle web app
|
||||||
|
manager: caddy
|
||||||
3
bootstrap/programs/castle-api.yaml
Normal file
3
bootstrap/programs/castle-api.yaml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
description: Castle API
|
||||||
|
source: repo:castle-api
|
||||||
|
stack: python-fastapi
|
||||||
6
bootstrap/programs/castle.yaml
Normal file
6
bootstrap/programs/castle.yaml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
description: Castle web app
|
||||||
|
source: repo:app
|
||||||
|
stack: react-vite
|
||||||
|
build:
|
||||||
|
outputs:
|
||||||
|
- dist/
|
||||||
92
install.sh
92
install.sh
@@ -215,6 +215,34 @@ ensure_caddy_dns_plugin() {
|
|||||||
log_ok
|
log_ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Castle CLI
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
ensure_uv() {
|
||||||
|
log_step "Ensuring uv (Python package manager)"
|
||||||
|
if cmd_exists uv; then
|
||||||
|
log_skip "already installed"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
curl -LsSf https://astral.sh/uv/install.sh | sh >/dev/null 2>&1 || log_fail "uv install failed"
|
||||||
|
# The installer drops uv in ~/.local/bin — make it visible for the rest of this run.
|
||||||
|
export PATH="${HOME}/.local/bin:${PATH}"
|
||||||
|
cmd_exists uv || log_fail "uv installed but not on PATH (expected ~/.local/bin)"
|
||||||
|
log_ok
|
||||||
|
}
|
||||||
|
|
||||||
|
# Install the `castle` CLI from this repo as an editable uv tool, so a fresh
|
||||||
|
# clone becomes a working `castle` command. Idempotent — reinstall is cheap and
|
||||||
|
# keeps the entry point pointed at the current checkout.
|
||||||
|
install_cli() {
|
||||||
|
log_step "Installing the castle CLI"
|
||||||
|
( cd "$CASTLE_ROOT" && uv tool install --editable ./cli >/dev/null 2>&1 ) \
|
||||||
|
|| log_fail "uv tool install of ./cli failed"
|
||||||
|
cmd_exists castle || log_info "NOTE: ensure ~/.local/bin is on your PATH to use 'castle'"
|
||||||
|
log_ok
|
||||||
|
}
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Directory structure
|
# Directory structure
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -247,6 +275,41 @@ create_directories() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Control plane (Castle's own gateway + API + dashboard)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Register Castle's own control-plane programs/deployments from bootstrap/ so a
|
||||||
|
# fresh registry is not empty. Without this, `castle deploy && castle start`
|
||||||
|
# would bring up nothing. Never clobbers existing entries (idempotent). The
|
||||||
|
# gateway deployment carries a `__SPECS_DIR__` placeholder (the source repo has
|
||||||
|
# no machine-specific paths) that we substitute with this machine's specs dir.
|
||||||
|
seed_control_plane() {
|
||||||
|
log_step "Registering Castle's control plane"
|
||||||
|
local specs="${CASTLE_HOME}/artifacts/specs"
|
||||||
|
|
||||||
|
# The `repo:` field lets `source: repo:<name>` resolve castle's own programs.
|
||||||
|
if ! grep -q "^repo:" "${CASTLE_HOME}/castle.yaml" 2>/dev/null; then
|
||||||
|
printf 'repo: %s\n' "$CASTLE_ROOT" >> "${CASTLE_HOME}/castle.yaml"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local seeded=0 f dst
|
||||||
|
for f in "${CASTLE_ROOT}"/bootstrap/programs/*.yaml; do
|
||||||
|
dst="${CASTLE_HOME}/programs/$(basename "$f")"
|
||||||
|
[ -f "$dst" ] || { cp "$f" "$dst"; seeded=1; }
|
||||||
|
done
|
||||||
|
for f in "${CASTLE_ROOT}"/bootstrap/deployments/*.yaml; do
|
||||||
|
dst="${CASTLE_HOME}/deployments/$(basename "$f")"
|
||||||
|
[ -f "$dst" ] || { sed "s#__SPECS_DIR__#${specs}#g" "$f" > "$dst"; seeded=1; }
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$seeded" = "1" ]; then
|
||||||
|
log_ok "castle-gateway, castle-api, castle (dashboard)"
|
||||||
|
else
|
||||||
|
log_skip "already registered"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Systemd lingering
|
# Systemd lingering
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -435,6 +498,28 @@ maybe_setup_neo4j() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Dashboard build
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Build the dashboard SPA so the gateway has something to serve at :9000. The
|
||||||
|
# `castle` program serves `app/dist/` in place; without a build there's no UI.
|
||||||
|
# Best-effort: a missing pnpm is a warning (the CLI/API still work), not a failure.
|
||||||
|
build_dashboard() {
|
||||||
|
log_step "Building the dashboard"
|
||||||
|
if [ ! -d "${CASTLE_ROOT}/app" ]; then
|
||||||
|
log_skip "no app/ directory"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if ! cmd_exists pnpm; then
|
||||||
|
log_skip "pnpm not found — build later with 'castle program build castle'"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
( cd "${CASTLE_ROOT}/app" && pnpm install --silent >/dev/null 2>&1 && pnpm build >/dev/null 2>&1 ) \
|
||||||
|
|| { log_skip "build failed — retry later with 'castle program build castle'"; return; }
|
||||||
|
log_ok "app/dist/"
|
||||||
|
}
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Summary
|
# Summary
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -469,7 +554,8 @@ print_summary() {
|
|||||||
|
|
||||||
printf "Next steps:\n"
|
printf "Next steps:\n"
|
||||||
printf " castle deploy # Generate registry, systemd units, Caddyfile\n"
|
printf " castle deploy # Generate registry, systemd units, Caddyfile\n"
|
||||||
printf " castle start # Start all deployments and the gateway\n"
|
printf " castle start # Start the gateway, API, and all deployments\n"
|
||||||
|
printf " open http://localhost:9000 # the dashboard\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -500,13 +586,17 @@ main() {
|
|||||||
ensure_docker
|
ensure_docker
|
||||||
ensure_caddy
|
ensure_caddy
|
||||||
[ -n "$WITH_DNS_PLUGIN" ] && ensure_caddy_dns_plugin "$WITH_DNS_PLUGIN"
|
[ -n "$WITH_DNS_PLUGIN" ] && ensure_caddy_dns_plugin "$WITH_DNS_PLUGIN"
|
||||||
|
ensure_uv
|
||||||
|
install_cli
|
||||||
create_directories
|
create_directories
|
||||||
|
seed_control_plane
|
||||||
enable_lingering
|
enable_lingering
|
||||||
seed_caddyfile
|
seed_caddyfile
|
||||||
migrate_old_containers
|
migrate_old_containers
|
||||||
setup_mqtt
|
setup_mqtt
|
||||||
setup_postgres
|
setup_postgres
|
||||||
maybe_setup_neo4j
|
maybe_setup_neo4j
|
||||||
|
build_dashboard
|
||||||
print_summary
|
print_summary
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user