nix/hive-network: bridge + dnsmasq resolver, opt-in (#805 v1)

Stand up the host-side bridge interface + per-agent DNS resolver
ahead of #14 (netns isolation). Mara on #805#11541: "we need it
before #14 so nothing breaks when we switch over". v1 ships the
endpoint live but containers stay on shared host netns — when #14
flips them to private netns the DNS contract is already there.

Shape:

- new `nix/modules/hive-network.nix` with `services.hyperhive.network.*`
  options (enable + bridgeName + bridgeIp + bridgePrefixLength +
  upstreamDns). Default off. Imported from `hive-c0re.nix`.
- bridge interface via `networking.bridges` (no slave NICs at v1;
  per-agent veth pairs attach once #14 lands).
- bridge IP assigned via `networking.interfaces`.
- `networking.firewall.interfaces.<bridge>.allowed{UDP,TCP}Ports =
  [ 53 ]` opens the resolver on the bridge interface only —
  other interfaces stay closed.
- dnsmasq config added to the existing `hive-gateway` container
  (mara on #805:10957: "put the resolver into the gateway container").
  Listens only on `bridgeName` + `lo`; authoritative for
  `<hive-domain>`, `forge.<hive>`, `matrix.<hive>` answering with
  the bridge IP; forwards everything else to upstream.
  `resolveLocalQueries = false` keeps the gateway container's own
  resolver untouched.

Asserts `services.hyperhive.domain != null` + `gateway.enable =
true` — both required for the resolver to be meaningful.

Docs: new `docs/network.md` covering v1 vs v2 split, container shape
rationale, default addressing, resolver behaviour, firewall posture.

`nix flake check` clean.
This commit is contained in:
atlas 2026-05-31 16:50:54 +02:00 committed by mara
commit ed3b9d853e
4 changed files with 301 additions and 0 deletions

View file

@ -9,6 +9,7 @@ let
hyperhiveDomain = config.services.hyperhive.domain;
matrixCfg = config.services.hyperhive.matrix;
forgeCfg = config.services.hyperhive.forge;
networkCfg = config.services.hyperhive.network;
# Per-agent port table for `/agent/<name>/` routing. C0re writes
# this JSON on every topology change; gateway reads at deploy time.
@ -551,6 +552,52 @@ in
};
};
};
# Hive-internal DNS resolver (#805 v1). Co-located in the
# gateway container per mara's call (#805:10957) — single
# front-door for both DNS and HTTP, saves a sibling
# container. Listens on the bridge interface from
# `services.hyperhive.network`; authoritative for the hive
# domain + sub-domains, forwards everything else upstream.
# No-op when `network.enable = false`.
services.dnsmasq = lib.mkIf networkCfg.enable {
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;
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;
# Don't read /etc/resolv.conf — we control upstream
# explicitly to dodge dependency on the gateway
# container's own resolver state.
no-resolv = true;
server = networkCfg.upstreamDns;
# Hive authoritative records — answer queries for the
# hive domain + its sub-domains with the bridge IP
# (where nginx is reachable from container netns once
# #14 lands; today it's the host loopback alias and
# works in either shape).
address = [
"/${hyperhiveDomain}/${networkCfg.bridgeIp}"
]
++ lib.optional (
(forgeCfg.enable or false) && (forgeCfg.behindGateway or false)
) "/${forgeCfg.domain}/${networkCfg.bridgeIp}"
++ lib.optional (
matrixCfg.enable && matrixCfg.gatewayHost != null
) "/${matrixCfg.gatewayHost}/${networkCfg.bridgeIp}";
};
};
};
};