diff --git a/docs/swarm.md b/docs/swarm.md index e9c8587c..54c074ec 100644 --- a/docs/swarm.md +++ b/docs/swarm.md @@ -93,79 +93,6 @@ services.hyperhive.swarm.peers."pr1ma.example.com" = { certFingerprint = "sha256 Mixed trust is fine: A trusts B via CA bundle (no fingerprint), B pins A's self-signed cert. -## WireGuard inter-hive mesh (optional) - -The peer config above uses public HTTPS for all inter-hive traffic. -For private deployments — or to reduce latency and TLS overhead on -intra-swarm traffic — hive-c0re can configure a host-to-host -WireGuard mesh. - -### Generating keys - -On each hive host: - -```bash -wg genkey | install -m 0400 /dev/stdin /etc/wireguard/hive.key -wg pubkey < /etc/wireguard/hive.key # → share this with peer operators -``` - -### Config example (two hives) - -```nix -# hive A (pr1ma.example.com, mesh IP 10.100.0.1) -services.hyperhive = { - swarm.wireguard = { - enable = true; - privateKeyFile = "/etc/wireguard/hive.key"; - address = "10.100.0.1/24"; - listenPort = 51820; # optional, default 51820 - }; - - swarm.peers."edge.corp" = { - certFingerprint = "sha256:…"; # TLS trust (unchanged) - wireguardPublicKey = "base64key="; # peer's wg pubkey - wireguardEndpoint = "203.0.113.42:51820"; # peer's public IP:port - wireguardAddress = "10.100.0.2/32"; # peer's mesh IP - }; -}; - -# hive B (edge.corp, mesh IP 10.100.0.2) -services.hyperhive = { - swarm.wireguard = { - enable = true; - privateKeyFile = "/etc/wireguard/hive.key"; - address = "10.100.0.2/24"; - }; - - swarm.peers."pr1ma.example.com" = { - wireguardPublicKey = "base64key="; # hive A's wg pubkey - wireguardEndpoint = "198.51.100.1:51820"; - wireguardAddress = "10.100.0.1/32"; - }; -}; -``` - -### What the mesh does - -- `networking.wireguard.interfaces.wg-hive` is configured on the host - (not inside agent containers; containers reach peers via the host's - routing table). -- UDP port 51820 (or `listenPort`) is opened on the host firewall. -- `HYPERHIVE_PEERS` gains a `wireguard_address` field for each mesh - peer so hive-c0re can reach intra-swarm services without a public - DNS round-trip. -- `persistentKeepalive = 25` is set by default; override or null to - disable (not needed when both sides have public IPs and no NAT). - -### NAT / one-sided endpoints - -If one host is behind NAT and can't accept incoming connections, only -that host needs a null `wireguardEndpoint` on the peer config — the -other side initiates. With keepalive on, the NAT hole stays open. - -If both hosts are behind NAT, a STUN relay or a third host (exit node) -is required. Out of scope for v0. - ## Cross-references - `docs/conventions.md` § Hive identity — env vars, qualified labels diff --git a/nix/modules/hive-c0re.nix b/nix/modules/hive-c0re.nix index 7b89f221..3fba5127 100644 --- a/nix/modules/hive-c0re.nix +++ b/nix/modules/hive-c0re.nix @@ -127,46 +127,6 @@ in Encrypt peers). Set to pin a self-signed cert. ''; }; - - wireguardPublicKey = lib.mkOption { - type = lib.types.nullOr lib.types.str; - default = null; - example = "base64pubkey="; - description = '' - WireGuard public key for this peer host. Required when - `services.hyperhive.swarm.wireguard.enable = true` and - you want this peer 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 peer in `host:port` form. - Required when the peer host is behind a firewall and - this host needs to initiate the tunnel. Null = this host - waits for the peer to connect (peer-initiates; peer must - have an endpoint pointing back at this host). - ''; - }; - - wireguardAddress = lib.mkOption { - type = lib.types.nullOr lib.types.str; - default = null; - example = "10.100.0.2/32"; - description = '' - IP address (with prefix) of the peer host on the - WireGuard mesh. Used as the `allowedIPs` for the peer's - 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 - the peer in the WireGuard mesh (peers missing this field - are silently excluded from `wg-hive`). - ''; - }; }; } ); @@ -181,75 +141,10 @@ in Peer hives in the same swarm. The attrset key is the peer's DNS domain — used for dashboard links and Matrix federation discovery. Null `certFingerprint` trusts the system CA bundle; set it to pin - a self-signed TLS cert. Add `wireguardPublicKey` + `wireguardAddress` - (and optionally `wireguardEndpoint`) to include the peer in the - WireGuard mesh when `swarm.wireguard.enable = true`. + a self-signed TLS cert. ''; }; - # WireGuard mesh config for the local host. - # When enabled, hive-c0re configures a `wg-hive` interface on the host - # connecting 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. - ''; - }; - }; - options.services.hyperhive.c0re = { enable = lib.mkOption { type = lib.types.bool; @@ -447,70 +342,14 @@ in # Open the per-agent web-port range when the gateway is *off* — # otherwise the gateway nginx is the sole external entry point. # See `docs/gateway.md::Firewall posture (host-level)`. - networking.firewall.allowedTCPPortRanges = lib.mkIf (!config.services.hyperhive.gateway.enable) [ - { - from = 8100; - to = 8999; - } - ]; - - # WireGuard inter-hive mesh. Enabled when - # `services.hyperhive.swarm.wireguard.enable = true`. Brings up a - # `wg-hive` interface and connects to each peer that has - # `wireguardPublicKey` set. Firewall opens the UDP listen port on - # the host (not inside containers — this is host-level networking). - networking.wireguard.interfaces = lib.mkIf config.services.hyperhive.swarm.wireguard.enable ( - let - wgCfg = config.services.hyperhive.swarm.wireguard; - meshPeers = lib.filterAttrs ( - _: p: p.wireguardPublicKey != null && p.wireguardAddress != null - ) config.services.hyperhive.swarm.peers; - in - { - wg-hive = { - ips = [ wgCfg.address ]; - listenPort = wgCfg.listenPort; - privateKeyFile = wgCfg.privateKeyFile; - peers = lib.mapAttrsToList ( - _domain: 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 when the mesh is on. - networking.firewall.allowedUDPPorts = lib.mkIf config.services.hyperhive.swarm.wireguard.enable [ - config.services.hyperhive.swarm.wireguard.listenPort - ]; - - assertions = lib.mkIf config.services.hyperhive.swarm.wireguard.enable [ - { - 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"). - ''; - } - ]; + networking.firewall = lib.mkIf (!config.services.hyperhive.gateway.enable) { + allowedTCPPortRanges = [ + { + from = 8100; + to = 8999; + } + ]; + }; systemd.services.hive-c0re = { description = "hyperhive coordinator daemon"; @@ -590,22 +429,15 @@ in HIVE_FORGE_PUBLIC_URL = "https://${config.services.hyperhive.forge.domain}"; } // lib.optionalAttrs (config.services.hyperhive.swarm.peers != { }) { - # Peer hives serialised as a JSON array of {domain, cert_fingerprint, - # wireguard_address?} objects. Consumed by hive-ag3nt::identity::peers() - # + the dashboard's peer_hives StateSnapshot field (P33RS tab). Domain - # is the attrset key; cert_fingerprint is null for CA-trusted peers; - # wireguard_address is omitted when not part of the mesh. + # Peer hives serialised as a JSON array of {domain, cert_fingerprint} + # objects. Consumed by hive-ag3nt::identity::peers() + the dashboard's + # peer_hives StateSnapshot field (P33RS tab). Domain is the attrset key; + # cert_fingerprint is null for CA-trusted peers. HYPERHIVE_PEERS = builtins.toJSON ( - lib.mapAttrsToList ( - domain: p: - { - inherit domain; - cert_fingerprint = p.certFingerprint; - } - // lib.optionalAttrs (p.wireguardAddress != null) { - wireguard_address = p.wireguardAddress; - } - ) config.services.hyperhive.swarm.peers + lib.mapAttrsToList (domain: p: { + inherit domain; + cert_fingerprint = p.certFingerprint; + }) config.services.hyperhive.swarm.peers ); }; serviceConfig = {