# Hive-internal DNS resolver + DHCP, running on the host alongside the # gateway's nginx — single front-door for both DNS and HTTP, and no # container of its own. Listens on the bridge interface from # `services.hyperhive.network`; authoritative for the hive domain + # sub-domains, forwards everything else upstream. Returns the # `services.dnsmasq` value (see ./default.nix); the DHCP pool bounds # are computed by hive-network. { lib, networkCfg, forgeCfg, matrixCfg, autheliaCfg, uiCfg, hyperhiveDomain, }: { enable = true; # ON for its *plumbing*, not for the address it publishes. # # This flag does two separable things upstream. The one that matters # here: it points dnsmasq's own upstream servers at a SEPARATE file # (`resolv-file = /etc/dnsmasq-resolv.conf`, kept current by # resolvconf). Without that, dnsmasq reads `/etc/resolv.conf` for its # upstreams — so the moment the host's resolver is pointed at dnsmasq, # every non-hive query goes in a circle. # # The other thing it does is publish `127.0.0.1` as the host's # nameserver, which is the wrong address for this hive: see the # `nameservers` override in ./default.nix, where the reason lives. resolveLocalQueries = true; settings = { # Bind only on the bridge interface (and lo for health-checks). # Outside hosts can't even see the listener. interface = [ networkCfg.bridgeName "lo" ]; bind-interfaces = true; port = 53; # Authoritative for the hive domain via the `address` rules below — # must not fall back to the host's /etc/hosts. dnsmasq reads # /etc/hosts by default, and `gateway.localHostsEntry` populates it # with 127.0.0.1 for every hive name (host-side dev convenience, # see default.nix). Since moving dnsmasq onto the host, that # file is now the *same* /etc/hosts dnsmasq reads for agent queries # — its entries win over `address=`, so every agent resolves the # hive's own domains back to itself (127.0.0.1 in its own netns) # instead of the bridge IP, and can't reach the forge, matrix, or # dashboard at all. `no-hosts = true` keeps the authoritative # `address=` rules in charge for containers while leaving # `networking.hosts` (the actual /etc/hosts entries) untouched for # host-side browsing. no-hosts = true; # Hive authoritative records — answer queries for the hive domain # + its sub-domains with the bridge IP, where nginx is reachable # from every container netns. # # The forge / matrix entries are redundant in the common case # where `forge.domain` / `matrix.gatewayHost` are sub-domains of # `hyperhive.domain` — dnsmasq's `//` rule already matches # sub-domains. Kept explicit because operators can override either # to a cross-domain hostname (e.g. `forge.domain = # "git.example.com"`); listing them explicitly keeps that case # routed without needing an extra config block. address = [ "/${hyperhiveDomain}/${networkCfg.bridgeIp}" ] ++ lib.optional ((forgeCfg.behindGateway or false)) "/${forgeCfg.domain}/${networkCfg.bridgeIp}" ++ lib.optional ( matrixCfg.enable && matrixCfg.gatewayHost != null ) "/${matrixCfg.gatewayHost}/${networkCfg.bridgeIp}" ++ lib.optional autheliaCfg.enable "/${autheliaCfg.domain}/${networkCfg.bridgeIp}" # The swarm UI's name is the swarm APEX by default — a sibling of # the three above, not a child of anything this resolver already # answers for, so the `//` rule does not cover it. # # Published to agents deliberately (mara: publishing it is fine). # Reachability is not the access control here: the vhost's # `auth_request` + authelia's `group:operators` rule are, and an # agent that resolves the name still cannot open the page. ++ lib.optional uiCfg.enable "/${uiCfg.domain}/${networkCfg.bridgeIp}"; # DHCP pool covering all usable host addresses on the bridge # subnet — bounds computed by hive-network.nix from # bridgeIp/bridgePrefixLength. All containers (agents and service # containers such as hive-ci) receive their IPs dynamically. dhcp-range = "${networkCfg.dhcpRangeStart},${networkCfg.dhcpRangeEnd},1h"; dhcp-leasefile = "/var/lib/dnsmasq/dnsmasq.leases"; # No explicit upstream: non-hive queries follow dnsmasq's # resolv.conf default — the host's own `/etc/resolv.conf`, so the # hive always uses the host's resolvers and follows them live with # no copy to go stale. Deliberately no fallback # `server=`: dnsmasq queries # all known upstreams in parallel, so a hardcoded public resolver # would take a share of *normal* traffic, not just fill in when the # host file is empty. }; }