diff --git a/nix/host-modules/hive-gateway/default.nix b/nix/host-modules/hive-gateway/default.nix index b573a93a..9f3b4306 100644 --- a/nix/host-modules/hive-gateway/default.nix +++ b/nix/host-modules/hive-gateway/default.nix @@ -157,6 +157,32 @@ in "f /var/lib/hive-gateway/conf/gateway.htpasswd 0644 hive-core hive-core - -" ]; + # The host asks the hive's own resolver, at the BRIDGE IP. + # + # Every container inherits a COPY of this host's `/etc/resolv.conf` + # at start (`nixos-containers.nix`: `cp --remove-destination`, one + # shot, not a bind-mount) — so whatever address is written here is + # the address every container will try, in its own netns. + # + # 🚨 That is why this is the bridge IP and not `127.0.0.1`, and the + # distinction is load-bearing rather than stylistic: + # + # value host host-netns containers bridged containers + # 127.0.0.1 ok ok THEIR OWN loopback + # bridge IP ok ok ok + # + # dnsmasq binds both `lo` and the bridge (./dnsmasq.nix), so the + # bridge IP is reachable from the host too — it is the only value + # correct on both sides of a netns boundary. `resolveLocalQueries` + # publishes loopback by default, hence both overrides here; the + # flag stays on for its `resolv-file` plumbing, which is what keeps + # dnsmasq's own upstreams out of the file we are pointing at it. + # + # Cost, stated because it is real: the host's DNS now depends on + # dnsmasq being up. Every container already did. + networking.nameservers = lib.mkForce [ networkCfg.bridgeIp ]; + networking.resolvconf.useLocalResolver = lib.mkForce false; + # ACME (Let's Encrypt) integration. nginx vhosts set # `enableACME = true` via the vhost builder; this provides the # shared ACME config (acceptTerms + email). diff --git a/nix/host-modules/hive-gateway/dnsmasq.nix b/nix/host-modules/hive-gateway/dnsmasq.nix index cd56e226..e790cf27 100644 --- a/nix/host-modules/hive-gateway/dnsmasq.nix +++ b/nix/host-modules/hive-gateway/dnsmasq.nix @@ -16,10 +16,19 @@ }: { enable = true; - # Don't substitute the container's /etc/resolv.conf — the gateway - # uses the host's resolver for its own outbound traffic; dnsmasq is - # purely for incoming queries from agent containers. - resolveLocalQueries = false; + # 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.