# In-container network plumbing: DHCP on the bridge veth, resolvconf # taken out of the loop, and the oneshot that points resolv.conf at # the hive bridge resolver. { pkgs, lib, ... }: { # Take resolvconf + dhcpcd out of the /etc/resolv.conf loop so the # bridge resolver the oneshot below writes actually sticks. At their # NixOS defaults, resolvconf regenerates resolv.conf from host-tracking # *after* the oneshot has pointed it at the bridge (dhcpcd re-triggers # that when the veth comes up under isolation) — silently clobbering the # bridge nameserver back to the host resolver, which isn't authoritative # for the hive's own zones, so `forge.` stops resolving. We # disable resolvconf and tell dhcpcd not to touch resolv.conf (without # disabling dhcpcd itself, so the veth still gets its address); then # the hyperhive-isolated-dns oneshot owns resolv.conf. (Same "take # resolvconf out of the loop" approach the matrix container uses.) # All agent containers receive their bridge IP via DHCP from the hive # dnsmasq pool (see nix/host-modules/hive-gateway.nix). useDHCP runs dhcpcd # on every interface (just eth0 in practice — the nspawn bridge veth). config = { networking.useDHCP = true; networking.resolvconf.enable = false; networking.dhcpcd.extraConfig = "nohook resolv.conf"; # Point resolv.conf at the hive bridge resolver when the container is # network-isolated. nixos-container copies the *host's* /etc/resolv.conf # into the container at every start — but the host resolver (e.g. # 127.0.0.53) is unreachable from a private netns and isn't # authoritative for the hive's own zones (forge. etc.). The # bridge dnsmasq (gateway IP) is. hive-priv drops the marker # `/etc/hyperhive-bridge-dns` (containing the gateway IP) since # isolation is always on; the oneshot reads it and rewrites # resolv.conf on every boot. Ordered before the first DNS consumer # (tea-login) and the network targets so name resolution works for # the very first turn. systemd.services.hyperhive-isolated-dns = { description = "point resolv.conf at the hive bridge resolver (isolated containers)"; wantedBy = [ "multi-user.target" ]; after = [ "local-fs.target" ]; # Ordered before every network consumer that does DNS on first # boot. `hive-agent` (the harness) is the load-bearing one: its # first-turn api.anthropic.com lookup must not race the resolv.conf # rewrite (it only declares `after network.target`, so without this # edge the harness can start before we've fixed resolv.conf and the # first turn errors — self-heals next turn, but better not to flap). # `hive-matrix-daemon` likewise syncs over the network; the `before` # is a harmless no-op when matrix is disabled (the unit is absent). before = [ "network-online.target" "tea-login.service" "hive-agent.service" "hive-matrix-daemon.service" ]; unitConfig.ConditionPathExists = "/etc/hyperhive-bridge-dns"; serviceConfig = { Type = "oneshot"; RemainAfterExit = true; # Pin the journal identity; without it systemd derives it from the # generated `script` store-path wrapper (an opaque `-…-start`). SyslogIdentifier = "hyperhive-isolated-dns"; }; path = [ pkgs.coreutils ]; script = '' set -eu gw=$(tr -d '[:space:]' < /etc/hyperhive-bridge-dns) if [ -z "$gw" ]; then echo "hyperhive-isolated-dns: empty marker; leaving resolv.conf as-is" exit 0 fi # resolv.conf is a regular file copied from the host by # nixos-container; replace it (rm first in case it's a symlink). rm -f /etc/resolv.conf printf 'nameserver %s\n' "$gw" > /etc/resolv.conf echo "hyperhive-isolated-dns: resolv.conf -> nameserver $gw" ''; }; }; }