From ea90814809fde19a7d4b13e6eadb4819eb3bc749 Mon Sep 17 00:00:00 2001 From: atlas Date: Sun, 31 May 2026 14:57:50 +0200 Subject: [PATCH] docs/gateway.md: extract hive-c0re + hive-forge prose blocks (#718 batch 2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Continues #718 docs-extraction. Three more blocks moved to `docs/gateway.md` (which already houses the gateway architecture story from #775): 1. **Firewall posture (gateway on vs off)** — was a 22-line block above `networking.firewall = lib.mkIf ...` in hive-c0re.nix. Trimmed to 3-line ref. New `docs/gateway.md::Firewall posture (host-level)` section covers the gateway-on / gateway-off trade-off + why dashboard port stays loopback-only. 2. **`HIVE_FORGE_URL` loopback rationale** — was a 14-line block above the env-var assignment. Trimmed to 5-line ref. New `docs/gateway.md::HIVE_FORGE_URL: loopback for in-cluster, sub-domain for the operator` section covers the in-cluster vs external split + why agent containers can't use the sub-domain. 3. **hive-forge container shape** — was a 15-line top-of-`config` block in hive-forge.nix explaining the nixos-container + host netns choices. Trimmed to 4-line ref. New `docs/gateway.md::hive-forge container shape` section captures the same content with state-dir + wipe-via-destroy notes. Net: hive-c0re.nix -29 lines, hive-forge.nix -11 lines, gateway.md +44 lines. Same pattern as #782 (first pass) per iris's #10114 guidance — substantive WHY moves to docs as named sub-paragraphs, in-code shrinks to `// see docs/::
` refs. Verified: `nix eval` on agent-base toplevel still resolves cleanly; firewall posture unchanged (still 0 ports opened in the gateway-on case + the same 8100..8999 range in the gateway-off case). Continues #718. Follow-up batches: remaining harness-base.nix blocks, nix/docs/default.nix, nix/assets.nix, nix/templates/weston-vnc.nix. --- docs/gateway.md | 51 ++++++++++++++++++++++++++++++++++++++ nix/modules/hive-c0re.nix | 44 ++++++-------------------------- nix/modules/hive-forge.nix | 19 +++----------- 3 files changed, 63 insertions(+), 51 deletions(-) diff --git a/docs/gateway.md b/docs/gateway.md index 698d681c..09d04c4d 100644 --- a/docs/gateway.md +++ b/docs/gateway.md @@ -76,3 +76,54 @@ SSH for forge stays direct on `cfg.sshPort` — separate listener protocol, not - #772 / #775 — fluffychat hops from `/matrix/` to `matrix./`. Next-up tracked separately: #14 (container netns isolation), TLS (#594). + +## Firewall posture (host-level) + +`hive-c0re.nix` opens the per-agent web-port range +`8100..8999` in the host firewall **only when +`services.hyperhive.gateway.enable = false`**. With the gateway on +(default), it's the sole external entry point and proxies to +`127.0.0.1:` internally — leaving the per-agent ports +firewall-open would defeat the single-front-door story (closes +#621). + +Manager hashes into the same range since #753 (no more +"manager pinned at 8000" special case), so one range opening covers +every container. + +The dashboard port (`cfg.dashboardPort`, default 7000) is *not* +listed in either case — since #652 it binds `127.0.0.1` only, so a +firewall hole would be a no-op. Remote dashboard access flows +through the gateway. Operators who opt out of the gateway lose +external dashboard reach by design — the surface is privileged +(approve / deny / destroy) and must not be exposed without a real +reverse proxy in front. + +## `HIVE_FORGE_URL`: loopback for in-cluster, sub-domain for the operator + +Agents poll `HIVE_FORGE_URL` for Forgejo notifications + run all +`hive-forge` calls against it. `hive-c0re.nix` pins this to +`http://127.0.0.1:` for the in-cluster path: every +agent container shares the host's network namespace, so loopback +reaches the forge container directly with no DNS lookup needed +(closes #761). + +The post-#754 sub-domain default (`forge.`) is for +**operator browsers + cross-host clients**, not in-cluster traffic. +Using the sub-domain URL inside agent containers would fail every +`hive-forge` invocation with "Name or service not known" — the +agent's nspawn doesn't have DNS for the external hostname. + +## hive-forge container shape + +Private Forgejo wrapped in a nixos-container (`hive-forge`, not +`h-*` — keeps c0re's lifecycle scanner out of the picture; the +operator manages it via the standard `nixos-container` CLI). +Container shares the host network namespace +(`privateNetwork = false`) so agents reach the forge at +`http://localhost:` without extra plumbing — nixos-container +is here for state + systemd-unit isolation, not network isolation. + +State lives at `/var/lib/nixos-containers/hive-forge/var/lib/forgejo/` +and survives container restart / host reboot. To wipe, destroy the +container. diff --git a/nix/modules/hive-c0re.nix b/nix/modules/hive-c0re.nix index 22171979..6856d9e8 100644 --- a/nix/modules/hive-c0re.nix +++ b/nix/modules/hive-c0re.nix @@ -238,28 +238,9 @@ in managerToplevel ]; - # Per-container web UIs share the host's network namespace and need - # their ports reachable when there's no gateway in front. Every - # container — including the manager (#753 dropped the pre-#753 - # "manager pinned at 8000" special case) — hashes into - # 8100..8999 via `lifecycle::agent_web_port`'s FNV-1a, so a single - # range opening covers all of them. - # - # The dashboard port (`cfg.dashboardPort`, default 7000) is *not* - # listed here — since #652 the dashboard binds `127.0.0.1` only, - # so opening the firewall hole would be a no-op. Remote dashboard - # access flows through hive-gateway (default-on); operators who - # opt out of the gateway lose external dashboard reach by design — - # the surface is privileged (approve / deny / destroy) and must - # not be exposed without a real reverse proxy in front. - # - # When `services.hyperhive.gateway.enable = true` (the default), the - # gateway nginx is the sole external entry point and proxies to - # `127.0.0.1:7000` etc. internally — leaving the per-agent ports - # open in the host firewall would defeat the gateway's "single - # front door" story (closes #621). Operators who opt out of the - # gateway still get those direct ports opened so the legacy - # `http://:/` flow works. + # Open the per-agent web-port range when the gateway is *off* — + # otherwise the gateway nginx is the sole external entry point. + # See `docs/gateway.md::Firewall posture (host-level)`. networking.firewall = lib.mkIf (!config.services.hyperhive.gateway.enable) { allowedTCPPortRanges = [ { @@ -307,20 +288,11 @@ in HYPERHIVE_SWARM_NAME = config.services.hyperhive.swarmName; } // lib.optionalAttrs config.services.hyperhive.forge.enable { - # Agents poll this URL for Forgejo notifications + run all - # `hive-forge` calls against it. Pinned to `127.0.0.1` for - # the in-cluster path: every agent container shares the - # host's network namespace so loopback reaches the forge - # container directly, no DNS lookup needed (closes #761). - # - # Post-#754 `cfg.domain` defaults to `forge.` - # for the external gateway vhost. Using that value here - # would route every in-cluster call through DNS for an - # external hostname agents can't resolve from inside their - # nspawn — every `hive-forge` invocation would fail with - # "Name or service not known". The external gateway URL - # is for operator browsers + cross-host clients; internal - # callers stay on loopback. + # Loopback for in-cluster calls (agents share host netns; + # external `forge.` sub-domain isn't DNS-resolvable + # from inside nspawn). See + # `docs/gateway.md::HIVE_FORGE_URL: loopback for in-cluster, + # sub-domain for the operator`. HIVE_FORGE_URL = "http://127.0.0.1:${toString config.services.hyperhive.forge.httpPort}"; } // lib.optionalAttrs config.services.hyperhive.matrix.gui.enable { diff --git a/nix/modules/hive-forge.nix b/nix/modules/hive-forge.nix index ce29e344..d71c2285 100644 --- a/nix/modules/hive-forge.nix +++ b/nix/modules/hive-forge.nix @@ -30,21 +30,10 @@ let effectiveRootUrl = if cfg.rootUrl != null then cfg.rootUrl else defaultRootUrl; in { - # Private Forgejo for hyperhive agents, wrapped in a nixos-container - # so it doesn't fight any `services.forgejo` the operator already - # runs on the host. The container shares the host network namespace - # (`privateNetwork = false`) so agents reach the forge at - # `http://localhost:` without any extra plumbing — - # nixos-container is just here for state + systemd-unit isolation, - # not network isolation. - # - # Container name is `hive-forge` (not `h-*`), so hive-c0re's - # lifecycle scanner ignores it; the operator manages it via the - # standard `nixos-container` CLI. - # - # State lives at `/var/lib/nixos-containers/hive-forge/var/lib/forgejo/` - # and survives container restart / host reboot. To wipe, destroy the - # container. + # Private Forgejo in a `hive-forge` nixos-container, shared host + # netns so agents reach it on loopback. State at + # `/var/lib/nixos-containers/hive-forge/var/lib/forgejo/` survives + # restart. See `docs/gateway.md::hive-forge container shape`. options.services.hyperhive.forge = { enable = lib.mkOption {