hyperhive/nix/agent-modules/network.nix

82 lines
3.9 KiB
Nix

# 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.<domain>` 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.<domain> 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 `<hash>-…-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"
'';
};
};
}