Watch
0
0
Fork
You've already forked hyperhive
0
hyperhive/docs/2-handbook/swarm/hive-directory.md
iris 0e99662710 docs: the swarm README and credentials page keep the overview, the rest moves to its own pages
The hive directory and the WireGuard mesh become handbook pages of their own,
the controller's mint-failure handling moves to controller.md, and the
credential-by-credential mechanics and the secret store UI's proxy move to a new
implementation page.
2026-11-14 13:33:50 +01:00

6.8 KiB

The hive directory

How a hive knows which swarm it belongs to and how to reach the others: the identity each hive carries, what the directory feeds, and how a hive reaches the queue and the secret store on an address other hives can route to. The swarm page says what a hive is.

Hive identity config

services.hyperhive = {
  swarm = {
    domain = "myswarm.example"; # required — the swarm's DNS domain
    name = "myswarm"; # shared swarm display name (optional)

    # required — the directory, identical on every host in the swarm.
    # Names only: each entry's `domain` defaults to <name>.<swarm.domain>.
    hives = {
      firsthive = { }; # this host, per hiveName
      secondhive = { }; # a second hive
      thirdhive.domain = "thirdhive.elsewhere.example"; # addressed off-convention
    };
  };

  # required on a hive host — this hive's label in the directory
  deploy.hive-controller.hiveName = "firsthive";
};

Swarm options are identical on every host in the swarm, so swarm.domain is required on every host that runs any hyperhive service; a host that imports the module and enables nothing evaluates without it. hiveName is required on a host that runs a hive (deploy.hive-controller.hiveName), and on no other. Eval fails with a hint naming each. Neither defaults, because a guessed value here is a wrong hostname that evaluates cleanly and deploys.

The directory is one attrset, identical on every host. It describes every hive in the swarm, including this one, keyed by hiveName; what differs between hosts is only hiveName. It must contain an entry for this host's hiveName, and an empty directory fails that check too — eval names the missing hive. Peers are every entry but this host's, so a directory without this host would make every hive a peer, itself included.

# hive A                                   # hive B
deploy.hive-controller.hiveName = "firsthive";   deploy.hive-controller.hiveName = "secondhive";
swarm.hives = { … };                       swarm.hives = { … };   # byte-identical

One entry per hive means two hosts can't hold different facts about the same third hive, such as a stale endpoint.

domain defaults to <name>.<swarm.domain>, so a conventional directory is names only. Set it only for a hive addressed by something else. This hive's own services.hyperhive.domain comes from its entry; it drives HYPERHIVE_HIVE_DOMAIN in every container so agents can form qualified labels (agent1@firsthive.myswarm.example). Setting services.hyperhive.domain directly overrides the directory entry, with a deprecation warning: the option is local to this host while every host shares the directory, so a value written only here is invisible to the rest of the swarm.

swarm.name is display only — the dashboard chrome header and per-agent system prompts — and federated hives at different domains can share one. hiveName surfaces in the same places but is also the leftmost label of the hive's domain. swarm.name names the group; hiveName names this hive.

How a hive name qualifies an agent, and the env vars that carry it: conventions.md § Hive identity.

Trust inside a swarm comes from the secret store's pki root (ca.md): every hive's gateway leaf chains to it. Trusting a hive whose root this swarm doesn't own — another swarm's — has no mechanism.

What the directory feeds

  1. Swarm-wide hive roster — swarm-controller reads this same directory and serves it at GET /api/hives; the swarm UI's front page renders it (ui.md).

  2. Matrix federation — when this host runs the homeserver (deploy.matrix.enable), tuwunel federates with the peer's matrix server (discovered via the peer's .well-known/matrix/server delegation, which the controller's host or the pinned hive's gateway serves). Federation validates the peer's TLS certificate against the matrix container's trust bundle, independent of this directory.

    ⚠️ On a hive with self-signed gateway certificates, the container also trusts this hive's trust-bundle.pem, bound in at runtime and added to the public CAs; it holds the store's pki root, so a peer whose certificate chains to the same root validates. A peer outside this swarm's root needs a CA-issued certificate (ACME). Federation firewall and TLS requirements: 2-handbook/swarm/matrix.md.

  3. WireGuard mesh (optional) — deploy.wireguard.enable reads each entry's wireguardPublicKey/wireguardEndpoint/wireguardAddress to configure wg-hive. See the WireGuard page.

Reaching the queue and the store on a routable address

The mesh isn't the only way to reach the queue and the secret store from another host. Set deploy.nats.openFirewall on the queue's host to open the queue's port on every interface, and deploy.bao.openFirewall together with deploy.bao.extraListenAddresses on the store's host to open the store's port on the addresses it listens on. Both default to off. Each exposes a service beyond the tunnel, which is the operator's decision. The queue requires TLS and its auth_callout refuses every client it can't identify. The store requires a client certificate on every listener, and evaluation fails when you set deploy.bao.openFirewall without deploy.bao.clientCaFile. Point nats.<swarm.domain> and bao.<swarm.domain> in your upstream DNS at the address the other hives reach.

Keeping a vhost off the public address

The gateway is one nginx per host, and a vhost answers on every address of the host by default, IPv4 and IPv6. To serve some vhosts only inside a private network (a VPN, say), list the host's addresses on that network in services.hyperhive.gateway.privateListenAddresses and set public = false on the vhosts that should stay inside:

services.hyperhive.gateway = {
  privateListenAddresses = [ "10.100.0.1" "fd00:100::1" ];
  vhosts."grafana.myswarm.example".public = false;
};

A public = false vhost listens on those addresses only. The public address never serves it, and a request for its name there closes without a response. Vhosts that keep the default public = true answer on the private addresses too. The addresses can be IPv4 or IPv6 (fd00::1 or [fd00::1]), and each must exist on the host when nginx starts. The firewall stays your call: this decides which vhosts nginx serves on which address, not which ports are open.

The gateway also listens on [::] by default, so the vhosts are reachable over IPv6. gateway.listenIPv6 follows networking.enableIPv6; turn it off on a host whose kernel can't open an IPv6 socket. Details are in gateway.md.