Commit Graph

15 Commits

Author SHA1 Message Date
Paul Payne
c525e154da ui: drop Graph page, add host switcher to left nav
The System Map subsumes the read-only Graph diagnostic, so remove the page,
its route, and nav entry (the /graph API + useGraph stay — the map and palette
consume them).

Add a host switcher under the sidebar brand: it shows which castle host you're
driving (the is_local node) and, when peers are present, jumps to a peer's
dashboard at castle.<domain>, preserving the current view. Node summaries now
carry gateway_domain so the switcher can build each host's URL.
2026-07-07 06:44:48 -07:00
Paul Payne
835f3f94eb feat(cli): castle mesh command + shared-config API + NATS integration tests
- castle mesh status|nodes|config (list/get/set) — hits the local castle-api;
  surfaces the shared-config write path (authority-gated)
- API: GET/PUT /mesh/config/{key:path}, GET /mesh/config; nats_client gains
  list_shared_config
- tests: NATS integration suite against a real broker via a docker fixture
  (peer discovery, presence, graceful-offline, shared config, secrets-off-wire);
  plus HTTP-layer /mesh/config tests. Closes the runtime-coverage gap.
- AGENTS.md §8 updated for NATS + the mesh CLI

100 api + 211 core tests pass.
2026-07-07 06:13:57 -07:00
Paul Payne
6c00a9d33c feat(mesh): NATS JetStream transport replacing MQTT
Replace the Mosquitto/paho mesh transport with NATS JetStream KV:
- mesh_wire.py: transport-agnostic registry (de)serialization (secret-stripped)
- nats_client.py: async CastleNATSClient — registry in the castle-registry KV
  bucket, heartbeat liveness, delete-on-stop offline, watch-driven mesh state
- rewire main/config/models/nodes; MeshStatus -> connected/nats_url
- frontend MeshStatus + MeshPanel to the new fields
- drop paho-mqtt/mqtt_client + _mqtt._tcp mDNS browse; add nats-py
- keep the secret-stripping invariant; mDNS peer discovery retained

Single-node verified; two-node parity pending the second node.
2026-07-07 04:55:02 -07:00
Paul Payne
b28645f2f4 mesh: carry gateway_domain so peers can launch remote apps
The mesh payload didn't include a node's acme domain, so a peer had no way
to build launch URLs for another machine's exposed apps. Publish
gateway_domain in the MQTT registry payload and surface it per-deployment on
/mesh/deployments as `domain`. The dashboard palette and System Map now build
`https://<subdomain>.<domain>` for remote http-exposed apps (references use
their base_url), making primer's apps launchable from civil.
2026-07-06 21:07:10 -07:00
Paul Payne
004df3ba4f fix: remote endpoint reach-gating + drag-to-connect resync race
- Mesh endpoints now gate the http port on `subdomain` (the registry's "exposed"
  signal), matching the local reach-gated derivation — so a remote reach:off
  service (e.g. primer's castle-gateway) no longer shows a phantom :9000.
- The focus/resync effect no longer rebuilds the node set while a connection is
  being dragged; a background refetch mid-drag was silently cancelling the
  connection, so drag-to-expose/-require intermittently "did nothing".
2026-07-06 20:36:52 -07:00
Paul Payne
ebd23e42c1 feat: cross-node consumption edges over the mesh
Carry each deployment's `requires` (deployment refs, no secrets) from config →
registry → the mesh MQTT payload, and expose it on /mesh/deployments. The System
Map resolves a remote deployment's ref against the provider set across nodes
(same machine → local provider → other machine) and draws a dashed cross-node
edge. This is the multi-node payoff: e.g. primer's castle-api → civil's mqtt.
2026-07-06 17:16:09 -07:00
Paul Payne
800f539ef6 feat(core+api): sockets, consumption audit, capabilities, mesh endpoints
Graph model gains, all derived (GET /graph auto-exposes via asdict):
- endpoints[{protocol,port}] + reach per node — reusing http_exposed/tcp_port,
  so raw-TCP infra (postgres/neo4j/mqtt) is finally visible and edges can be
  protocol-typed by the target's socket.
- base_url for reference (external) nodes.
- provides/consumes capability types, activating the dormant ProgramSpec fields.

New surfaces:
- core/audit.py + GET /graph/suggestions: SUGGESTS undeclared `requires` by
  matching a deployment's env endpoint values against provider sockets. Never
  writes; the graph stays declaration-derived (docs/relationships.md).
- kind-scoped PUT/DELETE /config/references/{name} for external resources.
- GET /mesh/deployments (flattened remote deployments + derived endpoints), and
  the mesh MQTT payload now carries tcp_port + base_url (forward-compatible,
  still no secrets) so peers can resolve cross-node endpoints.
2026-07-06 16:09:12 -07:00
Paul Payne
315afe3f5f feat(api): kind-scoped deployment resolution
Resolve deployments by (name, kind) via the core per-kind stores / registry.get:
- get_service resolves a service-or-static; get_job a job; get_component returns
  the first kind for a name (the /services|/jobs|/tools/{name} endpoints are the
  unambiguous addresses). _save_deployment stores into the kind store, preferring
  the existing same-named deployment on a partial patch (can't derive kind from a
  partial payload). delete/set-enabled act across a name's kinds.
- All iterate-all loops use registry.all(); lookups use registry.get/named.
- services.py/logs.py resolve the managed deployment and use kind-aware unit names.
- mqtt registry (de)serialization keyed by composite "<kind>/<name>".
API + core suites green (85 + 182).
2026-07-06 02:35:20 -07:00
Paul Payne
317232ca6a Manager-first deployment model: split runner, merge service/job, frontend→static
Replace the conflated `runner` axis with two orthogonal ones: `manager`
(systemd|caddy|path|none) — who supervises/realizes a deployment — and, for
systemd only, a nested `launcher` (python|command|container|compose|node) — how
the process starts. ServiceSpec and JobSpec collapse into one manager-
discriminated DeploymentSpec union (Systemd/Caddy/Path/Remote); the services/
and jobs/ config dirs collapse into one deployments/ dir. The human "kind"
(service|job|tool|static|reference) is fully derived (kind_for), never stored —
the frontend kind is renamed static. behavior is gone.

- core: DeploymentSpec union + LaunchSpec + kind_for; legacy-aware loader
  normalizes old runner shapes; CastleConfig.deployments with derived
  services/jobs/tools views; registry.Deployment carries manager/launcher/kind.
- cli: service/job/tool as filtered views + a deployment group; --behavior→--kind,
  create --runner→--launcher; lifecycle dispatches over config.deployments.
- castle-api: /deployments primary with /services,/jobs as views; summaries
  derive kind; PUT/DELETE /config/deployments/{name} (services/jobs aliased).
- app: KindBadge, frontend→static everywhere, pick-a-kind creation wizard,
  per-kind config editors.
- docs: single deployments/ layout, manager/launcher, static kind throughout.

Live migration verified byte-identical: regenerated Caddyfile and every unit
ExecStart line unchanged, so nothing restarted. Suites: core 124, cli 25,
castle-api 55; dashboard build + type-check clean.
2026-07-01 10:23:03 -07:00
Paul Payne
43ef1cc588 Subdomain-only gateway routing; drop path prefixes
Collapse the two proxy shapes (path_prefix + host) to a single checkbox: a
service is either port-only, or exposed at <service-name>.<gateway.domain>. Path
routes and their prefix-stripping foot-guns are gone; the subdomain is always the
service name (rename the service to change it).

- Schema: CaddySpec is just `enable` (presence = expose). service_proxy_targets
  returns (expose, port, base_url); Deployment carries `subdomain` (was
  proxy_path/proxy_host). Registry/deploy/mesh/CLI updated in lockstep.
- Generator: compute_routes emits one host route per exposed service/frontend
  (address = name). acme mode = one *.<domain> site with a reverse_proxy matcher
  per service + a file_server matcher per frontend; the :<port> site redirects to
  the dashboard. off mode = a HTTP control plane on :<port> (dashboard at / +
  /api → castle-api); other services are port-only.
- Dashboard/API: castle-app and castle-api are ordinary subdomains. The dashboard
  derives the API base at runtime (castle-api.<domain> on a subdomain, else /api)
  and calls it cross-origin — castle-api already allows CORS *. Frontends build
  with VITE_BASE=/ (served at their subdomain root).
- CLI: `service create` drops --path/--host; --no-proxy makes it port-only.
- Docs/tests reworked for the model; docs use generic placeholders only (no
  personal host/domain/IP details).
2026-06-30 20:47:03 -07:00
Paul Payne
3f3b88f17b refactor: Align vocabulary — component → program / deployment
Canonical terms (see docs/registry.md glossary):
- program: any catalog entry (tool/daemon/frontend) — the software
- service / job: how a program is deployed (systemd .service / .timer)
- deployment: umbrella for the unified service+job+program view

Changes:
- core: ServiceSpec/JobSpec component: → program: (component accepted as
  back-compat validation_alias); DeployedComponent → Deployment.
- api: ComponentSummary/Detail → DeploymentSummary/Detail; GET /components
  → /deployments; GatewayRoute.component → program; GatewayInfo
  .component_count → deployment_count; ServiceActionResponse/action keys
  component → program.
- app: matching type renames, /components → /deployments hook, route
  /component/:name → /deployment/:name, GatewayRoute.program.
- docs: component-registry.md → registry.md (+ canonical glossary);
  CLAUDE.md endpoint list refreshed (drop removed /tools, add typed
  program/service/job + config routes).

Tests: core 92, cli 24, api 52 green; app build clean; ruff clean.
2026-06-14 11:05:22 -07:00
Paul Payne
400e0b253b Less stack-centric and location-centric model. 2026-06-13 17:26:49 -07:00
Paul Payne
f559fba143 feat: Introduce job and service categorization for components, enhancing routing and display logic across the application 2026-02-23 17:02:55 -08:00
Paul Payne
56b9c8ddf1 refactor: Update component structure to use behavior and stack attributes, enhancing clarity and functionality across services, tools, and frontends 2026-02-23 16:32:55 -08:00
Paul Payne
3343e955fd feat: Implement multi-node support with MQTT and mDNS for service discovery and coordination 2026-02-23 02:30:12 -08:00