hyperhive/nix/docs/default.nix
atlas 32661cf806 nix/docs: extract CSS into sibling file (#625)
Per mara on #622 comment 7442:
> follow up moving scripts and css and stuff out of the nix file.
> can live in the same dir.

Layout:

    nix/docs/
      default.nix   ← what was nix/docs.nix
      style.css     ← extracted from inline `styleCSS = '' ... ''`

`builtins.readFile ./style.css` loads the stylesheet at evaluation
time, so the rendered HTML stays byte-identical (CSS inlined into
each page's `<style>` block — verified). Future client-side scripts
can land at `nix/docs/script.js` with the same `builtins.readFile`
pattern.

Bonus: the docs.nix stub NixOS eval was still force-disabling
`hyperhive.{forge,matrix}.enable` on the pre-#615 namespace; updated
to `services.hyperhive.{forge,matrix}.enable` so `nix flake check`
passes against current main. (Same fix lives on PRs #619 + #620;
whichever lands first wins, the others rebase to a no-op.)

`flake.nix` references updated: `./nix/docs.nix` → `./nix/docs`.

Verified:
- `nix flake check --no-build` passes clean
- `nix build .#docs` produces 5-file bundle identical to pre-PR shape
- inline CSS still appears 3× per HTML page (one per index/host/agent)
2026-05-30 12:51:44 +02:00

254 lines
9.7 KiB
Nix

{
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.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;
}
)
];
};
# 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 "<!-- Auto-generated from the hyperhive flake."
echo " Source: nix/docs/default.nix · regenerate with"
echo " \`nix build .#${name}\` -->"
echo
cat ${doc.optionsCommonMark}
} > $out
'';
# Inline-stylesheet, loaded as plain text from `./style.css` so it's
# editable with normal CSS tooling (#625). 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 = builtins.readFile ./style.css;
# 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 '<!doctype html>'
echo '<html lang="en">'
echo '<head>'
echo '<meta charset="utf-8">'
echo '<meta name="viewport" content="width=device-width, initial-scale=1">'
echo '<title>${title}</title>'
echo '<style>'
cat ${pkgs.writeText "hyperhive-docs-style.css" styleCSS}
echo '</style>'
echo '</head>'
echo '<body>'
echo '<nav>'
echo '<a href="./index.html">index</a>'
echo '<a href="./host.html">host options</a>'
echo '<a href="./agent.html">agent options</a>'
echo '</nav>'
echo '<main>'
echo '<h1>${title}</h1>'
echo '<p><em>Auto-generated from the hyperhive flake. Source: <a href="${forgeRoot}/nix/docs/default.nix">nix/docs/default.nix</a>.</em></p>'
cmark-gfm ${doc.optionsCommonMark}
echo '</main>'
echo '<footer>'
echo '<p>hyperhive · <a href="${forgeRoot}">source</a></p>'
echo '</footer>'
echo '</body>'
echo '</html>'
} > $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 '<!doctype html>'
echo '<html lang="en">'
echo '<head>'
echo '<meta charset="utf-8">'
echo '<meta name="viewport" content="width=device-width, initial-scale=1">'
echo '<title>hyperhive nix options reference</title>'
echo '<style>'
cat ${pkgs.writeText "hyperhive-docs-style.css" styleCSS}
echo '</style>'
echo '</head>'
echo '<body>'
echo '<nav>'
echo '<a href="./index.html">index</a>'
echo '<a href="./host.html">host options</a>'
echo '<a href="./agent.html">agent options</a>'
echo '</nav>'
echo '<main>'
echo '<h1>hyperhive nix options reference</h1>'
echo '<p>Auto-generated from the <a href="${forgeRoot}">hyperhive flake</a>. Two reading paths:</p>'
echo '<h3><a href="./host.html">host options</a></h3>'
echo '<p>Options exposed by <code>hyperhive.nixosModules.default</code> to operator host configurations: <code>services.hyperhive.{enable,domain,c0re,forge,matrix,gateway}.*</code>.</p>'
echo '<h3><a href="./agent.html">agent options</a></h3>'
echo '<p>Per-agent options declared in <code>nix/templates/harness-base.nix</code> and visible from every <code>agent.nix</code>: <code>hyperhive.model</code>, <code>hyperhive.allowedRecipients</code>, <code>hyperhive.extraMcpServers</code>, <code>hyperhive.frontend.*</code>, <code>hyperhive.forge.*</code>, <code>hyperhive.matrix.*</code>, <code>hyperhive.gui.*</code>.</p>'
echo '<h3>Regenerate</h3>'
echo '<pre><code>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)</code></pre>'
echo '<p>The bundle also ships <code>.md</code> versions of each page (<code>host.md</code>, <code>agent.md</code>) same content, source-of-truth shape alongside the HTML.</p>'
echo '</main>'
echo '<footer>'
echo '<p>hyperhive · <a href="${forgeRoot}">source</a></p>'
echo '</footer>'
echo '</body>'
echo '</html>'
} > $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
'';
}