# 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](README.md#hives-the-substrate) says what a hive is. ## Hive identity config ```nix 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 .. 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 `.`, 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`](../../5-project/process/conventions.md) § Hive identity. Trust inside a swarm comes from the secret store's `pki` root ([`ca.md`](../../4-implementation/swarm/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`](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`](matrix.md). ## Reaching the queue and the store on a routable address Other hives reach the queue and the secret store on a routable address. 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 agent bridge, 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.` and `bao.` 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: ```nix 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`](../../4-implementation/networking/gateway.md#listen-addresses).