hyperhive — host options
services.hyperhive.enable
Whether to enable hyperhive — the agent swarm coordinator.
Type: boolean
Default:
false
Example:
true
Declared by:
services.hyperhive.enableAllLocalDefaults
Run the whole swarm on this host. Turning this on asserts the
swarm-level toggles that an all-on-one-box deployment implies:
the swarm’s shared services
(services.hyperhive.swarm.enableRequiredServices), the swarm
CA (services.hyperhive.swarm.ca.autoConfigure), and the swarm
controller (services.hyperhive.swarm.controller.enable).
Off by default, and that is the load-bearing part. A swarm’s services and its hives can live on different hosts, and a host has no way to tell which ones it is meant to be — so this is an operator saying “this is that box”, never something inferred. Turn it on for a dev box or a single-hive swarm and get a working deployment with no further configuration; leave it off and every swarm-level artifact is operator-provided.
Each toggle it asserts can still be set explicitly, which wins — so “all local except X” needs no new option.
Type: boolean
Default:
false
Example:
true
Declared by:
services.hyperhive.c0re.enable
Enable hive-c0re coordinator daemon (auto-enabled by services.hyperhive.enable).
Type: boolean
Default:
config.services.hyperhive.enable
Declared by:
services.hyperhive.c0re.package
hyperhive workspace package. Provides /bin/hive-c0re
(coordinator daemon) and /bin/hivectl (operator-facing host
CLI for ad-hoc administration + the host admin socket). Wired to
this flake’s packages.<system>.default by
nixosModules.default (via lib.mkDefault, so setting it here
wins).
Type: package
Default:
hyperhive.packages.${system}.default
Declared by:
services.hyperhive.c0re.adminUsers
Login users granted sudoless hivectl. Each is added to the
hive-admin group, which group-owns the host admin socket
(/run/hyperhive/host.sock, mode 0660) — so listed users drive
hivectl (and thus the whole hive) without sudo.
This is a real privilege grant: the admin socket is full hive
control — spawn / kill / destroy / deploy — see docs/boundary.md.
Keep the list to trusted operators. Empty (the default) keeps the
socket root-only, as before.
Type: list of string
Default:
[ ]
Example:
[
"alice"
]
Declared by:
services.hyperhive.c0re.agentBaseToplevel
Pre-built agent-base container system closure, pulled into the
host system closure when preBuildAgentTemplates is on. Wired
by nixosModules.default; only evaluated when that option is
enabled.
Type: package
Default:
hyperhive.packages.x86_64-linux.agent-base-toplevel
Declared by:
services.hyperhive.c0re.agentCpuQuota
systemd CPUQuota= applied to every agent container via a
container@h-<name>.service.d/ drop-in written on each
spawn/rebuild. Expressed as a percentage of one CPU core —
"200%" allows each agent to use up to 2 cores. Bump this if
agents are hitting CPU limits during builds or heavy tool use.
For a hive-wide cap across all containers, set
systemd.slices.machine.serviceConfig.CPUQuota in your NixOS
config (all nspawn containers live in machine.slice).
Type: string
Default:
"200%"
Example:
"400%"
Declared by:
services.hyperhive.c0re.agentCpuWeight
systemd CPUWeight= applied to every agent container via the
same drop-in as agentCpuQuota. This is the cgroup v2
cpu.weight relative share, not a cap: a low-weight
container still gets the whole machine when nothing else wants
it, and the weight only decides who yields under contention.
That makes it the complement of agentCpuQuota, which throttles
even on an idle host.
The kernel default is 100. The hyperhive default of 80 means
agent containers yield slightly to everything that is not on
this drop-in path — host services and the infrastructure
containers (hive-ci, hive-forge, hive-gateway,
hive-matrix), which stay at 100. Note this is a hive-wide
value, so it does not rank agents against each other: they all
share one weight.
Set to null to leave CPUWeight= out of the drop-in entirely —
the container then inherits the kernel default and the generated
unit file is identical to one from before this option existed.
Type: null or integer between 1 and 10000 (both inclusive)
Default:
80
Example:
null
Declared by:
services.hyperhive.c0re.agentIoWeight
systemd IOWeight= applied to every agent container via the
same drop-in as agentCpuQuota — the block-IO counterpart of
agentCpuWeight, with the same relative-share, contention-only
semantics.
Caveat: IOWeight= maps to the cgroup v2 io.weight knob, which
is only honoured when the io.cost (blk-iocost) controller is
enabled for the backing device, or when the device uses the BFQ
scheduler. On a host running none/mq-deadline/kyber without
iocost QoS configured, systemd writes the value and the kernel
ignores it — harmless, but it will measure as a no-op. Check with
cat /sys/fs/cgroup/io.cost.qos on the host, and set this to
null to omit the setting rather than write one nothing reads.
Type: null or integer between 1 and 10000 (both inclusive)
Default:
80
Example:
null
Declared by:
services.hyperhive.c0re.agentMemoryMax
systemd MemoryMax= applied to every agent container via the
same drop-in as agentCpuQuota.
Type: string
Default:
"4G"
Example:
"8G"
Declared by:
services.hyperhive.c0re.assets
Bundled static runtime assets (see nix/packages/assets.nix): the
project’s branding family + the claude system-prompt template +
claude-settings JSON. Output has share/hyperhive/{branding,prompts}/;
passed to hive-c0re’s systemd unit via HIVE_ASSETS_DIR
(hive_sh4re::assets::* resolve paths underneath). Override to
ship customised branding or prompts without rebuilding the
rust derivation.
Type: package
Default:
hyperhive.packages.${system}.assets
Declared by:
services.hyperhive.c0re.buildSlots
Number of nix-heavy job-queue nodes (container prebuilds, profile swaps, first-spawn creates, meta lock bumps) hive-c0re runs concurrently. The default of 1 serializes all heavy nix work; raise it on hosts with the cores/RAM to build several agent toplevels at once. Per-agent correctness is independent of this count — each agent’s container-affecting operations are serialized by its lifecycle lease regardless.
Type: positive integer, meaning >0
Default:
1
Example:
2
Declared by:
services.hyperhive.c0re.claudeCodePackage
The claude-code build every agent runs, or null (the
default) to leave each agent on the claude-code from its own
nixpkgs — i.e. whatever nixpkgsFlake resolves to.
This is the one binary the whole hive is built around, and it
moves fast enough that a release channel routinely trails
unstable by weeks on it. An agent cannot fix that for itself:
agents evaluate against the single nixpkgs hive-c0re injects,
so an agent.nix has no other tree to reach for. Set this from
a second nixpkgs in the host flake and every agent follows,
without moving the nixpkgs the rest of the container is built
from.
What travels into the container is the store path, not the
derivation: agents share the host’s /nix/store, so the binary
and its full closure are already reachable there — nothing
needs rebuilding or copying. hive-c0re writes the path into
each agent’s generated flake as a plain string literal (a bare
path fed to lib.types.package would run builtins.storePath,
which is illegal under pure evaluation) and the agent module
puts its bin/ on the harness’s PATH.
The flip side of a plain string is that nothing in the agent’s
own closure refers to it, so the container cannot keep it
alive. The host does that instead: this package is
interpolated into /etc/hyperhive/serve.json, which puts it in
the host’s system closure — so it is gc-rooted by the running
generation for exactly as long as that generation is the one
the agents were rendered from. The cost is that
nix-collect-garbage cannot reclaim an old claude-code until
every agent has been rebuilt past it and the old generations
are gone.
Type: null or package
Default:
null
Example:
inputs.nixpkgs-unstable.legacyPackages.x86_64-linux.claude-code
Declared by:
services.hyperhive.c0re.contextWindowTokens
Per-model context-window sizes in tokens. Each key is a
model-family short name matched case-insensitively as a
substring of the active model name at runtime (e.g. "sonnet"
matches "claude-sonnet-4-5"). The defaults cover the known
Anthropic families; add entries for new models or override
existing ones here to change the window for all agents at once.
Passed to hive-c0re serve as JSON and injected into every
container’s harness service environment as
HIVE_CONTEXT_WINDOW_TOKENS_<KEY_UPPER>. Changes propagate
on the next ↻ R3BU1LD — no per-agent approval needed.
Type: attribute set of signed integer
Default:
{
haiku = 200000;
opus = 1000000;
sonnet = 1000000;
}
Example:
{
haiku = 150000;
sonnet = 900000;
}
Declared by:
services.hyperhive.c0re.dashboardPort
TCP port the hive-c0re dashboard listens on.
Type: 16 bit unsigned integer; between 0 and 65535 (both inclusive)
Default:
7000
Declared by:
services.hyperhive.c0re.frontend
Bundled frontend dist (see nix/packages/frontend.nix). Output
has dashboard/ and agent/ subdirectories — hive-c0re serves
dashboard/ via tower_http::ServeDir from the path passed
in HIVE_STATIC_DIR. Override to ship a custom dashboard SPA;
the JSON contract (/api/state, the SSE streams, the action
endpoints) is the source of truth for any replacement.
Type: package
Default:
hyperhive.packages.${system}.frontend
Declared by:
services.hyperhive.c0re.hyperhiveDocs
URL of the narrow docs/ source (no fragment). Inlined into the
generated meta flake.nix at inputs.hyperhive-docs.url and
threaded to each agent as hyperhive.docs.source, from which the
harness resolves $HIVE_DOCS_DIR. Its own store path — separate
from hyperhiveFlake — so a doc edit only re-locks this input
instead of rebuilding every agent container.
Type: string
Default: the docs/ tree’s own store path
Declared by:
services.hyperhive.c0re.hyperhiveFlake
URL of the hyperhive flake (no fragment). Inlined into each
per-agent flake.nix at inputs.hyperhive.url. The per-agent
flake then pulls hyperhive.nixosConfigurations.agent-base to
build the container. Wired by nixosModules.default to this
flake’s own filtered source — only override if you want agents
tracking a different ref.
Type: string
Default: the hyperhive flake’s own filtered source store path
Declared by:
services.hyperhive.c0re.managerToplevel
Pre-built manager (ruth) container system closure — see
agentBaseToplevel.
Type: package
Default:
hyperhive.packages.x86_64-linux.ruth-toplevel
Declared by:
services.hyperhive.c0re.modelPrices
Per-model USD prices (per million tokens) used for the
hive-wide cost estimate on the dashboard’s ST4TS tab. Each key
is a model-family short name matched case-insensitively as a
substring of the active model id at runtime (e.g. "sonnet"
matches "claude-sonnet-4-5"); the longest matching key wins, so
a specific entry beats a generic family name. Any model not
covered by this table falls back to hive-c0re’s built-in
estimate.
The defaults track Anthropic list pricing at the time of
writing — override them here to keep the estimate current
without a code change. Passed to hive-c0re serve as JSON via
--model-prices; read only by hive-c0re itself (not injected
into containers). Changes apply on the next host rebuild.
Type: attribute set of (submodule)
Default:
{
haiku = {
cache_read = 0.1;
cache_write = 2.0;
input = 1.0;
output = 5.0;
};
opus = {
cache_read = 0.5;
cache_write = 10.0;
input = 5.0;
output = 25.0;
};
sonnet = {
cache_read = 0.3;
cache_write = 6.0;
input = 3.0;
output = 15.0;
};
}
Example:
{
sonnet = {
cache_read = 0.3;
cache_write = 6.0;
input = 3.0;
output = 15.0;
};
}
Declared by:
services.hyperhive.c0re.modelPrices.<name>.cache_read
USD per million cache-read tokens.
Type: nonnegative integer or floating point number, meaning >=0
Declared by:
services.hyperhive.c0re.modelPrices.<name>.cache_write
USD per million cache-creation (write) tokens.
Type: nonnegative integer or floating point number, meaning >=0
Declared by:
services.hyperhive.c0re.modelPrices.<name>.input
USD per million input tokens.
Type: nonnegative integer or floating point number, meaning >=0
Declared by:
services.hyperhive.c0re.modelPrices.<name>.output
USD per million output tokens.
Type: nonnegative integer or floating point number, meaning >=0
Declared by:
services.hyperhive.c0re.nixpkgsFlake
Store-path URL for the nixpkgs input in the generated meta
flake. The meta flake declares this as a top-level input and
wires inputs.hyperhive.inputs.nixpkgs.follows = "nixpkgs" so
every agent container evaluates with this exact nixpkgs.
Defaults to "path:${pkgs.path}" — the store path of the
nixpkgs the host NixOS module was evaluated with. When the
operator sets inputs.hyperhive.inputs.nixpkgs.follows = "nixpkgs" in their host flake, pkgs.path resolves to the
host’s own nixpkgs, so agents transparently track the same
channel as the host.
Override to pin agents to a specific nixpkgs version regardless of the host’s channel.
Type: string
Default:
"path:${pkgs.path}"
Declared by:
services.hyperhive.c0re.operatorPronouns
Operator pronouns, free text. Threaded into every agent
container as the HIVE_OPERATOR_PRONOUNS env var; the
harness substitutes it into the agent / manager system
prompt at boot so claude refers to the operator naturally
in third person (“ask her”, “tell them”, etc.). Changes
propagate to running agents on the next ↻ R3BU1LD —
forwards as a meta flake env-var bump, no per-agent
approval needed.
Type: string
Default:
"she/her"
Example:
"they/them"
Declared by:
services.hyperhive.c0re.orgAvatarPng
PNG uploaded once as the agent-configs Forgejo org’s avatar
(forge::users::ensure_config_org_avatar, one-shot,
marker-guarded — delete forge/agent-configs-avatar-set under
the state dir to force a re-upload after changing this).
Defaults (null) to the bundled agent-configs.png from
assets. Set this to override just the org avatar without
replacing the whole assets package.
Type: null or absolute path
Default:
null
Declared by:
services.hyperhive.c0re.preBuildAgentTemplates
Pre-fetch the per-container system closures (agent-base + manager toplevels) into the host’s /nix/store as part of this host’s NixOS build, instead of letting the first agent spawn do all the work.
Enabling this adds roughly the full nixpkgs runtime closure +
claude-code + the harness binary to your system closure size
(low single-digit GB), but the first nixos-container start
for any agent then completes in seconds instead of minutes
because nothing’s left to fetch.
Off by default because the toplevels are pinned to
x86_64-linux (nixos-containers run native arch). Enabling
on an aarch64 host would force nix to build the x86 closure
via cross or a remote builder, which is rarely what you want.
Flip to true on an x86_64 host when you care more about
first-spawn latency than host store size — or just
nix build .#agent-base-toplevel once manually to warm the
store.
Type: boolean
Default:
false
Example:
true
Declared by:
services.hyperhive.c0re.xdgIcons
XDG icon set + .desktop entries for hyperhive processes (see
nix/packages/hive-xdg-icons.nix), installed into the host
system packages so desktop environments can match hyperhive
processes to their icon.
Type: package
Default:
hyperhive.packages.${system}.xdg-icons
Declared by:
services.hyperhive.domain
Canonical host domain for hyperhive subsystems that need a
stable name (currently: services.hyperhive.swarm.matrix.serverName
derives from this, defaulting to
matrix.${services.hyperhive.domain} when serverName is
null). Required when services.hyperhive.enable — eval fails
with a helpful message if it’s unset (it’s baked into matrix on
first boot and drives the gateway/forge/agent URLs, with no safe
default; changing it later is destructive). Exposed to agents as
HYPERHIVE_HIVE_DOMAIN; consumed by
hive-agent::identity::hive_domain() for <name>@<domain>
qualified labels.
Deprecated as a place to write. It is read from this hive’s
own entry in services.hyperhive.swarm.hives, whose domain
defaults to <hiveName>.<swarm.domain> — so a conventional hive
states nothing at all, and a non-conventional one states its
address in the directory every other hive reads. Setting it here
still wins and still works, with a warning: the directory is
shared, this option is not, so a value written only here is
invisible to the rest of the swarm.
Type: null or string
Default:
services.hyperhive.swarm.hives.${hiveName}.domain, or null when there is no entry for this hive
Example:
"darkest.space"
Declared by:
services.hyperhive.gateway.auth.enable
Whether to enable HTTP basic auth on the gateway using an htpasswd file. When
enabled, every request to the gateway’s main vhost requires a
valid username and password. nginx’s built-in auth_basic
module validates credentials against
/var/lib/hive-gateway/conf/gateway.htpasswd. Off by default.
Manage users with hivectl gateway create-user, delete-user,
and list-users — see hivectl gateway --help for usage.
The htpasswd file is created automatically when auth is enabled;
add at least one user before enabling to avoid locking everyone out.
.
Type: boolean
Default:
false
Example:
true
Declared by:
services.hyperhive.gateway.auth.realm
HTTP Basic auth realm value sent in the WWW-Authenticate
header when credentials are absent or rejected. Must not
contain " or $ (nginx string metacharacters).
Type: string matching the pattern [^"$]*
Default:
"hyperhive"
Example:
"my-hive"
Declared by:
services.hyperhive.gateway.hsts.enable
Add Strict-Transport-Security to all gateway vhosts.
Disabled by default: HSTS pins HTTPS in the browser’s HSTS preload list; enabling it on a deployment that later loses TLS will lock browsers out until the max-age expires. Only enable this when you are certain TLS is permanent.
The gateway always terminates TLS (self-signed floor), so HSTS is always served over https when enabled — but mind the warning above: HSTS pins https in the browser, so only enable it when TLS is permanent for this deployment.
Type: boolean
Default:
false
Declared by:
services.hyperhive.gateway.hsts.includeSubDomains
Whether to include includeSubDomains in the HSTS header.
Only disable this if the gateway host has sub-domains that
intentionally serve plain HTTP.
Type: boolean
Default:
true
Declared by:
services.hyperhive.gateway.hsts.maxAge
Value for the max-age directive in seconds.
Default: 31536000 (1 year), which is the value required for
HSTS preload list submission. Use a shorter value (e.g. 86400)
while testing so browsers forget the pin quickly.
Type: positive integer, meaning >0
Default:
31536000
Example:
86400
Declared by:
services.hyperhive.gateway.httpsPort
TCP port for the TLS-terminated vhosts. Default 443. The gateway
always terminates TLS (self-signed is the implicit floor when no
tls.certDir / ACME is configured), so this port is always active
alongside the plain-http port.
Type: 16 bit unsigned integer; between 0 and 65535 (both inclusive)
Default:
443
Example:
8443
Declared by:
services.hyperhive.gateway.localHostsEntry
Add an /etc/hosts entry mapping services.hyperhive.domain
to 127.0.0.1 on the host. Useful for local deployments +
tests where there’s no real DNS for services.hyperhive.domain
but the operator (or browser-based tests) want to hit
http://${services.hyperhive.domain} to exercise the
gateway shape. Off by default — operators running with real
DNS shouldn’t have a stale /etc/hosts entry sticking
around. Requires services.hyperhive.domain to be set.
Type: boolean
Default:
false
Example:
true
Declared by:
services.hyperhive.gateway.openFirewall
Open port in the host firewall. Off by default (secure-by-default).
Flip to true to expose the gateway to
the operator’s browser / external clients — required for any
out-of-host reach, since the agents themselves talk to
hive-c0re via the per-agent unix sockets and don’t need the
nginx vhost. Leave off when running behind another reverse
proxy (e.g. caddy / traefik on the host) that handles TLS
termination + forwards to port.
Note: this used to default to true. Add
services.hyperhive.gateway.openFirewall = true; to your host
config if external reach stopped working after a recent upgrade.
Type: boolean
Default:
false
Example:
true
Declared by:
services.hyperhive.gateway.port
TCP port the gateway listens on. Default 80 (canonical web port). nginx runs on the host as root, so it can bind <1024; if 80 is already taken on the host (existing nginx, traefik, etc.) override to an unused port like 8080 or move the conflicting service.
Type: 16 bit unsigned integer; between 0 and 65535 (both inclusive)
Default:
80
Example:
8080
Declared by:
services.hyperhive.gateway.swaggerUiTheme
Full Swagger UI static dist, hyperhive-themed (see
nix/packages/swagger-ui-theme.nix, built on
nix/packages/swagger-ui-dist.nix). The gateway serves this
whole tree directly at /api/docs/ — hive-c0re hosts none of
it, only the dynamic /api/openapi.json route (proxied
through, unaffected by this option). Override to ship a
custom theme (or the plain vendored dist) without a gateway
rebuild.
Type: package
Default:
hyperhive.packages.${system}.swagger-ui-theme
Declared by:
services.hyperhive.gateway.tls.acme.enable
Let the gateway’s nginx obtain and renew TLS certificates
automatically via ACME (Let’s Encrypt). When enabled, each
vhost calls out to Let’s Encrypt using the HTTP-01
challenge on port (default 80) and stores certs in the
gateway’s persistent state dir on the host.
Requirements:
services.hyperhive.domainmust be set and publicly DNS-resolvable to this host.services.hyperhive.gateway.openFirewall = trueso Let’s Encrypt can reach/.well-known/acme-challenge/.tls.acme.emailmust be set (ACME account contact).
Mutual exclusion: tls.certDir set together with
tls.acme.enable = true fails at eval — pick one TLS source.
Typical setup:
services.hyperhive.gateway = {
openFirewall = true;
tls.acme = {
enable = true;
email = "admin@example.com";
};
};
After enabling, this hive’s entry in swarm.hives can omit
certFingerprint — Let’s Encrypt certs are CA-trusted
by default.
Type: boolean
Default:
false
Example:
true
Declared by:
services.hyperhive.gateway.tls.acme.email
Email address for the ACME account registration with
Let’s Encrypt. Required when tls.acme.enable = true.
Let’s Encrypt sends expiry warnings to this address.
Type: null or string
Default:
null
Example:
"admin@example.com"
Declared by:
services.hyperhive.gateway.tls.certDir
Path to a host directory containing a TLS certificate and
private key for nginx. When set, nginx listens on httpsPort
and uses this cert, overriding the self-signed default — the
auto-generated hive-CA-signed leaf is skipped entirely.
nginx reads <certDir>/<tls.certName> and
<certDir>/<tls.keyName> directly — it runs on the host, so
the directory needs no bind mount and no copy.
Default filenames (cert.pem / key.pem) match the output
layout of nixpkgs’s security.acme module.
Typical ACME setup:
security.acme.certs."example.com" = { ... };
services.hyperhive.gateway.tls.certDir =
config.security.acme.certs."example.com".directory;
When using an external CA cert, other hives can declare this
one in services.hyperhive.swarm.hives without
certFingerprint — the standard CA bundle validates.
Mutual exclusion with tls.acme.enable — set one or the other,
not both.
Type: null or absolute path
Default:
null
Example:
"/var/lib/acme/example.com"
Declared by:
services.hyperhive.gateway.tls.certName
Filename of the TLS certificate within tls.certDir. Defaults
to cert.pem which matches nixpkgs’s security.acme output.
Type: string
Default:
"cert.pem"
Declared by:
services.hyperhive.gateway.tls.keyName
Filename of the TLS private key within tls.certDir. Defaults
to key.pem which matches nixpkgs’s security.acme output.
Type: string
Default:
"key.pem"
Declared by:
services.hyperhive.gateway.upstreamHost
Host the gateway proxies non-static requests to. Defaults to
127.0.0.1 because nginx runs on the host itself, so loopback
resolves directly to hive-c0re.
Type: string
Default:
"127.0.0.1"
Declared by:
services.hyperhive.gateway.upstreamPort
TCP port the gateway proxies non-static requests to. Defaults
to 7000 (hive-c0re’s out-of-the-box dashboard port). Operators
who change services.hyperhive.c0re.dashboardPort should set
upstreamPort to match — kept as a hardcoded default rather
than a cross-reference to keep this module’s options eval
independent of c0re’s option tree shape.
Type: 16 bit unsigned integer; between 0 and 65535 (both inclusive)
Default:
7000
Declared by:
services.hyperhive.github.enable
Hive-wide switch for the per-agent GitHub integration (the gh CLI
wrapper + git credential helper, per hyperhive.github.enable). On by
default: every agent gets the integration, inert until a PAT is
provisioned via the dashboard credentials tab or hivectl github set-token. Set false to turn it off for the whole hive — the
meta-flake renderer (hive-c0re/src/meta.rs) then injects
hyperhive.github.enable = false into every agent. Exposed to hive-c0re
as HYPERHIVE_GITHUB_DISABLED (set only when the integration is off).
Type: boolean
Default:
true
Example:
false
Declared by:
services.hyperhive.hiveName
Human-readable name of this single-host hive instance.
Required when services.hyperhive.enable. Distinct from
services.hyperhive.domain (the machine-addressable DNS name)
but no longer merely cosmetic: a hive occupies
<hiveName>.<swarm.domain>, so this is the label the hive is
addressed by as well as the one it is called. Exposed to
agents as HYPERHIVE_HIVE_NAME; surfaced in the dashboard
chrome and per-agent system prompt.
Type: null or string
Default:
null
Example:
"pr1ma"
Declared by:
services.hyperhive.network.bridgeIp
IPv4 address assigned to the bridge interface on the host
side. Agents use this address as their DNS server (the hive’s
dnsmasq binds here). Default 10.42.0.1
is in RFC 1918 space and unlikely to clash with operator’s
existing setup; override if a different range is already in
use.
Type: string
Default:
"10.42.0.1"
Example:
"172.30.0.1"
Declared by:
services.hyperhive.network.bridgeName
Name of the host-side bridge interface the hive uses for
inter-container traffic. Kept short so it survives the
IFNAMSIZ (15-char) cap, and prefixed so it’s obviously
hive-managed in ip link output.
Type: string
Default:
"hive-br0"
Example:
"h0"
Declared by:
services.hyperhive.network.bridgePrefixLength
Netmask prefix length for the bridge subnet. Default /24
gives 254 usable per-agent addresses, enough for any
single-host hive. Operator with a larger swarm or a tighter
addressing scheme overrides.
Type: signed integer
Default:
24
Example:
16
Declared by:
services.hyperhive.network.exposeHostPorts
TCP ports on the host that agent containers may reach at the bridge
IP (bridgeIp). Each listed port P is opened on the bridge-interface
firewall, so an agent can connect to ${bridgeIp}:P (default
10.42.0.1:P).
Use this to let agents reach a host-local service — e.g. an
OpenTelemetry collector for services.hyperhive.otel.endpoint (set
endpoint = "http://${bridgeIp}:P").
The host service must bind an address reachable from the bridge —
0.0.0.0 or the bridge IP (bridgeIp) — not loopback-only. The
bridge→127.0.0.0/8 DROP rule (defence-in-depth) is unchanged: this
only opens the firewall, it does not bridge loopback. A service that
binds 127.0.0.1 only is still unreachable; rebind it to 0.0.0.0.
The exposed port is reachable by EVERY agent on the bridge subnet (same as DNS/gateway), so only expose services safe for any agent to reach.
Type: list of 16 bit unsigned integer; between 0 and 65535 (both inclusive)
Default:
[ ]
Example:
[
4318
]
Declared by:
services.hyperhive.otel.enable
Whether to enable hive-wide export of every agent’s Claude Code stats (token usage, cost, tool calls) to an OTLP endpoint via Claude Code’s built-in OpenTelemetry. One switch for all agents; each harness exports directly to the collector, so it keeps working even when hive-c0re is down .
Type: boolean
Default:
false
Example:
true
Declared by:
services.hyperhive.otel.debug
Emit OTEL SDK diagnostic messages to every agent’s stderr by
setting CLAUDE_CODE_OTEL_DIAG_STDERR=1. Useful when
troubleshooting collector connectivity or endpoint config;
leave off in normal operation to avoid noise in agent logs.
Only meaningful when enable is true.
Type: boolean
Default:
false
Declared by:
services.hyperhive.otel.endpoint
OTLP collector endpoint, set as OTEL_EXPORTER_OTLP_ENDPOINT
for every agent. Required when enable is true.
Type: string
Default:
""
Example:
"https://collector.example.com/otel"
Declared by:
services.hyperhive.otel.extraResourceAttributes
Extra comma-separated entries appended to
OTEL_RESOURCE_ATTRIBUTES after the built-in
service.name / agent / hive / swarm labels.
Type: string
Default:
""
Example:
"deployment.environment=prod"
Declared by:
services.hyperhive.otel.headersCredential
Absolute path to an operator-provided secret file whose contents
become OTEL_EXPORTER_OTLP_HEADERS (e.g.
Authorization=Bearer <token>). hive-c0re forwards this host
file into each agent container’s credential store via
systemd-nspawn --load-credential=otel-headers:<path>; the inner
harness unit inherits it by name (LoadCredential), so the token
is never copied into the nix store, the generated config, a bind
mount, or argv. Must be absolute. Leave null if the endpoint
needs no auth header. A configured-but-missing file is skipped
with a log warning (OTEL still exports, without the auth header).
Type: null or string
Default:
null
Example:
"/run/secrets/otel-headers"
Declared by:
services.hyperhive.otel.metricIntervalMs
Metric export interval in milliseconds, set as
OTEL_METRIC_EXPORT_INTERVAL for every agent. Claude Code’s
default is 60000 (60s). Leave null to use that default.
Each agent runs claude as a short-lived per-turn process; claude force-flushes metrics on shutdown, so this is not required for metrics to be exported, but a lower value gives more frequent intermediate flushes within long turns. Cosmetic, not a correctness knob.
Type: null or (positive integer, meaning >0)
Default:
null
Example:
10000
Declared by:
services.hyperhive.otel.protocol
OTLP wire protocol, set as OTEL_EXPORTER_OTLP_PROTOCOL.
Type: one of “http/protobuf”, “http/json”, “grpc”
Default:
"http/protobuf"
Declared by:
services.hyperhive.ruthless
Run this hive “ruthless” — with no root (manager) agent at all (no
ruth). When true, hive-c0re skips the root-agent auto-management
sweep entirely (it otherwise creates the root agent’s container when
missing and restarts it when present but stopped). Defaults to
false (the root agent is auto-managed as required
infrastructure). Exposed to hive-c0re as HYPERHIVE_RUTHLESS.
Type: boolean
Default:
false
Example:
true
Declared by:
services.hyperhive.snapshotStore.enable
Run the swarm snapshot store on this host: a btrfs receive
endpoint that hives push agent snapshots to over the WireGuard
mesh. Off by default — it is a distinct deployment role, not
part of a hive.
Requires services.hyperhive.swarm.wireguard.enable: the mesh
is both the transport and the authentication, so there is no
meaningful configuration without it.
Type: boolean
Default:
false
Declared by:
services.hyperhive.snapshotStore.path
Root of the snapshot tree. Must be on a btrfs filesystem —
btrfs receive fails otherwise. One subvolume directory per
agent is created beneath it, so an agent’s incremental chain
stays contiguous across a migration between hives.
Type: absolute path
Default:
"/var/lib/hyperhive-snapshots"
Declared by:
services.hyperhive.snapshotStore.port
TCP port the receiver listens on. Bound to this host’s WireGuard mesh address only — never a wildcard — so it is reachable exactly by mesh peers and by nothing else.
Type: 16 bit unsigned integer; between 0 and 65535 (both inclusive)
Default:
51821
Declared by:
services.hyperhive.swarm.enableRequiredServices
Host the swarm’s shared services on this hive. The services that
exist once per swarm rather than once per hive and are optional
— the matrix homeserver, the SSO provider — have their enable
asserted from this, so a swarm’s service host is declared in one
place.
The forge is swarm-wide too but has nothing to assert: it is the canonical store for the meta flake and every agent’s config repo, so it deploys with hyperhive itself and is not optional.
services.hyperhive.enableAllLocalDefaults turns this on as part
of the all-on-one-box mode. Set it directly to run the swarm’s
services on a host that is not otherwise all-local — a dedicated
services box with hives elsewhere is exactly that shape.
With it off, this hive is a client of those services: it still configures how to reach them, it just doesn’t run them.
Type: boolean
Default:
false
Example:
true
Declared by:
services.hyperhive.swarm.authelia.enable
Run the swarm’s authelia in a swarm-authelia container on this
host. services.hyperhive.swarm.enableRequiredServices turns
this on — a swarm has one SSO provider, and that says it lives
here.
With it off, this hive is a client: url below still points
at whoever runs it, and no container is created.
Type: boolean
Default:
false
Example:
true
Declared by:
services.hyperhive.swarm.authelia.package
authelia package to run in the container. Defaults to nixpkgs’s; override to pin a specific upstream.
Type: package
Default:
pkgs.authelia
Declared by:
services.hyperhive.swarm.authelia.domain
Public hostname for the SSO provider — the sub-domain shape the
forge and matrix already use, under the swarm’s domain because a
swarm has one SSO provider. Must be the name browsers
actually visit: it is the authelia_url the session cookie is
validated against.
⚠️ Unlike the forge and matrix names, this one carries no
migration pin: nothing depends on the previous
auth.${services.hyperhive.domain} yet, so it moves outright.
Type: string
Default:
"auth.${services.hyperhive.swarm.domain}"
Example:
"login.example.com"
Declared by:
services.hyperhive.swarm.authelia.hostClientSecretDir
Where the minted client secrets sit as seen from the host —
<id>.secret holds a plaintext, <id>.digest the hash authelia
itself reads.
Published for the same reason as hostUsersFile: the plaintext’s
other reader lives in a different container, and containers
that share this host’s network namespace still have separate
filesystem roots. The host is the only place both trees are
addressable, so the host is where a delivery step has to run.
⚠️ Nothing here exists until authelia’s first boot has run.
A consumer must wait for it — it cannot be a bindMounts source,
because nixos-container refuses to start when a bind source is
missing, and that turns a fresh hive into a boot-order deadlock.
Type: string (read only)
Default:
"/var/lib/nixos-containers/swarm-authelia/var/lib/authelia-swarm/oidc-clients"
Declared by:
services.hyperhive.swarm.authelia.hostUsersFile
usersFile as seen from the host — the container’s root
prefixed onto the path authelia sees.
The distinction is load-bearing: the users database is written from the host by a program that does not live in this container, while authelia only ever sees the inner path. Handing the wrong one to either side yields a file nobody reads rather than an error.
Type: string (read only)
Default:
"/var/lib/nixos-containers/swarm-authelia/var/lib/authelia-swarm/users.yml"
Declared by:
services.hyperhive.swarm.authelia.machine
Name of the nixos-container authelia runs in. Read-only: it is
what this module declares, published so callers of
systemctl -M and /var/lib/nixos-containers/<name> do not
have to hardcode it.
Type: string (read only)
Default:
"swarm-authelia"
Declared by:
services.hyperhive.swarm.authelia.oidc.clients
OIDC relying parties this provider will issue tokens to. Declaring one turns the provider on; the default empty list leaves this module exactly as it was — a session provider and nothing else.
⚠️ There is deliberately no secret here. A client secret has
two holders in two containers (authelia keeps a hash, the
relying party the plaintext), and
services.authelia.instances.<n>.settings is rendered into the
nix store, which is world-readable and permanent. So this
option carries only the parts that are safe to evaluate: the
secret is minted on first boot and never passes through a nix
expression. See docs/swarm/ for what goes where.
Type: list of (submodule)
Default:
[ ]
Declared by:
services.hyperhive.swarm.authelia.oidc.clients.*.description
Human-readable name, shown on authelia’s consent screen. This is the string a person reads when deciding whether to hand an application their identity, so it should name the application rather than the protocol.
Type: string
Example:
"HyperHive forge"
Declared by:
services.hyperhive.swarm.authelia.oidc.clients.*.id
OAuth2 client id, as the relying party knows itself.
Type: string
Example:
"forgejo"
Declared by:
services.hyperhive.swarm.authelia.oidc.clients.*.redirectUris
Exact callback URLs the provider will redirect to. Matched literally by authelia — a trailing-slash difference is a rejected login, not a warning.
Type: list of string
Default:
[ ]
Example:
[
"https://forge.example.com/user/oauth2/authelia/callback"
]
Declared by:
services.hyperhive.swarm.authelia.port
TCP port authelia listens on. 9091 is upstream’s default and sits outside hyperhive’s claimed ranges (dashboard 7000, forge 3000, matrix 8008, every agent in 8100…8999 via FNV-1a hash).
Type: 16 bit unsigned integer; between 0 and 65535 (both inclusive)
Default:
9091
Declared by:
services.hyperhive.swarm.authelia.unit
authelia’s systemd unit inside the container. Read-only, and derived from the instance name exactly like the unit itself.
Type: string (read only)
Default:
"authelia-swarm.service"
Declared by:
services.hyperhive.swarm.authelia.url
Base URL clients are sent to for authentication — the half of this module that exists on every hive, not just the one running the container.
Defaults to this host’s own instance only when this module is
the thing running it; in that case the URL is not a guess, it
is where this module just put the container. Otherwise null,
and a hive that federates with a swarm sets it explicitly to
wherever the swarm’s authelia lives. Null means “no SSO
configured” and consumers say so rather than inventing an
address — an endpoint baked in as a fallback is one that
resolves cleanly and points at the wrong machine.
Type: null or string
Default:
if enable then "https://${domain}" else null
Example:
"https://auth.example.com"
Declared by:
services.hyperhive.swarm.authelia.usersFile
Path (inside the container) of authelia’s file users database.
Written by swarm-controller, not by hand: agents come and go continuously, so the subject set is dynamic and belongs to a program. This module only guarantees the file exists and is valid YAML at first boot, so authelia starts with no subjects rather than failing to start — a provider with nobody in it yet is the correct state before anything has provisioned users.
Type: string
Default:
"/var/lib/authelia-swarm/users.yml"
Declared by:
services.hyperhive.swarm.ca.autoConfigure
Run the whole swarm CA on this one host: generate the swarm root when it is missing, and issue this hive’s CA under it.
services.hyperhive.enableAllLocalDefaults turns this on as
part of the all-on-one-box mode. Set it here directly to run the
CA on a host that is not otherwise all-local.
Off by default, deliberately. A swarm’s services and its hives can live on different hosts, and this host has no way to tell whether it is the one holding the root — so the swarm CA is something an operator sets up, not something a host decides it is. Turn this on for an all-on-one-host deployment (dev boxes, single-hive swarms) and get the hierarchy for free.
With it off, both artifacts are operator-provided: the root
under stateDir, and this hive’s CA under
services.hyperhive.tls.stateDir. A hive given neither keeps
the self-signed CA it has always had — it simply isn’t part of
a swarm’s trust hierarchy, which is the correct outcome for a
hive nobody has federated yet.
Type: boolean
Default:
false
Example:
true
Declared by:
services.hyperhive.swarm.ca.servicesValidityDays
Validity window of the swarm-services sub-CA in days (~5y). Deliberately far shorter than the root’s: this CA is meant to be re-issued — adding a swarm service changes its name constraints and rotates it — so a long window buys nothing, and a short one keeps the rotation path exercised rather than theoretical.
Rotating it is cheap in the way rotating the root is not: it touches only the swarm-service vhosts, and no peer hive holds it as an anchor.
Type: signed integer
Default:
1825
Declared by:
services.hyperhive.swarm.ca.stateDir
Host directory holding the swarm root CA: root.pem (the
anchor, safe to distribute — copy it to this same path on every
other host in the swarm) and root-key.pem
(0600, the one file that must never reach the nix store or
another host). The directory itself is 0700: nothing reads
out of it but the hive CA issuance in hive-tls.nix.
Moving the swarm CA to its own host is a matter of moving this
directory and setting autoConfigure = false here.
Type: string
Default:
"/var/lib/swarm-ca"
Declared by:
services.hyperhive.swarm.ca.validityDays
Validity window of the swarm root CA in days (default ~30y).
Deliberately longer than services.hyperhive.tls.caValidityDays:
the root must outlive the hive CAs it issues, or those chains
expire out from under hives that are still perfectly happy with
their own intermediate. Rotating a root is the one operation in
this system with no partial-failure mode — it invalidates every
peer at once, paced by the slowest peer’s rebuild — so it is
never automatic and this window is meant to be uneventful.
Type: signed integer
Default:
10950
Declared by:
services.hyperhive.swarm.controller.enable
Run the swarm-controller daemon on this host. Off by default and
deliberately not derived from services.hyperhive.enable: a swarm
has one controller, so enabling it per hive is a decision about
swarm topology, not about whether hyperhive is installed.
services.hyperhive.enableAllLocalDefaults does assert it, and
that is not an exception to the rule above — it is the rule
applied. That mode says “this box is the whole deployment”, which
answers the topology question outright, where
services.hyperhive.enable alone never can.
Type: boolean
Default:
false
Declared by:
services.hyperhive.swarm.controller.package
swarm-controller package. Wired by default from this flake’s own
package set (see flake.nix); override to run a different build.
Type: package
Default:
hyperhive.packages.${system}.swarm-controller
Declared by:
services.hyperhive.swarm.controller.socketPath
Unix socket the daemon serves on, and the path the gateway’s nginx proxies to.
The directory is the access control here, not the socket mode:
the socket itself is 0666 (nginx runs as another user, and
connect(2) needs write), exactly as hive-c0re publishes the
per-agent sockets. What keeps that safe is that the directory holds
exactly one socket and is traverse-only (0751) for everyone else.
Moving this path under a directory that carries anything else —
/run/hyperhive, which holds the host admin socket, above all —
exposes whatever else lives there to the same reachability.
Changing this therefore means re-checking what else lives in the new directory, not just the daemon.
Type: string
Default:
"/run/swarm-controller/controller.sock"
Declared by:
services.hyperhive.swarm.controller.swarmctlPackage
swarmctl package — the swarm operator’s CLI, installed on this
host alongside the daemon and wrapped with the paths it needs.
A separate option from package rather than a second binary in
the same derivation: the CLI runs as root and acts directly,
the daemon runs unprivileged and serves a socket, and pinning one
without the other is a thing an operator may legitimately want.
Type: package
Default:
hyperhive.packages.${system}.swarmctl
Declared by:
services.hyperhive.swarm.domain
DNS domain of the wider swarm this hive belongs to. Each hive
occupies its own sub-domain of it, which is why every entry in
services.hyperhive.swarm.hives defaults its domain to
<name>.<swarm.domain> — set this plus
services.hyperhive.hiveName, list the hives by name, and no
hive in the swarm states an address at all.
Required when services.hyperhive.enable, and deliberately
not defaulted: there is no fallback worth having. A guessed
swarm domain is a wrong hostname that evaluates cleanly and
deploys, which is worse than an eval failure telling an
operator to write down the one address their swarm answers to.
Upgrading past this costs one line, once.
Type: null or string
Default:
null
Example:
"darkest.space"
Declared by:
services.hyperhive.swarm.forge.package
Forgejo package to run inside the container. Defaults to
pkgs.forgejo (the latest release line) rather than the
nixpkgs-module default of pkgs.forgejo-lts, because LTS
lags far behind on schema and the DB easily ends up “newer
than the binary” if the operator ever ran a non-LTS forgejo
against the same state dir. Override to pkgs.forgejo-lts
if you actively want the slower release train.
Type: package
Default:
pkgs.forgejo
Declared by:
services.hyperhive.swarm.forge.behindGateway
Serve forgejo through the hive-gateway nginx as a sub-domain
vhost (server_name = cfg.domain) instead of directly on
httpPort (sub-domain routing — see docs/gateway.md).
When true:
- The gateway adds a
server { server_name = ${cfg.domain}; }block that proxies all/→http://127.0.0.1:${httpPort}/. - Forgejo’s
ROOT_URLflips tohttp(s)://${cfg.domain}/(sub-domain root, no port suffix when gateway is on 80). gateway.localHostsEntry = trueextends/etc/hoststo includecfg.domain → 127.0.0.1for local dev.
Defaults to services.hyperhive.enable (the gateway always runs
alongside hyperhive, so forge auto-routes through it). Set false
explicitly to keep forge on the direct port even though the
gateway is running (e.g. an external git client that doesn’t
traverse the gateway).
Sub-domain routing is the preferred shape for forge + matrix
(both are external standard apps with sub-domain-native config
defaults). Per-agent UIs stay on sub-path (/agent/<name>/)
because they’re hyperhive-internal + already base-path-aware.
Type: boolean
Default:
config.services.hyperhive.enable
Declared by:
services.hyperhive.swarm.forge.ci.enable
Run a Forgejo Actions runner in a hive-ci nixos-container.
Grouped under services.hyperhive.swarm.forge because the runner is
tightly coupled to the forge instance it registers against.
Disabled by default; the internal forge it registers against is
always present (mandatory), so enabling this is all that’s needed.
On first start the container auto-registers against hive-forge using hive-c0re’s admin token — no manual token provisioning needed. Runner credentials are persisted in the container’s state dir and reused on every subsequent boot.
Type: boolean
Default:
false
Example:
true
Declared by:
services.hyperhive.swarm.forge.ci.package
gitea-actions-runner package.
Type: package
Default:
pkgs.gitea-actions-runner
Declared by:
services.hyperhive.swarm.forge.ci.concurrency
Maximum number of workflow jobs the runner executes in parallel. Each job gets its own temporary working directory; multiple parallel jobs share the container’s nix store and cargo registry cache. Higher values trade memory + CPU headroom for throughput.
Type: positive integer, meaning >0
Default:
1
Example:
4
Declared by:
services.hyperhive.swarm.forge.ci.jobTimeout
Per-job wall-clock timeout the runner enforces (act_runner’s
runner.timeout). A job that exceeds it is killed, so a hung or
runaway build is bounded instead of holding the runner’s single
slot indefinitely. Default 1h comfortably covers a cold-cache
nix build while still bounding a stuck job; raise it (e.g.
"3h") if you legitimately run jobs longer than that. Accepts a
Go duration string (30m, 1h, 2h30m). Note: this is
enforced by the runner process, so it only fires while that
process is itself healthy.
Type: string
Default:
"1h"
Example:
"3h"
Declared by:
services.hyperhive.swarm.forge.ci.labels
Runner labels in <name>:<scheme> format. The host scheme runs
commands directly in the container (no docker/podman). Workflow
files target this runner with runs-on: [hive-ci].
Type: list of string
Default:
[
"hive-ci:host"
]
Example:
[
"hive-ci:host"
"nix:host"
]
Declared by:
services.hyperhive.swarm.forge.ci.name
Runner name as shown in the Forgejo admin panel. Defaults to “hive-ci”; override when multiple hives share a Forgejo instance.
Type: string
Default:
"hive-ci"
Example:
"prod-hive"
Declared by:
services.hyperhive.swarm.forge.domain
Public hostname for the forge. Doubles as both the forgejo
DOMAIN setting (clone URLs forgejo advertises) AND the
gateway vhost server-name when behindGateway = true
(sub-domain routing — see docs/gateway.md).
Defaults to forge.${services.hyperhive.swarm.domain} — the
swarm’s domain, not this hive’s, because a swarm runs one
forge that every hive in it talks to.
⚠️ A deployment that was running before this moved keeps its
current name by pinning it here:
forge.${services.hyperhive.domain}, which is exactly what
the old default rendered. Certificates follow either way: the
swarm-services sub-CA is name-constrained to the configured
names (see ./swarm-ca.nix), not to a fixed tree.
Set to a full hostname (git.example.com,
forge.internal.lan, etc.) for a bespoke vhost shape — the
full domain goes here, no separate sub-domain-label option.
Type: string
Default:
"forge.${services.hyperhive.swarm.domain}"
Example:
"git.example.com"
Declared by:
services.hyperhive.swarm.forge.httpPort
TCP port the forge serves HTTP on. Default 3000 sits outside hyperhive’s claimed ranges (dashboard 7000, every agent in 8100…8999 via FNV-1a hash). Change this if you already have another forgejo bound to 3000.
Type: 16 bit unsigned integer; between 0 and 65535 (both inclusive)
Default:
3000
Declared by:
services.hyperhive.swarm.forge.mirrors
General-purpose Forgejo pull-mirrors to auto-seed on the local
forge. Each entry is created as a real Forgejo pull-mirror (it
re-syncs from upstream out-of-band), not a one-off pushed clone —
so a host-resolver blip leaves a stale mirror, never a hard
failure on whatever reads it.
When services.hyperhive.swarm.forge.ci.enable is set, an
actions/checkout mirror is auto-appended to this list and
forgejo’s DEFAULT_ACTIONS_URL is pointed at this instance, so CI
uses: actions/checkout@vN steps resolve entirely on loopback with
no external DNS on the critical path (the seed/re-sync needs
external DNS, but that’s off the CI path).
Type: list of (submodule)
Default:
[ ]
Example:
[ { upstream = "https://github.com/actions/checkout"; dest = "actions/checkout"; } ]
Declared by:
services.hyperhive.swarm.forge.mirrors.*.dest
Local <owner>/<repo> the pull-mirror is created at. The
<owner> org is auto-created if missing. Keep mirror dests
in their own orgs (e.g. actions/*) — separate from the
hive-c0re-managed namespaces (config/shared/agents/core) so
the seed never collides with core’s own provisioning.
Type: string
Example:
"actions/checkout"
Declared by:
services.hyperhive.swarm.forge.mirrors.*.upstream
Upstream clone URL to mirror from.
Type: string
Example:
"https://github.com/actions/checkout"
Declared by:
services.hyperhive.swarm.forge.openFirewall
Open httpPort + sshPort in the host firewall. Off by
default (secure-by-default): agent containers reach the forge
at forge.<domain> via the gateway (not directly), and the
host reaches it on loopback — so the firewall opens only
matter for access from outside the host. Flip to true when
you want the operator’s browser or external git clients to
hit the forge directly.
Breaking change: this used to default to true. If you
relied on the old default for external reach, add
services.hyperhive.swarm.forge.openFirewall = true; to your host
config before rebuilding.
Type: boolean
Default:
false
Example:
true
Declared by:
services.hyperhive.swarm.forge.publicUrl
Browser-facing forge URL the dashboard uses to build clickable
forge links (the H0M3 Forge tile, per-agent-row forge links,
the approval-queue’s “review PR on forge” link) — sourced into
every agent container + hive-c0re as HIVE_FORGE_PUBLIC_URL.
Defaults to https://${cfg.domain} when behindGateway = true (the gateway vhost is genuinely reachable at that URL)
and null otherwise. When null, the dashboard hides
forge links rather than guessing one — see
docs/web-ui/dashboard.md::H0M3 page for the rationale (a
link built from the operator’s own browser hostname + a
container port is only an accident away from wrong on any
deployment that isn’t plain localhost).
Set this explicitly if behindGateway = false and the
forge is still reachable at a stable URL you want linked from
the dashboard (e.g. http://<lan-host>:${toString cfg.httpPort}
for an all-LAN deployment) — leaving it unset there means the
dashboard’s forge links are simply absent, not broken.
Type: null or string
Default:
if behindGateway then "https://${domain}" else null
Example:
"https://forge.example.com"
Declared by:
services.hyperhive.swarm.forge.rootUrl
Override the auto-derived forgejo ROOT_URL. When null
(default), ROOT_URL is derived from cfg.domain + gateway
state, including the scheme:
behindGateway = true→https://${cfg.domain}/. The gateway always terminates TLS (self-signed is the implicit floor when nogateway.tls.certDir/ ACME is set), so the forge is always advertised over https. A non-canonicalgateway.httpsPortis appended as:<port>.behindGateway = false→http://${cfg.domain}:${cfg.httpPort}/
The TLS scheme is derived automatically now, so you only need to
set this for a genuinely bespoke shape (e.g. an external reverse
proxy on a different host/path). Must end with / per forgejo’s
ROOT_URL contract.
Type: null or string
Default:
null
Example:
"https://forge.example.com/"
Declared by:
services.hyperhive.swarm.forge.sshPort
TCP port the forge’s built-in SSH server listens on. Kept off
22 so it doesn’t clash with the host’s openssh. Agents push
with ssh -p <sshPort> git@<domain>:<owner>/<repo>.git.
Type: 16 bit unsigned integer; between 0 and 65535 (both inclusive)
Default:
2222
Declared by:
services.hyperhive.swarm.forge.sso.enable
Register the swarm’s authelia as an OpenID Connect login source on this forge.
Additive, never exclusive. Forgejo keeps its local password database and gains an extra “sign in with” button; this does not disable local login. Deliberate: an identity provider that can take the forge offline when it hiccups is a worse forge than one with two ways in.
Type: boolean
Default:
false
Example:
true
Declared by:
services.hyperhive.swarm.forge.sso.clientId
OAuth2 client id this forge identifies itself with. Must match
the id of the corresponding entry in
services.hyperhive.swarm.authelia.oidc.clients.
Type: string
Default:
"forgejo"
Declared by:
services.hyperhive.swarm.forge.sso.clientSecretFile
Path inside the forge container holding the client secret’s plaintext.
A path, never a value: an OIDC client secret has two holders in two containers (authelia keeps a hash, this forge needs the plaintext), and a literal written here would be rendered into the world-readable nix store.
Required when enable is set — deliberately no fallback. A
forge that boots with SSO half-configured presents as a login
button that always fails, which is harder to diagnose than an
eval error.
Type: null or string
Default:
null
Example:
"/var/lib/hyperhive/forge-oidc-secret"
Declared by:
services.hyperhive.swarm.hives
Every hive in this swarm, keyed by hiveName — including this
host’s own hive. The same attrset is meant to be identical on
every host in the swarm, so it can be written once and shared;
services.hyperhive.hiveName is what makes a given host read it
as “me and four others” rather than “five peers”.
Each entry needs no fields at all in the conventional case: a
hive’s domain defaults to <name>.<swarm.domain>, so the whole
directory is usually a list of names.
It must contain an entry for hiveName, which is asserted — this
host’s own address is read out of it (it is where
services.hyperhive.domain derives from), and a hive that lists
everyone but itself would otherwise derive its own peer set as
everything and peer with itself.
Type: attribute set of (submodule)
Default:
{ }
Example:
{
edge = {
domain = "edge.elsewhere.example";
wireguardAddress = "10.100.0.2/32";
};
pr1ma = {
wireguardAddress = "10.100.0.1/32";
wireguardEndpoint = "203.0.113.1:51820";
};
}
Declared by:
services.hyperhive.swarm.hives.<name>.certFingerprint
Expected TLS certificate fingerprint for this hive’s HTTPS endpoint. Null = trust the CA bundle — which for a hive inside the swarm CA hierarchy is the normal case, since every hive under the swarm root already chains to it. Set it to pin a leaf that no CA in the bundle vouches for.
Format: the literal sha256: followed by exactly 64
hex digits (case-insensitive, no colon separators) — the
SHA-256 digest of the DER-encoded leaf certificate.
Generate with openssl x509 -noout -fingerprint -sha256,
then strip the colons and prepend sha256:. A malformed
value is ignored with a warning rather than weakening
trust. See docs/swarm/README.md for the full recipe.
Scopes only to hive-c0re’s own peer HTTPS checks — it does NOT help Matrix federation, which validates against the container’s trust bundle. There is no per-hive CA field to cover that case any more: the swarm root is the trust path (see ./swarm-ca.nix).
Type: null or string
Default:
null
Example:
"sha256:b1946ac92492d2347c6235b4d2611184a3f5b6cae6c19d6e3c2f0a8e7d4c9f12"
Declared by:
services.hyperhive.swarm.hives.<name>.domain
Public DNS domain this hive occupies — used for dashboard links, peer HTTPS checks and Matrix federation discovery.
Defaults to <name>.<swarm.domain>, the convention every
hive in a swarm follows, so a conventional directory is
names only. Set it for a hive that is addressed by
something else.
Type: string
Default:
"${name}.${services.hyperhive.swarm.domain}"
Example:
"lab.example.com"
Declared by:
services.hyperhive.swarm.hives.<name>.wireguardAddress
IP address (with prefix) of this hive’s host on the
WireGuard mesh. Used as the allowedIPs for its
WireGuard config entry and injected into HYPERHIVE_PEERS
so hive-c0re can route intra-swarm traffic to the mesh
address rather than the public domain. Required to include
a hive in the mesh (entries missing this field are
silently excluded from wg-hive).
Type: null or string
Default:
null
Example:
"10.100.0.2/32"
Declared by:
services.hyperhive.swarm.hives.<name>.wireguardEndpoint
WireGuard endpoint for this hive in host:port form.
Null = this hive has no reachable endpoint, so the tunnel
is initiated from the other side.
Reads like a fact about the relationship and is not: it says whether this hive can be dialled, which every other hive in the swarm needs the same answer to.
Type: null or string
Default:
null
Example:
"203.0.113.1:51820"
Declared by:
services.hyperhive.swarm.hives.<name>.wireguardPublicKey
WireGuard public key for this hive’s host. Required when
services.hyperhive.swarm.wireguard.enable = true and
you want this hive reachable over the mesh. Null = TLS-
only peering (public internet, no mesh tunnel).
Type: null or string
Default:
null
Example:
"base64pubkey="
Declared by:
services.hyperhive.swarm.matrix.enable
Run hive-matrix — a private matrix-tuwunel homeserver (in a nixos-container) for hyperhive agents.
Matrix is a swarm-wide service — one homeserver, not one per
hive — so services.hyperhive.swarm.enableRequiredServices
turns this on as part of saying the swarm’s shared services live
on this host. Set it here directly to run the homeserver
somewhere other than the host that holds the rest of them.
Type: boolean
Default:
false
Declared by:
services.hyperhive.swarm.matrix.package
matrix-tuwunel package to run inside the container. Defaults
to nixpkgs’s pkgs.matrix-tuwunel. Override to pin a
specific upstream if you need an unreleased feature.
Type: package
Default:
pkgs.matrix-tuwunel
Declared by:
services.hyperhive.swarm.matrix.allowEncryption
Server-side switch for matrix end-to-end encryption — sets
tuwunel’s allow_encryption. Off by default: on the hive-internal
homeserver the operator already controls the transport, so server
E2EE adds key-management overhead (cross-signing, device
verification, undecryptable-message recovery) without a clear
threat-model win for the common single-hive case. Turn on when
agents join encrypted rooms on external / federated homeservers,
or when the operator wants message contents opaque to the
homeserver admin. Independent of the agent matrix client, which
always supports decryption so it can read encrypted rooms it is
invited to regardless of this flag; this option only governs
whether THIS homeserver permits room encryption.
Type: boolean
Default:
false
Declared by:
services.hyperhive.swarm.matrix.apiUrl
Client-server API base URL hive-c0re itself uses to
provision matrix (register agent users, create the hive space
and chat room, invite members). Distinct from the agent-facing
hyperhive.matrix.url, which is the gateway vhost handed to
each agent’s hive-matrix-daemon.
Defaults to the loopback listener only when this module is the
thing running tuwunel — in that case the address is not a
guess, it is where this module just put the container. Set it
explicitly (with enable = false) when the homeserver runs on
another machine; “everything on one host” is a special case of
the full deployment, not the assumption.
null means hive-c0re has no homeserver to provision against
and matrix provisioning no-ops. There is deliberately no
fallback compiled into the daemon: an address baked into the
binary is one that builds fine and then talks to the wrong
machine.
Type: null or string
Default:
if services.hyperhive.swarm.matrix.enable
then "http://127.0.0.1:${toString services.hyperhive.swarm.matrix.httpPort}"
else null
Example:
"https://matrix.example.com"
Declared by:
services.hyperhive.swarm.matrix.gatewayHost
Public hostname for the matrix homeserver behind the gateway.
Defaults to chat.${services.hyperhive.swarm.domain} — the
swarm’s domain, because a swarm runs one homeserver. Set to
null to skip the gateway vhost (tuwunel stays direct on
httpPort). See docs/gateway.md for the vhost map + matrix
discovery flow, and the federation port-8448 caveat at the
bottom of that doc.
⚠️ gatewayHost and serverName are different things, and
they carry very different costs. gatewayHost is the API
listener hostname (where nginx proxies /_matrix/*) and is
free to change: it is a routing detail clients rediscover
through .well-known. serverName is the matrix-identifier
domain embedded irrevocably in every user and room id —
adopting a new one is a different homeserver, not a rename.
Both defaults now sit under the swarm domain, but only this
one is safe to move on a running deployment.
A deployment that was running before this moved keeps its
current name by pinning
matrix.${services.hyperhive.domain} here — exactly what the
old default rendered.
Type: null or string
Default:
"chat.${services.hyperhive.swarm.domain}"
Example:
"matrix.example.com"
Declared by:
services.hyperhive.swarm.matrix.gui.enable
Serve a matrix web client at matrix.${services.hyperhive.domain}/.
Requires matrix.gatewayHost != null (default matrix.<hive>
when hive-domain set); the gateway itself always runs. When
off, the dashboard’s M4TR1X → tab is hidden. See
docs/gateway.md for the discovery flow that lets clients
auto-find the sub-domain.
Type: boolean
Default:
config.services.hyperhive.swarm.matrix.enable
Declared by:
services.hyperhive.swarm.matrix.gui.package
Static web client dist served at matrix.<hive>/. Override
to swap fluffychat for hydrogen-web, cinny, element-web, or
an out-of-tree dist — any replacement is mounted at the
sub-domain root with the upstream-default <base href "/">,
no sub-path gymnastics needed.
Type: package
Default:
pkgs.fluffychat-web with a postInstall patch that adds
the three files flutter341.buildFlutterApplication skips.
Declared by:
services.hyperhive.swarm.matrix.httpPort
TCP port tuwunel serves the matrix client-server API on.
Default 8008 is the matrix-spec well-known port. Sits
outside hyperhive’s claimed ranges (dashboard 7000, every
agent in 8100…8999 via FNV-1a hash). Federation listens on
federationPort separately.
Type: 16 bit unsigned integer; between 0 and 65535 (both inclusive)
Default:
8008
Declared by:
services.hyperhive.swarm.matrix.maxRequestSize
Maximum size in bytes of a single matrix client request body. Default 20 MB matches the matrix-spec recommendation for media uploads + the upstream tuwunel default.
Type: positive integer, meaning >0
Default:
20000000
Declared by:
services.hyperhive.swarm.matrix.openFirewall
Open httpPort in the host firewall. Off by default
(secure-by-default): the host reaches the homeserver on
loopback, and agent containers reach it at matrix.<domain>
via the gateway — so the firewall open only matters for
access from outside the host. Flip to true when announcing
the homeserver to other hives or when an external matrix
client needs to reach the client-server API directly.
Breaking change: this used to default to true. If you
relied on the old default for external reach, add
services.hyperhive.swarm.matrix.openFirewall = true; to your host
config before rebuilding.
Note: federation (the matrix-spec well-known port 8448) is
intentionally not opened here. tuwunel serves the federation
API on the same httpPort as the client-server API by
default; reaching it on 8448 requires either binding tuwunel
to that port explicitly OR a reverse-proxy + .well-known/ matrix/server delegation, neither of which lives in this
module. Add that proxy config alongside whatever serves your
dashboard or forge on 443.
Type: boolean
Default:
false
Example:
true
Declared by:
services.hyperhive.swarm.matrix.registrationTokenFile
Host path to a file containing the matrix registration token
tuwunel reads to authorise new-account creation. The token is
generated automatically by hive-c0re on first boot (32-byte
random hex, mode 0600) and is bind-mounted read-only into the
tuwunel container at the same path. Agents never see this
token — hive-c0re uses it to provision per-agent accounts
and the agent only receives the resulting access_token.
Override only when integrating with externally-managed
registration tokens.
Type: absolute path
Default:
"/var/lib/hyperhive/matrix-register-token"
Declared by:
services.hyperhive.swarm.matrix.serverName
Matrix server_name — the host part of every user ID
(@argus:<server_name>) and room ID minted on this
homeserver. CRITICAL: must be stable from day one because
it’s embedded irrevocably in the identifiers.
Defaults to services.hyperhive.swarm.domain (the bare swarm
domain), because a swarm runs one homeserver — tying its
identity to a single hive’s domain would make relocating the
container between hives look like a different homeserver.
Combined with the .well-known/matrix/{client,server} routes
the gateway serves at that domain, clients auto-discover the
actual matrix endpoint without needing a subdomain. Override
here only if you need a different server_name shape (e.g.
chat.example.org for a bespoke hostname).
Breaking change, and the one on this page that cannot be
undone by rebuilding. This default has now moved twice — from
matrix.${services.hyperhive.domain}, then to the bare hive
domain, and now to the swarm domain. Every existing homeserver
must pin whichever value it already minted ids under, e.g.
services.hyperhive.swarm.matrix.serverName =
config.services.hyperhive.domain; # or "matrix.${…domain}"
before rebuilding. Adopting a new server_name does not rename
the old users and rooms — it strands them, because their ids
still name a homeserver that no longer answers.
Type: null or string
Default:
null
Example:
"chat.example.org"
Declared by:
services.hyperhive.swarm.matrix.trustedServers
List of trusted matrix servers (homeservers whose signing keys this server will fetch identity-server-style). Empty by default — federation is enabled at the protocol level but no peer is trusted until listed here, so the homeserver is effectively closed until the operator declares hive peers explicitly.
Type: list of string
Default:
[ ]
Example:
[
"matrix.org"
]
Declared by:
services.hyperhive.swarm.name
Human-readable name of the wider swarm this hive belongs to.
Hives at different DNS domains can share a swarm name when
they federate together. Exposed to agents as
HYPERHIVE_SWARM_NAME; surfaced in the dashboard chrome and
per-agent system prompt when set.
Type: null or string
Default:
null
Example:
"constellat1on"
Declared by:
services.hyperhive.swarm.snapshotStore.address
Mesh address of the swarm’s snapshot store — the single
btrfs receive endpoint every hive in this swarm pushes agent
snapshots to. Bare IP, no prefix.
There is exactly one store per swarm, not one per peer: the receiver keys destinations by agent, so an agent that migrates between hives keeps a single unbroken incremental chain. Per-hive stores would split that chain in two, which is the case the store exists to serve.
Null means this swarm has no store configured, and pushing fails
saying so rather than guessing an address. Set it on every hive
that pushes; the receiving host separately sets
services.hyperhive.snapshotStore.enable.
Type: null or string
Default:
null
Example:
"10.100.0.1"
Declared by:
services.hyperhive.swarm.snapshotStore.port
TCP port the swarm’s snapshot store listens on. Must match the
receiving host’s services.hyperhive.snapshotStore.port.
Defaulted (unlike address) because it is a shared convention
both sides read from the same option docs — whereas an address
is deployment-specific and cannot be guessed.
Type: 16 bit unsigned integer; between 0 and 65535 (both inclusive)
Default:
51821
Declared by:
services.hyperhive.swarm.ui.enable
Serve the swarm UI from this host.
Derived from swarm.controller.enable rather than from
enableRequiredServices: the UI is a view onto the controller’s
state and reaches it over that daemon’s unix socket, so the host
that runs the controller is the host that can serve the UI. A
hive that merely uses a swarm has nothing to serve here.
Type: boolean
Default:
services.hyperhive.swarm.controller.enable
Example:
true
Declared by:
services.hyperhive.swarm.ui.package
Static build of the swarm UI. nginx serves this store path directly — there is no server-side component beyond the controller’s own API.
Wired by default from this flake’s own package set (see
flake.nix), the same way swarm.controller.package is. There
is deliberately no overlay in this project, so a
pkgs.swarm-ui default here would name an attribute that does
not exist on any real deployment.
Type: package
Default:
hyperhive.packages.${system}.swarm-ui
Declared by:
services.hyperhive.swarm.ui.domain
Host name the swarm UI answers on. Defaults to the swarm apex itself — the swarm’s front page is the swarm’s name.
An option rather than a hardcoded derivation so a hive can pin a
different name, the same way swarm.forge.domain and
swarm.matrix.gatewayHost can.
Total on a null swarm domain (.invalid, RFC 2606) so the
required-domain assertion is what fires rather than a coercion
error naming this option — same reasoning as
hive-network.nix’s.
Type: string
Default:
services.hyperhive.swarm.domain
Example:
"swarm.example.com"
Declared by:
services.hyperhive.swarm.wireguard.enable
Enable the WireGuard inter-hive mesh. When true, a wg-hive
interface is brought up connecting to all swarm peers that
declare a wireguardPublicKey. Requires
privateKeyFile to be set.
Type: boolean
Default:
false
Declared by:
services.hyperhive.swarm.wireguard.address
IP address (with prefix) of this host on the WireGuard mesh.
Use a /24 (or broader) prefix so the routing table covers all
peer /32 routes. Example: "10.100.0.1/24" for a 256-host mesh.
Type: string
Default:
""
Example:
"10.100.0.1/24"
Declared by:
services.hyperhive.swarm.wireguard.listenPort
UDP port the local WireGuard interface listens on. Must be reachable from peer hosts when they initiate the tunnel. Default: 51820 (standard WireGuard port).
Type: 16 bit unsigned integer; between 0 and 65535 (both inclusive)
Default:
51820
Declared by:
services.hyperhive.swarm.wireguard.persistentKeepalive
Seconds between keepalive packets sent to each peer. Useful when this host (or a peer) is behind NAT — keeps the UDP hole open. Set to null to disable. Default: 25 seconds.
Type: null or signed integer
Default:
25
Example:
25
Declared by:
services.hyperhive.swarm.wireguard.privateKeyFile
Path to the host’s WireGuard private key file. The file must
be readable by root and should have mode 0400. Generate with
wg genkey > /etc/wireguard/hive.key. Required when
swarm.wireguard.enable = true.
Type: null or absolute path
Default:
null
Example:
"/etc/wireguard/hive.key"
Declared by:
services.hyperhive.tls.caValidityDays
Validity window of the hive CA in days (default ~20y). Kept long
and well beyond leafValidityDays so the CA outlives many leaf
rotations — the whole point of the CA is to be a stable anchor
that consumers trust once. The CA is regenerated only if missing
or already expired.
Type: signed integer
Default:
7300
Declared by:
services.hyperhive.tls.leafValidityDays
Validity window of the gateway leaf cert in days (default 30).
Short-lived by design — ahead of the CA/Browser-Forum’s move
toward ~47-day max lifetimes — which bounds the blast radius of a
leaf-key compromise. The leaf is re-signed by the (stable) CA
when it is missing or near expiry; because it shares the CA
anchor, a rotation does not disturb consumer trust. Agents and
federation peers validate against the CA, not browser CA/B-forum
limits. The weekly hive-tls-resign timer re-signs the leaf once
it is within half its validity of expiry and propagates the new
leaf into the running gateway, so a long-uptime host renews
automatically without a reboot.
Type: signed integer
Default:
30
Declared by:
services.hyperhive.tls.stateDir
Host directory holding the hive CA + gateway leaf cert for the
self-signed gateway mode. ca.pem (the anchor agents and
federation peers trust), ca-key.pem (0600, never leaves the
host), gateway.pem / gateway-key.pem (the leaf the gateway
container bind-mounts and nginx serves). Persistent so the CA
survives reboots — re-deriving it would re-break every consumer.
Type: string
Default:
"/var/lib/hive-tls"
Declared by: