{ pkgs, lib, self, nixosSystem, }: # Options documentation for hyperhive's NixOS module surfaces. # Closes #616. HTML output added per mara on internal-requests #8. # # 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/`. # # 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 # 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 pick = path: if lib.hasAttrByPath path options 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; }; # 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}.md" { } '' { echo "# ${title}" echo echo "" echo 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 '' 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 '' 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 { # Individual page outputs (HTML is the primary surface; the .md # source is one `nix build` step away if needed). host = hostHTML; agent = agentHTML; # 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 ${indexHTML} $out/index.html cp ${hostHTML} $out/host.html cp ${agentHTML} $out/agent.html cp ${hostMD} $out/host.md cp ${agentMD} $out/agent.md ''; }