Commit graph hyperhive/docs/swarm.md
Author SHA1 Message Date
atlas
cc74a2c1f6 feat(nix): make the swarm CA hierarchy opt-in
A swarm's services and its hives can live on different hosts, and a host
has no way to tell whether it is the one holding the root — so setting
the swarm CA up is an operator action, not something a host infers.
`swarm.ca.autoConfigure` is off by default and is the only thing that
issues a hive sub-CA, which is also the only case that *can*: signing
one needs the root's private key, and that key is deliberately not on a
hive host otherwise.

With the flag off, a hive self-signs its CA exactly as it always has.
That keeps a plain hive working out of the box; what it gives up is
membership of a swarm's trust hierarchy, which is the right thing to
give up for a hive nobody has federated.

Replaces a default derived from `swarm.peers == { }`. That read "no
peers declared" as "everything is local", which is not the same claim —
a hive can belong to a swarm it has not declared yet, or to one whose
services run elsewhere entirely.
2026-08-05 15:57:50 +02:00
atlas
06710e83b4 feat(nix): issue each hive's CA under a swarm root CA
Cross-hive trust was O(n²) hand-pinning: every hive had to name every
peer's CA. A swarm root makes it O(1) — trust the root once and every
present and future peer validates.

The root is generated by a new `swarm-ca` unit on a single-host swarm
and operator-provided otherwise; `swarm.ca.autoConfigure` picks between
them and derives its default from `swarm.peers` being empty, so "all on
one host" is read off the deployment rather than remembered. Both modes
produce the same artifacts in the same places, so splitting hosts later
is moving the service dirs, not switching code paths. The root key never
enters the nix store, and the root is never regenerated automatically —
replacing it invalidates every peer at once.

Each hive CA carries `nameConstraints` pinned to that hive's domain, so
a leaked hive CA can only mint names inside its own subdomain, enforced
by verifiers rather than by convention.

`ca.pem` was serving as both the issuer and the anchor consumers trust;
those are the same file only while it is self-signed. openssl will not
terminate a chain at a trusted cert that isn't self-signed (rustls and
Go will), so the promotion would have broken some consumers and not
others. `hive-tls-ca` now also writes `trust-bundle.pem` — the hive CA
plus whatever it is rooted at — and every anchor consumer reads that:
agents, the CI and forge containers, and the peer-config recipe. On a
hive with no swarm root the bundle is just that CA, so nothing consuming
it needs a mode to branch on.
2026-08-05 15:57:50 +02:00
atlas
fbf3757551 feat(nix): require swarm.domain and hiveName
Neither has a fallback worth having: a guessed swarm domain is a wrong
hostname that evaluates cleanly and deploys, which is worse than an
eval failure naming the one line an operator has to write. Upgrading
past this sets both, once.

Requiring them also makes the hive domain fully derived rather than
merely derivable — `<hiveName>.<swarm.domain>` now always resolves, so
an operator writes the swarm's address and this hive's label and never
writes the hive domain at all.

`hiveName` stops being display-only in the process: it is the leftmost
label of the domain the hive is addressed by, which the option text and
docs now say.

Each of the three required options asserts separately, so a missing one
names itself. A missing `swarm.domain` legitimately fails two of them —
its own, and `domain`, which can no longer derive — and nix reports all
failing assertions together, so the operator sees the whole set rather
than one at a time.
2026-08-05 15:56:49 +02:00
atlas
747f405c6f fix(nix): keep the domain-derived defaults total
The required-domain assertion in hive-network.nix could not be reached:
`forge.<domain>` and `matrix.<domain>` are evaluated while the
assertion list is, so an unset domain threw `cannot coerce null to a
string` naming one of those options instead of printing the message
that says which option to set.

Both defaults now fall back to a name under the reserved `.invalid`
TLD, which the assertion refuses to let out the door.
2026-08-05 15:56:49 +02:00
atlas
ec565120af feat(nix): derive the hive domain from a new swarm.domain
Every hive in a swarm occupies its own sub-domain of the swarm's, so
the hive domain is derivable rather than something each hive restates.
`services.hyperhive.swarm.domain` is new and nullable; the hive's own
`domain` keeps its existing required-ness and its existing assertion,
and gains a default of `<hiveName>.<swarm.domain>`.

Deliberately a default and not a rename: an alias would reinterpret the
domains hives have already deployed, while a default only fills in the
ones that never set one. Same reason there is no new assertion — the
existing message just names the derivation, so this adds a way to stop
failing rather than a way to fail.
2026-08-05 15:56:49 +02:00
atlas
dde0e1a8c1 docs: let the crate README own the technical detail, point at it
Per review: crate READMEs will be served on the docs page, so the same
technical content in docs/swarm.md and the repo map is redundancy, not
thoroughness.

docs/swarm.md keeps only what is operator-facing and specific to it --
the option, and why enable is not derived from services.hyperhive.enable
-- and points at the README for the socket-directory constraint. The repo
map keeps the one-line warning and the pointer, not the argument.

This is the same correction as the AgentWindow comments: I had written
the socket rationale into five places and called it coverage. Correcting
every copy is what preserves the cause.
2026-08-05 14:59:37 +02:00
atlas
0fe2babbee docs: put swarm-controller in the repo map and the swarm doc
The crate was a workspace member with no entry in CLAUDE.md, which is the
index that auto-loads into every turn -- a member missing from it is
invisible to everyone who comes after.

Both entries carry the socket-directory constraint rather than just
naming the daemon, because that is the one thing about this service a
reader can get wrong from a position that looks correct.
2026-08-05 14:59:37 +02:00
atlas
1a0cb0fb44 docs: name the swarm display name by its new path
Two sites spelled it as a brace group, services.hyperhive.{hiveName,
swarmName}, which no anchored rewrite can handle correctly now that only
one of the two moves; both are written out separately. One of them is an
MCP tool description, so it is rendered into every agent's system prompt.
2026-08-05 11:15:41 +02:00
atlas
7b23b53b75 docs(#2862): document the push side and regenerate the CLI reference
docs/tools/hivectl-cli.md is generated by `hivectl markdown-docs` and
diffed against a fresh run by the hivectl-docs flake check, so adding
the push verb without regenerating it would have failed CI. The
regeneration also retires two copies of a sentence describing the
cross-hive leg as an ssh pipe that "isn't wired up yet" -- ssh was
dropped when the WireGuard mesh became the authentication, and the leg
is wired up now.

snapshot-store.md documented only the receiving host, so a reader had
no way to learn how a pushing hive is told where the store is. It now
covers services.hyperhive.swarm.snapshotStore, including why address
has no default and port does: an address is a deployment fact that
cannot be guessed, a port is a convention both ends read from the same
docs. It also states the namespace rule the two options illustrate --
swarm.* describes the swarm as seen from here, a bare
services.hyperhive.<service> describes a role this host performs.

swarm.md never mentioned the store even though the option lives in its
namespace, so a reader configuring swarm.peers had no signal it exists.
2026-07-31 22:15:37 +02:00
atlas
8406a45275 feat(#1997): add prettier markdown formatter to treefmt
- .prettierrc: proseWrap=preserve (no prose reflow)
- .prettierignore: exclude hivectl-cli.md (auto-generated) + 11 docs
  with multi-line list-item continuations prettier would strip to col 0
  (CommonMark limitation in prettier's list handling)
- format 16 markdown files: cosmetic only (*→_, table alignment,
  heading normalisation) — verified no broken continuations, idempotent
2026-07-02 23:33:11 +02:00
atlas
edad6f863c feat(#1886): trust a peer hive's root CA hive-wide for self-signed federation
Add swarm.peers.<domain>.caCert (path to a peer hive's root CA PEM),
trusted everywhere the hive's own internal CA is — so a self-signed
peer hive can federate (matrix) and any in-hive consumer validates its
certs.

Mechanism (reuses the existing hive-CA embedding): the meta-flake
renderer embeds a LIST of CA files next to each agent's flake —
hive-ca.pem (the hive's own self-signed CA, when active) plus each peer
caCert as peer-ca-<N>.pem — and emits them all in
security.pki.certificateFiles, so every agent trusts them at build
time. The matrix container trusts the same peer CAs for federation TLS.
Nothing is installed in the host trust store; the certs live in the nix
store (no mutable host file).

- meta.rs: embedded_ca_files() = hive CA + peer CAs (from new
  HIVE_PEER_CA_PATHS env); ca_embed_state() tracks the list (content +
  add/remove); sync_agents materialises + stages the list; render emits
  the multi-entry certificateFiles. Tests cover hive-only / hive+peers
  / peers-only / none.
- hive-c0re.nix: HIVE_PEER_CA_PATHS service env (colon-joined caCerts);
  caCert / certFingerprint option docs updated to the hive-wide scope.
- hive-matrix.nix + docs/swarm.md: scope + comment updates.

certFingerprint stays the c0re-only leaf-pin path.
2026-06-22 16:54:44 +02:00
damocles
57b1a2d3ea docs: clarify certFingerprint does not govern matrix federation tls 2026-06-06 00:21:52 +02:00
damocles
e029944cb3 docs: specify certFingerprint format and generation recipe 2026-06-05 23:38:54 +02:00
atlas
89609aaa6a feat(#569): wireguard inter-hive mesh option
Add opt-in WireGuard mesh support to services.hyperhive.swarm:

- swarm.peers.<domain>.wireguardPublicKey — peer's wg public key
- swarm.peers.<domain>.wireguardEndpoint  — peer's UDP endpoint (optional)
- swarm.peers.<domain>.wireguardAddress   — peer's mesh IP with prefix

- swarm.wireguard.enable           — bring up wg-hive interface
- swarm.wireguard.privateKeyFile   — path to host's wg private key
- swarm.wireguard.address          — this host's mesh IP/prefix
- swarm.wireguard.listenPort       — UDP listen port (default 51820)
- swarm.wireguard.persistentKeepalive — keepalive seconds (default 25)

When enabled, generates networking.wireguard.interfaces.wg-hive with
one peer entry per mesh-enabled swarm.peers entry. Opens listenPort
UDP on the host firewall. Adds wireguard_address to HYPERHIVE_PEERS
JSON so hive-c0re can use mesh IPs for intra-swarm routing.

Assertions guard against enable=true without privateKeyFile or address.

Also refactors networking.firewall.allowedTCPPortRanges from the
nested attrset form (which conflicted with the new allowedUDPPorts
line) to the per-attribute form.

docs/swarm.md: adds WireGuard setup section with key generation
commands, two-hive config example, NAT/keepalive notes.
2026-06-03 15:19:16 +02:00
iris
803ea9d7f6 docs: fix swarm.md — peer list reaches agents via HYPERHIVE_PEERS env, not get_agent_meta 2026-05-31 23:51:31 +02:00
iris
b79ef005b5 docs: add swarm.md — multi-hive peer config guide 2026-05-31 23:51:31 +02:00