One attrset describing every hive in the swarm including this one,
identical on every host, with hiveName selecting which entry is us.
"My peers" is derived (swarm.peerHives) rather than declared.
Every field in the old per-host peer list was intrinsic to the hive it
described, never to the pair -- so the list was a directory each host
kept its own copy of. Beyond the deduplication it removes a bug class:
two hosts could hold different endpoints for the same third hive with
nothing to detect the disagreement.
Drops the per-hive caCert. Trust inside a swarm derives from the swarm
root, which every hive chains to. What that genuinely removes is
trusting a hive whose root this swarm does not own -- a cross-swarm
problem that wants a mechanism of its own, not a field that happened to
work.
The matrix container's certificateFiles block goes with it and could
NOT be migrated: that list is read at build time and the swarm root is
a runtime file (its key must never enter the store), so there is no
build-time name to put there. caCert being a nix path was precisely
what made it the build-time distribution channel. Agents are unaffected
-- hive-tls folds the root into the hive trust bundle and the meta
renderer embeds that one file. Tracked separately.
Migration is an assertion plus warnings, not a rename: hives is peers
union {self}, and the set gains a member no existing config has written
down. A rename migrates a name and a default can re-root a meaning;
neither can conjure a new member. The warning explains, the self-entry
assertion stops the build.
145 lines
5.3 KiB
Nix
145 lines
5.3 KiB
Nix
# The WireGuard inter-hive mesh for the local host. Split out of
|
|
# ./swarm.nix because the two are different concerns with different
|
|
# audiences: that file declares WHO the peers are (data hive-c0re
|
|
# serialises into HYPERHIVE_PEERS and the dashboard renders), while
|
|
# this one is plain host networking that a machine which runs no hive
|
|
# at all --- the snapshot store, for one --- still needs.
|
|
#
|
|
# The two stay coupled by data, not by structure: the per-peer
|
|
# `wireguard*` fields live on the peer submodule in ./swarm.nix, since
|
|
# that is where a peer is described, and this module reads them.
|
|
{
|
|
lib,
|
|
config,
|
|
...
|
|
}:
|
|
{
|
|
# WireGuard mesh config for the local host.
|
|
# When enabled, a `wg-hive` interface connects to all peers that have
|
|
# `wireguardPublicKey` declared. Peers reachable over the mesh are
|
|
# preferred for inter-hive traffic (no public TLS round-trip needed);
|
|
# peers without a public key still work via normal HTTPS.
|
|
options.services.hyperhive.swarm.wireguard = {
|
|
enable = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
description = ''
|
|
Enable the WireGuard inter-hive mesh. When true, a `wg-hive`
|
|
interface is brought up connecting to all swarm peers that
|
|
declare a `wireguardPublicKey`. Requires
|
|
`privateKeyFile` to be set.
|
|
'';
|
|
};
|
|
|
|
privateKeyFile = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.path;
|
|
default = null;
|
|
example = "/etc/wireguard/hive.key";
|
|
description = ''
|
|
Path to the host's WireGuard private key file. The file must
|
|
be readable by root and should have mode 0400. Generate with
|
|
`wg genkey > /etc/wireguard/hive.key`. Required when
|
|
`swarm.wireguard.enable = true`.
|
|
'';
|
|
};
|
|
|
|
address = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "";
|
|
example = "10.100.0.1/24";
|
|
description = ''
|
|
IP address (with prefix) of this host on the WireGuard mesh.
|
|
Use a /24 (or broader) prefix so the routing table covers all
|
|
peer /32 routes. Example: `"10.100.0.1/24"` for a 256-host mesh.
|
|
'';
|
|
};
|
|
|
|
listenPort = lib.mkOption {
|
|
type = lib.types.port;
|
|
default = 51820;
|
|
description = ''
|
|
UDP port the local WireGuard interface listens on. Must be
|
|
reachable from peer hosts when they initiate the tunnel.
|
|
Default: 51820 (standard WireGuard port).
|
|
'';
|
|
};
|
|
|
|
persistentKeepalive = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.int;
|
|
default = 25;
|
|
example = 25;
|
|
description = ''
|
|
Seconds between keepalive packets sent to each peer. Useful
|
|
when this host (or a peer) is behind NAT — keeps the UDP hole
|
|
open. Set to null to disable. Default: 25 seconds.
|
|
'';
|
|
};
|
|
};
|
|
|
|
# Gated on the mesh itself, NOT on the c0re daemon. The mesh is host
|
|
# networking, not a c0re feature: a swarm host that runs no hive —
|
|
# the snapshot store, for one — still has to join the mesh, and under
|
|
# the old `c0re.enable` gate it silently got no `wg-hive` interface
|
|
# at all. Nothing below is c0re-specific; the peer data
|
|
# c0re consumes (HYPERHIVE_PEERS / HIVE_PEER_CA_PATHS) is rendered in
|
|
# ./hive-c0re and stays gated there.
|
|
config = lib.mkIf config.services.hyperhive.swarm.wireguard.enable {
|
|
assertions = [
|
|
{
|
|
assertion = config.services.hyperhive.swarm.wireguard.privateKeyFile != null;
|
|
message = ''
|
|
services.hyperhive.swarm.wireguard.enable requires
|
|
services.hyperhive.swarm.wireguard.privateKeyFile to be set.
|
|
Generate a key: wg genkey > /etc/wireguard/hive.key
|
|
'';
|
|
}
|
|
{
|
|
assertion = config.services.hyperhive.swarm.wireguard.address != "";
|
|
message = ''
|
|
services.hyperhive.swarm.wireguard.enable requires
|
|
services.hyperhive.swarm.wireguard.address to be set
|
|
(e.g. "10.100.0.1/24").
|
|
'';
|
|
}
|
|
];
|
|
|
|
# WireGuard inter-hive mesh. Brings up a `wg-hive` interface and
|
|
# connects to each peer that has `wireguardPublicKey` set.
|
|
networking.wireguard.interfaces =
|
|
let
|
|
wgCfg = config.services.hyperhive.swarm.wireguard;
|
|
# `peerHives` is `swarm.hives` minus this hive (../swarm.nix) —
|
|
# a mesh that included our own entry would configure a tunnel to
|
|
# ourselves.
|
|
meshPeers = lib.filterAttrs (
|
|
_: p: p.wireguardPublicKey != null && p.wireguardAddress != null
|
|
) config.services.hyperhive.swarm.peerHives;
|
|
in
|
|
{
|
|
wg-hive = {
|
|
ips = [ wgCfg.address ];
|
|
listenPort = wgCfg.listenPort;
|
|
privateKeyFile = wgCfg.privateKeyFile;
|
|
peers = lib.mapAttrsToList (
|
|
_name: p:
|
|
{
|
|
publicKey = p.wireguardPublicKey;
|
|
allowedIPs = [ p.wireguardAddress ];
|
|
}
|
|
// lib.optionalAttrs (p.wireguardEndpoint != null) {
|
|
endpoint = p.wireguardEndpoint;
|
|
}
|
|
// lib.optionalAttrs (wgCfg.persistentKeepalive != null) {
|
|
persistentKeepalive = wgCfg.persistentKeepalive;
|
|
}
|
|
) meshPeers;
|
|
};
|
|
};
|
|
|
|
# Open the WireGuard UDP port on the host firewall (host-level
|
|
# networking — not inside containers).
|
|
networking.firewall.allowedUDPPorts = [
|
|
config.services.hyperhive.swarm.wireguard.listenPort
|
|
];
|
|
};
|
|
}
|