From 39b4c65922bb4cb1fbc88e691962a2d6ce959b1f Mon Sep 17 00:00:00 2001 From: atlas Date: Sun, 31 May 2026 20:34:17 +0200 Subject: [PATCH 1/7] network: add isolateContainers option for #14 netns isolation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds `services.hyperhive.network.isolateContainers` (bool, default false). When enabled alongside `network.enable`, activates: - IP forwarding + NAT masquerade so isolated agents reach the internet - nftables DROP rule blocking bridge-subnet → loopback (defence-in-depth against compromised agent reaching the c0re dashboard) - `HIVE_NETWORK_ISOLATION`, `HIVE_NETWORK_BRIDGE`, `HIVE_NETWORK_SUBNET` injected into the hive-c0re service env; the Rust lifecycle reads these to set `PRIVATE_NETWORK`, `LOCAL_ADDRESS`, and `HOST_BRIDGE` in each agent container's conf Config block rewritten as `lib.mkMerge [...]` — the prior `lib.mkIf // lib.mkIf` pattern was invalid nix (mkIf returns a tagged value, not an attrset; // on it is a type error). See docs/network.md for full design. --- nix/modules/hive-network.nix | 210 ++++++++++++++++++++++++++++------- 1 file changed, 168 insertions(+), 42 deletions(-) diff --git a/nix/modules/hive-network.nix b/nix/modules/hive-network.nix index e304230b..8e91e45b 100644 --- a/nix/modules/hive-network.nix +++ b/nix/modules/hive-network.nix @@ -87,52 +87,178 @@ in regardless of upstream choice. ''; }; - }; - config = lib.mkIf cfg.enable { - assertions = [ - { - assertion = config.services.hyperhive.domain != null; - message = '' - services.hyperhive.network.enable = true requires - services.hyperhive.domain to be set — the resolver needs a - domain to be authoritative for. Either pin a hostname - (`services.hyperhive.domain = "example.com";`) or leave - `network.enable` at its default of false. - ''; - } - { - assertion = config.services.hyperhive.gateway.enable; - message = '' - services.hyperhive.network.enable = true requires - services.hyperhive.gateway.enable = true — the dnsmasq - resolver runs inside the hive-gateway container (single - front-door for both DNS and HTTP). Enable the gateway or - leave `network.enable` at its default of false. - ''; - } - ]; + isolateContainers = lib.mkOption { + type = lib.types.bool; + default = false; + example = true; + description = '' + Flip agent containers from shared host netns to private netns + (#14). When true, each agent container gets a dedicated veth + pair attached to `bridgeName` and a deterministic IP from + the bridge subnet. The bridge (already up when `enable = true`) + becomes the sole routed path between the host and agent + containers. - # Bridge interface on the host. Empty interfaces list = purely - # virtual bridge (no slave NICs attached). Per-agent veth pairs - # will join this bridge once #14 lands; at v1 it stands alone. - networking.bridges.${cfg.bridgeName}.interfaces = [ ]; + The host-side nix effect (this option) is: + - Sets `HIVE_NETWORK_ISOLATION=1` in the c0re service env so + the Rust lifecycle knows to pass `--private-network` + + bridge settings when creating/updating containers. + - Enables IP forwarding + NAT so agents can reach the internet + through the host. + - Adds a firewall rule DROP'ing traffic from the bridge subnet + to the host's loopback addresses — defence-in-depth so a + compromised agent can't reach the c0re dashboard (already + bound to 127.0.0.1) or other host-loopback services even if + the routing table somehow leaks. + - Allows HTTP/HTTPS (80/443) traffic from the bridge subnet to + the host so agents can reach the gateway container (shared + host netns, proxies the operator's per-agent UI). - # Host-side IP assignment on the bridge. This is what dnsmasq - # (inside the gateway container, shared host netns) binds on. - networking.interfaces.${cfg.bridgeName}.ipv4.addresses = [ - { - address = cfg.bridgeIp; - prefixLength = cfg.bridgePrefixLength; - } - ]; + **Prerequisite**: all agents must have + `hyperhive.web.useUnixSocket = true` before enabling isolation. + Agents that still bind TCP on `0.0.0.0:` will be + reachable at their bridge IP from other agents on the same + subnet — defeating the isolation goal. The gateway routes via + unix sockets so gateway reach still works regardless. - # Open the resolver port in the host firewall for traffic from - # the bridge subnet only. Other interfaces stay closed — - # external DNS-amplification surface is not exposed. - networking.firewall.interfaces.${cfg.bridgeName} = { - allowedUDPPorts = [ 53 ]; - allowedTCPPorts = [ 53 ]; + **Migration**: containers are destroyed and re-created when + the network isolation flag flips. Operator state under + `/agents//state/` is bind-mounted and survives; the + container rootfs (nix store paths) is recreated cleanly. + + **Rust counterpart**: `hive-c0re` reads `HIVE_NETWORK_ISOLATION` + and `HIVE_NETWORK_BRIDGE` from its service env and uses them in + `lifecycle::set_nspawn_flags` to configure `PRIVATE_NETWORK`, + `LOCAL_ADDRESS`, and `HOST_BRIDGE` in each container's + `nixos-containers/.conf`. See `docs/network.md` for the + full design. + ''; }; }; + + config = lib.mkMerge [ + (lib.mkIf cfg.enable { + assertions = [ + { + assertion = config.services.hyperhive.domain != null; + message = '' + services.hyperhive.network.enable = true requires + services.hyperhive.domain to be set — the resolver needs a + domain to be authoritative for. Either pin a hostname + (`services.hyperhive.domain = "example.com";`) or leave + `network.enable` at its default of false. + ''; + } + { + assertion = config.services.hyperhive.gateway.enable; + message = '' + services.hyperhive.network.enable = true requires + services.hyperhive.gateway.enable = true — the dnsmasq + resolver runs inside the hive-gateway container (single + front-door for both DNS and HTTP). Enable the gateway or + leave `network.enable` at its default of false. + ''; + } + ]; + + # Bridge interface on the host. Empty interfaces list = purely + # virtual bridge (no slave NICs attached). Per-agent veth pairs + # will join this bridge once #14 lands; at v1 it stands alone. + networking.bridges.${cfg.bridgeName}.interfaces = [ ]; + + # Host-side IP assignment on the bridge. This is what dnsmasq + # (inside the gateway container, shared host netns) binds on. + networking.interfaces.${cfg.bridgeName}.ipv4.addresses = [ + { + address = cfg.bridgeIp; + prefixLength = cfg.bridgePrefixLength; + } + ]; + + # Open the resolver port in the host firewall for traffic from + # the bridge subnet only. Other interfaces stay closed — + # external DNS-amplification surface is not exposed. + networking.firewall.interfaces.${cfg.bridgeName} = { + allowedUDPPorts = [ 53 ]; + allowedTCPPorts = [ 53 ]; + }; + }) + + # Container network isolation (#14 v1). Ships as a separate overlay + # on top of the base bridge config (which stays unconditional) so + # operators can stand the bridge + resolver up first, validate + # everything, then flip isolation on independently. + (lib.mkIf (cfg.enable && cfg.isolateContainers) { + assertions = [ + { + # Isolation without the bridge is a no-op: agents would get + # private netns but no reachable gateway. The assertion on + # `cfg.enable` above already gates the bridge, but making the + # dependency explicit here avoids confusing "bridge up, no + # isolation" vs "isolation on, no bridge" states. + assertion = cfg.enable; + message = '' + services.hyperhive.network.isolateContainers = true requires + services.hyperhive.network.enable = true (the bridge and + resolver must be running before isolation is flipped on). + ''; + } + ]; + + # IP forwarding — agents need to route through the bridge to reach + # the internet. NixOS firewall's `nat.enable` sets this too, but + # making it explicit here keeps the intent visible alongside the + # NAT rule. + boot.kernel.sysctl."net.ipv4.ip_forward" = 1; + + # NAT/masquerade: translate agent bridge IPs → host's outbound + # IP for internet-bound traffic. Without masquerade, packets from + # 10.42.0.X arrive at external servers with an RFC-1918 source + # that can't be routed back. + networking.nat = { + enable = true; + # `internalInterfaces` causes `MASQUERADE` on packets from the + # bridge leaving via any external interface. Only traffic from + # agents crosses the bridge — hive-gateway / forge / matrix + # stay on host netns and don't need NAT. + internalInterfaces = [ cfg.bridgeName ]; + }; + + # DROP traffic from the bridge subnet to host loopback addresses. + # Defence-in-depth: the c0re dashboard already binds 127.0.0.1 + # (not 0.0.0.0), so bridge-sourced traffic can't reach it via + # the bridge IP. But a misconfigured service that slips to + # 0.0.0.0 would otherwise be reachable. The DROP rule closes that + # window. Use `extraInputRules` (nftables `input` chain, priority + # 0, same ruleset as `allowedTCPPorts`) so it's processed before + # the accept rules for bridge-side DNS we added above. + # + # The rule fires only when an agent container has a bridge IP + # (i.e. after the Rust side also ships `PRIVATE_NETWORK=1`); + # until then all containers share host netns and no traffic + # originates from 10.42.0.0/24 so this is a dead letter. + networking.firewall.extraInputRules = '' + ip saddr ${cfg.bridgeIp}/${toString cfg.bridgePrefixLength} ip daddr 127.0.0.0/8 drop + ''; + + # Signal to the c0re Rust side that container isolation is + # enabled. c0re reads `HIVE_NETWORK_ISOLATION` from its service + # environment and uses it in `lifecycle::set_nspawn_flags` to set + # `PRIVATE_NETWORK=1`, `LOCAL_ADDRESS=`, and + # `HOST_BRIDGE=` in each agent's container config. + # Also forwards the bridge name + subnet so c0re can wire the + # veth without hardcoding. + # + # `systemd.services.hive-c0re.environment` is an attrset; NixOS + # merges contributions from all modules that set it, so this + # cross-module injection is idiomatic and doesn't require a + # dedicated option in hive-c0re.nix. + systemd.services.hive-c0re.environment = { + HIVE_NETWORK_ISOLATION = "1"; + HIVE_NETWORK_BRIDGE = cfg.bridgeName; + HIVE_NETWORK_SUBNET = "${cfg.bridgeIp}/${toString cfg.bridgePrefixLength}"; + }; + }) + ]; } From c1b40ef819a1bd325e3b134e01d9e1838fffee0e Mon Sep 17 00:00:00 2001 From: atlas Date: Sun, 31 May 2026 21:03:56 +0200 Subject: [PATCH 2/7] network: fix dead assertion + document HIVE_NETWORK_SUBNET host-address MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two argus review fixups: 1. Move isolateContainers assertion to an unconditional `lib.mkIf cfg.isolateContainers` arm. The prior placement inside `mkIf (enable && isolateContainers)` was dead code — the assertion could never fire because both flags were already true by the time the block activated. Now `isolateContainers=true; enable=false;` raises a NixOS assertion error at eval time. 2. Add comment on HIVE_NETWORK_SUBNET noting it carries the host-side bridge IP (e.g. "10.42.0.1/24"), not the canonical network address ("10.42.0.0/24"). Rust consumer must normalize via bitwise AND before subnet membership checks or address arithmetic. --- nix/modules/hive-network.nix | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/nix/modules/hive-network.nix b/nix/modules/hive-network.nix index 8e91e45b..8da7fe17 100644 --- a/nix/modules/hive-network.nix +++ b/nix/modules/hive-network.nix @@ -185,18 +185,13 @@ in }; }) - # Container network isolation (#14 v1). Ships as a separate overlay - # on top of the base bridge config (which stays unconditional) so - # operators can stand the bridge + resolver up first, validate - # everything, then flip isolation on independently. - (lib.mkIf (cfg.enable && cfg.isolateContainers) { + # Guard: isolateContainers requires the bridge to be up. This arm is + # unconditional on cfg.enable so the assertion fires even when an + # operator sets isolateContainers=true but forgets enable=true (the + # combined-condition arm below would silently do nothing in that case). + (lib.mkIf cfg.isolateContainers { assertions = [ { - # Isolation without the bridge is a no-op: agents would get - # private netns but no reachable gateway. The assertion on - # `cfg.enable` above already gates the bridge, but making the - # dependency explicit here avoids confusing "bridge up, no - # isolation" vs "isolation on, no bridge" states. assertion = cfg.enable; message = '' services.hyperhive.network.isolateContainers = true requires @@ -205,6 +200,13 @@ in ''; } ]; + }) + + # Container network isolation (#14 v1). Ships as a separate overlay + # on top of the base bridge config (which stays unconditional) so + # operators can stand the bridge + resolver up first, validate + # everything, then flip isolation on independently. + (lib.mkIf (cfg.enable && cfg.isolateContainers) { # IP forwarding — agents need to route through the bridge to reach # the internet. NixOS firewall's `nat.enable` sets this too, but @@ -254,6 +256,12 @@ in # merges contributions from all modules that set it, so this # cross-module injection is idiomatic and doesn't require a # dedicated option in hive-c0re.nix. + # + # Note: HIVE_NETWORK_SUBNET is the host-side bridge IP + prefix + # length (e.g. "10.42.0.1/24"), not the canonical network address + # ("10.42.0.0/24"). The Rust side must normalize (bitwise AND the + # IP with the mask) before using it for subnet membership checks or + # address arithmetic. systemd.services.hive-c0re.environment = { HIVE_NETWORK_ISOLATION = "1"; HIVE_NETWORK_BRIDGE = cfg.bridgeName; From a141d157bad0ac24ec3ce630206aadba34f1cbf3 Mon Sep 17 00:00:00 2001 From: atlas Date: Wed, 3 Jun 2026 01:09:09 +0200 Subject: [PATCH 3/7] network: default isolateContainers to true --- nix/modules/hive-network.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nix/modules/hive-network.nix b/nix/modules/hive-network.nix index 8da7fe17..38536709 100644 --- a/nix/modules/hive-network.nix +++ b/nix/modules/hive-network.nix @@ -90,8 +90,8 @@ in isolateContainers = lib.mkOption { type = lib.types.bool; - default = false; - example = true; + default = true; + example = false; description = '' Flip agent containers from shared host netns to private netns (#14). When true, each agent container gets a dedicated veth From 8e50ddf01679e947eba37c5c1c7dd41c8cbe0943 Mon Sep 17 00:00:00 2001 From: atlas Date: Wed, 3 Jun 2026 01:13:48 +0200 Subject: [PATCH 4/7] network: revert isolateContainers default to false --- nix/modules/hive-network.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nix/modules/hive-network.nix b/nix/modules/hive-network.nix index 38536709..8da7fe17 100644 --- a/nix/modules/hive-network.nix +++ b/nix/modules/hive-network.nix @@ -90,8 +90,8 @@ in isolateContainers = lib.mkOption { type = lib.types.bool; - default = true; - example = false; + default = false; + example = true; description = '' Flip agent containers from shared host netns to private netns (#14). When true, each agent container gets a dedicated veth From 3db51deace30bd83ad7e9427100124ba91ccd1df Mon Sep 17 00:00:00 2001 From: atlas Date: Wed, 3 Jun 2026 01:18:13 +0200 Subject: [PATCH 5/7] network: default network.enable to services.hyperhive.enable --- nix/modules/hive-network.nix | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/nix/modules/hive-network.nix b/nix/modules/hive-network.nix index 8da7fe17..b64fb186 100644 --- a/nix/modules/hive-network.nix +++ b/nix/modules/hive-network.nix @@ -15,18 +15,22 @@ in options.services.hyperhive.network = { enable = lib.mkOption { type = lib.types.bool; - default = false; - example = true; + default = config.services.hyperhive.enable; + defaultText = lib.literalExpression "config.services.hyperhive.enable"; + example = false; description = '' Stand up the hive-internal bridge + dnsmasq resolver. - Off by default while v1 phases in. When enabled: - a bridge interface (`bridgeName`) appears on the host with - `bridgeIp` assigned, and the hive-gateway container runs a - dnsmasq listening on that IP for `` + + Defaults to `config.services.hyperhive.enable` so it comes + on automatically with the rest of hyperhive. Requires + `services.hyperhive.domain` to be set — the dnsmasq resolver + is authoritative for `` and its sub-domains. + When enabled: a bridge interface (`bridgeName`) appears on the + host with `bridgeIp` assigned, and the hive-gateway container + runs a dnsmasq listening on that IP for `` + sub-domains. Agent containers still default to shared host - netns at v1 — the endpoint is up but only used once #14 - lands and flips containers to a private netns + veth peer - on this bridge. + netns — the endpoint is up but only used once + `isolateContainers = true` flips containers to private netns + + veth peers on this bridge. ''; }; From b89c5f5334c936ff133f3c7fb821e59eebf59d5a Mon Sep 17 00:00:00 2001 From: atlas Date: Wed, 3 Jun 2026 09:39:53 +0200 Subject: [PATCH 6/7] network: fix stale assertion messages; move prose to docs/network.md --- docs/network.md | 46 +++++++++++++++++++++ nix/modules/hive-network.nix | 78 +++++++----------------------------- 2 files changed, 61 insertions(+), 63 deletions(-) diff --git a/docs/network.md b/docs/network.md index 4e67b470..79f03ff7 100644 --- a/docs/network.md +++ b/docs/network.md @@ -95,6 +95,52 @@ agent containers. interface only. Other interfaces stay closed. The hive resolver isn't an external-facing service. +## Container isolation + +`services.hyperhive.network.isolateContainers` (default `false`) flips +agent containers from shared host netns to private netns. Set only after +`enable = true` is stable in production — an assertion blocks the reverse. + +### What the nix side does when `isolateContainers = true` + +| effect | mechanism | +|---|---| +| IP forwarding | `boot.kernel.sysctl."net.ipv4.ip_forward" = 1` | +| Internet NAT | `networking.nat { enable = true; internalInterfaces = [ bridgeName ]; }` — MASQUERADE on packets leaving via any external NIC | +| Loopback DROP | `networking.firewall.extraInputRules` — drops bridge-subnet → `127.0.0.0/8` traffic; defence-in-depth against routing table leaks | +| c0re signal | `HIVE_NETWORK_ISOLATION=1`, `HIVE_NETWORK_BRIDGE`, `HIVE_NETWORK_SUBNET` in `systemd.services.hive-c0re.environment` | + +`HIVE_NETWORK_SUBNET` is the host-side bridge IP + prefix (e.g. +`10.42.0.1/24`), **not** the canonical network address. The Rust side +must normalise (bitwise-AND with mask) before subnet membership checks or +address arithmetic. + +### What the Rust side does + +`hive-c0re` reads `HIVE_NETWORK_ISOLATION` and, when set, passes +`PRIVATE_NETWORK=1`, `LOCAL_ADDRESS=`, and +`HOST_BRIDGE=` via `lifecycle::set_nspawn_flags` when +creating or updating containers. Each agent gets a deterministic IP +derived from its name so the address is reproducible across destroy/recreate. + +### Prerequisites before flipping on + +- All agents must have `hyperhive.web.useUnixSocket = true`. Agents that + still bind TCP on `0.0.0.0:` will be reachable at their bridge IP + from other agents on the same subnet — defeating the isolation goal. The + gateway routes via unix sockets so gateway reach is unaffected. + +### Migration behaviour + +Containers are destroyed and re-created when the flag flips. Agent state +under `/agents//state/` is bind-mounted and survives; the container +rootfs is recreated cleanly from the nix store. + +### Follow-up + +Issue #1119 tracks defaulting `isolateContainers` to `true` once the +bridge is stable in production. + ## Cross-references - `docs/gateway.md` — vhost map + the gateway container's other duties diff --git a/nix/modules/hive-network.nix b/nix/modules/hive-network.nix index b64fb186..66725bc6 100644 --- a/nix/modules/hive-network.nix +++ b/nix/modules/hive-network.nix @@ -150,8 +150,8 @@ in services.hyperhive.network.enable = true requires services.hyperhive.domain to be set — the resolver needs a domain to be authoritative for. Either pin a hostname - (`services.hyperhive.domain = "example.com";`) or leave - `network.enable` at its default of false. + (`services.hyperhive.domain = "example.com";`) or set + `services.hyperhive.network.enable = false` explicitly. ''; } { @@ -161,18 +161,15 @@ in services.hyperhive.gateway.enable = true — the dnsmasq resolver runs inside the hive-gateway container (single front-door for both DNS and HTTP). Enable the gateway or - leave `network.enable` at its default of false. + set `services.hyperhive.network.enable = false` explicitly. ''; } ]; - # Bridge interface on the host. Empty interfaces list = purely - # virtual bridge (no slave NICs attached). Per-agent veth pairs - # will join this bridge once #14 lands; at v1 it stands alone. + # Virtual bridge — veth pairs attach when isolateContainers flips on. networking.bridges.${cfg.bridgeName}.interfaces = [ ]; - # Host-side IP assignment on the bridge. This is what dnsmasq - # (inside the gateway container, shared host netns) binds on. + # Bridge IP — dnsmasq (in the gateway container) binds here. networking.interfaces.${cfg.bridgeName}.ipv4.addresses = [ { address = cfg.bridgeIp; @@ -180,19 +177,15 @@ in } ]; - # Open the resolver port in the host firewall for traffic from - # the bridge subnet only. Other interfaces stay closed — - # external DNS-amplification surface is not exposed. + # DNS only on the bridge interface — no external amplification surface. networking.firewall.interfaces.${cfg.bridgeName} = { allowedUDPPorts = [ 53 ]; allowedTCPPorts = [ 53 ]; }; }) - # Guard: isolateContainers requires the bridge to be up. This arm is - # unconditional on cfg.enable so the assertion fires even when an - # operator sets isolateContainers=true but forgets enable=true (the - # combined-condition arm below would silently do nothing in that case). + # Guard: fires unconditionally on isolateContainers so the assertion + # is not silently swallowed when enable=false. (lib.mkIf cfg.isolateContainers { assertions = [ { @@ -206,66 +199,25 @@ in ]; }) - # Container network isolation (#14 v1). Ships as a separate overlay - # on top of the base bridge config (which stays unconditional) so - # operators can stand the bridge + resolver up first, validate - # everything, then flip isolation on independently. + # Container isolation overlay — see docs/network.md#container-isolation. (lib.mkIf (cfg.enable && cfg.isolateContainers) { - # IP forwarding — agents need to route through the bridge to reach - # the internet. NixOS firewall's `nat.enable` sets this too, but - # making it explicit here keeps the intent visible alongside the - # NAT rule. + # Agents route internet traffic via the bridge; NAT masquerades their RFC-1918 IPs. boot.kernel.sysctl."net.ipv4.ip_forward" = 1; - - # NAT/masquerade: translate agent bridge IPs → host's outbound - # IP for internet-bound traffic. Without masquerade, packets from - # 10.42.0.X arrive at external servers with an RFC-1918 source - # that can't be routed back. networking.nat = { enable = true; - # `internalInterfaces` causes `MASQUERADE` on packets from the - # bridge leaving via any external interface. Only traffic from - # agents crosses the bridge — hive-gateway / forge / matrix - # stay on host netns and don't need NAT. internalInterfaces = [ cfg.bridgeName ]; }; - # DROP traffic from the bridge subnet to host loopback addresses. - # Defence-in-depth: the c0re dashboard already binds 127.0.0.1 - # (not 0.0.0.0), so bridge-sourced traffic can't reach it via - # the bridge IP. But a misconfigured service that slips to - # 0.0.0.0 would otherwise be reachable. The DROP rule closes that - # window. Use `extraInputRules` (nftables `input` chain, priority - # 0, same ruleset as `allowedTCPPorts`) so it's processed before - # the accept rules for bridge-side DNS we added above. - # - # The rule fires only when an agent container has a bridge IP - # (i.e. after the Rust side also ships `PRIVATE_NETWORK=1`); - # until then all containers share host netns and no traffic - # originates from 10.42.0.0/24 so this is a dead letter. + # Defence-in-depth: DROP bridge→loopback so compromised agents can't + # reach host-loopback services even via routing table leaks. networking.firewall.extraInputRules = '' ip saddr ${cfg.bridgeIp}/${toString cfg.bridgePrefixLength} ip daddr 127.0.0.0/8 drop ''; - # Signal to the c0re Rust side that container isolation is - # enabled. c0re reads `HIVE_NETWORK_ISOLATION` from its service - # environment and uses it in `lifecycle::set_nspawn_flags` to set - # `PRIVATE_NETWORK=1`, `LOCAL_ADDRESS=`, and - # `HOST_BRIDGE=` in each agent's container config. - # Also forwards the bridge name + subnet so c0re can wire the - # veth without hardcoding. - # - # `systemd.services.hive-c0re.environment` is an attrset; NixOS - # merges contributions from all modules that set it, so this - # cross-module injection is idiomatic and doesn't require a - # dedicated option in hive-c0re.nix. - # - # Note: HIVE_NETWORK_SUBNET is the host-side bridge IP + prefix - # length (e.g. "10.42.0.1/24"), not the canonical network address - # ("10.42.0.0/24"). The Rust side must normalize (bitwise AND the - # IP with the mask) before using it for subnet membership checks or - # address arithmetic. + # Tells hive-c0re to pass PRIVATE_NETWORK + bridge settings to each + # container. HIVE_NETWORK_SUBNET is host-bridge IP/prefix, not canonical + # network address — the Rust side normalises before subnet arithmetic. systemd.services.hive-c0re.environment = { HIVE_NETWORK_ISOLATION = "1"; HIVE_NETWORK_BRIDGE = cfg.bridgeName; From 302e5e28697591a1f1e8e6bd1a92d17ebdc2120e Mon Sep 17 00:00:00 2001 From: atlas Date: Wed, 3 Jun 2026 09:48:19 +0200 Subject: [PATCH 7/7] docs: remove forward-looking issue reference from network.md --- docs/network.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/docs/network.md b/docs/network.md index 79f03ff7..e7bb1f87 100644 --- a/docs/network.md +++ b/docs/network.md @@ -136,11 +136,6 @@ Containers are destroyed and re-created when the flag flips. Agent state under `/agents//state/` is bind-mounted and survives; the container rootfs is recreated cleanly from the nix store. -### Follow-up - -Issue #1119 tracks defaulting `isolateContainers` to `true` once the -bridge is stable in production. - ## Cross-references - `docs/gateway.md` — vhost map + the gateway container's other duties