From 78482cb6240851c494202340511555b0971006d3 Mon Sep 17 00:00:00 2001 From: atlas Date: Wed, 17 Jun 2026 15:29:52 +0200 Subject: [PATCH 1/4] nix: remove the non-isolated (shared-netns) mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Network isolation is validated working end to end (forge + matrix reach the gateway under private netns), so the shared-host-netns mode is removed — there is now one mode. - hive-network.nix: the bridge/DNS block and the isolation overlay are now unconditional whenever hyperhive is enabled (gated on services.hyperhive.enable, not the per-feature toggles). The redundant isolateContainers-implies-enable guard is dropped. - network.enable and isolateContainers are kept as DEPRECATED no-op options so existing configs that set them to true still evaluate cleanly; setting either to false now warns (gated on hyperhive being enabled, so a non-hyperhive host gets no spurious warning) and has no effect. Both are slated for removal in a future release. - hive-c0re.nix: the HIVE_FORGE_URL / HIVE_MATRIX_URL conditionals drop the "&& isolateContainers" clause — agents always reach forge/matrix through the gateway vhost now. - hive-matrix.nix: refresh one stale comment. The Rust lifecycle is env-driven (HIVE_NETWORK_ISOLATION), so no Rust change is needed: the env is now always set, so the existing private-netns path always runs. Supersedes the default-on flip (the option it would have defaulted is gone). --- nix/modules/hive-c0re.nix | 29 +++++------ nix/modules/hive-matrix.nix | 2 +- nix/modules/hive-network.nix | 97 +++++++++++++++++++++--------------- 3 files changed, 74 insertions(+), 54 deletions(-) diff --git a/nix/modules/hive-c0re.nix b/nix/modules/hive-c0re.nix index ef3f9e98..2aa1b879 100644 --- a/nix/modules/hive-c0re.nix +++ b/nix/modules/hive-c0re.nix @@ -756,10 +756,13 @@ in # needed — nginx proxies to forgejo as it does for the operator. # - Shared netns: host loopback is reachable, use direct port. # See `docs/gateway.md::HIVE_FORGE_URL`. + # Network isolation is now unconditional (the shared-netns mode was + # removed), so agents always reach the forge through the gateway + # vhost rather than host loopback. The `network.enable` guard remains + # only so a deliberately network-less deployment still falls back to + # loopback; in the normal case it is always on. HIVE_FORGE_URL = - if - config.services.hyperhive.network.enable && config.services.hyperhive.network.isolateContainers - then + if config.services.hyperhive.network.enable then "http://${config.services.hyperhive.forge.domain}" else "http://127.0.0.1:${toString config.services.hyperhive.forge.httpPort}"; @@ -767,19 +770,17 @@ in // lib.optionalAttrs config.services.hyperhive.matrix.enable { # In-cluster matrix homeserver URL for each agent's # hive-matrix-daemon. Same shape + rationale as HIVE_FORGE_URL: - # - Isolated (private netns): reach tuwunel via the gateway vhost - # (`matrix.`) on plain http:80 — host loopback is dead. - # - Shared netns: direct host loopback on the tuwunel port. - # gatewayHost null-guard falls back to loopback so a domain-less - # config doesn't break eval (it just won't work under isolation, - # which needs a gateway anyway). Forwarded to agents by meta.rs - # alongside HIVE_FORGE_URL; shares the same env-forwarding ordering - # caveat (value baked at config-generation time). + # network isolation is now unconditional, so agents reach tuwunel + # via the gateway vhost (`matrix.`) on plain http:80 rather + # than host loopback. The gatewayHost null-guard falls back to + # loopback so a domain-less config still evals; the `network.enable` + # guard covers a deliberately network-less deployment. Forwarded to + # agents by meta.rs alongside HIVE_FORGE_URL; shares the same + # env-forwarding ordering caveat (value baked at config-generation + # time). HIVE_MATRIX_URL = if - config.services.hyperhive.network.enable - && config.services.hyperhive.network.isolateContainers - && config.services.hyperhive.matrix.gatewayHost != null + config.services.hyperhive.network.enable && config.services.hyperhive.matrix.gatewayHost != null then "http://${config.services.hyperhive.matrix.gatewayHost}" else diff --git a/nix/modules/hive-matrix.nix b/nix/modules/hive-matrix.nix index ca287a7b..555b8d4f 100644 --- a/nix/modules/hive-matrix.nix +++ b/nix/modules/hive-matrix.nix @@ -377,7 +377,7 @@ in # `environment.etc."resolv.conf".text` is `nameserver `. # This container always shares the host netns # (`privateNetwork = false`), so it reaches `bridgeIp` regardless - # of `isolateContainers`. Network module off → inherit the host's + # of agent-container isolation. Network module off → inherit the host's # resolv.conf. See `docs/network.md`. networking = lib.mkMerge [ (lib.mkIf networkCfg.enable { diff --git a/nix/modules/hive-network.nix b/nix/modules/hive-network.nix index b20e7553..f4a6f9e9 100644 --- a/nix/modules/hive-network.nix +++ b/nix/modules/hive-network.nix @@ -19,18 +19,18 @@ in defaultText = lib.literalExpression "config.services.hyperhive.enable"; example = false; description = '' - Stand up the hive-internal bridge + dnsmasq resolver. - 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 — the endpoint is up but only used once - `isolateContainers = true` flips containers to private netns - + veth peers on this bridge. + **DEPRECATED — ignored.** The hive network (bridge + dnsmasq + resolver + private-netns isolation) is now always on whenever + hyperhive is enabled; setting this to `false` warns and has no + effect. Retained as a no-op so existing configs eval; will be + removed in a future release. + + The network requires `services.hyperhive.domain` to be set — the + dnsmasq resolver is authoritative for `` and its + sub-domains. A bridge interface (`bridgeName`) appears on the host + with `bridgeIp` assigned, the hive-gateway container runs a dnsmasq + on that IP, and each agent container runs in a private netns with a + veth pair on the bridge. ''; }; @@ -94,11 +94,16 @@ in isolateContainers = lib.mkOption { type = lib.types.bool; - default = false; + default = true; example = true; description = '' - Flip agent containers from shared host netns to private netns. - When true, each agent container gets a dedicated veth + **DEPRECATED — ignored.** Network isolation is now the only mode and + is always on whenever hyperhive is enabled; the shared-netns path was + removed. This option is retained as a no-op so existing configs eval; + setting it to `false` warns and has no effect. It will be removed in + a future release. + + 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 @@ -142,21 +147,27 @@ in }; config = lib.mkMerge [ - (lib.mkIf cfg.enable { + # The hive network + container isolation are unconditional whenever + # hyperhive is enabled: the shared-netns mode was removed, so there is + # one mode (private netns behind the bridge). `network.enable` and + # `isolateContainers` are kept as deprecated no-op options (see the + # warnings block below) so existing configs that set them still eval. + (lib.mkIf config.services.hyperhive.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 set - `services.hyperhive.network.enable = false` explicitly. + hyperhive requires services.hyperhive.domain to be set — the + hive resolver is authoritative for `` and its + sub-domains, and agents reach the forge/matrix through the + gateway by that domain. Pin a hostname + (`services.hyperhive.domain = "example.com";`). ''; } ]; - # Virtual bridge — veth pairs attach when isolateContainers flips on. + # Virtual bridge — each agent container attaches a veth pair (isolation + # is unconditional now). networking.bridges.${cfg.bridgeName}.interfaces = [ ]; # Bridge IP — dnsmasq (in the gateway container) binds here. @@ -174,23 +185,9 @@ in }; }) - # Guard: fires unconditionally on isolateContainers so the assertion - # is not silently swallowed when enable=false. - (lib.mkIf cfg.isolateContainers { - assertions = [ - { - 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). - ''; - } - ]; - }) - - # Container isolation overlay — see docs/network.md#container-isolation. - (lib.mkIf (cfg.enable && cfg.isolateContainers) { + # Container isolation overlay — now unconditional (the shared-netns + # mode was removed). See docs/network.md#container-isolation. + (lib.mkIf config.services.hyperhive.enable { # Agents route internet traffic via the bridge; NAT masquerades their RFC-1918 IPs. boot.kernel.sysctl."net.ipv4.ip_forward" = 1; @@ -222,5 +219,27 @@ in HIVE_NETWORK_SUBNET = "${cfg.bridgeIp}/${toString cfg.bridgePrefixLength}"; }; }) + + # Deprecation surface for the removed toggles. Both options are kept so + # existing configs that set them to `true` still eval cleanly; setting + # either to `false` no longer does anything (network + isolation are + # unconditional now), so warn rather than silently ignore. + { + # Only warn when hyperhive itself is enabled — otherwise `cfg.enable` + # defaults to `false` (tracking `hyperhive.enable`) and we'd fire a + # spurious deprecation warning on a host that doesn't run hyperhive. + warnings = lib.optionals config.services.hyperhive.enable ( + lib.optional (!cfg.enable) '' + services.hyperhive.network.enable = false is deprecated and ignored + — the hive network is now always on (private-netns isolation is the + only mode). Remove the setting. + '' + ++ lib.optional (!cfg.isolateContainers) '' + services.hyperhive.network.isolateContainers = false is deprecated + and ignored — network isolation is now the only mode and is always + on. Remove the setting. + '' + ); + } ]; } From 61db5afc229d9ba4300ae9d422a19cd021632e61 Mon Sep 17 00:00:00 2001 From: atlas Date: Wed, 17 Jun 2026 16:07:39 +0200 Subject: [PATCH 2/4] nix(c0re): guard agent forge/matrix URLs on hyperhive.enable, not network.enable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses argus's review of #1718: the HIVE_FORGE_URL / HIVE_MATRIX_URL conditionals still keyed off the now-deprecated `network.enable`, so setting it to `false` would point agents at host loopback (unreachable from their private netns) even though isolation stays on — contradicting the "deprecated and ignored" warning. Guard on the top-level `hyperhive.enable` instead, so `network.enable` truly has no effect anywhere and the loopback branch only covers a hyperhive-disabled host. Eval-proven: with `network.enable = false`, HIVE_FORGE_URL now resolves to the gateway vhost (`http://forge.`), and the deprecation warning still fires. --- nix/modules/hive-c0re.nix | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/nix/modules/hive-c0re.nix b/nix/modules/hive-c0re.nix index 2aa1b879..5c782bcb 100644 --- a/nix/modules/hive-c0re.nix +++ b/nix/modules/hive-c0re.nix @@ -758,11 +758,13 @@ in # See `docs/gateway.md::HIVE_FORGE_URL`. # Network isolation is now unconditional (the shared-netns mode was # removed), so agents always reach the forge through the gateway - # vhost rather than host loopback. The `network.enable` guard remains - # only so a deliberately network-less deployment still falls back to - # loopback; in the normal case it is always on. + # vhost rather than host loopback. Guard on the top-level + # `hyperhive.enable` (not the deprecated `network.enable`): the + # network is always on with hyperhive, so the deprecated toggle has + # no effect here either — the loopback branch only covers a config + # with hyperhive itself disabled (no agents to serve). HIVE_FORGE_URL = - if config.services.hyperhive.network.enable then + if config.services.hyperhive.enable then "http://${config.services.hyperhive.forge.domain}" else "http://127.0.0.1:${toString config.services.hyperhive.forge.httpPort}"; @@ -772,16 +774,14 @@ in # hive-matrix-daemon. Same shape + rationale as HIVE_FORGE_URL: # network isolation is now unconditional, so agents reach tuwunel # via the gateway vhost (`matrix.`) on plain http:80 rather - # than host loopback. The gatewayHost null-guard falls back to - # loopback so a domain-less config still evals; the `network.enable` - # guard covers a deliberately network-less deployment. Forwarded to - # agents by meta.rs alongside HIVE_FORGE_URL; shares the same - # env-forwarding ordering caveat (value baked at config-generation - # time). + # than host loopback. Guard on the top-level `hyperhive.enable` (the + # deprecated `network.enable` has no effect); the gatewayHost + # null-guard falls back to loopback so a domain-less config still + # evals. Forwarded to agents by meta.rs alongside HIVE_FORGE_URL; + # shares the same env-forwarding ordering caveat (value baked at + # config-generation time). HIVE_MATRIX_URL = - if - config.services.hyperhive.network.enable && config.services.hyperhive.matrix.gatewayHost != null - then + if config.services.hyperhive.enable && config.services.hyperhive.matrix.gatewayHost != null then "http://${config.services.hyperhive.matrix.gatewayHost}" else "http://127.0.0.1:${toString config.services.hyperhive.matrix.httpPort}"; From e8c2ef087cb5316b0b3da5bfd5203241ef107b5d Mon Sep 17 00:00:00 2001 From: atlas Date: Wed, 17 Jun 2026 16:56:26 +0200 Subject: [PATCH 3/4] nix(c0re): set HIVE_FORGE_URL to the gateway URL directly, drop dead if/else MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per mara's review of #1718: this env block only exists when hyperhive is enabled (it lives inside `config = mkIf cfg.enable`), so the `if hyperhive.enable then gateway else loopback` branch was dead — the loopback fallback could never be reached. Set HIVE_FORGE_URL to the gateway vhost directly. HIVE_MATRIX_URL drops the same redundant `hyperhive.enable` term but keeps the gatewayHost null-guard — that one is a real fallback for a domain-less config, not the dead enable branch. --- nix/modules/hive-c0re.nix | 42 ++++++++++++++------------------------- 1 file changed, 15 insertions(+), 27 deletions(-) diff --git a/nix/modules/hive-c0re.nix b/nix/modules/hive-c0re.nix index 5c782bcb..c141f771 100644 --- a/nix/modules/hive-c0re.nix +++ b/nix/modules/hive-c0re.nix @@ -750,38 +750,26 @@ in HYPERHIVE_SWARM_NAME = config.services.hyperhive.swarmName; } // lib.optionalAttrs config.services.hyperhive.forge.enable { - # In-cluster forge URL. - # - Isolated (private netns): containers resolve `forge.` via - # the bridge dnsmasq and reach nginx on port 80. No raw forge port - # needed — nginx proxies to forgejo as it does for the operator. - # - Shared netns: host loopback is reachable, use direct port. + # In-cluster forge URL. Network isolation is unconditional (the + # shared-netns mode was removed), so agents always resolve + # `forge.` via the bridge dnsmasq and reach nginx on port 80 + # (nginx proxies to forgejo as it does for the operator) — there is + # no host-loopback path. This env is only set when hyperhive is + # enabled, so the gateway URL is unconditionally correct here. # See `docs/gateway.md::HIVE_FORGE_URL`. - # Network isolation is now unconditional (the shared-netns mode was - # removed), so agents always reach the forge through the gateway - # vhost rather than host loopback. Guard on the top-level - # `hyperhive.enable` (not the deprecated `network.enable`): the - # network is always on with hyperhive, so the deprecated toggle has - # no effect here either — the loopback branch only covers a config - # with hyperhive itself disabled (no agents to serve). - HIVE_FORGE_URL = - if config.services.hyperhive.enable then - "http://${config.services.hyperhive.forge.domain}" - else - "http://127.0.0.1:${toString config.services.hyperhive.forge.httpPort}"; + HIVE_FORGE_URL = "http://${config.services.hyperhive.forge.domain}"; } // lib.optionalAttrs config.services.hyperhive.matrix.enable { # In-cluster matrix homeserver URL for each agent's - # hive-matrix-daemon. Same shape + rationale as HIVE_FORGE_URL: - # network isolation is now unconditional, so agents reach tuwunel - # via the gateway vhost (`matrix.`) on plain http:80 rather - # than host loopback. Guard on the top-level `hyperhive.enable` (the - # deprecated `network.enable` has no effect); the gatewayHost - # null-guard falls back to loopback so a domain-less config still - # evals. Forwarded to agents by meta.rs alongside HIVE_FORGE_URL; - # shares the same env-forwarding ordering caveat (value baked at - # config-generation time). + # hive-matrix-daemon — the gateway vhost (`matrix.`) on plain + # http:80. Network isolation is unconditional, so there is no + # host-loopback path; the only remaining conditional is the + # gatewayHost null-guard, which falls back to loopback so a + # domain-less config still evals. Forwarded to agents by meta.rs + # alongside HIVE_FORGE_URL; shares the same env-forwarding ordering + # caveat (value baked at config-generation time). HIVE_MATRIX_URL = - if config.services.hyperhive.enable && config.services.hyperhive.matrix.gatewayHost != null then + if config.services.hyperhive.matrix.gatewayHost != null then "http://${config.services.hyperhive.matrix.gatewayHost}" else "http://127.0.0.1:${toString config.services.hyperhive.matrix.httpPort}"; From 75dbcc2aa2f56d408ad7f8dc718c264ea8cef870 Mon Sep 17 00:00:00 2001 From: atlas Date: Wed, 17 Jun 2026 16:58:20 +0200 Subject: [PATCH 4/4] nix(c0re): trim the network-isolation narrative from the forge/matrix URL comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per mara's review of #1718: the HIVE_FORGE_URL / HIVE_MATRIX_URL comments carried "network isolation is now unconditional / shared-netns removed" backstory that doesn't belong on those keys. Trimmed to describe just the value (the gateway vhost) + the gatewayHost null-guard. The isolation-removal narrative stays where it's on-topic — the network module's own deprecated-option descriptions and warning text. --- nix/modules/hive-c0re.nix | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/nix/modules/hive-c0re.nix b/nix/modules/hive-c0re.nix index c141f771..90d1a22d 100644 --- a/nix/modules/hive-c0re.nix +++ b/nix/modules/hive-c0re.nix @@ -750,24 +750,18 @@ in HYPERHIVE_SWARM_NAME = config.services.hyperhive.swarmName; } // lib.optionalAttrs config.services.hyperhive.forge.enable { - # In-cluster forge URL. Network isolation is unconditional (the - # shared-netns mode was removed), so agents always resolve - # `forge.` via the bridge dnsmasq and reach nginx on port 80 - # (nginx proxies to forgejo as it does for the operator) — there is - # no host-loopback path. This env is only set when hyperhive is - # enabled, so the gateway URL is unconditionally correct here. - # See `docs/gateway.md::HIVE_FORGE_URL`. + # In-cluster forge URL — the gateway vhost (`forge.`), which + # nginx proxies to forgejo. Set directly: this env only exists when + # hyperhive is enabled. See `docs/gateway.md::HIVE_FORGE_URL`. HIVE_FORGE_URL = "http://${config.services.hyperhive.forge.domain}"; } // lib.optionalAttrs config.services.hyperhive.matrix.enable { # In-cluster matrix homeserver URL for each agent's - # hive-matrix-daemon — the gateway vhost (`matrix.`) on plain - # http:80. Network isolation is unconditional, so there is no - # host-loopback path; the only remaining conditional is the - # gatewayHost null-guard, which falls back to loopback so a - # domain-less config still evals. Forwarded to agents by meta.rs - # alongside HIVE_FORGE_URL; shares the same env-forwarding ordering - # caveat (value baked at config-generation time). + # hive-matrix-daemon — the gateway vhost (`matrix.`). The + # gatewayHost null-guard falls back to loopback so a domain-less + # config still evals. Forwarded to agents by meta.rs alongside + # HIVE_FORGE_URL; shares the same env-forwarding ordering caveat + # (value baked at config-generation time). HIVE_MATRIX_URL = if config.services.hyperhive.matrix.gatewayHost != null then "http://${config.services.hyperhive.matrix.gatewayHost}"