From 3069c11c3d26a636d4e4735e78fbf1947c827dc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?m=C3=BCde?= Date: Mon, 13 Jul 2026 21:05:12 +0200 Subject: [PATCH] refactor: build workspace once, extract per-bin packages --- nix/packages/default.nix | 210 ++++++++++++++++++--------------------- 1 file changed, 96 insertions(+), 114 deletions(-) diff --git a/nix/packages/default.nix b/nix/packages/default.nix index 5bf74b55..9e3b6e84 100644 --- a/nix/packages/default.nix +++ b/nix/packages/default.nix @@ -9,6 +9,7 @@ nixpkgs, }: let + inherit (pkgs) lib; inherit (rust) cleanSrc cargoArtifacts nativeBuildInputs; docsAttrs = import ../docs { @@ -17,133 +18,114 @@ let 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. + # Every per-binary package: name → description. The single source of + # truth for the bin list — it drives the per-bin extractor packages + # and the `default` bundle, so adding a binary is one entry here. + daemonBins = { + hive-c0re = "hyperhive host coordinator daemon"; + hive-priv = "hyperhive privileged root helper"; + hive-agent = "hyperhive in-container agent harness serve loop"; + hive-agent-mcp = "hyperhive agent-surface MCP server"; + hive-agent-wake = "hyperhive external wake CLI — push a message into an agent's own inbox"; + hive-bash-daemon = "hyperhive per-agent bash-task runner daemon"; + hive-bash-mcp = "hyperhive bash-task MCP bridge"; + hive-matrix-daemon = "hyperhive per-agent matrix-sdk daemon"; + hive-matrix-mcp = "hyperhive matrix MCP bridge"; + hive-metric = "hyperhive agent-emitted custom metrics CLI"; + hive-forge = "hyperhive Forgejo CLI"; + }; + + # ONE compile of the whole workspace (every bin, sharing the + # prebuilt `cargoArtifacts` dep cache). The per-bin packages below + # are cheap copy-extractors over this, so workspace lib crates + # (hive-sh4re, hive-claude, …) compile exactly once instead of once + # per bin derivation. # # 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 { + # assertions in hive-ag3nt::prompt::tests). Keeping them out of this + # derivation means a prompt edit doesn't bust the cargo cache. + workspaceBuild = craneLib.buildPackage { src = cleanSrc; inherit cargoArtifacts nativeBuildInputs; - cargoExtraArgs = "--bin hive-forge"; - pname = "hive-forge"; + pname = "hyperhive-workspace"; version = "0.1.0"; - meta.description = "hyperhive Forgejo CLI"; doCheck = false; }; + + # Per-bin extractor: COPIES one binary out of the workspace build. + # A copy, not a symlink — a symlink would keep the whole workspace + # output (and thus every other binary) in the consumer's runtime + # closure, defeating the point of the per-bin split. The copied + # binary's RPATH references only the libs it links, so nix's + # reference scan gives each package a narrow closure. + mkBinPackage = + bin: description: + pkgs.runCommand bin + { + meta = { + inherit description; + mainProgram = bin; + }; + } + '' + install -Dm755 ${workspaceBuild}/bin/${bin} $out/bin/${bin} + ''; + + # Operator CLI — extractor plus shell completions and the `wg` + # wrapper. Suitable for `nix profile install .#hivectl` / + # `environment.systemPackages = [ …packages..hivectl ]` + # when the operator only wants the admin CLI. + # Completions come from the binary's own `completions ` verb + # (the single source of truth, so they never drift from the actual + # verbs). The wireguard-tools wrap makes `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. + hivectlPkg = + pkgs.runCommand "hivectl" + { + nativeBuildInputs = [ + pkgs.installShellFiles + pkgs.makeWrapper + ]; + meta = { + description = "hyperhive operator CLI"; + mainProgram = "hivectl"; + }; + } + '' + install -Dm755 ${workspaceBuild}/bin/hivectl $out/bin/hivectl + 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 + ''; + + # 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 redundant binaries out of every agent's + # closure. + binPkgs = lib.mapAttrs mkBinPackage daemonBins // { + hivectl = hivectlPkg; + }; 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. + # All workspace binaries in one derivation — a symlinkJoin over the + # per-bin packages, pure assembly with no extra 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 - ]; + paths = lib.attrValues binPkgs; }; - # 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; - +} +// binPkgs +// { # Bundled browser assets — see ./frontend.nix. Output is # $out/{dashboard,agent}/ which the Rust binaries serve via # tower_http::ServeDir.