From 6a64770c7905d4865c31b0c57cbe826472ca7805 Mon Sep 17 00:00:00 2001 From: atlas Date: Fri, 29 May 2026 23:42:24 +0200 Subject: [PATCH 1/6] nix: add options docs outputs for host + agent surfaces (#616) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Auto-generate CommonMark references for hyperhive's two NixOS module surfaces via `pkgs.nixosOptionsDoc`: - `packages..docs-host` — operator-facing options exposed by `hyperhive.nixosModules.default` (`services.hive-c0re.*`, `hyperhive.domain`, `hyperhive.forge.*`, `hyperhive.matrix.*`). - `packages..docs-agent` — per-agent options declared in `nix/templates/harness-base.nix` (model, allowedRecipients, extraMcpServers, frontend, forge, matrix, gui, …). - `packages..docs` — both pages plus a thin `README.md` index, bundled for publishing. Declaration links are rewritten to point at the forge source tree instead of nix-store paths. Host options come from a stubbed `nixosSystem` eval that force-disables all hyperhive subsystems — only the *declarations* feed the doc renderer, no heavy build inputs end up in the closure. Agent options reuse `nixosConfigurations.agent-base.options` (already evaluated). Also wired as `checks..docs` so CI fails fast on eval breakage. --- flake.nix | 29 +++++++++ nix/docs.nix | 177 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 206 insertions(+) create mode 100644 nix/docs.nix diff --git a/flake.nix b/flake.nix index 1161ae21..f0804f7a 100644 --- a/flake.nix +++ b/flake.nix @@ -115,6 +115,13 @@ nativeBuildInputs, ... }: + let + docsAttrs = import ./nix/docs.nix { + inherit pkgs self; + inherit (nixpkgs) lib; + inherit (nixpkgs.lib) nixosSystem; + }; + in { # Build the workspace binaries without running tests. Tests # are run as a separate check (`checks.cargo-test`) that @@ -167,6 +174,15 @@ # host would only succeed via a remote x86 builder. agent-base-toplevel = self.nixosConfigurations.agent-base.config.system.build.toplevel; manager-toplevel = self.nixosConfigurations.manager.config.system.build.toplevel; + + # Auto-generated nix options reference for hyperhive (#616). + # `docs` bundles host + agent pages into one tree; the split + # outputs are useful when consumers only want one surface. + # All three are pure markdown — no rust or frontend deps in + # the closure, so `nix build .#docs` is cheap. + docs = docsAttrs.bundle; + docs-host = docsAttrs.host; + docs-agent = docsAttrs.agent; } ); @@ -328,6 +344,19 @@ cargoTestExtraArgs = "--workspace"; HIVE_ASSETS_DIR = "${self.packages.${system}.assets}/share/hyperhive"; }; + # Nix options docs evaluation (#616). Cheap: pulls in + # `nixosOptionsDoc` + the host module's stub eval, no rust or + # frontend deps. CI fails fast if a module change breaks + # option declarations or the doc rendering. `docs-host` + + # `docs-agent` are exposed as `packages` outputs (not checks) + # — they share this eval, so building the bundle here covers + # both surfaces in one shot. + docs = + (import ./nix/docs.nix { + inherit pkgs self; + inherit (nixpkgs) lib; + inherit (nixpkgs.lib) nixosSystem; + }).bundle; } ); }; diff --git a/nix/docs.nix b/nix/docs.nix new file mode 100644 index 00000000..ba222889 --- /dev/null +++ b/nix/docs.nix @@ -0,0 +1,177 @@ +{ + pkgs, + lib, + self, + nixosSystem, +}: +# Options documentation for hyperhive's NixOS module surfaces. +# Closes #616. +# +# Two output trees: +# docs-host — operator-facing options exposed by the meta module +# (`hyperhive.nixosModules.default`). Covers +# `hyperhive.*` (domain, enable, c0re, forge, matrix) +# plus the deprecated `services.hive-c0re.*` alias. +# docs-agent — per-agent options declared by the shared harness +# module (`nix/templates/harness-base.nix`, transitively +# imported by `agent-base.nix` and `manager.nix`). +# Covers `hyperhive.*` (model, allowedRecipients, +# extraMcpServers, frontend, forge, matrix, gui, …). +# +# Both render as CommonMark via `pkgs.nixosOptionsDoc`. `docs` bundles +# them into one derivation alongside a small `README.md` index so it +# can be published verbatim. +let + # Evaluate the host module under a stub NixOS system. Stubs satisfy + # the few hard-required options (filesystems, stateVersion) without + # actually enabling the hive — we only want the option *declarations* + # to evaluate, not the config. + hostEval = nixosSystem { + system = pkgs.stdenv.hostPlatform.system; + modules = [ + self.nixosModules.default + ( + { lib, ... }: + { + nixpkgs.overlays = [ self.overlays.default ]; + fileSystems."/" = { + device = "/dev/null"; + fsType = "tmpfs"; + }; + boot.loader.grub.enable = false; + system.stateVersion = "25.11"; + # Force-disable every hyperhive subsystem so config evaluation + # doesn't pull in heavy build inputs (matrix container, forge, + # etc.). Options are still fully declared either way — that's + # what nixosOptionsDoc traverses. + services.hive-c0re.enable = lib.mkForce false; + hyperhive.forge.enable = lib.mkForce false; + hyperhive.matrix.enable = lib.mkForce false; + } + ) + ]; + }; + + # Agent options live in the already-evaluated `agent-base` container + # config. Reusing it avoids re-evaluating the harness module against + # a fresh stub — the options tree is identical to what a real agent + # container sees. + agentEval = self.nixosConfigurations.agent-base; + + # Strip the nix-store prefix from option declaration paths and rewrite + # them as forge URLs so the rendered docs link back to the source. + forgeRoot = "https://forge.darkest.space/hyperhive/hyperhive/src/branch/main"; + storePrefix = toString self + "/"; + transformOptions = + opt: + opt + // { + declarations = map ( + decl: + let + declStr = toString decl; + relPath = + if lib.hasPrefix storePrefix declStr then + lib.removePrefix storePrefix declStr + else + baseNameOf declStr; + in + { + url = "${forgeRoot}/${relPath}"; + name = relPath; + } + ) opt.declarations; + }; + + # Filter an evaluated `options` tree down to a set of top-level + # subtrees we care about. Anything outside the listed roots is + # dropped — keeps the rendered docs focused on hyperhive's surface + # instead of NixOS's 10k+ default options. + pickSubtrees = + options: roots: + let + walk = + path: tree: + if path == [ ] then + lib.getAttrFromPath path options + else + lib.setAttrByPath path (lib.getAttrFromPath path tree); + pick = + path: + let + exists = lib.hasAttrByPath path options; + in + if exists then lib.setAttrByPath path (lib.getAttrFromPath path options) else { }; + in + lib.foldl' lib.recursiveUpdate { } (map pick roots); + + hostOptions = pickSubtrees hostEval.options [ + [ "hyperhive" ] + [ + "services" + "hive-c0re" + ] + ]; + + agentOptions = pickSubtrees agentEval.options [ + [ "hyperhive" ] + ]; + + hostDoc = pkgs.nixosOptionsDoc { + options = hostOptions; + inherit transformOptions; + }; + + agentDoc = pkgs.nixosOptionsDoc { + options = agentOptions; + inherit transformOptions; + }; + + mkPage = + name: title: doc: + pkgs.runCommand "hyperhive-${name}-options.md" { } '' + { + echo "# ${title}" + echo + echo "" + echo + cat ${doc.optionsCommonMark} + } > $out + ''; +in +{ + host = mkPage "docs-host" "hyperhive — host options" hostDoc; + agent = mkPage "docs-agent" "hyperhive — per-agent options" agentDoc; + + # Bundle both pages plus a thin index so the whole thing can be + # published as a single static tree. + bundle = pkgs.runCommand "hyperhive-options-docs" { } '' + mkdir -p $out + cp ${mkPage "docs-host" "hyperhive — host options" hostDoc} $out/host.md + cp ${mkPage "docs-agent" "hyperhive — per-agent options" agentDoc} $out/agent.md + cat > $out/README.md <<'EOF' + # hyperhive — nix options reference + + auto-generated from the hyperhive flake. + + - [host.md](./host.md) — options exposed by `hyperhive.nixosModules.default` + to operator host configurations (`services.hive-c0re.*`, + `hyperhive.domain`, `hyperhive.forge.*`, `hyperhive.matrix.*`). + - [agent.md](./agent.md) — per-agent options declared in + `nix/templates/harness-base.nix` and visible from every `agent.nix` + (`hyperhive.model`, `hyperhive.allowedRecipients`, + `hyperhive.extraMcpServers`, `hyperhive.frontend.*`, + `hyperhive.forge.*`, `hyperhive.matrix.*`, `hyperhive.gui.*`). + + regenerate: + + ```sh + nix build .#docs # bundled tree + nix build .#docs-host # host page only + nix build .#docs-agent # agent page only + ``` + EOF + ''; +} From 24f61f33869a6a5d36dc3027e6b76150f41c24e5 Mon Sep 17 00:00:00 2001 From: atlas Date: Fri, 29 May 2026 23:46:39 +0200 Subject: [PATCH 2/6] nix/docs: drop dead `walk` helper + dedupe checks.docs eval argus review notes on #618: - `walk` in `pickSubtrees` was leftover from an earlier traversal design; `pick` does everything we need. drop it. - `checks.docs` was re-importing `nix/docs.nix` independently of `packages.docs`; the comment claimed they shared eval but they didn't (nix's lazy eval + import caching made the *result* identical, not the eval). switch to `inherit (self.packages.\${system}) docs;` so the check is literally the package output, no second import. --- flake.nix | 14 ++++---------- nix/docs.nix | 14 ++++---------- 2 files changed, 8 insertions(+), 20 deletions(-) diff --git a/flake.nix b/flake.nix index f0804f7a..ad424a7e 100644 --- a/flake.nix +++ b/flake.nix @@ -347,16 +347,10 @@ # Nix options docs evaluation (#616). Cheap: pulls in # `nixosOptionsDoc` + the host module's stub eval, no rust or # frontend deps. CI fails fast if a module change breaks - # option declarations or the doc rendering. `docs-host` + - # `docs-agent` are exposed as `packages` outputs (not checks) - # — they share this eval, so building the bundle here covers - # both surfaces in one shot. - docs = - (import ./nix/docs.nix { - inherit pkgs self; - inherit (nixpkgs) lib; - inherit (nixpkgs.lib) nixosSystem; - }).bundle; + # option declarations or the doc rendering. Reuses the + # `packages..docs` derivation so the per-system eval + # of `nix/docs.nix` happens once. + inherit (self.packages.${system}) docs; } ); }; diff --git a/nix/docs.nix b/nix/docs.nix index ba222889..c4427c94 100644 --- a/nix/docs.nix +++ b/nix/docs.nix @@ -90,18 +90,12 @@ let pickSubtrees = options: roots: let - walk = - path: tree: - if path == [ ] then - lib.getAttrFromPath path options - else - lib.setAttrByPath path (lib.getAttrFromPath path tree); pick = path: - let - exists = lib.hasAttrByPath path options; - in - if exists then lib.setAttrByPath path (lib.getAttrFromPath path options) else { }; + if lib.hasAttrByPath path options then + lib.setAttrByPath path (lib.getAttrFromPath path options) + else + { }; in lib.foldl' lib.recursiveUpdate { } (map pick roots); From 32148179e6788d37780301a9842a9c0a87244b45 Mon Sep 17 00:00:00 2001 From: atlas Date: Fri, 29 May 2026 23:23:58 +0200 Subject: [PATCH 3/6] refactor: move hive-c0re options to hyperhive namespace (#612) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Move options.services.hive-c0re → options.hyperhive.c0re - Add options.hyperhive.enable to auto-enable c0re + subsystems - Add deprecation alias for services.hive-c0re.enable (backward compat) - Update doc references in README, flake.nix, docs, harness-base.nix - Simplifies config: 'hyperhive.enable = true' now enables everything Existing operator configs using services.hive-c0re.enable will continue to work but emit a deprecation warning. Aligns the option namespace with the existing hyperhive.* family (matrix, forge, domain). fixes #612 --- README.md | 6 +- docs/turn-loop.md | 4 +- flake.nix | 6 +- nix/modules/hive-c0re.nix | 177 +++++++++++++++++++-------------- nix/templates/harness-base.nix | 4 +- 5 files changed, 113 insertions(+), 84 deletions(-) diff --git a/README.md b/README.md index 83aa3084..5cac0715 100644 --- a/README.md +++ b/README.md @@ -61,8 +61,8 @@ Minimal `flake.nix` for a host that runs hive-c0re: modules = [ hyperhive.nixosModules.default # hive-c0re + hive-forge in one import ({ ... }: { - services.hive-c0re.enable = true; - # services.hive-c0re.operatorPronouns = "they/them"; # default: "she/her" + hyperhive.enable = true; + # hyperhive.c0re.operatorPronouns = "they/them"; # default: "she/her" # ... rest of your host config system.stateVersion = "25.11"; @@ -78,7 +78,7 @@ 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.hive-c0re.preBuildAgentTemplates = true;` +Optional: set `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 completes in seconds instead of minutes (no nixpkgs/claude-code diff --git a/docs/turn-loop.md b/docs/turn-loop.md index de3c3297..324dff12 100644 --- a/docs/turn-loop.md +++ b/docs/turn-loop.md @@ -76,7 +76,7 @@ match wins): 1. `HIVE_CONTEXT_WINDOW_TOKENS_` env var, where `KEY` (lowercased) is a substring of the active model name. Injected - by the meta flake from `services.hive-c0re.contextWindowTokens` + by the meta flake from `hyperhive.c0re.contextWindowTokens` (host-level NixOS option, defaults: haiku=200k, sonnet=1M, opus=1M). Override these for all agents at once without a per-agent config change. @@ -178,7 +178,7 @@ socket at `/run/hive/` once at startup: #519); everything else is shared. Then `{label}` and `{operator_pronouns}` get substituted in the assembled output. Pronouns come from `HIVE_OPERATOR_PRONOUNS` env (set by the meta - flake from `services.hive-c0re.operatorPronouns`, default + flake from `hyperhive.c0re.operatorPronouns`, default `she/her`). Passed via `--system-prompt-file`. The shared per-turn plumbing lives in `hive_ag3nt::turn::{write_mcp_config, diff --git a/flake.nix b/flake.nix index ad424a7e..964a15f3 100644 --- a/flake.nix +++ b/flake.nix @@ -225,7 +225,7 @@ agent-base = ./nix/templates/agent-base.nix; manager = ./nix/templates/manager.nix; # The hive-c0re module wants `pkgs.hyperhive` for its default - # `services.hive-c0re.package`. To avoid making operators apply an + # `hyperhive.c0re.package`. To avoid making operators apply an # overlay (which would also pollute their host pkgs with our # build), we thread the package straight from this flake's # `packages..default` via a `hyperhivePackage` argument. @@ -237,7 +237,7 @@ hyperhiveAssets = system: self.packages.${system}.assets; hyperhiveFlake = "${self}"; # Per-container toplevels — wired into `system.extraDependencies` - # when `services.hive-c0re.preBuildAgentTemplates` is on so the + # when `hyperhive.c0re.preBuildAgentTemplates` is on so the # host system closure pre-fetches the heavy build inputs (#97). # Defined only for x86_64-linux because nixosConfigurations are # hardcoded to that system; the option's default keeps the @@ -252,7 +252,7 @@ # in hive-forge). Intended usage: # # imports = [ hyperhive.nixosModules.default ]; - # services.hive-c0re.enable = true; + # hyperhive.enable = true; # default = self.nixosModules.hive-c0re; }; diff --git a/nix/modules/hive-c0re.nix b/nix/modules/hive-c0re.nix index ae288a34..f6c7753d 100644 --- a/nix/modules/hive-c0re.nix +++ b/nix/modules/hive-c0re.nix @@ -13,7 +13,7 @@ ... }: let - cfg = config.services.hive-c0re; + cfg = config.hyperhive.c0re; in { # The forge is part of the standard install — hive-c0re mirrors @@ -26,6 +26,10 @@ in ./hive-matrix.nix ]; + # Top-level hyperhive enable flag. When true, automatically enables + # hive-c0re and hyperhive subsystems. + options.hyperhive.enable = lib.mkEnableOption "hyperhive — the agent swarm coordinator"; + # Top-level option shared by any hyperhive subsystem that needs a # stable hostname (matrix server_name today, forge ROOT_URL likely # next). Type is nullable + default null so existing operator @@ -46,8 +50,23 @@ in ''; }; - options.services.hive-c0re = { - enable = lib.mkEnableOption "hive-c0re — hyperhive coordinator daemon"; + # Deprecated alias for backward compatibility. Remove in v0.2. + options.services.hive-c0re.enable = lib.mkOption { + type = lib.types.bool; + default = false; + description = '' + **DEPRECATED** (as of #612). Use `hyperhive.enable = true` or + `hyperhive.c0re.enable = true` instead. This option is maintained + for backward compatibility and will be removed in a future release. + ''; + }; + + options.hyperhive.c0re = { + enable = lib.mkOption { + type = lib.types.bool; + default = config.hyperhive.enable; + description = "Enable hive-c0re coordinator daemon (auto-enabled by hyperhive.enable)."; + }; package = lib.mkOption { type = lib.types.package; default = hyperhivePackage pkgs.stdenv.hostPlatform.system; @@ -166,77 +185,87 @@ in }; }; - config = lib.mkIf cfg.enable { - environment.systemPackages = [ - cfg.package - pkgs.git - ]; - - # Pull the per-container toplevels into the host system closure - # (#97). `system.extraDependencies` adds paths to the system build - # without referencing them at runtime — nixos-rebuild fetches / - # builds them, they end up in /nix/store, and the first - # nixos-container update + start for an agent has nothing left to - # do. Gated because the closure is sizeable and pinned to x86_64. - system.extraDependencies = lib.optionals cfg.preBuildAgentTemplates [ - agentBaseToplevel - managerToplevel - ]; - - # Dashboard + per-container web UIs share the host's network namespace and - # need their ports reachable. Dashboard: `cfg.dashboardPort` (default 7000). - # Manager: 8000. Sub-agents: 8100..8999 (deterministic hash; see - # `lifecycle::agent_web_port`). - networking.firewall.allowedTCPPorts = [ - cfg.dashboardPort - 8000 - ]; - networking.firewall.allowedTCPPortRanges = [ - { - from = 8100; - to = 8999; - } - ]; - - systemd.services.hive-c0re = { - description = "hyperhive coordinator daemon"; - wantedBy = [ "multi-user.target" ]; - path = [ - pkgs.git - "/run/current-system/sw" + config = lib.mkMerge [ + # Backward-compatibility redirect for deprecated services.hive-c0re.enable + (lib.mkIf config.services.hive-c0re.enable { + hyperhive.c0re.enable = true; + warnings = [ + "services.hive-c0re.enable is deprecated (as of #612). Use 'hyperhive.enable = true' or 'hyperhive.c0re.enable = true' instead." ]; - environment = { - HYPERHIVE_GIT = "${pkgs.git}/bin/git"; - # Path to the dashboard static dist. The hive-c0re axum router - # serves this via `tower_http::ServeDir` for any path it doesn't - # match against an API/action route. - HIVE_STATIC_DIR = "${cfg.frontend}/dashboard"; - # Path to the static runtime asset tree (branding + claude - # prompts). `hive_sh4re::assets::*` reads paths underneath. - # `forge.rs` reads the avatar PNGs from here on startup. - HIVE_ASSETS_DIR = "${cfg.assets}/share/hyperhive"; - } - // lib.optionalAttrs config.hyperhive.forge.enable { - # Agents poll this URL for Forgejo notifications. Derived from - # hyperhive.forge.{domain,httpPort} so it tracks forge config changes. - HIVE_FORGE_URL = "http://${config.hyperhive.forge.domain}:${toString config.hyperhive.forge.httpPort}"; - } - // lib.optionalAttrs config.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.hyperhive.matrix.gui.package}"; + }) + # Main config block + (lib.mkIf cfg.enable { + environment.systemPackages = [ + cfg.package + pkgs.git + ]; + + # Pull the per-container toplevels into the host system closure + # (#97). `system.extraDependencies` adds paths to the system build + # without referencing them at runtime — nixos-rebuild fetches / + # builds them, they end up in /nix/store, and the first + # nixos-container update + start for an agent has nothing left to + # do. Gated because the closure is sizeable and pinned to x86_64. + system.extraDependencies = lib.optionals cfg.preBuildAgentTemplates [ + agentBaseToplevel + managerToplevel + ]; + + # Dashboard + per-container web UIs share the host's network namespace and + # need their ports reachable. Dashboard: `cfg.dashboardPort` (default 7000). + # Manager: 8000. Sub-agents: 8100..8999 (deterministic hash; see + # `lifecycle::agent_web_port`). + networking.firewall.allowedTCPPorts = [ + cfg.dashboardPort + 8000 + ]; + networking.firewall.allowedTCPPortRanges = [ + { + from = 8100; + to = 8999; + } + ]; + + systemd.services.hive-c0re = { + description = "hyperhive coordinator daemon"; + wantedBy = [ "multi-user.target" ]; + path = [ + pkgs.git + "/run/current-system/sw" + ]; + environment = { + HYPERHIVE_GIT = "${pkgs.git}/bin/git"; + # Path to the dashboard static dist. The hive-c0re axum router + # serves this via `tower_http::ServeDir` for any path it doesn't + # match against an API/action route. + HIVE_STATIC_DIR = "${cfg.frontend}/dashboard"; + # Path to the static runtime asset tree (branding + claude + # prompts). `hive_sh4re::assets::*` reads paths underneath. + # `forge.rs` reads the avatar PNGs from here on startup. + HIVE_ASSETS_DIR = "${cfg.assets}/share/hyperhive"; + } + // lib.optionalAttrs config.hyperhive.forge.enable { + # Agents poll this URL for Forgejo notifications. Derived from + # hyperhive.forge.{domain,httpPort} so it tracks forge config changes. + HIVE_FORGE_URL = "http://${config.hyperhive.forge.domain}:${toString config.hyperhive.forge.httpPort}"; + } + // lib.optionalAttrs config.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.hyperhive.matrix.gui.package}"; + }; + 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"; + RestartSec = 2; + RuntimeDirectory = "hyperhive"; + RuntimeDirectoryMode = "0750"; + RuntimeDirectoryPreserve = "yes"; + StateDirectory = "hyperhive"; + }; }; - 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"; - RestartSec = 2; - RuntimeDirectory = "hyperhive"; - RuntimeDirectoryMode = "0750"; - RuntimeDirectoryPreserve = "yes"; - StateDirectory = "hyperhive"; - }; - }; - }; + }) + ]; } diff --git a/nix/templates/harness-base.nix b/nix/templates/harness-base.nix index 720b6633..8cf9a68f 100644 --- a/nix/templates/harness-base.nix +++ b/nix/templates/harness-base.nix @@ -36,7 +36,7 @@ `"haiku"`, `"sonnet"`, `"opus"` (or any future identifier). Context window sizes are looked up at runtime from the `HIVE_CONTEXT_WINDOW_TOKENS_` env vars injected by the - meta flake; override sizes via `services.hive-c0re.contextWindowTokens` + meta flake; override sizes via `hyperhive.c0re.contextWindowTokens` on the host. ''; }; @@ -595,7 +595,7 @@ # both the harness binary and any user-shell `cargo run` inside the # container resolve them from the same path. # HIVE_CONTEXT_WINDOW_TOKENS_* are injected by the meta flake from the - # host-level `services.hive-c0re.contextWindowTokens` option — not set here. + # host-level `hyperhive.c0re.contextWindowTokens` option — not set here. environment.variables = { HIVE_DEFAULT_MODEL = config.hyperhive.model; HIVE_ASSETS_DIR = "${pkgs.hyperhive-assets}/share/hyperhive"; From 3b500bba1b857f78f5395efaf1a8340342c9e785 Mon Sep 17 00:00:00 2001 From: atlas Date: Sat, 30 May 2026 10:32:04 +0200 Subject: [PATCH 4/6] nix: pivot to services.hyperhive.* per mara directive (#612) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per [mara on PR #615 comment 7349](http://localhost:3000/hyperhive/hyperhive/pulls/615#issuecomment-7349): > follow nix conventions, services.hyperhive it is. the earlier we > change this, the less breakage. Renames the entire host-side option tree under `services.hyperhive.*`: - `services.hive-c0re.*` → `services.hyperhive.c0re.*` - `hyperhive.enable` → `services.hyperhive.enable` - `hyperhive.domain` → `services.hyperhive.domain` - `hyperhive.forge.*` → `services.hyperhive.forge.*` - `hyperhive.matrix.*` → `services.hyperhive.matrix.*` Per mara's "earlier = less breakage", the previous `services.hive-c0re.enable` deprecation alias is dropped. Operators get a clear eval error on the old paths pointing at the rename. Single migration moment. Per-agent options in `nix/templates/harness-base.nix` (`hyperhive.model`, `hyperhive.allowedRecipients`, etc.) stay at `hyperhive.*` — they're container-level config, not services in the NixOS sense. Verified via `nix flake check --no-build` + an end-to-end NixOS eval exercising every renamed path. Follow-up needed: rust source comments referencing the old NixOS option names (`hive-c0re/src/{meta,coordinator,main,dashboard}.rs`) should be updated in a separate pure-rust PR to keep this one strictly nix-only. --- README.md | 27 ++--- docs/turn-loop.md | 4 +- flake.nix | 6 +- nix/modules/hive-c0re.nix | 193 +++++++++++++++------------------ nix/modules/hive-forge.nix | 33 +++--- nix/modules/hive-matrix.nix | 20 ++-- nix/templates/harness-base.nix | 4 +- 7 files changed, 136 insertions(+), 151 deletions(-) diff --git a/README.md b/README.md index 5cac0715..c5f36cf8 100644 --- a/README.md +++ b/README.md @@ -61,8 +61,8 @@ Minimal `flake.nix` for a host that runs hive-c0re: modules = [ hyperhive.nixosModules.default # hive-c0re + hive-forge in one import ({ ... }: { - hyperhive.enable = true; - # hyperhive.c0re.operatorPronouns = "they/them"; # default: "she/her" + services.hyperhive.enable = true; + # services.hyperhive.c0re.operatorPronouns = "they/them"; # default: "she/her" # ... rest of your host config system.stateVersion = "25.11"; @@ -78,7 +78,7 @@ 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 `hyperhive.c0re.preBuildAgentTemplates = true;` +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 completes in seconds instead of minutes (no nixpkgs/claude-code @@ -88,19 +88,20 @@ system closure. Off by default (the toplevels are pinned to Alternatively warm the store manually: `nix build git+https://forge.darkest.space/hyperhive/hyperhive#agent-base-toplevel`. -Optional: set `hyperhive.domain = "example.com";` to define the +Optional: set `services.hyperhive.domain = "example.com";` to define the canonical hostname for hyperhive subsystems that need a stable public name. No default — subsystems that require it (currently: -`hyperhive.matrix`) assert non-null at eval time with a clear error -message if it is missing. +`services.hyperhive.matrix`) assert non-null at eval time with a clear +error message if it is missing. -Optional: set `hyperhive.matrix.enable = true;` to spin up a private -[matrix-tuwunel](https://github.com/matrix-construct/tuwunel) homeserver -in a nixos-container. Requires either `hyperhive.domain` or -`hyperhive.matrix.serverName` to be set (eval fails with a clear error -if both are absent). The `server_name` (embedded irrevocably in every -user and room ID) defaults to `matrix.`; override with -`hyperhive.matrix.serverName = "chat.example.com";` if needed. State +Optional: set `services.hyperhive.matrix.enable = true;` to spin up a +private [matrix-tuwunel](https://github.com/matrix-construct/tuwunel) +homeserver in a nixos-container. Requires either +`services.hyperhive.domain` or `services.hyperhive.matrix.serverName` +to be set (eval fails with a clear error if both are absent). The +`server_name` (embedded irrevocably in every user and room ID) +defaults to `matrix.`; override with +`services.hyperhive.matrix.serverName = "chat.example.com";` if needed. State lives at `/var/lib/nixos-containers/hive-matrix/`. Federation is enabled with an empty `trusted_servers` list; e2ee is deferred to a follow-up (#551). diff --git a/docs/turn-loop.md b/docs/turn-loop.md index 324dff12..1817a613 100644 --- a/docs/turn-loop.md +++ b/docs/turn-loop.md @@ -76,7 +76,7 @@ match wins): 1. `HIVE_CONTEXT_WINDOW_TOKENS_` env var, where `KEY` (lowercased) is a substring of the active model name. Injected - by the meta flake from `hyperhive.c0re.contextWindowTokens` + by the meta flake from `services.hyperhive.c0re.contextWindowTokens` (host-level NixOS option, defaults: haiku=200k, sonnet=1M, opus=1M). Override these for all agents at once without a per-agent config change. @@ -178,7 +178,7 @@ socket at `/run/hive/` once at startup: #519); everything else is shared. Then `{label}` and `{operator_pronouns}` get substituted in the assembled output. Pronouns come from `HIVE_OPERATOR_PRONOUNS` env (set by the meta - flake from `hyperhive.c0re.operatorPronouns`, default + flake from `services.hyperhive.c0re.operatorPronouns`, default `she/her`). Passed via `--system-prompt-file`. The shared per-turn plumbing lives in `hive_ag3nt::turn::{write_mcp_config, diff --git a/flake.nix b/flake.nix index 964a15f3..28656c70 100644 --- a/flake.nix +++ b/flake.nix @@ -225,7 +225,7 @@ agent-base = ./nix/templates/agent-base.nix; manager = ./nix/templates/manager.nix; # The hive-c0re module wants `pkgs.hyperhive` for its default - # `hyperhive.c0re.package`. To avoid making operators apply an + # `services.hyperhive.c0re.package`. To avoid making operators apply an # overlay (which would also pollute their host pkgs with our # build), we thread the package straight from this flake's # `packages..default` via a `hyperhivePackage` argument. @@ -237,7 +237,7 @@ hyperhiveAssets = system: self.packages.${system}.assets; hyperhiveFlake = "${self}"; # Per-container toplevels — wired into `system.extraDependencies` - # when `hyperhive.c0re.preBuildAgentTemplates` is on so the + # when `services.hyperhive.c0re.preBuildAgentTemplates` is on so the # host system closure pre-fetches the heavy build inputs (#97). # Defined only for x86_64-linux because nixosConfigurations are # hardcoded to that system; the option's default keeps the @@ -252,7 +252,7 @@ # in hive-forge). Intended usage: # # imports = [ hyperhive.nixosModules.default ]; - # hyperhive.enable = true; + # services.hyperhive.enable = true; # default = self.nixosModules.hive-c0re; }; diff --git a/nix/modules/hive-c0re.nix b/nix/modules/hive-c0re.nix index f6c7753d..8d1630e6 100644 --- a/nix/modules/hive-c0re.nix +++ b/nix/modules/hive-c0re.nix @@ -13,22 +13,22 @@ ... }: let - cfg = config.hyperhive.c0re; + cfg = config.services.hyperhive.c0re; in { # The forge is part of the standard install — hive-c0re mirrors # every agent's applied config repo into it. On by default; opt out - # with `hyperhive.forge.enable = false`. hive-matrix is opt-in (off - # by default) and asserts that `hyperhive.domain` is set before it - # can be enabled. + # with `services.hyperhive.forge.enable = false`. hive-matrix is + # opt-in (off by default) and asserts that `services.hyperhive.domain` + # is set before it can be enabled. imports = [ ./hive-forge.nix ./hive-matrix.nix ]; # Top-level hyperhive enable flag. When true, automatically enables - # hive-c0re and hyperhive subsystems. - options.hyperhive.enable = lib.mkEnableOption "hyperhive — the agent swarm coordinator"; + # hive-c0re and the on-by-default hyperhive subsystems. + options.services.hyperhive.enable = lib.mkEnableOption "hyperhive — the agent swarm coordinator"; # Top-level option shared by any hyperhive subsystem that needs a # stable hostname (matrix server_name today, forge ROOT_URL likely @@ -36,36 +36,27 @@ in # configs that don't set it still evaluate; subsystems that # actually need it (matrix) assert non-null in their own config # block with a helpful message. - options.hyperhive.domain = lib.mkOption { + options.services.hyperhive.domain = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; example = "darkest.space"; description = '' Canonical host domain for hyperhive subsystems that need a - stable name (currently: `hyperhive.matrix.serverName` derives - from this, defaulting to `matrix.''${hyperhive.domain}` when - `serverName` is null). No default — subsystems that opt to - require it assert non-null in their own config and fail eval - with a helpful message if it's missing. + stable name (currently: `services.hyperhive.matrix.serverName` + derives from this, defaulting to + `matrix.''${services.hyperhive.domain}` when `serverName` is + null). No default — subsystems that opt to require it assert + non-null in their own config and fail eval with a helpful + message if it's missing. ''; }; - # Deprecated alias for backward compatibility. Remove in v0.2. - options.services.hive-c0re.enable = lib.mkOption { - type = lib.types.bool; - default = false; - description = '' - **DEPRECATED** (as of #612). Use `hyperhive.enable = true` or - `hyperhive.c0re.enable = true` instead. This option is maintained - for backward compatibility and will be removed in a future release. - ''; - }; - - options.hyperhive.c0re = { + options.services.hyperhive.c0re = { enable = lib.mkOption { type = lib.types.bool; - default = config.hyperhive.enable; - description = "Enable hive-c0re coordinator daemon (auto-enabled by hyperhive.enable)."; + default = config.services.hyperhive.enable; + defaultText = lib.literalExpression "config.services.hyperhive.enable"; + description = "Enable hive-c0re coordinator daemon (auto-enabled by services.hyperhive.enable)."; }; package = lib.mkOption { type = lib.types.package; @@ -185,87 +176,77 @@ in }; }; - config = lib.mkMerge [ - # Backward-compatibility redirect for deprecated services.hive-c0re.enable - (lib.mkIf config.services.hive-c0re.enable { - hyperhive.c0re.enable = true; - warnings = [ - "services.hive-c0re.enable is deprecated (as of #612). Use 'hyperhive.enable = true' or 'hyperhive.c0re.enable = true' instead." - ]; - }) - # Main config block - (lib.mkIf cfg.enable { - environment.systemPackages = [ - cfg.package + config = lib.mkIf cfg.enable { + environment.systemPackages = [ + cfg.package + pkgs.git + ]; + + # Pull the per-container toplevels into the host system closure + # (#97). `system.extraDependencies` adds paths to the system build + # without referencing them at runtime — nixos-rebuild fetches / + # builds them, they end up in /nix/store, and the first + # nixos-container update + start for an agent has nothing left to + # do. Gated because the closure is sizeable and pinned to x86_64. + system.extraDependencies = lib.optionals cfg.preBuildAgentTemplates [ + agentBaseToplevel + managerToplevel + ]; + + # Dashboard + per-container web UIs share the host's network namespace and + # need their ports reachable. Dashboard: `cfg.dashboardPort` (default 7000). + # Manager: 8000. Sub-agents: 8100..8999 (deterministic hash; see + # `lifecycle::agent_web_port`). + networking.firewall.allowedTCPPorts = [ + cfg.dashboardPort + 8000 + ]; + networking.firewall.allowedTCPPortRanges = [ + { + from = 8100; + to = 8999; + } + ]; + + systemd.services.hive-c0re = { + description = "hyperhive coordinator daemon"; + wantedBy = [ "multi-user.target" ]; + path = [ pkgs.git + "/run/current-system/sw" ]; - - # Pull the per-container toplevels into the host system closure - # (#97). `system.extraDependencies` adds paths to the system build - # without referencing them at runtime — nixos-rebuild fetches / - # builds them, they end up in /nix/store, and the first - # nixos-container update + start for an agent has nothing left to - # do. Gated because the closure is sizeable and pinned to x86_64. - system.extraDependencies = lib.optionals cfg.preBuildAgentTemplates [ - agentBaseToplevel - managerToplevel - ]; - - # Dashboard + per-container web UIs share the host's network namespace and - # need their ports reachable. Dashboard: `cfg.dashboardPort` (default 7000). - # Manager: 8000. Sub-agents: 8100..8999 (deterministic hash; see - # `lifecycle::agent_web_port`). - networking.firewall.allowedTCPPorts = [ - cfg.dashboardPort - 8000 - ]; - networking.firewall.allowedTCPPortRanges = [ - { - from = 8100; - to = 8999; - } - ]; - - systemd.services.hive-c0re = { - description = "hyperhive coordinator daemon"; - wantedBy = [ "multi-user.target" ]; - path = [ - pkgs.git - "/run/current-system/sw" - ]; - environment = { - HYPERHIVE_GIT = "${pkgs.git}/bin/git"; - # Path to the dashboard static dist. The hive-c0re axum router - # serves this via `tower_http::ServeDir` for any path it doesn't - # match against an API/action route. - HIVE_STATIC_DIR = "${cfg.frontend}/dashboard"; - # Path to the static runtime asset tree (branding + claude - # prompts). `hive_sh4re::assets::*` reads paths underneath. - # `forge.rs` reads the avatar PNGs from here on startup. - HIVE_ASSETS_DIR = "${cfg.assets}/share/hyperhive"; - } - // lib.optionalAttrs config.hyperhive.forge.enable { - # Agents poll this URL for Forgejo notifications. Derived from - # hyperhive.forge.{domain,httpPort} so it tracks forge config changes. - HIVE_FORGE_URL = "http://${config.hyperhive.forge.domain}:${toString config.hyperhive.forge.httpPort}"; - } - // lib.optionalAttrs config.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.hyperhive.matrix.gui.package}"; - }; - 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"; - RestartSec = 2; - RuntimeDirectory = "hyperhive"; - RuntimeDirectoryMode = "0750"; - RuntimeDirectoryPreserve = "yes"; - StateDirectory = "hyperhive"; - }; + environment = { + HYPERHIVE_GIT = "${pkgs.git}/bin/git"; + # Path to the dashboard static dist. The hive-c0re axum router + # serves this via `tower_http::ServeDir` for any path it doesn't + # match against an API/action route. + HIVE_STATIC_DIR = "${cfg.frontend}/dashboard"; + # Path to the static runtime asset tree (branding + claude + # prompts). `hive_sh4re::assets::*` reads paths underneath. + # `forge.rs` reads the avatar PNGs from here on startup. + HIVE_ASSETS_DIR = "${cfg.assets}/share/hyperhive"; + } + // lib.optionalAttrs config.services.hyperhive.forge.enable { + # 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}"; }; - }) - ]; + 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"; + RestartSec = 2; + RuntimeDirectory = "hyperhive"; + RuntimeDirectoryMode = "0750"; + RuntimeDirectoryPreserve = "yes"; + StateDirectory = "hyperhive"; + }; + }; + }; } diff --git a/nix/modules/hive-forge.nix b/nix/modules/hive-forge.nix index fb83ff94..73639373 100644 --- a/nix/modules/hive-forge.nix +++ b/nix/modules/hive-forge.nix @@ -5,7 +5,7 @@ ... }: let - cfg = config.hyperhive.forge; + cfg = config.services.hyperhive.forge; in { # Private Forgejo for hyperhive agents, wrapped in a nixos-container @@ -24,7 +24,7 @@ in # and survives container restart / host reboot. To wipe, destroy the # container. - options.hyperhive.forge = { + options.services.hyperhive.forge = { enable = lib.mkOption { type = lib.types.bool; default = true; @@ -33,7 +33,7 @@ in hyperhive agents. On by default: hive-c0re mirrors every agent's applied config repo into the forge's `agent-configs` org, so the forge is part of the standard install. Set - `hyperhive.forge.enable = false` to opt out. + `services.hyperhive.forge.enable = false` to opt out. ''; }; @@ -220,19 +220,22 @@ in Group = "forgejo"; }; environment.GNUPGHOME = "/var/lib/forgejo/.gnupg"; - path = [ pkgs.gnupg pkgs.coreutils ]; + path = [ + pkgs.gnupg + pkgs.coreutils + ]; script = '' - mkdir -p "$GNUPGHOME" - chmod 700 "$GNUPGHOME" - gpg --batch --gen-key <<'EOF' -%no-protection -Key-Type: RSA -Key-Length: 4096 -Name-Real: HyperHive Forge -Name-Email: forgejo@hive -Expire-Date: 0 -EOF - touch "$GNUPGHOME/hive-key-init.stamp" + mkdir -p "$GNUPGHOME" + chmod 700 "$GNUPGHOME" + gpg --batch --gen-key <<'EOF' + %no-protection + Key-Type: RSA + Key-Length: 4096 + Name-Real: HyperHive Forge + Name-Email: forgejo@hive + Expire-Date: 0 + EOF + touch "$GNUPGHOME/hive-key-init.stamp" ''; }; }; diff --git a/nix/modules/hive-matrix.nix b/nix/modules/hive-matrix.nix index fb5244e2..a2ef7abe 100644 --- a/nix/modules/hive-matrix.nix +++ b/nix/modules/hive-matrix.nix @@ -5,8 +5,8 @@ ... }: let - cfg = config.hyperhive.matrix; - hyperhiveDomain = config.hyperhive.domain; + cfg = config.services.hyperhive.matrix; + hyperhiveDomain = config.services.hyperhive.domain; effectiveServerName = if cfg.serverName != null then cfg.serverName else "matrix.${hyperhiveDomain}"; in @@ -45,7 +45,7 @@ in # so the agent's matrix MCP client can authenticate without ever # seeing the shared registration token. - options.hyperhive.matrix = { + options.services.hyperhive.matrix = { enable = lib.mkOption { type = lib.types.bool; default = false; @@ -53,7 +53,7 @@ in Run hive-matrix — a private matrix-tuwunel homeserver (in a nixos-container) for hyperhive agents. Off by default while the integration phases in; flip to `true` once the operator - has set `hyperhive.domain` and is ready to onboard agents. + has set `services.hyperhive.domain` and is ready to onboard agents. ''; }; @@ -77,7 +77,7 @@ in (`@argus:`) and room ID minted on this homeserver. CRITICAL: must be stable from day one because it's embedded irrevocably in the identifiers. Defaults to - `matrix.''${hyperhive.domain}` (always a subdomain — keeps + `matrix.''${services.hyperhive.domain}` (always a subdomain — keeps the root domain free for the dashboard or forge). Override here only if you need a name that doesn't follow the `matrix.` shape. @@ -169,7 +169,7 @@ in without standing up a separate gateway. Same-origin via hive-c0re is the simplest single-host - shape; the post-#15 nginx-front re-root (`https://matrix.''${hyperhive.domain}`) + shape; the post-#15 nginx-front re-root (`https://matrix.''${services.hyperhive.domain}`) is tracked separately in #609. fluffychat-web supports per-login server pick — point it at the in-host tuwunel URL (`http://localhost:8008` by default) the first time. @@ -196,17 +196,17 @@ in # mara on #548: "there is no default, but it is required. add # assertion." — fail eval with a helpful message rather than # spawning a homeserver with a bogus server_name we can never - # change later. `hyperhive.domain` is host-wide; matrix derives + # change later. `services.hyperhive.domain` is host-wide; matrix derives # the server_name from it (or from `cfg.serverName` if the # operator wants to override). assertions = [ { assertion = hyperhiveDomain != null || cfg.serverName != null; message = '' - hyperhive.matrix.enable = true requires either: - - hyperhive.domain set to your host's canonical domain + services.hyperhive.matrix.enable = true requires either: + - services.hyperhive.domain set to your host's canonical domain (recommended; shared with forge / dashboard), or - - hyperhive.matrix.serverName set explicitly. + - services.hyperhive.matrix.serverName set explicitly. The matrix server_name is embedded into every user ID and room ID on this homeserver — it cannot be changed later diff --git a/nix/templates/harness-base.nix b/nix/templates/harness-base.nix index 8cf9a68f..f86e600c 100644 --- a/nix/templates/harness-base.nix +++ b/nix/templates/harness-base.nix @@ -36,7 +36,7 @@ `"haiku"`, `"sonnet"`, `"opus"` (or any future identifier). Context window sizes are looked up at runtime from the `HIVE_CONTEXT_WINDOW_TOKENS_` env vars injected by the - meta flake; override sizes via `hyperhive.c0re.contextWindowTokens` + meta flake; override sizes via `services.hyperhive.c0re.contextWindowTokens` on the host. ''; }; @@ -595,7 +595,7 @@ # both the harness binary and any user-shell `cargo run` inside the # container resolve them from the same path. # HIVE_CONTEXT_WINDOW_TOKENS_* are injected by the meta flake from the - # host-level `hyperhive.c0re.contextWindowTokens` option — not set here. + # host-level `services.hyperhive.c0re.contextWindowTokens` option — not set here. environment.variables = { HIVE_DEFAULT_MODEL = config.hyperhive.model; HIVE_ASSETS_DIR = "${pkgs.hyperhive-assets}/share/hyperhive"; From 0cc27fbe32047560205a5991feeab25c6172b386 Mon Sep 17 00:00:00 2001 From: atlas Date: Sat, 30 May 2026 11:11:32 +0200 Subject: [PATCH 5/6] nix/docs: add HTML output for the options reference (mara/internal-requests#8) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mara on mara/internal-requests#8: > nix/docs.nix currently only emits CommonMark via doc.optionsCommonMark; > add HTML output alongside. Renders host + agent option pages as standalone HTML using cmark-gfm (stock nixpkgs, no pandoc). Each page is wrapped in a minimal inline-CSS template — no external stylesheets, no second HTTP fetch. New bundle layout (consumed by nginx at `hyperhive.darkest.space/options/`): index.html — landing page with cross-links + regenerate snippet host.html — services.hive-c0re.* / hyperhive.{domain,forge,matrix}.* agent.html — hyperhive.{model,allowedRecipients,extraMcpServers,…} host.md — same content, CommonMark source-of-truth agent.md — same content, CommonMark source-of-truth All asset paths inside the rendered HTML are relative (`./host.html` etc.) per mara's spec — the bundle mounts at any URL prefix without rebuild. Forge source links from `transformOptions` are preserved as proper `` (verified: `forge.darkest.space/.../nix/...`). `packages..docs` now emits HTML primarily; `docs-host` and `docs-agent` outputs flip from .md to .html (the .md content is still in the `docs` bundle for callers that want the source shape). The native nixos-render-docs `options html` subcommand doesn't exist (only `manpage` / `commonmark` / `asciidoc`). The `manual html` path exists but needs a full manual structure for what we're treating as two standalone pages — overkill. cmark-gfm over the existing CommonMark output is the leanest path. Verified: nix flake check --no-build nix build .#docs # bundled site (5 files) nix build .#docs-host # standalone HTML page nix build .#docs-agent # standalone HTML page --- nix/docs.nix | 246 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 202 insertions(+), 44 deletions(-) diff --git a/nix/docs.nix b/nix/docs.nix index c4427c94..8cadf7db 100644 --- a/nix/docs.nix +++ b/nix/docs.nix @@ -5,22 +5,28 @@ nixosSystem, }: # Options documentation for hyperhive's NixOS module surfaces. -# Closes #616. +# Closes #616. HTML output added per mara on internal-requests #8. # -# Two output trees: -# docs-host — operator-facing options exposed by the meta module -# (`hyperhive.nixosModules.default`). Covers -# `hyperhive.*` (domain, enable, c0re, forge, matrix) -# plus the deprecated `services.hive-c0re.*` alias. -# docs-agent — per-agent options declared by the shared harness -# module (`nix/templates/harness-base.nix`, transitively -# imported by `agent-base.nix` and `manager.nix`). -# Covers `hyperhive.*` (model, allowedRecipients, -# extraMcpServers, frontend, forge, matrix, gui, …). +# Three rendering layers: +# CommonMark — `pkgs.nixosOptionsDoc.optionsCommonMark`. Source of +# truth; kept as `.md` files in the bundle. +# HTML — `pkgs.cmark-gfm` over the CommonMark output, wrapped +# in a minimal inline-CSS template. Primary surface; the +# bundle's `index.html` / `host.html` / `agent.html` are +# what the operator's nginx serves from +# `hyperhive.darkest.space/options/`. # -# Both render as CommonMark via `pkgs.nixosOptionsDoc`. `docs` bundles -# them into one derivation alongside a small `README.md` index so it -# can be published verbatim. +# Three output trees consumed by `flake.nix`: +# docs-host — operator-facing host-module options +# (`services.hive-c0re.*`, `hyperhive.{domain,forge,matrix}.*`) +# docs-agent — per-agent harness options +# (`hyperhive.{model,allowedRecipients,extraMcpServers,…}`) +# docs — bundled static site (index + host + agent, .html + .md) +# +# All asset paths inside the rendered HTML are relative (e.g. +# `./host.html`) so the bundle can be mounted at any URL prefix +# without rewriting; styles are inline so there's no second-fetch +# request for the operator's browser. let # Evaluate the host module under a stub NixOS system. Stubs satisfy # the few hard-required options (filesystems, stateVersion) without @@ -121,9 +127,11 @@ let inherit transformOptions; }; - mkPage = + # Plain-markdown page (with a short header). Source of truth; the + # HTML version is rendered from this. + mkMarkdownPage = name: title: doc: - pkgs.runCommand "hyperhive-${name}-options.md" { } '' + pkgs.runCommand "hyperhive-${name}.md" { } '' { echo "# ${title}" echo @@ -134,38 +142,188 @@ let cat ${doc.optionsCommonMark} } > $out ''; + + # Single self-contained stylesheet. Inlined into every page so the + # bundle doesn't depend on a second HTTP fetch — keeps the + # `/options/` mount trivial for nginx (no MIME guessing for separate + # .css files, no cache-busting needed when this updates). + styleCSS = '' + :root { + color-scheme: light dark; + --fg: #1a1a1a; + --bg: #fafafa; + --muted: #6b6b6b; + --accent: #5e548e; + --code-bg: rgba(94, 84, 142, 0.08); + --rule: rgba(94, 84, 142, 0.2); + } + @media (prefers-color-scheme: dark) { + :root { + --fg: #e6e6e6; + --bg: #0d0d0d; + --muted: #9b9b9b; + --accent: #c4a7e7; + --code-bg: rgba(196, 167, 231, 0.08); + --rule: rgba(196, 167, 231, 0.2); + } + } + * { box-sizing: border-box; } + body { + font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; + max-width: 56rem; + margin: 0 auto; + padding: 2rem 1.25rem 4rem; + color: var(--fg); + background: var(--bg); + line-height: 1.55; + } + nav { + margin-bottom: 2rem; + padding-bottom: 1rem; + border-bottom: 1px solid var(--rule); + font-size: 0.9rem; + color: var(--muted); + } + nav a { color: var(--accent); text-decoration: none; } + nav a:hover { text-decoration: underline; } + nav a + a { margin-left: 0.5rem; } + nav a + a::before { content: "· "; color: var(--muted); margin-right: 0.25rem; } + h1, h2, h3 { color: var(--accent); line-height: 1.25; } + h1 { font-size: 1.75rem; margin-top: 0; } + h2 { + font-size: 1.15rem; + margin-top: 2.5rem; + padding-top: 0.5rem; + border-top: 1px solid var(--rule); + font-family: ui-monospace, "SFMono-Regular", Menlo, monospace; + } + h3 { font-size: 1rem; } + p { margin: 0.75rem 0; } + code { + background: var(--code-bg); + padding: 0.1em 0.35em; + border-radius: 0.25em; + font-family: ui-monospace, "SFMono-Regular", Menlo, monospace; + font-size: 0.9em; + } + pre { + background: var(--code-bg); + padding: 0.85rem 1rem; + border-left: 2px solid var(--accent); + border-radius: 0 0.25rem 0.25rem 0; + overflow-x: auto; + line-height: 1.4; + } + pre code { background: transparent; padding: 0; } + a { color: var(--accent); } + em { color: var(--muted); font-style: normal; font-size: 0.9em; } + footer { + margin-top: 4rem; + padding-top: 1rem; + border-top: 1px solid var(--rule); + font-size: 0.85rem; + color: var(--muted); + } + ''; + + # HTML page: CommonMark → cmark-gfm → minimal template with inline + # CSS + relative-only links. cmark-gfm rather than plain cmark so + # any future tables / autolinks Just Work without revisiting. + mkHtmlPage = + name: title: doc: + pkgs.runCommand "hyperhive-${name}.html" { nativeBuildInputs = [ pkgs.cmark-gfm ]; } '' + { + echo '' + echo '' + echo '' + echo '' + echo '' + echo '${title}' + echo '' + echo '' + echo '' + echo '' + echo '
' + echo '

${title}

' + echo '

Auto-generated from the hyperhive flake. Source: nix/docs.nix.

' + cmark-gfm ${doc.optionsCommonMark} + echo '
' + echo '
' + echo '

hyperhive · source

' + echo '
' + echo '' + echo '' + } > $out + ''; + + # Landing page — same template shape as the option pages but + # hand-authored content (short intro + cross-links). Kept tight; the + # detail lives on the two option pages. + indexHTML = pkgs.runCommand "hyperhive-docs-index.html" { } '' + { + echo '' + echo '' + echo '' + echo '' + echo '' + echo 'hyperhive — nix options reference' + echo '' + echo '' + echo '' + echo '' + echo '
' + echo '

hyperhive — nix options reference

' + echo '

Auto-generated from the hyperhive flake. Two reading paths:

' + echo '

host options

' + echo '

Options exposed by hyperhive.nixosModules.default to operator host configurations: services.hive-c0re.*, hyperhive.domain, hyperhive.forge.*, hyperhive.matrix.*.

' + echo '

agent options

' + echo '

Per-agent options declared in nix/templates/harness-base.nix and visible from every agent.nix: hyperhive.model, hyperhive.allowedRecipients, hyperhive.extraMcpServers, hyperhive.frontend.*, hyperhive.forge.*, hyperhive.matrix.*, hyperhive.gui.*.

' + echo '

Regenerate

' + echo '
nix build .#docs          # bundled static site (index + host + agent)'
+      echo 'nix build .#docs-host     # host page only (HTML)'
+      echo 'nix build .#docs-agent    # agent page only (HTML)
' + echo '

The bundle also ships .md versions of each page (host.md, agent.md) — same content, source-of-truth shape — alongside the HTML.

' + echo '
' + echo '
' + echo '

hyperhive · source

' + echo '
' + echo '' + echo '' + } > $out + ''; + + hostHTML = mkHtmlPage "docs-host" "hyperhive — host options" hostDoc; + agentHTML = mkHtmlPage "docs-agent" "hyperhive — per-agent options" agentDoc; + hostMD = mkMarkdownPage "docs-host" "hyperhive — host options" hostDoc; + agentMD = mkMarkdownPage "docs-agent" "hyperhive — per-agent options" agentDoc; in { - host = mkPage "docs-host" "hyperhive — host options" hostDoc; - agent = mkPage "docs-agent" "hyperhive — per-agent options" agentDoc; + # Individual page outputs (HTML is the primary surface; the .md + # source is one `nix build` step away if needed). + host = hostHTML; + agent = agentHTML; - # Bundle both pages plus a thin index so the whole thing can be - # published as a single static tree. + # Bundled static site for nginx to serve at `/options/`. Asset paths + # are all relative, no root-absolute references, so the prefix can + # change without rebuild. bundle = pkgs.runCommand "hyperhive-options-docs" { } '' mkdir -p $out - cp ${mkPage "docs-host" "hyperhive — host options" hostDoc} $out/host.md - cp ${mkPage "docs-agent" "hyperhive — per-agent options" agentDoc} $out/agent.md - cat > $out/README.md <<'EOF' - # hyperhive — nix options reference - - auto-generated from the hyperhive flake. - - - [host.md](./host.md) — options exposed by `hyperhive.nixosModules.default` - to operator host configurations (`services.hive-c0re.*`, - `hyperhive.domain`, `hyperhive.forge.*`, `hyperhive.matrix.*`). - - [agent.md](./agent.md) — per-agent options declared in - `nix/templates/harness-base.nix` and visible from every `agent.nix` - (`hyperhive.model`, `hyperhive.allowedRecipients`, - `hyperhive.extraMcpServers`, `hyperhive.frontend.*`, - `hyperhive.forge.*`, `hyperhive.matrix.*`, `hyperhive.gui.*`). - - regenerate: - - ```sh - nix build .#docs # bundled tree - nix build .#docs-host # host page only - nix build .#docs-agent # agent page only - ``` - EOF + cp ${indexHTML} $out/index.html + cp ${hostHTML} $out/host.html + cp ${agentHTML} $out/agent.html + cp ${hostMD} $out/host.md + cp ${agentMD} $out/agent.md ''; } From 983a4fa229925e84802d11e3ded9822e5a98e76e Mon Sep 17 00:00:00 2001 From: atlas Date: Sat, 30 May 2026 11:15:44 +0200 Subject: [PATCH 6/6] =?UTF-8?q?nix/docs:=20demote=20index=20sections=20h2?= =?UTF-8?q?=20=E2=86=92=20h3=20(argus=20#622=20nit)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit argus on #622 comment 7419 🟡: > monospace h2 on index — the CSS styles `h2` with `ui-monospace` > font (intended for option-name headings in the per-option pages). > on the index page, "host options" / "agent options" / "Regenerate" > headings also get monospace treatment. cosmetic; reads as > intentional if not, easy to scope. Index page now uses h3 for section headers, leaves h2 free for the auto-generated option-name headings on host.html / agent.html where the monospace styling is appropriate. (Other argus nit — stale namespace text in indexHTML's option listings — will be addressed when this branch rebases post-#615 merge, same as #620.) --- nix/docs.nix | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nix/docs.nix b/nix/docs.nix index 8cadf7db..ed6655e4 100644 --- a/nix/docs.nix +++ b/nix/docs.nix @@ -286,11 +286,11 @@ let echo '
' echo '

hyperhive — nix options reference

' echo '

Auto-generated from the hyperhive flake. Two reading paths:

' - echo '

host options

' + echo '

host options

' echo '

Options exposed by hyperhive.nixosModules.default to operator host configurations: services.hive-c0re.*, hyperhive.domain, hyperhive.forge.*, hyperhive.matrix.*.

' - echo '

agent options

' + echo '

agent options

' echo '

Per-agent options declared in nix/templates/harness-base.nix and visible from every agent.nix: hyperhive.model, hyperhive.allowedRecipients, hyperhive.extraMcpServers, hyperhive.frontend.*, hyperhive.forge.*, hyperhive.matrix.*, hyperhive.gui.*.

' - echo '

Regenerate

' + echo '

Regenerate

' echo '
nix build .#docs          # bundled static site (index + host + agent)'
       echo 'nix build .#docs-host     # host page only (HTML)'
       echo 'nix build .#docs-agent    # agent page only (HTML)
'