From 4f8bb6ded23d0966fa5be11b6092bdc1b3c63086 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?m=C3=BCde?= Date: Mon, 13 Jul 2026 20:55:43 +0200 Subject: [PATCH] refactor: slim flake.nix into nix/ entry files --- flake.nix | 450 +++----------------------- nix/checks.nix | 87 +++++ nix/devshell.nix | 18 ++ nix/{ => packages}/assets.nix | 10 +- nix/packages/default.nix | 201 ++++++++++++ nix/{ => packages}/frontend.nix | 2 +- nix/{ => packages}/reference-docs.nix | 2 +- nix/rust.nix | 56 ++++ nix/sources.nix | 49 +++ nix/treefmt.nix | 11 + 10 files changed, 470 insertions(+), 416 deletions(-) create mode 100644 nix/checks.nix create mode 100644 nix/devshell.nix rename nix/{ => packages}/assets.nix (89%) create mode 100644 nix/packages/default.nix rename nix/{ => packages}/frontend.nix (99%) rename nix/{ => packages}/reference-docs.nix (98%) create mode 100644 nix/rust.nix create mode 100644 nix/sources.nix create mode 100644 nix/treefmt.nix diff --git a/flake.nix b/flake.nix index 317a965b..95cd1f0d 100644 --- a/flake.nix +++ b/flake.nix @@ -3,9 +3,9 @@ inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05"; - # Crane (replaces the former naersk-based build). Stateless — no nixpkgs input to - # follow; `crane.mkLib pkgs` returns the lib at whatever pkgs we - # pass it (we use the project's pinned nixpkgs). + # Crane is stateless — no nixpkgs input to follow; `crane.mkLib + # pkgs` returns the lib at whatever pkgs we pass it (we use the + # project's pinned nixpkgs). crane.url = "github:ipetkov/crane"; treefmt-nix = { url = "github:numtide/treefmt-nix"; @@ -13,6 +13,14 @@ }; }; + # Thin entry point — the real logic lives under nix/: + # nix/sources.nix filtered source views (meta-flake + docs inputs) + # nix/rust.nix shared crane wiring (cleanSrc, cargoArtifacts) + # nix/packages/ every package output + # nix/checks.nix flake checks + # nix/devshell.nix dev shell + # nix/treefmt.nix formatter config + # nix/modules/, nix/templates/ the NixOS module + container trees outputs = inputs@{ self, @@ -26,61 +34,7 @@ "aarch64-linux" "x86_64-linux" ]; - # Explicit per-consumer source filter for the hyperhive flake path - # handed to hive-c0re (`hyperhiveFlake`, below). A filtered view of - # this repo that drops files no nix or cargo derivation reads — - # shell helper scripts and root-level markdown — so editing them - # does NOT change the store path and therefore does NOT force a - # rebuild of every agent container. - # - # Companion to the rust `cleanSrc` (crane's cargo-source filter): - # each real source is a named, explicitly-filtered derivation used - # as `src`, rather than an inline filter at the use site. - # - # Dropped: scripts/, docs/, root-level *.md (README/CLAUDE/TODO/…). - # Kept (build needs them): .nix, .rs, Cargo.*, branding/, - # frontend/, prompts/, flake.lock. docs/ is deliberately dropped: - # it is shipped to agent containers as its OWN narrow - # meta-flake input (`hyperhiveDocsSource`, below), threaded through - # hive-c0re → the meta flake → `hyperhive.docs.source`. Keeping docs/ - # out of THIS source means a doc edit only re-hashes the docs input - # (a cheap re-link), not the whole flake source (which would rebuild - # every agent container). The old `nix/reference-docs.nix` build from - # `../docs` stays only for standalone `nix build .#reference-docs` - # from a full checkout — the meta path never evaluates it. - hyperhiveFlakeSource = lib.cleanSourceWith { - name = "hyperhive-flake-source"; - src = ./.; - filter = - path: type: - let - # Repo-relative path (strip the absolute source-dir prefix). - rel = lib.removePrefix (toString ./. + "/") (toString path); - in - !(lib.hasPrefix "scripts/" rel || rel == "scripts") - && !(lib.hasPrefix "docs/" rel || rel == "docs") - && !(type == "regular" && !lib.hasInfix "/" rel && lib.hasSuffix ".md" rel); - }; - # The repo `docs/` tree as a standalone narrow source. Its - # store path moves ONLY on doc edits, decoupled from - # `hyperhiveFlakeSource`. hive-c0re threads this to the meta flake as - # the `hyperhive-docs` input (same pattern as `hyperhiveFlake`); the - # harness resolves `$HIVE_DOCS_DIR` from it via `hyperhive.docs.source`. - # Evaluated here on the host where docs/ exists — it cannot be derived - # from inside the docs-stripped `hyperhiveFlakeSource`. - hyperhiveDocsSource = lib.cleanSourceWith { - name = "hyperhive-docs-source"; - src = ./docs; - }; - treefmt-config = { - projectRootFile = "flake.nix"; - programs = { - keep-sorted.enable = true; - nixfmt.enable = true; - rustfmt.enable = true; - taplo.enable = true; - }; - }; + sources = import ./nix/sources.nix { inherit lib; }; forAllSystems = f: lib.genAttrs systems ( @@ -88,70 +42,9 @@ f rec { inherit system; pkgs = nixpkgs.legacyPackages.${system}; - treefmt-eval = treefmt-nix.lib.evalModule pkgs treefmt-config; + treefmt-eval = treefmt-nix.lib.evalModule pkgs (import ./nix/treefmt.nix); craneLib = crane.mkLib pkgs; - # Narrowed source tree the rust derivations consume. - # `cleanCargoSource` is crane's standard "everything cargo - # cares about" filter (Cargo.toml/Cargo.lock + *.rs). All - # non-rust runtime assets — branding + the claude system - # prompt template + claude-settings.json — live in the - # separate `hyperhive-assets` derivation and are - # loaded by the binaries at runtime from `$HIVE_ASSETS_DIR`. - # The unit tests in `hive-ag3nt::prompt` read the same - # `prompts/system.md` directly from the workspace tree at - # *test* runtime (via `env!("CARGO_MANIFEST_DIR")` — a - # compile-time string, no file open at compile), so the - # prompt template doesn't have to be in this fileset to - # keep `cargo test` honest. Net effect: tweaks to any - # non-`*.rs` / non-`Cargo.*` file (README, branding, - # nix modules, frontend tree, OR `hive-ag3nt/prompts/*`) - # do NOT bust this src hash, so the rust derivations - # stay fully cached. - cleanSrc = craneLib.cleanCargoSource ./.; - # Build the workspace's dependency tree once, cached as - # its own derivation. `buildPackage` and `cargoClippy` - # both reuse this via `inherit cargoArtifacts;` so a - # workspace-only edit doesn't rebuild deps. All three - # derivations consume the same `cleanSrc` so the input - # hash stays consistent across the chain. - cargoArtifacts = craneLib.buildDepsOnly { - src = cleanSrc; - # Workspace Cargo.toml is virtual (no `[package].name`), - # so crane can't auto-derive a name. Spell it out - # explicitly here and below — keeps the derivation name - # stable across crane bumps + silences the placeholder - # warning. Same `pname` everywhere so dep + workspace + - # clippy share a clean naming family. - pname = "hyperhive-workspace"; - version = "0.1.0"; - inherit nativeBuildInputs; - }; - # Shared between buildDepsOnly + buildPackage + cargoClippy - # so the three derivations see the same toolchain shape. - # git: naersk used to auto-include it; crane is more - # minimal, so we add it explicitly so hive-c0re's - # `lifecycle::tests::setup_proposed_*` (which shell out to - # `git init` + commit) pass under `cargo test` in the - # sandbox. - # `librsvg` used to live here for `hive-c0re/build.rs`'s - # rsvg-convert call — that whole codepath moved into the - # `hyperhive-assets` derivation, so the rust - # derivation no longer needs the dependency. - # `sqlite` required by matrix-sdk's `sqlite` feature - # (`hive-matrix-mcp` workspace member) — the - # matrix-sdk-sqlite + rusqlite stack links against system - # libsqlite3 by default. - # `cmake` builds `aws-lc-sys` (BoringSSL) from source — pulled in by - # the `rustls` (aws-lc-rs) crypto provider under the OTLP/reqwest - # stack in `hive-metric`. Without it the crane deps build fails on - # `cargo-package-reqwest-0.13.4`. Kept in the shared inputs since - # more metric/crypto deps are expected to land in the workspace. - nativeBuildInputs = [ - pkgs.git - pkgs.sqlite - pkgs.pkg-config - pkgs.cmake - ]; + rust = import ./nix/rust.nix { inherit pkgs craneLib; }; } ); in @@ -160,195 +53,17 @@ { pkgs, craneLib, - cleanSrc, - cargoArtifacts, - nativeBuildInputs, + rust, ... }: - let - docsAttrs = import ./nix/docs { - inherit pkgs self; - inherit (nixpkgs) lib; - inherit (nixpkgs.lib) nixosSystem; - }; - # One package per daemon/harness/MCP-server binary — matches the - # `hivectl` / `hive-forge` split below rather than grouping them - # into a single derivation. Consumers (agent containers, host - # module, `nix profile install`) depend on exactly the binaries - # they need instead of an all-or-nothing bundle. All share - # `cargoArtifacts` (built once via `buildDepsOnly` above), so - # splitting doesn't cost extra rustc invocations — each call - # below just asks crane to build+link its one requested `--bin`. - # - # Tests are kept in the separate `checks.cargo-test` derivation - # (carries the hyperhive-assets build input for the prompt-template - # assertions in hive-ag3nt::prompt::tests). Keeping them out of the - # binary derivations means a prompt edit doesn't bust the cargo cache. - mkDaemonBin = - bin: description: - craneLib.buildPackage { - src = cleanSrc; - inherit cargoArtifacts nativeBuildInputs; - cargoExtraArgs = "--bin ${bin}"; - pname = bin; - version = "0.1.0"; - meta.description = description; - doCheck = false; - }; - hiveC0rePkg = mkDaemonBin "hive-c0re" "hyperhive host coordinator daemon"; - hivePrivPkg = mkDaemonBin "hive-priv" "hyperhive privileged root helper"; - hiveAgentPkg = mkDaemonBin "hive-agent" "hyperhive in-container agent harness serve loop"; - hiveAgentMcpPkg = mkDaemonBin "hive-agent-mcp" "hyperhive agent-surface MCP server"; - hiveAgentWakePkg = mkDaemonBin "hive-agent-wake" "hyperhive external wake CLI — push a message into an agent's own inbox"; - hiveBashDaemonPkg = mkDaemonBin "hive-bash-daemon" "hyperhive per-agent bash-task runner daemon"; - hiveBashMcpPkg = mkDaemonBin "hive-bash-mcp" "hyperhive bash-task MCP bridge"; - hiveMatrixDaemonPkg = mkDaemonBin "hive-matrix-daemon" "hyperhive per-agent matrix-sdk daemon"; - hiveMatrixMcpPkg = mkDaemonBin "hive-matrix-mcp" "hyperhive matrix MCP bridge"; - hiveMetricPkg = mkDaemonBin "hive-metric" "hyperhive agent-emitted custom metrics CLI"; - # Operator CLI — ships `hivectl` (with shell completions and the - # `wg` wrapper) without the daemon binaries. Suitable for - # `nix profile install .#hivectl` / `environment.systemPackages - # = [ inputs.hyperhive.packages.${system}.hivectl ]` when the - # operator only wants the admin CLI. Shares `cargoArtifacts` with - # `daemonBins` so there is no double-rustc cost. - hivectlPkg = craneLib.buildPackage { - src = cleanSrc; - inherit cargoArtifacts; - cargoExtraArgs = "--bin hivectl"; - pname = "hivectl"; - version = "0.1.0"; - meta.description = "hyperhive operator CLI"; - doCheck = false; - # `installShellFiles` + `makeWrapper` scoped to this derivation - # only — daemon bins don't need them. - nativeBuildInputs = nativeBuildInputs ++ [ - pkgs.installShellFiles - pkgs.makeWrapper - ]; - # Ship shell completions (the binary's own `completions ` - # verb is the single source of truth, so they never drift from - # the actual verbs). Wrap with wireguard-tools so `hivectl wg` - # subcommands work before `swarm.wireguard.enable` is set (wg - # init is the very first setup step). Completion generation runs - # before wrapProgram since wrapProgram renames the real binary. - postInstall = '' - installShellCompletion --cmd hivectl \ - --bash <("$out/bin/hivectl" completions bash) \ - --zsh <("$out/bin/hivectl" completions zsh) \ - --fish <("$out/bin/hivectl" completions fish) - wrapProgram "$out/bin/hivectl" \ - --prefix PATH : ${pkgs.wireguard-tools}/bin - ''; - }; - # Forgejo CLI — ships `hive-forge` without the rest of the - # workspace. Useful for operator workstations / CI environments - # that only need forge access. Shares `cargoArtifacts` with - # `daemonBins`. - hiveForgePkg = craneLib.buildPackage { - src = cleanSrc; - inherit cargoArtifacts nativeBuildInputs; - cargoExtraArgs = "--bin hive-forge"; - pname = "hive-forge"; - version = "0.1.0"; - meta.description = "hyperhive Forgejo CLI"; - doCheck = false; - }; - in - { - # All workspace binaries in one derivation via symlinkJoin. - # Each binary is compiled exactly once (one rustc per bin, all - # sharing `cargoArtifacts`); symlinkJoin assembles the outputs - # without any additional compilation. The NixOS module's - # `pkgs.hyperhive` (= this) and `nix build .#` both land here. - default = pkgs.symlinkJoin { - name = "hyperhive"; - paths = [ - hiveC0rePkg - hivePrivPkg - hiveAgentPkg - hiveAgentMcpPkg - hiveAgentWakePkg - hiveBashDaemonPkg - hiveBashMcpPkg - hiveMatrixDaemonPkg - hiveMatrixMcpPkg - hiveMetricPkg - hivectlPkg - hiveForgePkg - ]; - }; - # Per-bin split packages. Agent containers depend on the - # individual bins they actually exec/PATH-need (see - # `harness-base.nix`) instead of the `default` bundle — that - # keeps `hivectl` (dials the *host* admin socket, unreachable - # from inside a container, drags in `wireguard-tools`) and a - # redundant `hive-forge` copy (already on agent PATH via - # `hive-forge-tools.nix`) out of every agent's closure. - hivectl = hivectlPkg; - hive-forge = hiveForgePkg; - hive-c0re = hiveC0rePkg; - hive-priv = hivePrivPkg; - hive-agent = hiveAgentPkg; - hive-agent-mcp = hiveAgentMcpPkg; - hive-agent-wake = hiveAgentWakePkg; - hive-bash-daemon = hiveBashDaemonPkg; - hive-bash-mcp = hiveBashMcpPkg; - hive-matrix-daemon = hiveMatrixDaemonPkg; - hive-matrix-mcp = hiveMatrixMcpPkg; - hive-metric = hiveMetricPkg; - # Bundled browser assets — see ./nix/frontend.nix. Output is - # $out/{dashboard,agent}/ which the Rust binaries serve via - # tower_http::ServeDir. - frontend = pkgs.callPackage ./nix/frontend.nix { - branding-svg = ./branding/hyperhive.svg; - }; - # Static runtime assets the rust binaries read via - # `hive_sh4re::assets::*`: branding/* + prompts/*, - # plus the rendered agent-configs.png. Split out of the - # rust derivation so a tweak to e.g. system.md doesn't bust - # the cargo cache. Build input of the `cargo-test` check but - # NOT of `packages.default`, so the binary derivation stays - # cached when a prompt edit ripples through. - assets = pkgs.callPackage ./nix/assets.nix { }; - # The repo docs/ markdown tree as a standalone derivation — - # agents read it in-container (added as a claude additional - # directory) and the website repo reuses it as a flake input, - # neither of which needs the branding/prompt assets. See - # nix/reference-docs.nix. (`docs` above is the auto-generated - # nix-options reference, a different artifact.) - reference-docs = pkgs.callPackage ./nix/reference-docs.nix { }; - # XDG icon set + .desktop entries for hyperhive processes. - # Narrow input: only the branding SVG, so unrelated source changes - # don't bust this derivation's cache. - xdg-icons = pkgs.callPackage ./nix/packages/hive-xdg-icons.nix { - hyperhiveSvg = ./branding/hyperhive.svg; - }; - # Pre-built per-container system closures. Exposed as packages - # so operators can `nix build .#agent-base-toplevel` (or wire - # them into their host system closure via the - # `preBuildAgentTemplates` option on the hive-c0re module — - # see nix/modules/hive-c0re.nix). Speeds up the first agent - # spawn dramatically because the heavy lifting (nixpkgs + - # claude-code + hive-ag3nt binary) is already in the store - # when the meta evaluator goes to build the container. - # - # nixosConfigurations are pinned to x86_64-linux (nixos- - # containers only run native arch), so these toplevels are - # only useful on an x86_64-linux host — flake check across - # systems still tolerates evaluating them on aarch64 because - # they're plain derivations, but `nix build` from a non-x86 - # host would only succeed via a remote x86 builder. - agent-base-toplevel = self.nixosConfigurations.agent-base.config.system.build.toplevel; - ruth-toplevel = self.nixosConfigurations.ruth.config.system.build.toplevel; - - # Auto-generated nix options reference for hyperhive. - # `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; + import ./nix/packages { + inherit + pkgs + craneLib + rust + self + nixpkgs + ; } ); @@ -358,8 +73,7 @@ # Per-binary daemon/harness/MCP-server packages, exposed via the # overlay so container nix evaluations can depend on exactly the # bin(s) they need instead of the full `hyperhive` bundle — see - # `packages..hive-*` above and their use in - # `harness-base.nix`. + # `packages..hive-*` and their use in `harness-base.nix`. hive-c0re = self.packages.${prev.stdenv.hostPlatform.system}.hive-c0re; hive-priv = self.packages.${prev.stdenv.hostPlatform.system}.hive-priv; hive-agent = self.packages.${prev.stdenv.hostPlatform.system}.hive-agent; @@ -370,8 +84,8 @@ hive-matrix-daemon = self.packages.${prev.stdenv.hostPlatform.system}.hive-matrix-daemon; hive-matrix-mcp = self.packages.${prev.stdenv.hostPlatform.system}.hive-matrix-mcp; hive-metric = self.packages.${prev.stdenv.hostPlatform.system}.hive-metric; - # Bundled frontend dist (see ./nix/frontend.nix). Output is - # $out/{dashboard,agent}/; consumers pick the surface they + # Bundled frontend dist (see nix/packages/frontend.nix). Output + # is $out/{dashboard,agent}/; consumers pick the surface they # need. Exposed via the overlay so containers' nix evaluations # can reach it as `pkgs.hyperhive-frontend` once the overlay # is applied (manager + agent containers both apply it via @@ -381,8 +95,8 @@ # so the harness module can wire $HIVE_ASSETS_DIR straight # to `${pkgs.hyperhive-assets}/share/hyperhive`. hyperhive-assets = self.packages.${prev.stdenv.hostPlatform.system}.assets; - # Standalone docs/ tree (see nix/reference-docs.nix). Exposed - # via the overlay so the harness module can build the + # Standalone docs/ tree (see nix/packages/reference-docs.nix). + # Exposed via the overlay so the harness module can build the # in-container agent docs dir from it. hyperhive-docs = self.packages.${prev.stdenv.hostPlatform.system}.reference-docs; }; @@ -401,10 +115,10 @@ hyperhiveFrontend = system: self.packages.${system}.frontend; hyperhiveAssets = system: self.packages.${system}.assets; hyperhiveXdgIcons = system: self.packages.${system}.xdg-icons; - hyperhiveFlake = "${hyperhiveFlakeSource}"; + hyperhiveFlake = "${sources.hyperhiveFlakeSource}"; # Narrow docs/ source, threaded as its own meta-flake input so # doc edits don't re-hash the whole flake source. - hyperhiveDocs = "${hyperhiveDocsSource}"; + hyperhiveDocs = "${sources.hyperhiveDocsSource}"; # Per-container toplevels — wired into `system.extraDependencies` # when `services.hyperhive.c0re.preBuildAgentTemplates` is on so the # host system closure pre-fetches the heavy build inputs. @@ -448,23 +162,7 @@ ruth = mkContainer self.nixosModules.ruth; }; - devShells = forAllSystems ( - { pkgs, ... }: - { - default = pkgs.mkShell { - packages = with pkgs; [ - cargo - clippy - librsvg # rsvg-convert — hive-c0re/build.rs invokes it - pkg-config - rust-analyzer - rustc - rustfmt - sqlite - ]; - }; - } - ); + devShells = forAllSystems ({ pkgs, rust, ... }: import ./nix/devshell.nix { inherit pkgs rust; }); formatter = forAllSystems ({ treefmt-eval, ... }: treefmt-eval.config.build.wrapper); @@ -474,84 +172,18 @@ system, treefmt-eval, craneLib, - cleanSrc, - cargoArtifacts, - nativeBuildInputs, + rust, ... }: - { - formatting = treefmt-eval.config.build.check self; - # Clippy as a check via crane's first-class `cargoClippy` - # builder. Reuses the shared `cargoArtifacts` (deps already - # built) and runs `cargo clippy --workspace --all-targets` - # directly — no `overrideAttrs` hack needed, because crane - # parses `cargoClippyExtraArgs` correctly (naersk's - # `mode = "clippy"` used to mangle the `--` separator, which - # is why the old wiring went through overrideAttrs). - # - # `-D warnings` makes the default/correctness/style lints a - # hard CI gate. `-A clippy::pedantic` then drops the pedantic - # group from that gate: pedantic is the "extra, opinionated" - # group the clippy team grows freely, so denying it means - # every toolchain bump that adds a new pedantic lint breaks CI - # with zero code changes. The `pedantic = warn` - # workspace lint (Cargo.toml) keeps it as advisory signal in - # local `cargo clippy` — it just no longer blocks the build. - # (`-A` rather than `-W` here: `-W clippy::pedantic` would - # re-enable the specific pedantic lints the workspace lints - # table allows, e.g. `must_use_candidate`.) - clippy = craneLib.cargoClippy { - src = cleanSrc; - inherit cargoArtifacts nativeBuildInputs; - pname = "hyperhive-workspace"; - version = "0.1.0"; - cargoClippyExtraArgs = "--workspace --all-targets -- -D warnings"; - }; - # `cargo test --workspace` lifted out of `buildPackage` so the - # `hyperhive-assets` dep (which `hive-ag3nt::prompt::tests` - # needs via `HIVE_ASSETS_DIR` to assert against the actual - # production prompt template) is scoped to this one check - # instead of bleeding into the binary derivation's input - # hash. Net: editing `hive-ag3nt/prompts/system.md` still - # rebuilds this test check (correct — the tests assert - # against its wording), but `packages.default` and the - # per-container toplevels stay fully cached. - cargo-test = craneLib.cargoTest { - src = cleanSrc; - inherit cargoArtifacts nativeBuildInputs; - pname = "hyperhive-workspace"; - version = "0.1.0"; - cargoTestExtraArgs = "--workspace"; - HIVE_ASSETS_DIR = "${self.packages.${system}.assets}/share/hyperhive"; - }; - # Nix options docs evaluation. 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. Reuses the - # `packages..docs` derivation so the per-system eval - # of `nix/docs/default.nix` happens once. - inherit (self.packages.${system}) docs; - # `hivectl` CLI reference freshness check. The committed - # markdown at `docs/tools/hivectl-cli.md` is the rendered - # output of the hidden `hivectl markdown-docs` subcommand - # (clap-markdown walks the binary's own command tree). This - # check regenerates it from the built binary and fails if the - # committed copy drifted — so a verb / flag / help-string edit - # that forgets to refresh the doc is caught in CI. Reuses the - # already-built `packages..default` (no extra compile). - # Regenerate locally with: - # nix build .#default - # ./result/bin/hivectl markdown-docs > docs/tools/hivectl-cli.md - hivectl-docs = pkgs.runCommand "hivectl-docs-fresh" { nativeBuildInputs = [ pkgs.diffutils ]; } '' - ${self.packages.${system}.default}/bin/hivectl markdown-docs > generated.md - if ! diff -u ${./docs/tools/hivectl-cli.md} generated.md; then - echo "" >&2 - echo "ERROR: docs/tools/hivectl-cli.md is out of date — regenerate it:" >&2 - echo " nix build .#default && ./result/bin/hivectl markdown-docs > docs/tools/hivectl-cli.md" >&2 - exit 1 - fi - touch "$out" - ''; + import ./nix/checks.nix { + inherit + pkgs + craneLib + rust + self + system + treefmt-eval + ; } ); }; diff --git a/nix/checks.nix b/nix/checks.nix new file mode 100644 index 00000000..e248c52b --- /dev/null +++ b/nix/checks.nix @@ -0,0 +1,87 @@ +# Flake checks: formatting, the clippy gate, the workspace test run, +# the nix-options docs eval, and the hivectl CLI-reference freshness +# check. Imported per system from flake.nix. +{ + pkgs, + craneLib, + rust, + self, + system, + treefmt-eval, +}: +let + inherit (rust) cleanSrc cargoArtifacts nativeBuildInputs; +in +{ + formatting = treefmt-eval.config.build.check self; + + # Clippy via crane's first-class `cargoClippy` builder. Reuses the + # shared `cargoArtifacts` (deps already built) and runs + # `cargo clippy --workspace --all-targets` directly. + # + # `-D warnings` makes the default/correctness/style lints a + # hard CI gate. `-A clippy::pedantic` then drops the pedantic + # group from that gate: pedantic is the "extra, opinionated" + # group the clippy team grows freely, so denying it means + # every toolchain bump that adds a new pedantic lint breaks CI + # with zero code changes. The `pedantic = warn` + # workspace lint (Cargo.toml) keeps it as advisory signal in + # local `cargo clippy` — it just no longer blocks the build. + # (`-A` rather than `-W` here: `-W clippy::pedantic` would + # re-enable the specific pedantic lints the workspace lints + # table allows, e.g. `must_use_candidate`.) + clippy = craneLib.cargoClippy { + src = cleanSrc; + inherit cargoArtifacts nativeBuildInputs; + pname = "hyperhive-workspace"; + version = "0.1.0"; + cargoClippyExtraArgs = "--workspace --all-targets -- -D warnings"; + }; + + # `cargo test --workspace` lifted out of the package builds so the + # `hyperhive-assets` dep (which `hive-ag3nt::prompt::tests` + # needs via `HIVE_ASSETS_DIR` to assert against the actual + # production prompt template) is scoped to this one check + # instead of bleeding into the binary derivations' input + # hash. Net: editing `hive-ag3nt/prompts/system.md` still + # rebuilds this test check (correct — the tests assert + # against its wording), but `packages.default` and the + # per-container toplevels stay fully cached. + cargo-test = craneLib.cargoTest { + src = cleanSrc; + inherit cargoArtifacts nativeBuildInputs; + pname = "hyperhive-workspace"; + version = "0.1.0"; + cargoTestExtraArgs = "--workspace"; + HIVE_ASSETS_DIR = "${self.packages.${system}.assets}/share/hyperhive"; + }; + + # Nix options docs evaluation. 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. Reuses the `packages..docs` derivation so the + # per-system eval of nix/docs/default.nix happens once. + inherit (self.packages.${system}) docs; + + # `hivectl` CLI reference freshness check. The committed + # markdown at `docs/tools/hivectl-cli.md` is the rendered + # output of the hidden `hivectl markdown-docs` subcommand + # (clap-markdown walks the binary's own command tree). This + # check regenerates it from the built binary and fails if the + # committed copy drifted — so a verb / flag / help-string edit + # that forgets to refresh the doc is caught in CI. Reuses the + # already-built `packages..default` (no extra compile). + # Regenerate locally with: + # nix build .#default + # ./result/bin/hivectl markdown-docs > docs/tools/hivectl-cli.md + hivectl-docs = pkgs.runCommand "hivectl-docs-fresh" { nativeBuildInputs = [ pkgs.diffutils ]; } '' + ${self.packages.${system}.default}/bin/hivectl markdown-docs > generated.md + if ! diff -u ${../docs/tools/hivectl-cli.md} generated.md; then + echo "" >&2 + echo "ERROR: docs/tools/hivectl-cli.md is out of date — regenerate it:" >&2 + echo " nix build .#default && ./result/bin/hivectl markdown-docs > docs/tools/hivectl-cli.md" >&2 + exit 1 + fi + touch "$out" + ''; +} diff --git a/nix/devshell.nix b/nix/devshell.nix new file mode 100644 index 00000000..68885df1 --- /dev/null +++ b/nix/devshell.nix @@ -0,0 +1,18 @@ +# Dev shell: the rust toolchain plus the exact native build inputs +# the crane derivations use (git, sqlite, pkg-config, cmake — see +# ../rust.nix), so a plain `cargo build` / `cargo test` in the shell +# sees the same toolchain shape as CI. +{ pkgs, rust }: +{ + default = pkgs.mkShell { + packages = + rust.nativeBuildInputs + ++ (with pkgs; [ + cargo + clippy + rust-analyzer + rustc + rustfmt + ]); + }; +} diff --git a/nix/assets.nix b/nix/packages/assets.nix similarity index 89% rename from nix/assets.nix rename to nix/packages/assets.nix index 46b4ce9c..0c0d9a41 100644 --- a/nix/assets.nix +++ b/nix/packages/assets.nix @@ -12,7 +12,7 @@ # $out/share/hyperhive/branding/{hyperhive,agent-configs}.{svg,png} # $out/share/hyperhive/prompts/{system.md, claude-settings.json} # -# The repo docs/ tree is a SEPARATE derivation (nix/reference-docs.nix) +# The repo docs/ tree is a SEPARATE derivation (./reference-docs.nix) # so agents can consume the docs without the branding+prompt assets and # the website repo can reuse it — see that file. @@ -22,13 +22,13 @@ stdenv.mkDerivation { # Narrow `srcs` (branding/ + hive-ag3nt/prompts/) is what decouples # this derivation's input hash from the rest of the tree. srcs = [ - ../branding - ../hive-ag3nt/prompts + ../../branding + ../../hive-ag3nt/prompts ]; unpackPhase = '' runHook preUnpack - cp -r ${../branding} branding - cp -r ${../hive-ag3nt/prompts} prompts + cp -r ${../../branding} branding + cp -r ${../../hive-ag3nt/prompts} prompts chmod -R u+w branding prompts runHook postUnpack ''; diff --git a/nix/packages/default.nix b/nix/packages/default.nix new file mode 100644 index 00000000..5bf74b55 --- /dev/null +++ b/nix/packages/default.nix @@ -0,0 +1,201 @@ +# All flake package outputs. Imported per system from flake.nix; the +# shared rust build wiring (cleanSrc / cargoArtifacts / +# nativeBuildInputs) comes in via `rust` (see ../rust.nix). +{ + pkgs, + craneLib, + rust, + self, + nixpkgs, +}: +let + inherit (rust) cleanSrc cargoArtifacts nativeBuildInputs; + + docsAttrs = import ../docs { + inherit pkgs self; + inherit (nixpkgs) lib; + inherit (nixpkgs.lib) nixosSystem; + }; + + # One package per daemon/harness/MCP-server binary — matches the + # `hivectl` / `hive-forge` split below rather than grouping them + # into a single derivation. Consumers (agent containers, host + # module, `nix profile install`) depend on exactly the binaries + # they need instead of an all-or-nothing bundle. All share + # `cargoArtifacts` (built once via `buildDepsOnly`), so each call + # below only pays for compiling + linking its own bin's workspace + # crates. + # + # Tests are kept in the separate `checks.cargo-test` derivation + # (carries the hyperhive-assets build input for the prompt-template + # assertions in hive-ag3nt::prompt::tests). Keeping them out of the + # binary derivations means a prompt edit doesn't bust the cargo cache. + mkDaemonBin = + bin: description: + craneLib.buildPackage { + src = cleanSrc; + inherit cargoArtifacts nativeBuildInputs; + cargoExtraArgs = "--bin ${bin}"; + pname = bin; + version = "0.1.0"; + meta.description = description; + doCheck = false; + }; + hiveC0rePkg = mkDaemonBin "hive-c0re" "hyperhive host coordinator daemon"; + hivePrivPkg = mkDaemonBin "hive-priv" "hyperhive privileged root helper"; + hiveAgentPkg = mkDaemonBin "hive-agent" "hyperhive in-container agent harness serve loop"; + hiveAgentMcpPkg = mkDaemonBin "hive-agent-mcp" "hyperhive agent-surface MCP server"; + hiveAgentWakePkg = mkDaemonBin "hive-agent-wake" "hyperhive external wake CLI — push a message into an agent's own inbox"; + hiveBashDaemonPkg = mkDaemonBin "hive-bash-daemon" "hyperhive per-agent bash-task runner daemon"; + hiveBashMcpPkg = mkDaemonBin "hive-bash-mcp" "hyperhive bash-task MCP bridge"; + hiveMatrixDaemonPkg = mkDaemonBin "hive-matrix-daemon" "hyperhive per-agent matrix-sdk daemon"; + hiveMatrixMcpPkg = mkDaemonBin "hive-matrix-mcp" "hyperhive matrix MCP bridge"; + hiveMetricPkg = mkDaemonBin "hive-metric" "hyperhive agent-emitted custom metrics CLI"; + + # Operator CLI — ships `hivectl` (with shell completions and the + # `wg` wrapper) without the daemon binaries. Suitable for + # `nix profile install .#hivectl` / `environment.systemPackages + # = [ inputs.hyperhive.packages.${system}.hivectl ]` when the + # operator only wants the admin CLI. Shares `cargoArtifacts` with + # the daemon bins so there is no double-rustc cost. + hivectlPkg = craneLib.buildPackage { + src = cleanSrc; + inherit cargoArtifacts; + cargoExtraArgs = "--bin hivectl"; + pname = "hivectl"; + version = "0.1.0"; + meta.description = "hyperhive operator CLI"; + doCheck = false; + # `installShellFiles` + `makeWrapper` scoped to this derivation + # only — daemon bins don't need them. + nativeBuildInputs = nativeBuildInputs ++ [ + pkgs.installShellFiles + pkgs.makeWrapper + ]; + # Ship shell completions (the binary's own `completions ` + # verb is the single source of truth, so they never drift from + # the actual verbs). Wrap with wireguard-tools so `hivectl wg` + # subcommands work before `swarm.wireguard.enable` is set (wg + # init is the very first setup step). Completion generation runs + # before wrapProgram since wrapProgram renames the real binary. + postInstall = '' + installShellCompletion --cmd hivectl \ + --bash <("$out/bin/hivectl" completions bash) \ + --zsh <("$out/bin/hivectl" completions zsh) \ + --fish <("$out/bin/hivectl" completions fish) + wrapProgram "$out/bin/hivectl" \ + --prefix PATH : ${pkgs.wireguard-tools}/bin + ''; + }; + + # Forgejo CLI — ships `hive-forge` without the rest of the + # workspace. Useful for operator workstations / CI environments + # that only need forge access. Shares `cargoArtifacts` with the + # daemon bins. + hiveForgePkg = craneLib.buildPackage { + src = cleanSrc; + inherit cargoArtifacts nativeBuildInputs; + cargoExtraArgs = "--bin hive-forge"; + pname = "hive-forge"; + version = "0.1.0"; + meta.description = "hyperhive Forgejo CLI"; + doCheck = false; + }; +in +{ + # All workspace binaries in one derivation via symlinkJoin. + # Each binary is compiled exactly once (one rustc per bin, all + # sharing `cargoArtifacts`); symlinkJoin assembles the outputs + # without any additional compilation. The NixOS module's + # `pkgs.hyperhive` (= this) and `nix build .#` both land here. + default = pkgs.symlinkJoin { + name = "hyperhive"; + paths = [ + hiveC0rePkg + hivePrivPkg + hiveAgentPkg + hiveAgentMcpPkg + hiveAgentWakePkg + hiveBashDaemonPkg + hiveBashMcpPkg + hiveMatrixDaemonPkg + hiveMatrixMcpPkg + hiveMetricPkg + hivectlPkg + hiveForgePkg + ]; + }; + # Per-bin split packages. Agent containers depend on the + # individual bins they actually exec/PATH-need (see + # `harness-base.nix`) instead of the `default` bundle — that + # keeps `hivectl` (dials the *host* admin socket, unreachable + # from inside a container, drags in `wireguard-tools`) and a + # redundant `hive-forge` copy out of every agent's closure. + hivectl = hivectlPkg; + hive-forge = hiveForgePkg; + hive-c0re = hiveC0rePkg; + hive-priv = hivePrivPkg; + hive-agent = hiveAgentPkg; + hive-agent-mcp = hiveAgentMcpPkg; + hive-agent-wake = hiveAgentWakePkg; + hive-bash-daemon = hiveBashDaemonPkg; + hive-bash-mcp = hiveBashMcpPkg; + hive-matrix-daemon = hiveMatrixDaemonPkg; + hive-matrix-mcp = hiveMatrixMcpPkg; + hive-metric = hiveMetricPkg; + + # Bundled browser assets — see ./frontend.nix. Output is + # $out/{dashboard,agent}/ which the Rust binaries serve via + # tower_http::ServeDir. + frontend = pkgs.callPackage ./frontend.nix { + branding-svg = ../../branding/hyperhive.svg; + }; + # Static runtime assets the rust binaries read via + # `hive_sh4re::assets::*`: branding/* + prompts/*, plus the + # rendered agent-configs.png. Split out of the rust derivation so + # a tweak to e.g. system.md doesn't bust the cargo cache. Build + # input of the `cargo-test` check but NOT of `packages.default`, + # so the binary derivation stays cached when a prompt edit ripples + # through. + assets = pkgs.callPackage ./assets.nix { }; + # The repo docs/ markdown tree as a standalone derivation — + # agents read it in-container (added as a claude additional + # directory) and the website repo reuses it as a flake input, + # neither of which needs the branding/prompt assets. See + # ./reference-docs.nix. (`docs` below is the auto-generated + # nix-options reference, a different artifact.) + reference-docs = pkgs.callPackage ./reference-docs.nix { }; + # XDG icon set + .desktop entries for hyperhive processes. + # Narrow input: only the branding SVG, so unrelated source changes + # don't bust this derivation's cache. + xdg-icons = pkgs.callPackage ./hive-xdg-icons.nix { + hyperhiveSvg = ../../branding/hyperhive.svg; + }; + + # Pre-built per-container system closures. Exposed as packages + # so operators can `nix build .#agent-base-toplevel` (or wire + # them into their host system closure via the + # `preBuildAgentTemplates` option on the hive-c0re module — + # see nix/modules/hive-c0re.nix). Speeds up the first agent + # spawn dramatically because the heavy lifting (nixpkgs + + # claude-code + hive-ag3nt binary) is already in the store + # when the meta evaluator goes to build the container. + # + # nixosConfigurations are pinned to x86_64-linux (nixos- + # containers only run native arch), so these toplevels are + # only useful on an x86_64-linux host — flake check across + # systems still tolerates evaluating them on aarch64 because + # they're plain derivations, but `nix build` from a non-x86 + # host would only succeed via a remote x86 builder. + agent-base-toplevel = self.nixosConfigurations.agent-base.config.system.build.toplevel; + ruth-toplevel = self.nixosConfigurations.ruth.config.system.build.toplevel; + + # Auto-generated nix options reference for hyperhive. + # `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; +} diff --git a/nix/frontend.nix b/nix/packages/frontend.nix similarity index 99% rename from nix/frontend.nix rename to nix/packages/frontend.nix index 94cd38a3..242752b5 100644 --- a/nix/frontend.nix +++ b/nix/packages/frontend.nix @@ -33,7 +33,7 @@ buildNpmPackage { pname = "hyperhive-frontend"; version = "0.0.0"; - src = ../frontend; + src = ../../frontend; # Computed from `frontend/package-lock.json` via # prefetch-npm-deps frontend/package-lock.json diff --git a/nix/reference-docs.nix b/nix/packages/reference-docs.nix similarity index 98% rename from nix/reference-docs.nix rename to nix/packages/reference-docs.nix index 74a5c8c2..216a398d 100644 --- a/nix/reference-docs.nix +++ b/nix/packages/reference-docs.nix @@ -19,7 +19,7 @@ stdenv.mkDerivation { version = "0.1.0"; # Narrow src (just docs/) keeps this derivation's input hash decoupled # from the rest of the tree — a doc edit only re-hashes this. - src = ../docs; + src = ../../docs; # No build: pure markdown, nothing to compile or render. dontBuild = true; diff --git a/nix/rust.nix b/nix/rust.nix new file mode 100644 index 00000000..0156534e --- /dev/null +++ b/nix/rust.nix @@ -0,0 +1,56 @@ +# Shared crane wiring for the rust workspace: the cargo source +# filter, the once-built dependency artifacts, and the native build +# inputs every rust derivation (packages + checks) consumes. +# Imported per system from flake.nix. +{ pkgs, craneLib }: +rec { + # Shared between buildDepsOnly + buildPackage + cargoClippy/cargoTest + # so every derivation in the chain sees the same toolchain shape. + # git: hive-c0re's `lifecycle::tests::setup_proposed_*` shell out to + # `git init` + commit under `cargo test` in the sandbox. + # sqlite: matrix-sdk's `sqlite` feature (`hive-matrix-mcp` workspace + # member) — the matrix-sdk-sqlite + rusqlite stack links against + # system libsqlite3 by default. + # cmake: builds `aws-lc-sys` (BoringSSL) from source — pulled in by + # the `rustls` (aws-lc-rs) crypto provider under the OTLP/reqwest + # stack in `hive-metric`. + nativeBuildInputs = [ + pkgs.git + pkgs.sqlite + pkgs.pkg-config + pkgs.cmake + ]; + + # Narrowed source tree the rust derivations consume. + # `cleanCargoSource` is crane's standard "everything cargo cares + # about" filter (Cargo.toml/Cargo.lock + *.rs). All non-rust runtime + # assets — branding + the claude system prompt template + + # claude-settings.json — live in the separate `hyperhive-assets` + # derivation and are loaded by the binaries at runtime from + # `$HIVE_ASSETS_DIR`. The unit tests in `hive-ag3nt::prompt` read + # `prompts/system.md` directly from the workspace tree at *test* + # runtime (via `env!("CARGO_MANIFEST_DIR")` — a compile-time string, + # no file open at compile), so the prompt template doesn't have to + # be in this fileset to keep `cargo test` honest. Net effect: tweaks + # to any non-`*.rs` / non-`Cargo.*` file (README, branding, nix + # modules, frontend tree, OR `hive-ag3nt/prompts/*`) do NOT bust + # this src hash, so the rust derivations stay fully cached. + cleanSrc = craneLib.cleanCargoSource ../.; + + # Build the workspace's dependency tree once, cached as its own + # derivation. Package builds and checks reuse this via + # `inherit cargoArtifacts;` so a workspace-only edit doesn't rebuild + # deps. All consumers use the same `cleanSrc` so the input hash + # stays consistent across the chain. + cargoArtifacts = craneLib.buildDepsOnly { + src = cleanSrc; + # Workspace Cargo.toml is virtual (no `[package].name`), so crane + # can't auto-derive a name. Spell it out explicitly — keeps the + # derivation name stable across crane bumps + silences the + # placeholder warning. Same `pname` for the dep + check + # derivations so they share a clean naming family. + pname = "hyperhive-workspace"; + version = "0.1.0"; + inherit nativeBuildInputs; + }; +} diff --git a/nix/sources.nix b/nix/sources.nix new file mode 100644 index 00000000..90083ee9 --- /dev/null +++ b/nix/sources.nix @@ -0,0 +1,49 @@ +# Named, explicitly-filtered source views of this repo. Each real +# consumer gets its own filtered derivation used as `src`, rather than +# an inline filter at the use site. Companion to the rust `cleanSrc` +# (crane's cargo-source filter — see ./rust.nix). +{ lib }: +{ + # Filtered view of this repo handed to hive-c0re as the `hyperhive` + # meta-flake input. Drops files no nix or cargo derivation reads — + # shell helper scripts and root-level markdown — so editing them + # does NOT change the store path and therefore does NOT force a + # rebuild of every agent container. + # + # Dropped: scripts/, docs/, root-level *.md (README/CLAUDE/TODO/…). + # Kept (build needs them): .nix, .rs, Cargo.*, branding/, + # frontend/, prompts/, flake.lock. docs/ is deliberately dropped: + # it is shipped to agent containers as its OWN narrow meta-flake + # input (`hyperhiveDocsSource`, below), threaded through hive-c0re + # → the meta flake → `hyperhive.docs.source`. Keeping docs/ out of + # THIS source means a doc edit only re-hashes the docs input (a + # cheap re-link), not the whole flake source (which would rebuild + # every agent container). The `reference-docs` build from `../docs` + # stays only for standalone `nix build .#reference-docs` from a + # full checkout — the meta path never evaluates it. + hyperhiveFlakeSource = lib.cleanSourceWith { + name = "hyperhive-flake-source"; + src = ../.; + filter = + path: type: + let + # Repo-relative path (strip the absolute source-dir prefix). + rel = lib.removePrefix (toString ../. + "/") (toString path); + in + !(lib.hasPrefix "scripts/" rel || rel == "scripts") + && !(lib.hasPrefix "docs/" rel || rel == "docs") + && !(type == "regular" && !lib.hasInfix "/" rel && lib.hasSuffix ".md" rel); + }; + + # The repo `docs/` tree as a standalone narrow source. Its store + # path moves ONLY on doc edits, decoupled from `hyperhiveFlakeSource`. + # hive-c0re threads this to the meta flake as the `hyperhive-docs` + # input (same pattern as the `hyperhive` input); the harness resolves + # `$HIVE_DOCS_DIR` from it via `hyperhive.docs.source`. Evaluated + # here on the host where docs/ exists — it cannot be derived from + # inside the docs-stripped `hyperhiveFlakeSource`. + hyperhiveDocsSource = lib.cleanSourceWith { + name = "hyperhive-docs-source"; + src = ../docs; + }; +} diff --git a/nix/treefmt.nix b/nix/treefmt.nix new file mode 100644 index 00000000..3d05e507 --- /dev/null +++ b/nix/treefmt.nix @@ -0,0 +1,11 @@ +# treefmt-nix module — shared by `nix fmt` (the flake `formatter` +# output) and the `formatting` flake check. +{ + projectRootFile = "flake.nix"; + programs = { + keep-sorted.enable = true; + nixfmt.enable = true; + rustfmt.enable = true; + taplo.enable = true; + }; +}