hyperhive — deploy options
services.hyperhive.deploy.allSwarmServices
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, the queue, the metrics and log stores — have their toggle asserted from this, so a swarm’s service host is declared in one place.
Every toggle it asserts is a services.hyperhive.deploy.*
one, because “does THIS host run it” is a per-host decision — which
is the same reason this option is a deploy.* one itself. See
./deploy.nix.
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.deploy.singleHostSwarm 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.deploy.authelia.enable
Run the swarm’s authelia in a swarm-authelia container on this
host. A swarm has one SSO provider, and this says it lives here.
With it off, this hive is a client:
services.hyperhive.swarm.authelia.url still points at
whoever runs it, and no container is created. That asymmetry is
why the two live in different namespaces — every hive needs the
client half, only one runs the server half.
Type: boolean
Default:
false
Example:
true
Declared by:
services.hyperhive.deploy.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.deploy.authelia.hostUsersFile
usersFile as seen from the host — the container’s root
prefixed onto the path authelia sees.
Published for callers that only ever need to read the file
(e.g. an operator diagnosing a bad entry). swarm-authelia-bridge
itself never uses this path — it runs inside the container, as
the file’s own owner, and writes the in-container path directly.
Type: string (read only)
Default:
"/var/lib/nixos-containers/swarm-authelia/var/lib/authelia-swarm/users.yml"
Declared by:
services.hyperhive.deploy.authelia.usersFile
Path (inside the container) of authelia’s file users database.
Written by swarm-authelia-bridge, not by hand: agents come and
go continuously, so the subject set is dynamic and belongs to a
program. swarm-controller cannot write this file itself — a
different uid owns it — so the bridge is the only writer,
running inside this same container as this file’s actual owner.
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.deploy.bao.enable
Run the swarm’s secret store in a swarm-bao container on this
host. A swarm has one store and it has to exist somewhere.
Where it runs is a separate question from that it runs: set
this directly to put the store on a host of its own, and clients
still reach it by name at
services.hyperhive.swarm.bao.domain rather than at a
local address.
With it off, this hive is a client: it still reads its own secrets from whoever runs the store, authenticating with its own client certificate. Every hive needs the client half; only one runs the server half, which is why the two live in different namespaces.
Type: boolean
Default:
false
Example:
true
Declared by:
services.hyperhive.deploy.bao.package
OpenBao package to run.
⚠️ An assertion below refuses 2.7.0 or newer, which drops the built-in PKCS11 seal.
Type: package
Default:
pkgs.openbao
Declared by:
services.hyperhive.deploy.bao.bootstrapTokenFile
Token used once per swarm to write the first authorisation grants, after which every client authenticates with a certificate instead.
Cert auth answers a role, so no client can authenticate until some role exists — and creating that first one is what this token is for. It has to come from outside that cycle, which is why an operator places it rather than the deployment minting it.
Produce it from the root token bao operator init printed, scoped to
that one policy write and nothing else, then delete it once the swarm
has come up — docs/getting-started/setup.md has the commands.
Setting this is what enables the granting unit; leaving it null means
the deployment writes those grants some other way.
A path, never a value.
Type: null or string
Default:
null
Example:
"/var/lib/swarm-bao-bootstrap/grant.token"
Declared by:
services.hyperhive.deploy.bao.clientCaFile
Authority the store validates hive client certificates against.
This module declares no default and does not reach for the hive CA: the hive CA is a future consumer of the store, so a store that authenticated against it could not come up before the thing it issues.
On a hive that runs the store, a glue module supplies the CA it
minted for exactly this, as a mkDefault. Point this at something
else — the swarm root, an operator’s own CA — and yours wins.
null leaves client-certificate verification off, which is only
appropriate where something else authenticates the connection.
Type: null or string
Default:
null
Example:
"/var/lib/swarm-ca/root.pem"
Declared by:
services.hyperhive.deploy.bao.clientCertFile
Certificate a reader on this machine presents to the store.
The counterpart to services.hyperhive.deploy.bao.clientCaFile,
which is the store’s side of the same handshake.
On a hive that runs the store, a glue module supplies the leaf it minted,
as a mkDefault. Everywhere else this is the credential an operator
places by hand — the one secret that cannot come out of the store,
because it is what opens it.
A path, never a value.
Type: null or string
Default:
null
Example:
"/var/lib/swarm-bao/client.pem"
Declared by:
services.hyperhive.deploy.bao.clientKeyFile
Private key for services.hyperhive.deploy.bao.clientCertFile.
Both or neither — a certificate with no key authenticates nothing.
Type: null or string
Default:
null
Example:
"/var/lib/swarm-bao/client-key.pem"
Declared by:
services.hyperhive.deploy.bao.extraListenAddresses
Addresses the store listens on in addition to loopback, each on
services.hyperhive.swarm.bao.port.
Loopback is unconditional and not listed here: the host running the store is always one of its readers. Every other address depends on which network the reading hives share with this one, and that is a deployment fact no other module’s config can be read to infer — a swarm meshed over wireguard names its mesh address, one on a trusted LAN names that interface, and an all-local swarm names nothing at all.
Addresses only, no port: a store reachable on two ports is a misconfiguration rather than a topology.
Type: list of string
Default:
[ ]
Example:
[
"10.100.0.1"
]
Declared by:
services.hyperhive.deploy.bao.metricsPort
Loopback port the store serves its Prometheus metrics on, scraped by a collector on this same host.
Here and not beside services.hyperhive.swarm.bao.port
because no client of the store ever needs it: reaching this port
means being the local collector, which is a property of the host
running the store.
⚠️ Cannot be services.hyperhive.swarm.bao.port + 1 —
openbao derives every listener’s cluster address as its own port plus
one, so the API listener already holds that number. The default sits
one above it, and its own derived cluster address one above that.
Type: 16 bit unsigned integer; between 0 and 65535 (both inclusive)
Default:
8202
Declared by:
services.hyperhive.deploy.bao.seal
How the store’s root key is sealed.
pkcs11 is the default and binds the key to the host’s TPM: the store
unseals itself at boot, and an attacker with the disk does not get the
secrets. shamir is openbao’s own default — unseal keys held by
whoever ran bao operator init, entered by hand after every restart —
and is the honest choice for a host with no TPM.
⚠️ This is a declaration, and nothing at evaluation time can check
it: nix runs on the build machine and cannot see the target’s TPM.
Saying pkcs11 on a host without one fails when the store’s container
starts and its provisioning unit cannot create the token — later than
activation, and on the container’s journal. That is deliberate — a
store that comes up sealed by software while the config says hardware
is weaker than it reads, and silently so.
Type: one of “pkcs11”, “shamir”
Default:
"pkcs11"
Example:
"shamir"
Declared by:
services.hyperhive.deploy.bao.serverCaFile
Authority a reader on this machine validates the store’s certificate against.
Not services.hyperhive.deploy.bao.clientCaFile with the words
rearranged: that one is the store choosing which readers to trust, this
one is a reader choosing which store to trust. A deployment that
self-signs both ends points them at the same file and reads that as
confirmation they are interchangeable — they are not, and they diverge
the moment either end gets a real CA.
Type: null or string
Default:
null
Example:
"/var/lib/swarm-bao/ca.pem"
Declared by:
services.hyperhive.deploy.bao.serverCertFile
Certificate the store serves, covering
services.hyperhive.swarm.bao.domain.
This module declares no default and deliberately does not know
what could provide one — for the same reason
services.hyperhive.deploy.bao.clientCaFile doesn’t: the
store never reaches for an authority.
On a hive that runs the store, a glue module supplies a path as a
mkDefault, so naming your own here wins over it.
A path, never a value.
Type: null or string
Default:
null
Example:
"/var/lib/swarm-bao/server.pem"
Declared by:
services.hyperhive.deploy.bao.serverKeyFile
Private key for services.hyperhive.deploy.bao.serverCertFile.
Both or neither — a certificate with no key is a listener that cannot
start.
Type: null or string
Default:
null
Example:
"/var/lib/swarm-bao/server-key.pem"
Declared by:
services.hyperhive.deploy.bao.tpmGid
Numeric group id owning /dev/tpmrm0, so the store’s seal can open it.
⚠️ A number, and it has a default, because a name cannot do this
job. The store runs as a DynamicUser whose uid is allocated inside
its container, and NixOS allocates tss — and every other system
group — at activation, per machine. A group declared on the host and
a group of the same name declared in the container therefore get
different ids, and the device node carries the number. Pinning one
value is what makes the two sides agree.
The default sits above the range NixOS auto-assigns system groups from
(400–999) and above the normal-user range (1000–29999), and below the
range systemd allocates DynamicUser ids from (61184–65519), so it
collides with nothing those allocate. Override it if it collides with
something this module cannot see.
Only read when services.hyperhive.deploy.bao.seal is
pkcs11; a shamir store never touches the TPM.
Type: signed integer
Default:
31337
Example:
4242
Declared by:
services.hyperhive.deploy.forgejo.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/networking/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.deploy.forgejo.ci.enable
Run a Forgejo Actions runner in a hive-ci nixos-container.
Grouped under services.hyperhive.deploy.forgejo 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.deploy.forgejo.ci.package
gitea-actions-runner package.
Type: package
Default:
pkgs.gitea-actions-runner
Declared by:
services.hyperhive.deploy.forgejo.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.deploy.forgejo.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.deploy.forgejo.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.deploy.forgejo.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.deploy.forgejo.hostSwarmControllerTokenFile
Host path where this forge deposits the freshly-minted forge
access token for the swarm’s swarm-controller account (see
systemd.services.forgejo-swarm-controller-account inside the
forge container, and hive-forge-swarm-controller-token on the
host, which copies the token out).
Same role for this token as
services.hyperhive.deploy.authelia.hostClientSecretDir plays
for the OIDC secret: a host-local path (not inside any
container), read directly by swarm-controller.nix’s
LoadCredential when the controller runs on this same host.
On any other host the token has to get there somehow — copy it
out of this path with whatever secret management this
deployment already uses, the same shape swarm.nix’s own
clientSecretFile documents for the analogous cross-host case.
Type: string
Default:
"/var/lib/hyperhive-forge/swarm-controller.token"
Declared by:
services.hyperhive.deploy.forgejo.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.deploy.forgejo.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.deploy.forgejo.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.deploy.forgejo.mirrors.*.upstream
Upstream clone URL to mirror from.
Type: string
Example:
"https://github.com/actions/checkout"
Declared by:
services.hyperhive.deploy.forgejo.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.deploy.forgejo.openFirewall = true; to your
host config before rebuilding.
Type: boolean
Default:
false
Example:
true
Declared by:
services.hyperhive.deploy.forgejo.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.deploy.grafana.enable
Run the swarm’s metrics UI on this host.
Off by default and not derived from
services.hyperhive.enable: a swarm has one Grafana, so
running it is a decision about this host rather than about
whether hyperhive is installed.
Type: boolean
Default:
false
Declared by:
services.hyperhive.deploy.grafana.datasourceUrl
Where the provisioned datasource points. Defaults to the metrics store on this host, which is the only place it can be: that store binds loopback, so a Grafana somewhere else could not reach it anyway. Set explicitly if a deployment fronts VictoriaMetrics with something that does listen wider.
Type: string
Default:
"http://127.0.0.1:${toString services.hyperhive.swarm.victoriametrics.port}"
Declared by:
services.hyperhive.deploy.grafana.logsDatasourceUrl
Where the provisioned logs datasource points. Same reasoning as
services.hyperhive.deploy.grafana.datasourceUrl: the store
binds loopback, so a Grafana elsewhere could not reach it anyway.
Provisioned unconditionally, like the metrics datasource — the store being off is a deployment choice rather than a reason to withhold the connection, and an operator whose logs live elsewhere sets this.
Type: string
Default:
"http://127.0.0.1:${toString services.hyperhive.swarm.victorialogs.port}"
Declared by:
services.hyperhive.deploy.grafana.plugins
Grafana plugins to install, as packages. Declarative rather than
installed through the UI, which is the only shape that works here:
plugin management is server-admin scoped, and on an SSO hive
nobody holds that role — users.auto_assign_org_role grants an
org role, and the built-in local admin that does hold server
admin cannot log in because the login form is disabled whenever
SSO is configured.
That is a deliberate pair of decisions rather than an oversight, and this option is the way through it: plugins live in the store and in git, so they survive a container rebuild and a state reset, and the container needs no runtime egress to grafana.com.
⚠️ This list is the ONLY way a plugin reaches this Grafana.
Setting it at all disables upstream’s preinstall mechanism (see
the declarativePlugins comment below), which is what normally
fetches the Drilldown apps on first start — so a plugin left off
this list is absent, not merely un-pinned.
Metrics Drilldown is on by default because this swarm’s Grafana exists to read VictoriaMetrics, and the queryless metrics browser is the part of that people actually reach for.
Its Logs / Traces / Profiles siblings are deliberately NOT here,
and for two different reasons. Traces and Profiles front Tempo
and Pyroscope, which this swarm does not run — a UI for a backend
that does not answer. Logs Drilldown is the subtler one: this
swarm does run a log store, so that argument no longer applies
to it, but the app is Loki-only and no configuration here changes
that. Its volume views call /loki/api/v1/index/volume, which
VictoriaLogs does not implement and answers “unsupported path
requested”. Logs are browsed through Explore instead.
Type: list of package
Default:
[
pkgs.grafanaPlugins.grafana-metricsdrilldown-app
pkgs.grafanaPlugins.victoriametrics-logs-datasource
]
Example:
[ pkgs.grafanaPlugins.grafana-piechart-panel ]
Declared by:
services.hyperhive.deploy.grafana.socketDir
Directory holding the unix socket Grafana serves on, shared between the host (where nginx runs) and the container (where Grafana runs).
⚠️ Grafana takes no TCP port at all, and that is the point. Every
swarm service container shares the host’s network namespace, so a
port is a swarm-wide resource that two modules can silently both
claim — which is exactly what happened: Grafana defaulted to
upstream’s 3000, so does the forge, and grafana.<swarm-domain>
served the forge with no bind error and nothing in any log.
A socket has a path, and a path collision is a build-time conflict rather than a runtime coin toss.
Type: string
Default:
"/run/swarm-grafana"
Declared by:
services.hyperhive.deploy.hive-controller.statusPublish.clientSecretFile
Path to a file holding the plaintext client secret for this
hive’s hive-<hiveName> identity.
A path and not a value: a secret in the Nix store is world
readable, and one in the environment is readable by anything
that can open /proc/<pid>/environ.
Defaults to authelia’s own minted secret when the IdP runs on
this host. On any other hive the secret has to get here somehow,
and the swarm does not distribute it — copy it out of the swarm
host’s
services.hyperhive.deploy.authelia.hostClientSecretDir
with whatever secret management this deployment already uses.
Type: null or string
Default:
"${deploy.authelia.hostClientSecretDir}/hive-${hiveName}.secret" when this host runs both the queue and the IdP, else null
Example:
"/var/lib/secrets/swarm-queue-client.secret"
Declared by:
services.hyperhive.deploy.hive-controller.statusPublish.natsUrl
Where the swarm queue listens, as seen from this hive.
Defaults to loopback when this host runs the queue container itself (it shares the host netns, so loopback is correct there and is not the “localhost means the wrong thing” trap that applies inside agent containers). A hive that is not the swarm host has to name the swarm’s mesh address.
Null disables status publishing: this hive computes its own
readiness as always, and simply offers it to nobody. The swarm
controller then reports it never_reported, which is the honest
reading.
Type: null or string
Default:
"nats://127.0.0.1:${swarm.nats.port}" when this host runs the queue and the IdP, else null
Example:
"nats://10.100.0.1:4222"
Declared by:
services.hyperhive.deploy.hive-controller.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.deploy.hive-controller.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.deploy.hive-controller.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:
services.hyperhive.deploy.matrix.enable
Run the swarm’s matrix homeserver — matrix-tuwunel, in a
hive-matrix container — on this host.
Set it directly to put the homeserver somewhere other than the host holding the rest of the swarm’s services.
Client-side settings stay in
services.hyperhive.swarm.matrix.*, which every hive
agrees on; this is only the decision to run it here.
Type: boolean
Default:
false
Declared by:
services.hyperhive.deploy.matrix.gui.enable
Serve a matrix web client at gatewayHost’s vhost (chat.<swarm-domain>
by default). Requires gatewayHost != null; the gateway itself always runs. When
off, the dashboard’s M4TR1X → tab is hidden. See
docs/networking/gateway.md for the discovery flow that lets clients
auto-find the sub-domain. The client build itself is
swarm.matrix.gui.package — which client, as opposed to whether
this host serves it.
Type: boolean
Default:
config.services.hyperhive.deploy.matrix.enable
Declared by:
services.hyperhive.deploy.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.
This is the only limit to set: the gateway’s own body cap is derived from it, with headroom, so the homeserver stays the one that rejects an oversized upload.
Type: positive integer, meaning >0
Default:
20000000
Declared by:
services.hyperhive.deploy.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 gatewayHost’s vhost
(chat.<swarm-domain> by default) 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.deploy.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.deploy.matrix.sso.clientSecretFile
Path inside the matrix 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 homeserver needs the plaintext), and a literal written here would be rendered into the world-readable nix store.
Deliberately no fallback — a homeserver that boots with SSO
half-configured is worse than one that fails to evaluate:
tuwunel reads OIDC from its config file rather than a database
row, so a malformed block can stop the server outright instead
of merely hiding a button. The id this pairs with is
swarm.matrix.sso.clientId, which is swarm-wide because it must
match authelia’s register.
Type: null or string
Default:
null
Example:
"/var/lib/tuwunel-oidc/tuwunel.secret"
Declared by:
services.hyperhive.deploy.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.deploy.nats.enable
Run the swarm’s message queue in a swarm-nats container on this
host. A swarm has one queue, so at most one host turns this on —
but which host is its own decision, not necessarily the one
running the swarm’s other shared services.
Off by default, and off means absent: no container is created and nothing else in the evaluated config changes.
Type: boolean
Default:
false
Declared by:
services.hyperhive.deploy.nats.autoGenerateCallout
Generate the auth-callout nkeys on this host instead of taking
them from calloutUserPublicKey / calloutIssuerPublicKey.
A first-boot unit mints both keypairs if absent, keeps the seeds
host-side at 0600, and writes only the public halves into a
fragment the server reads. Nothing secret is evaluated, so
nothing secret reaches the nix store.
Leave it off wherever the queue and its clients are not the same
operator’s problem: the seeds must reach whoever runs the
responder, and minting them here only moves that distribution
somewhere less visible. singleHostSwarm turns it on.
Type: boolean
Default:
false
Example:
true
Declared by:
services.hyperhive.deploy.nats.calloutIssuerPublicKey
Public half of the account nkey whose signature the server accepts on a user JWT minted by the auth-callout responder.
A public key, and therefore a value rather than a path —
the deliberate exception to the rule that credentials are
*File options. It is published to every client that connects
and its whole job is to be widely known; the matching seed is
the secret, is never named here, and reaches only the responder.
Required when deploy.nats.enable is set. Without it the server
has no issuer to trust and no client can be authorized — which is
the fail-closed state described below, but arrived at by accident
rather than on purpose, so it fails at eval instead.
Type: string
Default:
""
Example:
"ACYR44YO3XZRZBJIYLI5SL6LOPIW37JTD52LNOBHUE34XMH7N5ABMFJH"
Declared by:
services.hyperhive.deploy.nats.calloutIssuerSeedFile
Absolute host path to the account seed whose public half is
calloutIssuerPublicKey. The responder signs the user JWTs it
issues with it, so possession of this file is the authority to
admit anyone to the queue.
A str for the same store-leak reason as
calloutUserSeedFile.
Type: string
Default:
""
Example:
"/run/secrets/swarm-nats-issuer.seed"
Declared by:
services.hyperhive.deploy.nats.calloutUserPublicKey
Public half of the user nkey the auth-callout responder authenticates as.
auth_callout.auth_users exempts this identity from needing
callout approval — it is the one that answers auth requests, so
it cannot wait for itself. That exemption is exactly why it
needs a credential of its own: without one the escape hatch is
an open door, and on a container sharing the host netns it is an
open door reachable from every agent container.
An nkey rather than a password for the same reason
calloutIssuerPublicKey is: only the public half appears here,
and nix renders it into the world-readable store harmlessly. The
seed reaches the responder and nothing else, so until the
responder exists nobody can authenticate as this user at all
— which is what makes a hive with no responder genuinely closed
rather than merely gated.
Required when deploy.nats.enable is set.
Type: string
Default:
""
Example:
"UDXU4RCSJNZOIQHZNWXHXORDPRTGNJAHAHFRGZNEEJCPQTT2M7NLCBBQ"
Declared by:
services.hyperhive.deploy.nats.calloutUserSeedFile
Absolute host path to the seed whose public half is
calloutUserPublicKey. The responder authenticates to the queue
with it.
A str rather than a path, and the reason is not style: a
path-typed literal is hash-copied into the world-readable nix
store at eval time, which is the opposite of what a seed wants.
Same discipline as otel.headersCredential.
Until this is set the responder cannot start, and the queue stays in its fail-closed state — which is the correct behaviour, not a gap.
Type: string
Default:
""
Example:
"/run/secrets/swarm-nats-callout-user.seed"
Declared by:
services.hyperhive.deploy.singleHostSwarm
Run the whole swarm on this host. Turning this on asserts the
toggles that an all-on-one-box deployment implies: the swarm’s
shared services
(services.hyperhive.deploy.allSwarmServices), the swarm
CA (services.hyperhive.swarm.ca.autoConfigure), the swarm
controller (services.hyperhive.deploy.swarm-controller.enable), and the
host’s /etc/hosts entries for the names this hive serves
(services.hyperhive.gateway.localHostsEntry) — with no real DNS
for those names, the operator is browsing them from the same box
that answers for them.
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.deploy.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 running it is a decision about this host rather than about
whether hyperhive is installed.
Type: boolean
Default:
false
Declared by:
services.hyperhive.deploy.swarm-controller.authBridgeUrl
Where swarm-authelia-bridge (the only writer of authelia’s
users database) answers — see that option’s own doc comment for
the cross-host caveat, since this default is only correct when
this host also runs swarm-authelia.
No new credential to configure: the bearer token presented to
the bridge is minted from THIS daemon’s own existing queue OIDC
identity — its endpoints stay in swarm.controller.queue, its secret is
queue.clientSecretFile below — “one identity per principal”
already covers it. null means no agent-identity support:
CreateIdentity jobs fail with a clear “no auth bridge
configured here” error rather than the daemon refusing to
start, the same graceful-absence shape forgeTokenFile uses.
Type: null or string
Default:
authelia's own `bridgeUrl` when this host also runs
`swarm-authelia`, else null
Example:
"http://127.0.0.1:9092"
Declared by:
services.hyperhive.deploy.swarm-controller.forgeTokenFile
Path to this host’s copy of the swarm-controller’s forge access
token (see hive-forge/default.nix’s
forgejo-swarm-controller-account + hive-forge-swarm-controller-token
units, which mint and collect it onto forge’s own host).
Defaults to forge’s own delivery path on every host running
hyperhive (forge deploys unconditionally alongside it — see
hive-forge/default.nix, it has no enable of its own).
Override explicitly if forge’s actual token file ends up
somewhere else — copy it out of forge’s
services.hyperhive.deploy.forgejo.hostSwarmControllerTokenFile
with whatever secret management this deployment already uses,
the same shape swarm.nix’s clientSecretFile documents for
its own cross-host case. null means no forge access — the
daemon logs that and continues without it, the same
graceful-absence shape the queue coordinates already use.
Type: null or string
Default:
forge's own `hostSwarmControllerTokenFile` when this host runs
hyperhive at all (forge has no separate enable), else null
Example:
"/var/lib/secrets/swarm-controller-forge.token"
Declared by:
services.hyperhive.deploy.swarm-controller.queue.clientSecretFile
Path on this host holding the plaintext of the controller’s OAuth2 client secret. A path, never a value: the secret would otherwise land in the world-readable nix store.
The controller cannot mint its own — minting happens inside
authelia’s state directory during its first boot — so away from
that host the operator places the secret and names it here.
singleHostSwarm points this at the minted file, which
is exactly the case where one exists locally.
Read by LoadCredential, so it needs to be readable by root at
unit start and nothing more; the daemon’s own user never sees
the original path.
Type: string
Default:
""
Example:
"/var/lib/secrets/swarm-controller-queue.secret"
Declared by:
services.hyperhive.deploy.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.deploy.swarm-otel.enable
Run the swarm’s telemetry collector on this host.
A swarm has one of these, and it belongs wherever the shared services live rather than on every hive.
Named swarm-otel rather than otel because there are two
collectors and the tier is the whole distinction:
services.hyperhive.otel.enable is the hive-tier one,
which every hive runs. A bare deploy.otel would not say which
it meant. A hive that does not run the swarm collector still runs
its own, and reaches this one by name at
services.hyperhive.swarm.otel.domain.
Type: boolean
Default:
false
Declared by:
services.hyperhive.deploy.swarm-otel.clientSecretFile
Path, inside this collector’s container, to its OIDC client secret.
null means it holds no credential: it still exports, and the
authenticated destinations refuse it. Set automatically where
authelia is co-located; a deployment that places authelia elsewhere
points this at a file it delivers itself.
⚠️ A path, never a value — a secret interpolated into a nix expression renders world-readable into the store.
Type: null or string
Default:
null
Example:
"/var/lib/swarm-otel-oidc/swarm-collector.secret"
Declared by:
services.hyperhive.deploy.swarm-ui.enable
Serve the swarm UI from this host.
Derived from
services.hyperhive.deploy.swarm-controller.enable: 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.deploy.swarm-controller.enable
Example:
true
Declared by:
services.hyperhive.deploy.victorialogs.enable
Run the swarm’s log store on this host.
A hive that is not the service host is a client of this store, not a second one.
Type: boolean
Default:
false
Declared by:
services.hyperhive.deploy.victorialogs.retentionPeriod
How long log data is kept.
Deliberately far shorter than the metrics store’s retention: logs are orders of magnitude larger per unit of time, and their value decays much faster. A log line answers “what happened during that incident”; a metric answers “is this worse than last quarter”.
Type: string
Default:
"30d"
Example:
"90d"
Declared by:
services.hyperhive.deploy.victoriametrics.enable
Run the swarm’s metrics store on this host.
Paired with
services.hyperhive.deploy.grafana.enable: a store
with no UI is unreadable and a UI with no store is empty, so there
is no sensible deployment that takes one and not the other from
that switch. Set either directly to run exactly one.
Type: boolean
Default:
false
Declared by:
services.hyperhive.deploy.victoriametrics.retentionPeriod
How long samples are kept.
Deliberately a high default rather than a required option: the two failure directions are not symmetric. Too long fills a disk, which is visible and recoverable by lowering this; too short destroys history, silently and permanently. So the safe default is generous and an operator lowers it once they have measured how fast this swarm actually accumulates data.
Type: string
Default:
"5y"
Example:
"90d"
Declared by:
services.hyperhive.deploy.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.deploy.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.deploy.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.deploy.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.deploy.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
deploy.wireguard.enable = true.
Type: null or absolute path
Default:
null
Example:
"/etc/wireguard/hive.key"
Declared by: