hyperhive/nix/docs.nix
atlas 6a64770c79 nix: add options docs outputs for host + agent surfaces (#616)
Auto-generate CommonMark references for hyperhive's two NixOS module
surfaces via `pkgs.nixosOptionsDoc`:

- `packages.<system>.docs-host` — operator-facing options exposed by
  `hyperhive.nixosModules.default` (`services.hive-c0re.*`,
  `hyperhive.domain`, `hyperhive.forge.*`, `hyperhive.matrix.*`).
- `packages.<system>.docs-agent` — per-agent options declared in
  `nix/templates/harness-base.nix` (model, allowedRecipients,
  extraMcpServers, frontend, forge, matrix, gui, …).
- `packages.<system>.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.<system>.docs` so CI fails fast on eval breakage.
2026-05-29 23:42:24 +02:00

177 lines
5.8 KiB
Nix

{
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 "<!-- Auto-generated from the hyperhive flake."
echo " Source: nix/docs.nix · regenerate with"
echo " \`nix build .#${name}\` -->"
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
'';
}