Watch
0
0
Fork
You've already forked hyperhive
0
hyperhive/flake.nix
atlas d5de1e4943 swarm UI: log an agent into claude from its card; the credentials go to bao and the agent picks them up
A new swarm service, `swarm-claude-login`, in its own container on the host
that sets `deploy.claude-login.enable` (defaulted on by `allSwarmServices`
where the host has the minter's store leaf, like the review classifier).

- swarm-ui: an agent card showing "login needed" gets a "log in" button.
  Its dialog starts the login, shows the URL claude printed, and takes the
  pasted code.
- swarm-controller: `POST/DELETE /api/agents/{name}/claude-login` and
  `POST /api/agents/{name}/claude-login/code`, behind the swarm UI's
  authelia-gated `/api/`, relay the steps to the minter as requests on
  `$SWARM.claude-login.request`.
- swarm-claude-login: runs `claude auth login` over piped stdio with HOME
  and CLAUDE_CONFIG_DIR in a per-login scratch dir, scrapes the first
  https URL, writes the code to stdin, and stores the `.credentials.json`
  at `swarm/agents/<agent>/claude-credentials` with a KV v2 write, a new
  version over any earlier one. The scratch dir goes on every exit path; a
  login waits 15 minutes for its code.
- hive-agent: while parked in `wait_for_login`, reads that leaf every 30 s
  under the agent's own cert and installs it only when its KV version is
  newer than the one recorded in `<harness>/claude-credentials-version`.
  An equal or older version is never put back, because claude rotates the
  refresh token and the stored copy is stale after the first refresh.
- swarm-secret-client: `claude_login` paths and `SecretStore::read_versioned`.
- Credentials: a `claude-login` store leaf minted by glue-bao-tls, a
  `swarm-claude-login` policy (create/update on
  `swarm/agents/+/claude-credentials`, read on its own queue client
  secret), an authelia machine client, and a `--claude-login-client`
  grant in the queue's auth-callout responder. The unit runs as a static
  system user.

The hive's own web-UI login is unchanged.

Refs #5268
2026-10-15 13:26:29 +02:00

237 lines
9.3 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/container-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, ... }:
{
services.hyperhive.agent.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
hive-subagent-daemon
swarm-logs
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.deploy.hive-controller.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;
};
services.hyperhive.deploy.swarm-controller.package =
lib.mkDefault
self.packages.${pkgs.stdenv.hostPlatform.system}.swarm-controller;
services.hyperhive.deploy.swarm-controller.swarmctlPackage =
lib.mkDefault
self.packages.${pkgs.stdenv.hostPlatform.system}.swarmctl;
services.hyperhive.deploy.swarm-ui.package =
lib.mkDefault
self.packages.${pkgs.stdenv.hostPlatform.system}.swarm-ui;
services.hyperhive.deploy.victorialogs.authPackage =
lib.mkDefault
self.packages.${pkgs.stdenv.hostPlatform.system}.swarm-logs-auth;
services.hyperhive.deploy.victoriametrics.authPackage =
lib.mkDefault
self.packages.${pkgs.stdenv.hostPlatform.system}.swarm-metrics-auth;
services.hyperhive.deploy.nats.authPackage =
lib.mkDefault
self.packages.${pkgs.stdenv.hostPlatform.system}.swarm-nats-auth;
services.hyperhive.deploy.matrix.ctlPackage =
lib.mkDefault
self.packages.${pkgs.stdenv.hostPlatform.system}.swarm-matrix-ctl;
services.hyperhive.deploy.review-classifier.package =
lib.mkDefault
self.packages.${pkgs.stdenv.hostPlatform.system}.swarm-review-classifier;
services.hyperhive.deploy.builder.package =
lib.mkDefault
self.packages.${pkgs.stdenv.hostPlatform.system}.swarm-builder;
services.hyperhive.deploy.claude-login.package =
lib.mkDefault
self.packages.${pkgs.stdenv.hostPlatform.system}.swarm-claude-login;
services.hyperhive.deploy.authelia.bridgePackage =
lib.mkDefault
self.packages.${pkgs.stdenv.hostPlatform.system}.swarm-authelia-bridge;
services.hyperhive.gateway.swaggerUiTheme =
lib.mkDefault
self.packages.${pkgs.stdenv.hostPlatform.system}.swagger-ui-theme;
};
hive-forge = ./nix/host-modules/hive-forge;
};
nixosConfigurations =
let
# These two configs are what hive-c0re extends per agent
# (meta.rs's `mkAgent` does `<base>.extendModules { … }`, so
# the built base and every container stay on one version).
# Nothing deployment-specific may be set here: it would be
# inherited by every container on the hive and collide with
# the per-agent values hive-c0re renders. The agent modules
# are written so a bare evaluation needs no such values —
# service URLs default to `null`, meaning "not configured",
# and the units that would use them simply aren't generated.
mkContainer =
module:
nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [ module ];
};
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
;
inherit (nixpkgs.lib) nixosSystem;
}
);
};
}