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:

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: