xvii-node
The root agent on every server. Repo node: cmd/xvii-node and proto (the agent protocol, imported
by apps and home). Two jobs (ADR 0010): on the control-plane server install and update the platform
itself (stage 2), and on every server run apps workloads (stage 5).
Joining
curl -fsSL https://<d>/install.sh | sh -s -- node --join <token> (or xvii server add-node over SSH)
installs Docker if missing, Caddy, the agent and its systemd unit.
- The one-time join token (from
apps, 1 h) is exchanged for a node credential (a key pair; the
public key is registered with appsd). The agent then dials wss://apps.<d>/agent (ADR 0008).
Protocol
One WebSocket, JSON messages, request ids, heartbeats every 15 s.
- Control plane → node: desired state (workloads with image digests, env, volumes, limits, endpoints,
health checks), build jobs, log and exec sessions, cert approvals, drain.
- Node → control plane: inventory and capacity, workload status, events (crash, OOM, health), metrics
batches, log chunks, build progress.
- The agent converges idempotently from the last desired state and keeps it on disk, so workloads keep
running when the control plane is unreachable.
On every node
Docker (or runsc) workloads, per-team networks, Caddy for the node's endpoints (config through the
admin API on localhost), on-demand TLS with the ask going to appsd, a TCP proxy for database access,
log rotation, metrics sampling, disk usage per volume, BuildKit when the node has the builder role,
status checks for status when the node is a probe location.
On the control-plane server
Reads the platform's desired state (services, versions, settings) written through home:
- downloads service releases from the release index, verifies
SHA256SUMS and its signature;
- writes systemd units and environment files under
/etc/xvii/, data under /var/lib/xvii/<service>/;
- creates PostgreSQL databases and roles per service;
- renders the platform's Caddy config (service hosts, wildcard zones, on-demand blocks);
- updates: stop old, start new (the new binary migrates), health check, roll back on failure;
xvii-node recover <user>: prints a sign-in link through id when nobody can sign in.
Branded builds
A white-label build is the same source with BRAND (and optionally DISPLAY) set for make, e.g.
make binaries BRAND=acme DISPLAY=Acme: every Makefile passes them as
-X go.xvii.network/kit/brand.Name=... -X .../brand.Display=... (kit/brand). The name derives the
binary and unit names (acme-node, acme-id.service), paths (/etc/acme, /var/lib/acme,
/run/acme), system users and databases (acme-id, acme_id), Docker names and labels
(acme-*, acme.team), and the environment prefix (ACME_ID_*). Branded builds credit the core:
the CLI and acme-node version print "Acme — powered by xvii". Module paths, API paths and the wire
protocol stay xvii — a branded install is fixed at install time; renaming a live install means
reinstalling. Two builds never share a server: each reads only its own labels and paths.
Stage 2 implementation
xvii-node serve (systemd, root) applies /etc/xvii/platform.json at start and every 10 minutes, and
answers home on /run/xvii/node.sock: GET /status, POST /apply, PUT /services/{name} (version),
POST /services/{name}/restart, GET /services/{name}/logs. A service is described by its release's
manifest.json (port, host, wildcard, aliases, database, panel, issuer, groups, writes, low_ports, env, commands); apply
creates its user, folders, database and role, OIDC client (a file id reads), env file, systemd unit and
Caddy site, swaps the current link, restarts on change, waits for /healthz and rolls back.
Edge
xvii-node edge <upstream IP> (/etc/xvii/edge.json) makes a server the public face of a service on
another one (mail, ADR 0016): it relays 25, 587, 465, 143 and 993 to the upstream's 10025, 5587, 4465,
1143, 1993 with a PROXY protocol v1 line, and runs a SOCKS5 egress on :1080 for the upstream's address
only. xvii-node edge off stops it.
Local commands
xvii-node status, xvii-node logs, xvii-node recover, xvii-node leave, xvii-node doctor (ports,
DNS, clock, Docker, disk).