Watch
0
0
Fork
You've already forked hyperhive
0
hyperhive/docs/2-handbook/swarm/hive-directory.md

137 lines
6.6 KiB
Markdown

# 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 <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";
};
```
<!-- vale write-good.Passive = NO -->
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.
<!-- vale write-good.Passive = YES -->
**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`](../../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.<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:
```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).