# 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 ]; }; }