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.
141 lines
6.8 KiB
Markdown
141 lines
6.8 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).
|
|
|
|
3. **WireGuard mesh** (optional) — `deploy.wireguard.enable` reads each
|
|
entry's `wireguardPublicKey`/`wireguardEndpoint`/`wireguardAddress`
|
|
to configure `wg-hive`. See [the WireGuard page](wireguard.md).
|
|
|
|
## 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:
|
|
|
|
```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).
|