# The swarm's directory: one entry per hive, **including this one**, # identical on every host in the swarm. `services.hyperhive.hiveName` # says which entry is us, and `peerHives` below derives the rest. # # Why a directory rather than a per-host peer list: every field here is # intrinsic to the hive it describes — none of them says anything about # the *pair*. A list where every field is intrinsic is a directory each # host was keeping its own copy of, which is O(n²) duplication that # deduplicates without loss. It is also a correctness gain: two hosts # could hold different endpoints for the same third hive and nothing # detected it. One entry per hive makes that unrepresentable. # # Consumed by hive-c0re's environment (HYPERHIVE_PEERS — see # ./hive-c0re), identity.rs + the dashboard's P33RS tab, and the mesh in # ./swarm-wireguard.nix. The mesh lives there rather than here because # bringing up an interface is host networking rather than swarm # bookkeeping, and a host that runs no hive still needs it. { lib, config, ... }: let cfg = config.services.hyperhive; swarmCfg = cfg.swarm; in { options.services.hyperhive.swarm.hives = lib.mkOption { type = lib.types.attrsOf ( lib.types.submodule { options = { domain = lib.mkOption { type = lib.types.str; example = "lab.example.com"; description = '' Public DNS domain this hive occupies — used for dashboard links, peer HTTPS checks and Matrix federation discovery. Deliberately has no default. It is conventionally `.`, but defaulting to that would let a typo'd swarm domain produce a name that resolves somewhere real; an eval failure naming the hive is the better outcome. ''; }; certFingerprint = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; example = "sha256:b1946ac92492d2347c6235b4d2611184a3f5b6cae6c19d6e3c2f0a8e7d4c9f12"; description = '' Expected TLS certificate fingerprint for this hive's HTTPS endpoint. Null = trust the CA bundle — which for a hive inside the swarm CA hierarchy is the normal case, since every hive under the swarm root already chains to it. Set it to pin a leaf that no CA in the bundle vouches for. Format: the literal `sha256:` followed by exactly 64 hex digits (case-insensitive, no colon separators) — the SHA-256 digest of the DER-encoded leaf certificate. Generate with `openssl x509 -noout -fingerprint -sha256`, then strip the colons and prepend `sha256:`. A malformed value is ignored with a warning rather than weakening trust. See docs/swarm/README.md for the full recipe. Scopes only to hive-c0re's own peer HTTPS checks — it does NOT help Matrix federation, which validates against the container's trust bundle. There is no per-hive CA field to cover that case any more: the swarm root is the trust path (see ./swarm-ca.nix). ''; }; wireguardPublicKey = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; example = "base64pubkey="; description = '' WireGuard public key for this hive's host. Required when `services.hyperhive.swarm.wireguard.enable = true` and you want this hive reachable over the mesh. Null = TLS- only peering (public internet, no mesh tunnel). ''; }; wireguardEndpoint = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; example = "203.0.113.1:51820"; description = '' WireGuard endpoint for this hive in `host:port` form. Null = this hive has no reachable endpoint, so the tunnel is initiated from the other side. Reads like a fact about the relationship and is not: it says whether *this* hive can be dialled, which every other hive in the swarm needs the same answer to. ''; }; wireguardAddress = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; example = "10.100.0.2/32"; description = '' IP address (with prefix) of this hive's host on the WireGuard mesh. Used as the `allowedIPs` for its WireGuard config entry and injected into `HYPERHIVE_PEERS` so hive-c0re can route intra-swarm traffic to the mesh address rather than the public domain. Required to include a hive in the mesh (entries missing this field are silently excluded from `wg-hive`). ''; }; }; } ); default = { }; example = { pr1ma = { domain = "pr1ma.example.com"; wireguardAddress = "10.100.0.1/32"; wireguardEndpoint = "203.0.113.1:51820"; }; edge = { domain = "edge.example.com"; wireguardAddress = "10.100.0.2/32"; }; }; description = '' Every hive in this swarm, keyed by `hiveName` — **including this host's own hive**. The same attrset is meant to be identical on every host in the swarm, so it can be written once and shared; `services.hyperhive.hiveName` is what makes a given host read it as "me and four others" rather than "five peers". Empty (the default) means this host is not part of a swarm. Once non-empty it must contain an entry for `hiveName`, which is asserted — a hive that lists everyone but itself would otherwise derive its own peer set as *everything* and peer with itself. ''; }; options.services.hyperhive.swarm.peerHives = lib.mkOption { type = lib.types.attrsOf (lib.types.attrsOf lib.types.unspecified); readOnly = true; internal = true; description = '' Read-only: `hives` minus this host's own entry. Derived once here rather than in each consumer, because "everything that isn't me" is a filter four different modules were re-implementing and only one of them has to be wrong for a hive to peer with itself. ''; }; config = { services.hyperhive.swarm.peerHives = lib.filterAttrs (name: _: name != cfg.hiveName) swarmCfg.hives; assertions = [ { # Guarded on `hiveName != null` so the required-hiveName # assertion in ./hyperhive.nix is what fires for that case — # two assertions naming the same missing value is noise. assertion = swarmCfg.hives == { } || cfg.hiveName == null || swarmCfg.hives ? ${cfg.hiveName}; message = '' services.hyperhive.swarm.hives has no entry for this hive (services.hyperhive.hiveName = "${toString cfg.hiveName}"). `hives` describes every hive in the swarm including this one, so that every host can share one identical attrset. Add: services.hyperhive.swarm.hives."${toString cfg.hiveName}" = { domain = "${toString cfg.domain}"; }; Declared hives: ${lib.concatStringsSep ", " (lib.attrNames swarmCfg.hives)} ''; } ]; }; # `enableRequiredServices` is declared in ./swarm-required-services.nix # together with the per-service `enable`s it asserts — it is a # deployment-shape switch rather than swarm bookkeeping, so it lives # with its consequences instead of here. options.services.hyperhive.swarm.snapshotStore = { address = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; example = "10.100.0.1"; description = '' Mesh address of the swarm's snapshot store — the single `btrfs receive` endpoint every hive in this swarm pushes agent snapshots to. Bare IP, no prefix. There is exactly **one** store per swarm, not one per peer: the receiver keys destinations by *agent*, so an agent that migrates between hives keeps a single unbroken incremental chain. Per-hive stores would split that chain in two, which is the case the store exists to serve. Null means this swarm has no store configured, and pushing fails saying so rather than guessing an address. Set it on every hive that pushes; the receiving host separately sets `services.hyperhive.snapshotStore.enable`. ''; }; port = lib.mkOption { type = lib.types.port; default = 51821; description = '' TCP port the swarm's snapshot store listens on. Must match the receiving host's `services.hyperhive.snapshotStore.port`. Defaulted (unlike `address`) because it is a shared convention both sides read from the same option docs — whereas an address is deployment-specific and cannot be guessed. ''; }; }; }