Watch
0
0
Fork
You've already forked hyperhive
0

move the c0re host options to deploy.hive-controller

This commit is contained in:
atlas 2026-10-29 01:02:45 +01:00
commit 74f463c40e
13 changed files with 257 additions and 94 deletions

View file

@ -35,7 +35,7 @@ let
# heavy build inputs (matrix container, forge, etc.) stay out of
# the eval — only option *declarations* matter for the doc walk.
# Import the host-module aggregator from the content-addressed
# nixSrc; the package options (`services.hyperhive.c0re.package`
# nixSrc; the package options (`services.hyperhive.deploy.hive-controller.package`
# etc.) carry no in-module defaults, but with hyperhive disabled
# nothing reads them, so no stubs are needed.
hostEval = nixosSystem {

View file

@ -195,6 +195,65 @@ in
[ "services" "hyperhive" "c0re" "dashboardPort" ]
[ "services" "hyperhive" "deploy" "hive-controller" "dashboardPort" ]
)
# The c0re daemon is the hive-controller service, so its knobs and package
# options sit under it. Same types, same meanings.
(lib.mkRenamedOptionModule
[ "services" "hyperhive" "c0re" "package" ]
[ "services" "hyperhive" "deploy" "hive-controller" "package" ]
)
(lib.mkRenamedOptionModule
[ "services" "hyperhive" "c0re" "frontend" ]
[ "services" "hyperhive" "deploy" "hive-controller" "frontend" ]
)
(lib.mkRenamedOptionModule
[ "services" "hyperhive" "c0re" "assets" ]
[ "services" "hyperhive" "deploy" "hive-controller" "assets" ]
)
(lib.mkRenamedOptionModule
[ "services" "hyperhive" "c0re" "xdgIcons" ]
[ "services" "hyperhive" "deploy" "hive-controller" "xdgIcons" ]
)
(lib.mkRenamedOptionModule
[ "services" "hyperhive" "c0re" "hyperhiveRev" ]
[ "services" "hyperhive" "deploy" "hive-controller" "hyperhiveRev" ]
)
(lib.mkRenamedOptionModule
[ "services" "hyperhive" "c0re" "agentBaseToplevel" ]
[ "services" "hyperhive" "deploy" "hive-controller" "agentBaseToplevel" ]
)
(lib.mkRenamedOptionModule
[ "services" "hyperhive" "c0re" "preBuildAgentTemplates" ]
[ "services" "hyperhive" "deploy" "hive-controller" "preBuildAgentTemplates" ]
)
(lib.mkRenamedOptionModule
[ "services" "hyperhive" "c0re" "modelPrices" ]
[ "services" "hyperhive" "deploy" "hive-controller" "modelPrices" ]
)
(lib.mkRenamedOptionModule
[ "services" "hyperhive" "c0re" "agentCpuQuota" ]
[ "services" "hyperhive" "deploy" "hive-controller" "agentCpuQuota" ]
)
(lib.mkRenamedOptionModule
[ "services" "hyperhive" "c0re" "agentMemoryMax" ]
[ "services" "hyperhive" "deploy" "hive-controller" "agentMemoryMax" ]
)
(lib.mkRenamedOptionModule
[ "services" "hyperhive" "c0re" "agentCpuWeight" ]
[ "services" "hyperhive" "deploy" "hive-controller" "agentCpuWeight" ]
)
(lib.mkRenamedOptionModule
[ "services" "hyperhive" "c0re" "agentIoWeight" ]
[ "services" "hyperhive" "deploy" "hive-controller" "agentIoWeight" ]
)
(lib.mkRenamedOptionModule
[ "services" "hyperhive" "c0re" "buildSlots" ]
[ "services" "hyperhive" "deploy" "hive-controller" "buildSlots" ]
)
(lib.mkRenamedOptionModule
[ "services" "hyperhive" "c0re" "adminUsers" ]
[ "services" "hyperhive" "deploy" "hive-controller" "adminUsers" ]
)
(lib.mkRenamedOptionModule
[ "services" "hyperhive" "otel" "extraResourceAttributes" ]
[ "services" "hyperhive" "deploy" "hive-controller" "otel" "extraResourceAttributes" ]

View file

@ -102,12 +102,12 @@ let
else
import ../../claude-code-no-alsa.nix lib controllerCfg.claudeCodePackage;
dashboard_port = controllerCfg.dashboardPort;
agent_cpu_quota = cfg.agentCpuQuota;
agent_memory_max = cfg.agentMemoryMax;
agent_cpu_weight = cfg.agentCpuWeight;
agent_io_weight = cfg.agentIoWeight;
model_prices = cfg.modelPrices;
build_slots = cfg.buildSlots;
agent_cpu_quota = controllerCfg.agentCpuQuota;
agent_memory_max = controllerCfg.agentMemoryMax;
agent_cpu_weight = controllerCfg.agentCpuWeight;
agent_io_weight = controllerCfg.agentIoWeight;
model_prices = controllerCfg.modelPrices;
build_slots = controllerCfg.buildSlots;
};
in
{
@ -167,11 +167,11 @@ in
];
environment.systemPackages = [
cfg.package
controllerCfg.package
pkgs.git
# XDG icons + .desktop entries so desktop environments can match
# hyperhive processes to their icon (task managers, CPU monitors, etc.).
cfg.xdgIcons
controllerCfg.xdgIcons
];
# Serve config at a stable /etc path so hive-c0re's ExecStart
@ -185,8 +185,8 @@ in
# builds them, they end up in /nix/store, and the first
# nixos-container update + start for an agent has nothing left to
# do. Gated because the closure is sizeable and pinned to x86_64.
system.extraDependencies = lib.optionals cfg.preBuildAgentTemplates [
cfg.agentBaseToplevel
system.extraDependencies = lib.optionals controllerCfg.preBuildAgentTemplates [
controllerCfg.agentBaseToplevel
];
# Unprivileged coordinator user. hive-c0re runs as this user;
@ -205,7 +205,7 @@ in
# hive control (spawn/kill/destroy/deploy, docs/trust-boundary/boundary.md), so this is
# an explicit opt-in allowlist — empty by default (root-only).
users.groups.hive-admin = {
members = cfg.adminUsers;
members = controllerCfg.adminUsers;
};
# Let `hive-admin` members run `hivectl agent <name> choom`
@ -259,7 +259,7 @@ in
];
environment = import ./environment.nix { inherit lib config pkgs; };
serviceConfig = {
ExecStart = "${cfg.package}/bin/hive-c0re --socket /run/hyperhive/host.sock serve --config /etc/hyperhive/serve.json";
ExecStart = "${controllerCfg.package}/bin/hive-c0re --socket /run/hyperhive/host.sock serve --config /etc/hyperhive/serve.json";
SyslogIdentifier = "hive-c0re";
# Migrate hive-c0re's *own* state to the service user after an
# upgrade from a root-run install (systemd's StateDirectory only
@ -378,7 +378,7 @@ in
# Must match the `--socket` arg passed to `hive-c0re serve`.
ListenStream = "/run/hyperhive/host.sock";
# `0660 root:hive-admin` — group-owned by `hive-admin` so operators in
# that group (services.hyperhive.c0re.adminUsers) drive `hivectl`
# that group (services.hyperhive.deploy.hive-controller.adminUsers) drive `hivectl`
# without root; an empty adminUsers list leaves the group memberless,
# so it stays effectively root-only. See docs/trust-boundary/boundary.md.
SocketMode = "0660";

View file

@ -8,7 +8,7 @@
pkgs,
}:
let
cfg = config.services.hyperhive.c0re;
controllerCfg = config.services.hyperhive.deploy.hive-controller;
baoDeploy = config.services.hyperhive.deploy.bao;
baoCfg = config.services.hyperhive.swarm.bao;
# A reader is defined by holding a certificate the store accepts, not by
@ -64,7 +64,7 @@ in
# nix store directly; dynamic API paths still proxied to the
# agent daemon. nginx runs on the host, which is where this
# store path already is, so it is reachable as written.
HIVE_AGENT_FRONTEND_DIR = "${cfg.servedFrontend}/agent";
HIVE_AGENT_FRONTEND_DIR = "${controllerCfg.servedFrontend}/agent";
}
// {
# Identity env vars threaded into c0re's own service env and

View file

@ -1,11 +1,12 @@
# Option declarations for `services.hyperhive.c0re.*` — the c0re
# daemon's knobs plus the package/source options the flake's
# `nixosModules.default` wires to its own outputs (they carry no
# in-module defaults; see ../../../flake.nix). The read-only
# Option declarations for the c0re daemon. `services.hyperhive.c0re.*`
# keeps the two flake-source options the swarm builder also reads
# (`hyperhiveFlake`, `nixpkgsFlake`); every other knob and the
# package/asset options the flake's `nixosModules.default` wires to its
# own outputs (they carry no in-module defaults; see ../../../flake.nix)
# live under `services.hyperhive.deploy.hive-controller`, alongside the
# values the meta-flake render hands the hive's agents. The read-only
# `servedFrontend` option lives in ./theme.nix with the stylix wiring
# that computes it. The hive's own values the meta-flake render hands
# its agents are declared here too, under
# `services.hyperhive.deploy.hive-controller`.
# that computes it.
{
pkgs,
lib,
@ -23,6 +24,33 @@
];
options.services.hyperhive.c0re = {
hyperhiveFlake = lib.mkOption {
type = lib.types.str;
defaultText = lib.literalMD "the hyperhive flake's own filtered source store path";
description = ''
Store path of the hyperhive flake source (no fragment). hive-c0re
reads it to tell which revision is running, and the swarm builder
builds on it. Wired by `nixosModules.default` to this flake's own
filtered source.
'';
};
nixpkgsFlake = lib.mkOption {
type = lib.types.str;
default = "path:${pkgs.path}";
defaultText = lib.literalMD "`\"path:\${pkgs.path}\"`";
description = ''
Store-path URL of the nixpkgs the swarm builder's check flake
builds on. The swarm meta flake does not use it: its `nixpkgs`
follows `hyperhive/nixpkgs`.
Defaults to `"path:''${pkgs.path}"`, the nixpkgs the host NixOS
module was evaluated with.
'';
};
};
# The hive-controller's deployment options.
options.services.hyperhive.deploy.hive-controller = {
package = lib.mkOption {
type = lib.types.package;
defaultText = lib.literalExpression "hyperhive.packages.\${system}.default";
@ -71,16 +99,6 @@
processes to their icon.
'';
};
hyperhiveFlake = lib.mkOption {
type = lib.types.str;
defaultText = lib.literalMD "the hyperhive flake's own filtered source store path";
description = ''
Store path of the hyperhive flake source (no fragment). hive-c0re
reads it to tell which revision is running, and the swarm builder
builds on it. Wired by `nixosModules.default` to this flake's own
filtered source.
'';
};
hyperhiveRev = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
@ -105,19 +123,6 @@
enabled.
'';
};
nixpkgsFlake = lib.mkOption {
type = lib.types.str;
default = "path:${pkgs.path}";
defaultText = lib.literalMD "`\"path:\${pkgs.path}\"`";
description = ''
Store-path URL of the nixpkgs the swarm builder's check flake
builds on. The swarm meta flake does not use it: its `nixpkgs`
follows `hyperhive/nixpkgs`.
Defaults to `"path:''${pkgs.path}"`, the nixpkgs the host NixOS
module was evaluated with.
'';
};
preBuildAgentTemplates = lib.mkOption {
type = lib.types.bool;
default = false;
@ -325,12 +330,7 @@
socket root-only, as before.
'';
};
};
# What this hive hands its own agents at container start, through
# `/etc/hyperhive-hive-facts`. Per host, so two hives may set them
# differently.
options.services.hyperhive.deploy.hive-controller = {
claudeCodePackage = lib.mkOption {
type = lib.types.nullOr lib.types.package;
default = null;

View file

@ -6,7 +6,7 @@
# the entire swap contract — `theme.css` derives every semantic var
# from the 16 base16 slots (see docs/web-ui/css-vars.md). The guarded
# access makes this a clean no-op when stylix isn't imported into the
# host config. Exposed as the read-only `c0re.servedFrontend` option.
# host config. Exposed as the read-only `deploy.hive-controller.servedFrontend` option.
#
# Detection + CSS-generation itself lives in `../stylix-theme.nix`
# (shared with `swarm-ui.nix`) — this file is the c0re-specific overlay
@ -18,7 +18,7 @@
...
}:
let
cfg = config.services.hyperhive.c0re;
cfg = config.services.hyperhive.deploy.hive-controller;
stylixTheme = import ../stylix-theme.nix { inherit lib config pkgs; };
inherit (stylixTheme) stylixThemeColors themedColorsCss;
# Overlay the generated colors.css onto both dist subtrees. Both the
@ -42,7 +42,7 @@ let
'';
in
{
options.services.hyperhive.c0re.servedFrontend = lib.mkOption {
options.services.hyperhive.deploy.hive-controller.servedFrontend = lib.mkOption {
type = lib.types.package;
internal = true;
readOnly = true;

View file

@ -17,7 +17,7 @@ let
inherit (gatewayCfg.lib) errorPages;
# Dashboard SPA dist, static-served by nginx.
dashboardDist = "${hyperhiveCfg.c0re.servedFrontend}/dashboard";
dashboardDist = "${hyperhiveCfg.deploy.hive-controller.servedFrontend}/dashboard";
# `/agent/` catch-all 404 + the two internal error-page targets it
# points at. Per-agent `location /agent/<name>/` blocks live in the

View file

@ -8,7 +8,7 @@
...
}:
let
cfg = config.services.hyperhive.c0re;
controllerCfg = config.services.hyperhive.deploy.hive-controller;
forgeGit = import ./lib/hive-forge-git.nix {
inherit pkgs;
@ -114,7 +114,7 @@ in
HIVE_PRIV_FORGE_URL = "http://${config.services.hyperhive.swarm.forge.domain}";
};
serviceConfig = {
ExecStart = "${cfg.package}/bin/hive-priv";
ExecStart = "${controllerCfg.package}/bin/hive-priv";
SyslogIdentifier = "hive-priv";
Type = "simple";
User = "root";

View file

@ -28,7 +28,7 @@
# Attributes for `systemd.services."container@<name>"`.
warnOnly = name: {
serviceConfig.ExecStartPre = [
"-${config.services.hyperhive.c0re.package}/bin/hive-priv check-rootfs --warn-only ${name}"
"-${config.services.hyperhive.deploy.hive-controller.package}/bin/hive-priv check-rootfs --warn-only ${name}"
];
};
}

View file

@ -209,7 +209,7 @@ let
if deployCfg.swarm-controller.configOrgAvatarPng != null then
"${deployCfg.swarm-controller.configOrgAvatarPng}"
else
"${config.services.hyperhive.c0re.assets}/share/hyperhive/branding/agent-configs.png";
"${config.services.hyperhive.deploy.hive-controller.assets}/share/hyperhive/branding/agent-configs.png";
};
# How the forge must address this controller to deliver a swarm-wide
@ -288,7 +288,7 @@ let
github_disabled = !swarmCfg.github.enable;
# The rev this controller runs, which `internal/meta` pins hyperhive
# to once it descends from the committed pin; null on a dirty tree.
hyperhive_rev = config.services.hyperhive.c0re.hyperhiveRev;
hyperhive_rev = config.services.hyperhive.deploy.hive-controller.hyperhiveRev;
};
};
@ -540,7 +540,7 @@ in
marker-guarded — delete `forge-config-org-avatar-set` under the
controller's state directory to force a re-upload after changing
this). Defaults (`null`) to the bundled `agent-configs.png` from
{option}`services.hyperhive.c0re.assets`. Set this to override just
{option}`services.hyperhive.deploy.hive-controller.assets`. Set this to override just
the org avatar without replacing the whole `assets` package.
'';
};

View file

@ -280,7 +280,7 @@ let
settings = builtins.fromJSON allLocal.systemd.services.swarm-controller.environment.SWARM_CONTROLLER_META_FLAKE;
in
settings ? hyperhive_rev
&& settings.hyperhive_rev == allLocal.services.hyperhive.c0re.hyperhiveRev
&& settings.hyperhive_rev == allLocal.services.hyperhive.deploy.hive-controller.hyperhiveRev
&& settings ? git_base;
}
{
@ -1412,6 +1412,106 @@ let
&& warned old
&& !(warned bare);
}
{
# Every c0re option that moved under `deploy.hive-controller` still
# takes its old spelling, with one rename warning each, and lands at
# the new path. The packages are distinct stand-ins so a crossed
# forward shows. `bare` is the control that the warning is not always
# present, and the new path is what the rendered values below read.
name = "services.hyperhive.c0re.* options that moved still set deploy.hive-controller.*, with rename warnings";
ok =
let
old = hiveWithController {
c0re.package = pkgs.hello;
c0re.frontend = pkgs.coreutils;
c0re.assets = pkgs.gnused;
c0re.xdgIcons = pkgs.gnugrep;
c0re.agentBaseToplevel = pkgs.findutils;
c0re.hyperhiveRev = "abc123";
c0re.preBuildAgentTemplates = true;
c0re.modelPrices.sonnet = {
input = 1.0;
output = 2.0;
cache_read = 3.0;
cache_write = 4.0;
};
c0re.agentCpuQuota = "300%";
c0re.agentMemoryMax = "8G";
c0re.agentCpuWeight = 50;
c0re.agentIoWeight = null;
c0re.buildSlots = 3;
c0re.adminUsers = [ "alice" ];
};
c = old.services.hyperhive.deploy.hive-controller;
warned =
m: name:
lib.any (
w: lib.hasInfix "services.hyperhive.c0re.${name}" w && lib.hasInfix "has been renamed" w
) m.warnings;
serve = builtins.fromJSON (
builtins.unsafeDiscardStringContext old.environment.etc."hyperhive/serve.json".text
);
path = p: builtins.unsafeDiscardStringContext "${p}";
in
path c.package == path pkgs.hello
&& path c.frontend == path pkgs.coreutils
&& path c.assets == path pkgs.gnused
&& path c.xdgIcons == path pkgs.gnugrep
&& path c.agentBaseToplevel == path pkgs.findutils
&& path c.servedFrontend == path pkgs.coreutils
&& c.hyperhiveRev == "abc123"
&& c.preBuildAgentTemplates
&& c.modelPrices.sonnet.cache_write == 4.0
&& c.agentCpuQuota == "300%"
&& c.agentMemoryMax == "8G"
&& c.agentCpuWeight == 50
&& c.agentIoWeight == null
&& c.buildSlots == 3
&& c.adminUsers == [ "alice" ]
&& lib.all (warned old) [
"package"
"frontend"
"assets"
"xdgIcons"
"agentBaseToplevel"
"hyperhiveRev"
"preBuildAgentTemplates"
"modelPrices"
"agentCpuQuota"
"agentMemoryMax"
"agentCpuWeight"
"agentIoWeight"
"buildSlots"
"adminUsers"
]
&& !(warned bare "package")
&& !(warned bare "adminUsers")
&& serve.agent_memory_max == "8G"
&& serve.agent_cpu_quota == "300%"
&& serve.build_slots == 3
&& old.users.groups.hive-admin.members == [ "alice" ];
}
{
# What the moved options render with their defaults: the agent limits
# and build slots in serve.json, the sudoless-hivectl group's members,
# and the model price table. The same values the options held at their
# old paths.
name = "the agent limits, build slots, admin group and price table render their defaults unchanged";
ok =
let
serve = builtins.fromJSON (
builtins.unsafeDiscardStringContext allLocal.environment.etc."hyperhive/serve.json".text
);
in
serve.agent_memory_max == "4G"
&& serve.agent_cpu_quota == "200%"
&& serve.agent_cpu_weight == 80
&& serve.agent_io_weight == 80
&& serve.build_slots == 1
&& serve.model_prices.opus.input == 5.0
&& serve.model_prices.haiku.cache_write == 2.0
&& allLocal.users.groups.hive-admin.members == [ ];
}
{
# A hive's rendered identity, pinned so moving the option changes none of
# it: the coordinator's name and client, the dashboard name, the CN of the
@ -1526,7 +1626,7 @@ let
carries =
m: name:
checks m name == [
(builtins.unsafeDiscardStringContext "-${m.services.hyperhive.c0re.package}/bin/hive-priv check-rootfs --warn-only ${name}")
(builtins.unsafeDiscardStringContext "-${m.services.hyperhive.deploy.hive-controller.package}/bin/hive-priv check-rootfs --warn-only ${name}")
];
names = lib.attrNames allLocal.containers;
in

View file

@ -140,7 +140,7 @@ in
{
# All workspace binaries in one derivation — a symlinkJoin over the
# per-bin packages, pure assembly with no extra compilation. The
# host module's `services.hyperhive.c0re.package` and `nix build .#`
# host module's `services.hyperhive.deploy.hive-controller.package` and `nix build .#`
# both land here.
default = pkgs.symlinkJoin {
name = "hyperhive";
@ -152,7 +152,7 @@ in
# Swarm-level controller daemon. Deliberately NOT in `daemonBins`:
# that list is the core stack — the binaries hive-c0re and the agent
# harness are made of — and it drives the `default` bundle that
# `services.hyperhive.c0re.package` points at. This daemon is a
# `services.hyperhive.deploy.hive-controller.package` points at. This daemon is a
# separate swarm-scoped service with its own module and its own
# `package` option, and one hive in a swarm runs it, so folding it
# into the core bundle would put it in every hive's closure to no end.

View file

@ -46,7 +46,7 @@ let
{ lib, ... }:
{
options.services.hyperhive = {
c0re.package = lib.mkOption { type = lib.types.package; };
deploy.hive-controller.package = lib.mkOption { type = lib.types.package; };
deploy.hive-controller.enable = lib.mkOption {
type = lib.types.bool;
default = true;
@ -56,7 +56,7 @@ let
default = "forge.t.local";
};
};
config.services.hyperhive.c0re.package = hivePriv;
config.services.hyperhive.deploy.hive-controller.package = hivePriv;
# hive-priv.socket's group, which hive-c0re's module creates.
config.users.groups.hive-core = { };
};