{ lib, config, ... }: let cfg = config.services.hyperhive.network; in { # Hive-internal network — host-side bridge + per-agent DNS resolver # (#805, prereq for #14 netns isolation). Containers stay on shared # host netns at v1 — this module just stands the bridge + resolver # up so the endpoint is in place before #14 flips containers to # private netns (mara on #805: "we need it before #14 so nothing # breaks when we switch over"). Full design: docs/network.md. options.services.hyperhive.network = { enable = lib.mkOption { type = lib.types.bool; default = false; example = true; description = '' Stand up the hive-internal bridge + dnsmasq resolver. Off by default while v1 phases in. When enabled: a bridge interface (`bridgeName`) appears on the host with `bridgeIp` assigned, and the hive-gateway container runs a dnsmasq listening on that IP for `` + sub-domains. Agent containers still default to shared host netns at v1 — the endpoint is up but only used once #14 lands and flips containers to a private netns + veth peer on this bridge. ''; }; bridgeName = lib.mkOption { type = lib.types.str; default = "hive-br0"; example = "h0"; description = '' Name of the host-side bridge interface the hive uses for inter-container traffic. Kept short so it survives the IFNAMSIZ (15-char) cap, and prefixed so it's obviously hive-managed in `ip link` output. ''; }; bridgeIp = lib.mkOption { type = lib.types.str; default = "10.42.0.1"; example = "172.30.0.1"; description = '' IPv4 address assigned to the bridge interface on the host side. Becomes the DNS server address agents point at (and the upstream the gateway proxies to once netns isolation lands). Default `10.42.0.1` is in RFC 1918 space and unlikely to clash with operator's existing setup; override if a different range is already in use. ''; }; bridgePrefixLength = lib.mkOption { type = lib.types.int; default = 24; example = 16; description = '' Netmask prefix length for the bridge subnet. Default `/24` gives 254 usable per-agent addresses, enough for any single-host hive. Operator with a larger swarm or a tighter addressing scheme overrides. ''; }; upstreamDns = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ "1.1.1.1" "9.9.9.9" ]; example = [ "192.168.1.1" "8.8.8.8" ]; description = '' Upstream DNS servers dnsmasq forwards non-hive queries to. Defaults to Cloudflare + Quad9. Override for operators on private networks who need a specific resolver (corporate DNS, pi-hole, etc.). The hive resolver itself stays authoritative for `` and its sub-domains regardless of upstream choice. ''; }; }; config = lib.mkIf cfg.enable { assertions = [ { assertion = config.services.hyperhive.domain != null; message = '' services.hyperhive.network.enable = true requires services.hyperhive.domain to be set — the resolver needs a domain to be authoritative for. Either pin a hostname (`services.hyperhive.domain = "example.com";`) or leave `network.enable` at its default of false. ''; } { assertion = config.services.hyperhive.gateway.enable; message = '' services.hyperhive.network.enable = true requires services.hyperhive.gateway.enable = true — the dnsmasq resolver runs inside the hive-gateway container (single front-door for both DNS and HTTP). Enable the gateway or leave `network.enable` at its default of false. ''; } ]; # Bridge interface on the host. Empty interfaces list = purely # virtual bridge (no slave NICs attached). Per-agent veth pairs # will join this bridge once #14 lands; at v1 it stands alone. networking.bridges.${cfg.bridgeName}.interfaces = [ ]; # Host-side IP assignment on the bridge. This is what dnsmasq # (inside the gateway container, shared host netns) binds on. networking.interfaces.${cfg.bridgeName}.ipv4.addresses = [ { address = cfg.bridgeIp; prefixLength = cfg.bridgePrefixLength; } ]; # Open the resolver port in the host firewall for traffic from # the bridge subnet only. Other interfaces stay closed — # external DNS-amplification surface is not exposed. networking.firewall.interfaces.${cfg.bridgeName} = { allowedUDPPorts = [ 53 ]; allowedTCPPorts = [ 53 ]; }; }; }