nix/docs: add HTML output for the options reference (mara/internal-requests#8)

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 `<a href>` (verified: `forge.darkest.space/.../nix/...`).

`packages.<system>.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
This commit is contained in:
atlas 2026-05-30 11:11:32 +02:00 committed by Mara
commit 0cc27fbe32

View file

@ -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 '<!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.nix">nix/docs.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 '<h2><a href="./host.html">host options</a></h2>'
echo '<p>Options exposed by <code>hyperhive.nixosModules.default</code> to operator host configurations: <code>services.hive-c0re.*</code>, <code>hyperhive.domain</code>, <code>hyperhive.forge.*</code>, <code>hyperhive.matrix.*</code>.</p>'
echo '<h2><a href="./agent.html">agent options</a></h2>'
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 '<h2>Regenerate</h2>'
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
{
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
'';
}