fix(3149): the host asks the hive's resolver, at the bridge IP

Per mara: a general fix, not one name in one container.

Every container inherits a COPY of the host's /etc/resolv.conf at start
(nixos-containers.nix: cp --remove-destination, one shot, not a
bind-mount), so the address written there is the address every container
tries - in its own netns. That makes the value load-bearing:

  value       host  host-netns containers  bridged containers
  127.0.0.1    ok           ok             THEIR OWN loopback
  bridge IP    ok           ok                    ok

dnsmasq binds lo and the bridge, so the bridge IP works for the host
too. It is the only value correct on both sides of a netns boundary.

resolveLocalQueries goes ON for its plumbing, not its address: it points
dnsmasq's own upstreams at a separate resolv-file, without which
dnsmasq reads /etc/resolv.conf and every non-hive query loops the moment
the host is pointed at dnsmasq. Its two loopback-publishing effects
(networking.nameservers and resolvconf.useLocalResolver) are overridden.

Cost: the host's DNS now depends on dnsmasq being up. Every container
already did.

The forge container keeps its hosts entry from the previous commit -
not redundancy, a fallback in a different failure domain: it works with
no DNS at all, so SSO does not ride on a host-wide resolver change.
This commit is contained in:
atlas 2026-08-12 17:34:57 +02:00 committed by mara
commit 0b1b08dfe6
2 changed files with 39 additions and 4 deletions

View file

@ -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).

View file

@ -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.