hyperhive/docs/network.md
iris 6bdfe52386 docs: scrub #NNN self-references (closes #719)
Last pass of the docs-from-code → docs/ epic (#708). Drops every
attribution cookie from docs/ + README.md + CLAUDE.md so the
source-tree files no longer reference the issue tracker. Issue
threads + commit history retain the references — those are the
canonical record.

- README.md: drop #701 / #660×2 / #551 from matrix + display-name
  sections, rephrase to convey the semantics directly
- CLAUDE.md: scrub 18 cookies from the file map (#655, #15, #784,
  #832, #444, #425, #361, #548, #598, #539, #544, #589, #701,
  #658, #280, #660, #551, #764, #772, #793, #14, #805)
- docs/agent-hierarchy.md: drop #658 ×3 (per-agent user is the
  current shape, not a transition)
- docs/conventions.md: drop #571 (replaced with a docs xref to
  persistence.md::matrix-avatar-sync)
- docs/gateway.md: scrub vhost-map table cookies + Sub-domain
  rationale + Per-agent unix-socket upstream + Self-signed TLS +
  Firewall posture + HIVE_FORGE_URL + Per-agent error pages
  sections; drop the trailing 'Sequencing history' issue list +
  the 'Next-up' issue-link footnote
- docs/matrix.md: scrub serverName/gatewayHost + Default-closed
  firewall + Provisioning flow + Initial rollout + Assertion
  rationale + fluffychat-web build fixes; drop the trailing
  'Sequencing history' issue list
- docs/network.md: drop 'Why ship before #14' #805 quote +
  Container shape #805 attribution + trailing 'Sequencing history'
  + Cross-references issue links; rename v2 column to 'after netns
  isolation'
- docs/web-ui.md: drop #784 from Container row, replace with a
  docs xref to docs/gateway.md::Per-agent unix-socket upstream

Only remaining #NNN in docs/ is the literal markdown-heading
example in docs/forge.md (`#tag`, `#123`, `#!/bin/bash`)
which demonstrates the renderer's behaviour — not an attribution
cookie.
2026-05-31 17:47:21 +02:00

100 lines
3.8 KiB
Markdown

# hive-network
Host-side bridge + per-agent DNS resolver — the foundation that
makes container netns isolation safe to land. Configured via
`services.hyperhive.network.*`; off by default during rollout.
## Why ship before netns isolation
If netns isolation lands first, agent containers lose
`/etc/resolv.conf` propagation from the host and DNS breaks until a
separate resolver is up. Inverting the sequence — bridge + dnsmasq
first, netns flip second — makes the flag day boring: the resolver
endpoint is already live, agents just discover it via veth instead
of shared netns.
## v1 vs v2
| feature | v1 (this PR) | v2 (after netns isolation) |
|---|---|---|
| bridge interface | created on host, no slave NICs | per-agent veth pairs attach |
| dnsmasq binding | bridge IP (reachable via host loopback in shared netns) | bridge IP (reachable via veth in private netns) |
| agent container netns | shared host | private |
| agent `/etc/resolv.conf` | unchanged (host DNS) | `nameserver <bridge-ip>` |
| `address` rules target | `<bridge-ip>` (works in both modes) | unchanged from v1 |
The `address` rules ship pointing at the bridge IP from v1 so the
DNS contract is fixed before any container actually depends on it
— minimises the things that flip on netns day.
## Container shape (where dnsmasq lives)
Co-located in the existing `hive-gateway` container — single
front-door for both DNS and HTTP, saves a sibling container, single
systemd-unit / state surface to monitor. The gateway shares host
netns (`privateNetwork = false`) so dnsmasq's `bind-interfaces`
listener on `bridgeIp` works without any veth gymnastics today; when
agent containers flip to private netns the binding doesn't change
(it's still on the host's bridge interface).
## Configuration
```nix
{
services.hyperhive = {
enable = true;
domain = "darkest.space";
network.enable = true; # opt in to bridge + DNS
network.bridgeIp = "10.42.0.1"; # default
network.upstreamDns = [ # default Cloudflare + Quad9
"1.1.1.1"
"9.9.9.9"
];
};
}
```
Asserts `services.hyperhive.domain != null` (resolver needs a domain
to be authoritative for) + `services.hyperhive.gateway.enable =
true` (resolver lives in the gateway container).
## Bridge addressing
Default subnet is `10.42.0.0/24`, host-side gateway at `10.42.0.1`.
RFC 1918 space, unlikely to clash with operator's existing setup;
override `bridgeIp` + `bridgePrefixLength` if a different range is
already in use. `/24` gives 254 usable per-agent addresses — enough
for any single-host hive; bigger swarms or tighter addressing
schemes pick their own.
## Resolver behaviour
dnsmasq is **authoritative** for the hive's own zones — answers
`<hive-domain>`, `forge.<hive-domain>`, `matrix.<hive-domain>`
queries with the bridge IP (where nginx is reachable). Everything
else gets forwarded to `upstreamDns`. Containers don't need to know
the upstream — they query the bridge IP and dnsmasq does the right
thing per-name.
`bind-interfaces` + `interface = [ bridgeName "lo" ]` means the
listener only accepts queries from the bridge interface (plus lo for
container health-checks). External hosts can't reach it — no
DNS-amplification surface even when the operator opens port 80 for
gateway HTTP.
`resolveLocalQueries = false` keeps dnsmasq out of the host's own
resolution stack — the host's resolver (systemd-resolved, plain
glibc nss, dnscrypt-proxy, etc.) keeps doing whatever the operator
configured. The hive resolver is purely for inbound queries from
agent containers.
## Firewall posture
`networking.firewall.interfaces.<bridge>.allowedUDPPorts = [ 53 ]`
+ `allowedTCPPorts = [ 53 ]` opens the resolver on the bridge
interface only. Other interfaces stay closed. The hive resolver
isn't an external-facing service.
## Cross-references
- `docs/gateway.md` — vhost map + the gateway container's other duties