Follow-up to the plumbing removal: docs/swarm/README.md gets the biggest rewrite (drops the whole "Fingerprint format" section, fixes the runtime-effects list, the WireGuard config example + "what the mesh does" bullet), docs/gateway.md and hive-gateway/options.nix drop 4 "needs no certFingerprint" mentions, swarm-peers-removed.nix's migration-warning text no longer tells an upgrading operator to carry a field over that no longer exists, swarm.nix/swarm-wireguard.nix/ swarm-controller.nix/swarm-controller's main.rs get comment fixes where they described the now-removed HYPERHIVE_PEERS shape. Also caught one more stale "peer hives" mention in docs/web-ui/README.md's SW4RM tab description that the first pass on this issue missed.
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 (consumed by
|
|
# swarm-controller's hive roster and, here, the mesh), 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
|
|
# (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
|
|
];
|
|
};
|
|
}
|