Making `hyperhive.forge.url` required broke `nix flake check`:
`nixosConfigurations.agent-base` and `.ruth` exist to typecheck the
agent modules and to pre-build the container closure, and nothing in
that path supplies a value — `forge.nix` reads the option
unconditionally for tea-login's `FORGE_URL`.
Real containers are unaffected: they are built from the generated meta
flake, where hive-c0re renders the option per agent from the host's
`HIVE_FORGE_URL` (meta.rs's `SERVICE_URL_OPTIONS`). They never evaluate
through `self.nixosConfigurations`, so this value cannot reach a
running agent.
Uses a `.invalid` host (RFC 2606, guaranteed not to resolve) rather
than a plausible loopback: if it ever did escape into a runtime path it
must fail at DNS instead of quietly connecting to whatever is listening
locally, which is the failure mode this issue exists to remove.
Verified without the build farm:
nix eval .#nixosConfigurations.<cfg>.config.assertions \
--apply 'l: builtins.filter (a: !a.assertion) l' # => []
nix eval --raw .#nixosConfigurations.<cfg>.config.systemd.services.tea-login.script
Refs #2860
212 lines
7.6 KiB
Nix
212 lines
7.6 KiB
Nix
{
|
|
description = "hyperhive — multi-Claude-Code-agent orchestration on nixos-containers";
|
|
|
|
inputs = {
|
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
|
|
# 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";
|
|
inputs.nixpkgs.follows = "nixpkgs";
|
|
};
|
|
};
|
|
|
|
# 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/host-modules/, nix/agent-modules/, nix/templates/ the NixOS module trees
|
|
outputs =
|
|
inputs@{
|
|
self,
|
|
nixpkgs,
|
|
crane,
|
|
treefmt-nix,
|
|
}:
|
|
let
|
|
inherit (nixpkgs) lib;
|
|
systems = [
|
|
"aarch64-linux"
|
|
"x86_64-linux"
|
|
];
|
|
sources = import ./nix/sources.nix { inherit lib; };
|
|
forAllSystems =
|
|
f:
|
|
lib.genAttrs systems (
|
|
system:
|
|
f rec {
|
|
inherit system;
|
|
pkgs = nixpkgs.legacyPackages.${system};
|
|
treefmt-eval = treefmt-nix.lib.evalModule pkgs (import ./nix/treefmt.nix);
|
|
craneLib = crane.mkLib pkgs;
|
|
rust = import ./nix/rust.nix { inherit pkgs craneLib; };
|
|
}
|
|
);
|
|
in
|
|
{
|
|
packages = forAllSystems (
|
|
{
|
|
pkgs,
|
|
craneLib,
|
|
rust,
|
|
...
|
|
}:
|
|
import ./nix/packages {
|
|
inherit
|
|
pkgs
|
|
craneLib
|
|
rust
|
|
self
|
|
nixpkgs
|
|
;
|
|
}
|
|
);
|
|
|
|
nixosModules =
|
|
let
|
|
# Package wiring for agent containers — the harness modules
|
|
# consume hyperhive's own packages via the `hyperhive.packages`
|
|
# option (see nix/agent-modules/packages.nix); no overlay.
|
|
# The `mkDefault` is applied PER KEY (`mapAttrs`), not to the
|
|
# whole attrset: definition-level priority filtering runs
|
|
# before `attrsOf`'s per-key merge, so a whole-set `mkDefault`
|
|
# would be discarded entirely the moment an agent.nix
|
|
# overrides a single key. Per-key priorities make an
|
|
# individual override win while every other key keeps the
|
|
# flake default.
|
|
agentPackages =
|
|
{ lib, pkgs, ... }:
|
|
{
|
|
hyperhive.packages = lib.mapAttrs (_: lib.mkDefault) {
|
|
inherit (self.packages.${pkgs.stdenv.hostPlatform.system})
|
|
hive-agent
|
|
hive-agent-mcp
|
|
hive-bash-daemon
|
|
hive-forge
|
|
hive-forge-notify
|
|
hive-github-notify
|
|
hive-matrix-daemon
|
|
hive-metric
|
|
hive-screen-mcp
|
|
assets
|
|
frontend
|
|
reference-docs
|
|
claude-plugins
|
|
;
|
|
};
|
|
};
|
|
in
|
|
{
|
|
agent-base.imports = [
|
|
./nix/templates/agent.nix
|
|
agentPackages
|
|
];
|
|
ruth.imports = [
|
|
./nix/templates/ruth.nix
|
|
agentPackages
|
|
];
|
|
# The full host stack (nix/host-modules/default.nix aggregator) plus
|
|
# the package/source wiring from this flake. The wiring is a
|
|
# plain config module setting the `services.hyperhive.c0re.*`
|
|
# package options via `lib.mkDefault` — no overlay involved, and
|
|
# an operator override still wins. Intended usage:
|
|
#
|
|
# imports = [ hyperhive.nixosModules.default ];
|
|
# services.hyperhive.enable = true;
|
|
#
|
|
default =
|
|
{ lib, pkgs, ... }:
|
|
{
|
|
imports = [ ./nix/host-modules ];
|
|
services.hyperhive.c0re = {
|
|
package = lib.mkDefault self.packages.${pkgs.stdenv.hostPlatform.system}.default;
|
|
frontend = lib.mkDefault self.packages.${pkgs.stdenv.hostPlatform.system}.frontend;
|
|
assets = lib.mkDefault self.packages.${pkgs.stdenv.hostPlatform.system}.assets;
|
|
xdgIcons = lib.mkDefault self.packages.${pkgs.stdenv.hostPlatform.system}.xdg-icons;
|
|
hyperhiveFlake = lib.mkDefault "${sources.hyperhiveFlakeSource}";
|
|
# Narrow docs/ source, threaded as its own meta-flake input
|
|
# so doc edits don't re-hash the whole flake source.
|
|
hyperhiveDocs = lib.mkDefault "${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. x86_64-linux only (nixosConfigurations are
|
|
# hardcoded to that system); the gate keeps aarch64 hosts
|
|
# from pulling them in via cross-build.
|
|
agentBaseToplevel = lib.mkDefault self.packages.x86_64-linux.agent-base-toplevel;
|
|
managerToplevel = lib.mkDefault self.packages.x86_64-linux.ruth-toplevel;
|
|
};
|
|
};
|
|
hive-ci = ./nix/host-modules/hive-ci.nix;
|
|
hive-forge = ./nix/host-modules/hive-forge;
|
|
};
|
|
|
|
nixosConfigurations =
|
|
let
|
|
# Values the agent modules require but that only a real
|
|
# deployment can know. Real containers are built from the
|
|
# generated meta flake, where hive-c0re renders these per
|
|
# agent from the host's `HIVE_FORGE_URL` (see meta.rs's
|
|
# `SERVICE_URL_OPTIONS`) — they never evaluate through
|
|
# `self.nixosConfigurations`, so nothing here can reach a
|
|
# running agent. These two configs exist only to typecheck
|
|
# the modules and to pre-build the container closure
|
|
# (`system.extraDependencies`, see hive-c0re/default.nix).
|
|
#
|
|
# Deliberately a `.invalid` host (RFC 2606: guaranteed not to
|
|
# resolve) rather than something plausible like a loopback
|
|
# port. If this value ever *did* escape into a runtime path,
|
|
# it must fail loudly at DNS instead of quietly connecting to
|
|
# whatever happens to be listening — which is the entire
|
|
# point of removing the `http://localhost:3000` default this
|
|
# replaces.
|
|
evalOnlyPlaceholders = {
|
|
hyperhive.forge.url = "http://forge.invalid";
|
|
};
|
|
mkContainer =
|
|
module:
|
|
nixpkgs.lib.nixosSystem {
|
|
system = "x86_64-linux";
|
|
modules = [
|
|
module
|
|
evalOnlyPlaceholders
|
|
];
|
|
};
|
|
in
|
|
{
|
|
agent-base = mkContainer self.nixosModules.agent-base;
|
|
ruth = mkContainer self.nixosModules.ruth;
|
|
};
|
|
|
|
devShells = forAllSystems ({ pkgs, rust, ... }: import ./nix/devshell.nix { inherit pkgs rust; });
|
|
|
|
formatter = forAllSystems ({ treefmt-eval, ... }: treefmt-eval.config.build.wrapper);
|
|
|
|
checks = forAllSystems (
|
|
{
|
|
pkgs,
|
|
system,
|
|
treefmt-eval,
|
|
craneLib,
|
|
rust,
|
|
...
|
|
}:
|
|
import ./nix/checks.nix {
|
|
inherit
|
|
pkgs
|
|
craneLib
|
|
rust
|
|
self
|
|
system
|
|
treefmt-eval
|
|
;
|
|
}
|
|
);
|
|
};
|
|
}
|