diff --git a/README.md b/README.md index 1b638514..c5f36cf8 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ Minimal `flake.nix` for a host that runs hive-c0re: nixosConfigurations.my-host = nixpkgs.lib.nixosSystem { system = "x86_64-linux"; modules = [ - hyperhive.nixosModules.default # hive-c0re + hive-forge + hive-gateway in one import + hyperhive.nixosModules.default # hive-c0re + hive-forge in one import ({ ... }: { services.hyperhive.enable = true; # services.hyperhive.c0re.operatorPronouns = "they/them"; # default: "she/her" @@ -78,16 +78,6 @@ manager container, and auto-rebuilds any container whose hyperhive rev goes stale. `claude-code` is unfree — hyperhive scopes the whitelist to itself, nothing for the operator to set. -Optional: set `services.hyperhive.gateway.enable = false;` to bypass -the default nginx in front. By default (`gateway.enable = true`) every -request hits a small nginx in its own nixos-container that proxies -to hive-c0re's dashboard on the upstream port; `/matrix/` is served -directly from `services.hyperhive.matrix.gui.package` when the matrix -GUI is on. v0 is HTTP-only (TLS lives in a follow-up); pair with -`services.hyperhive.gateway.localHostsEntry = true;` for local dev so -`http://` resolves to the host without -setting up real DNS. - Optional: set `services.hyperhive.c0re.preBuildAgentTemplates = true;` to pre-fetch the per-container system closures into your host's /nix/store as part of `nixos-rebuild`. First-agent-spawn then diff --git a/nix/docs.nix b/nix/docs.nix index bfaf5d06..ed6655e4 100644 --- a/nix/docs.nix +++ b/nix/docs.nix @@ -50,10 +50,9 @@ let # doesn't pull in heavy build inputs (matrix container, forge, # etc.). Options are still fully declared either way — that's # what nixosOptionsDoc traverses. - services.hyperhive.enable = lib.mkForce false; - services.hyperhive.forge.enable = lib.mkForce false; - services.hyperhive.matrix.enable = lib.mkForce false; - services.hyperhive.gateway.enable = lib.mkForce false; + services.hive-c0re.enable = lib.mkForce false; + hyperhive.forge.enable = lib.mkForce false; + hyperhive.matrix.enable = lib.mkForce false; } ) ]; diff --git a/nix/modules/hive-c0re.nix b/nix/modules/hive-c0re.nix index 19863253..8d1630e6 100644 --- a/nix/modules/hive-c0re.nix +++ b/nix/modules/hive-c0re.nix @@ -23,7 +23,6 @@ in # is set before it can be enabled. imports = [ ./hive-forge.nix - ./hive-gateway.nix ./hive-matrix.nix ]; @@ -231,12 +230,14 @@ in # Agents poll this URL for Forgejo notifications. Derived from # services.hyperhive.forge.{domain,httpPort} so it tracks forge config changes. HIVE_FORGE_URL = "http://${config.services.hyperhive.forge.domain}:${toString config.services.hyperhive.forge.httpPort}"; + } + // lib.optionalAttrs config.services.hyperhive.matrix.gui.enable { + # Optional matrix-GUI static dist mounted at /matrix/ by the + # dashboard router (#607 v0). Pre-#15 / pre-nginx-front: this is + # the simplest same-origin shape — fluffychat-web ships as a + # static dist, no runtime daemon needed. + HIVE_MATRIX_GUI_DIR = "${config.services.hyperhive.matrix.gui.package}"; }; - # Matrix GUI static serving lives entirely on the hive-gateway - # nginx since #609 — when gateway is off the operator opts out - # of matrix-GUI serving entirely (mara on PR #620: "if you - # disable gateway you have to static host that yourself - # somewhere"). c0re no longer touches /matrix/. serviceConfig = { ExecStart = "${cfg.package}/bin/hive-c0re --socket /run/hyperhive/host.sock serve --hyperhive-flake ${cfg.hyperhiveFlake} --dashboard-port ${toString cfg.dashboardPort} --operator-pronouns ${lib.escapeShellArg cfg.operatorPronouns} --context-window-tokens ${lib.escapeShellArg (builtins.toJSON cfg.contextWindowTokens)}"; Restart = "on-failure"; diff --git a/nix/modules/hive-gateway.nix b/nix/modules/hive-gateway.nix deleted file mode 100644 index c4c288c2..00000000 --- a/nix/modules/hive-gateway.nix +++ /dev/null @@ -1,186 +0,0 @@ -{ - pkgs, - lib, - config, - ... -}: -let - cfg = config.services.hyperhive.gateway; - hyperhiveDomain = config.services.hyperhive.domain; - matrixCfg = config.services.hyperhive.matrix; -in -{ - # Single nginx in front of every hyperhive surface (#609 / #15 v0). - # Lives in its own nixos-container (like hive-forge / hive-matrix) so - # the operator can opt out without touching the host's own nginx, and - # so the static-serve responsibility for the matrix GUI moves off - # hive-c0re's axum router. Shares host netns so `localhost` - # upstream resolution works without any port-forward dance. - # - # Routes (v0): - # `location /matrix/` → static-serve fluffychat-web dist (when - # `services.hyperhive.matrix.gui.enable` is true) - # `location /` → proxy_pass to hive-c0re's dashboard upstream - # - # Container name `hive-gateway` keeps hive-c0re's lifecycle scanner - # (which only sees `h-*`) out of the picture. State-free — nginx - # config lives in the nix store, no runtime persistence to manage. - - options.services.hyperhive.gateway = { - enable = lib.mkOption { - type = lib.types.bool; - default = true; - description = '' - Run hive-gateway — a single nginx in front of every hyperhive - surface. On by default: the gateway hosts the matrix GUI static - dist (when `services.hyperhive.matrix.gui.enable` is true) and - proxies everything else to hive-c0re's dashboard upstream. Set - `services.hyperhive.gateway.enable = false` to bypass nginx - entirely and reach hive-c0re directly on its dashboard port - (7000 by default). - - v0 is HTTP-only; TLS / public-domain shape is tracked - separately. - ''; - }; - - port = lib.mkOption { - type = lib.types.port; - default = 80; - example = 8080; - description = '' - TCP port the gateway listens on. Default 80 (canonical web - port). nginx inside the container binds <1024 because the - container's init runs as root; if 80 is already taken on the - host (existing nginx, traefik, etc.) override to an unused - port like 8080 or move the conflicting service. - ''; - }; - - upstreamHost = lib.mkOption { - type = lib.types.str; - default = "127.0.0.1"; - description = '' - Host the gateway proxies non-static requests to. Defaults to - `127.0.0.1` because the gateway container shares the host - netns, so loopback resolves directly to hive-c0re. - ''; - }; - - upstreamPort = lib.mkOption { - type = lib.types.port; - default = 7000; - description = '' - TCP port the gateway proxies non-static requests to. Defaults - to `7000` (hive-c0re's out-of-the-box dashboard port). Operators - who change `services.hyperhive.c0re.dashboardPort` should set - `upstreamPort` to match — kept as a hardcoded default rather - than a cross-reference to keep this module's options eval - independent of c0re's option tree shape. - ''; - }; - - openFirewall = lib.mkOption { - type = lib.types.bool; - default = true; - description = '' - Open `port` in the host firewall. Off when the gateway should - only be reachable from inside the host (e.g. behind another - reverse proxy that handles TLS termination). - ''; - }; - - localHostsEntry = lib.mkOption { - type = lib.types.bool; - default = false; - example = true; - description = '' - Add an `/etc/hosts` entry mapping `services.hyperhive.domain` - to `127.0.0.1` on the host. Useful for local deployments + - tests where there's no real DNS for `services.hyperhive.domain` - but the operator (or browser-based tests) want to hit - `http://''${services.hyperhive.domain}` to exercise the - gateway shape. Off by default — operators running with real - DNS shouldn't have a stale `/etc/hosts` entry sticking - around. Requires `services.hyperhive.domain` to be set. - ''; - }; - }; - - config = lib.mkIf cfg.enable { - assertions = [ - { - assertion = !cfg.localHostsEntry || hyperhiveDomain != null; - message = '' - services.hyperhive.gateway.localHostsEntry = true requires - services.hyperhive.domain to be set. Either pin a hostname - or leave `localHostsEntry` at its default of false. - ''; - } - ]; - - containers.hive-gateway = { - autoStart = true; - ephemeral = false; - # Share host netns — nginx then binds host-level ports directly, - # `localhost` upstream resolution reaches hive-c0re without any - # port-forward dance, and the firewall config below is the only - # layer that matters. - privateNetwork = false; - config = - { pkgs, ... }: - { - system.stateVersion = "26.05"; - services.nginx = { - enable = true; - recommendedProxySettings = true; - recommendedOptimisation = true; - virtualHosts."_" = { - listen = [ - { - addr = "0.0.0.0"; - port = cfg.port; - } - ]; - locations = - # Matrix GUI: when the operator has flipped both - # `services.hyperhive.matrix.enable` and `matrix.gui.enable` - # on, nginx serves fluffychat-web (or whatever override) - # as a static dist at `/matrix/`. fluffychat is a SPA - # — fall back to its index.html on deep links. - lib.optionalAttrs (matrixCfg.enable && matrixCfg.gui.enable) { - "/matrix/" = { - alias = "${matrixCfg.gui.package}/"; - extraConfig = '' - try_files $uri $uri/ /matrix/index.html; - ''; - }; - } - // { - # Everything else proxies to hive-c0re. Upgrade - # headers stay set so SSE (`/dashboard/stream`, - # `/events/stream`) + websocket (`/screen/ws`) - # endpoints keep working transparently. - "/" = { - proxyPass = "http://${cfg.upstreamHost}:${toString cfg.upstreamPort}"; - proxyWebsockets = true; - extraConfig = '' - proxy_buffering off; - proxy_read_timeout 1d; - ''; - }; - }; - }; - }; - }; - }; - - networking.firewall = lib.mkIf cfg.openFirewall { - allowedTCPPorts = [ cfg.port ]; - }; - - networking.hosts = lib.mkIf (cfg.localHostsEntry && hyperhiveDomain != null) { - "127.0.0.1" = [ hyperhiveDomain ]; - }; - }; -}