Compare commits

...
Author SHA1 Message Date
atlas
f798662396 docs(swarm): fix two relative links broken by the README move
Both worked from the flat docs/swarm.md and resolve one directory
short from docs/swarm/README.md: snapshot-store.md stayed in docs/,
and swarm-controller/README.md is at the repo root.
2026-08-05 18:12:52 +02:00
atlas
b3b1ed19c6 docs: split swarm.md into a directory, starting with the services page
`docs/swarm.md` becomes `docs/swarm/README.md` and the shared-services
material moves to `docs/swarm/services.md`, following the shape
`docs/turn-loop/` and `docs/web-ui/` already use. The README keeps a
pointer so the reading path is unbroken.

Every referrer moved with it — five docs pages, two option descriptions
in swarm.nix, and CLAUDE.md's reading path. A pointer to a file that
moved is worse than one to a file that was deleted: the content still
exists, so the reader concludes the note is wrong rather than the path.
2026-08-05 18:07:04 +02:00
atlas
ade0bf24c5 docs: the forge is swarm-wide but not optional, so it derives nothing
Both the option description and docs/swarm.md listed the forge among the
services whose `enable` defaults from `swarm.enableRequiredServices`.
It has no `enable`: it is the canonical store for the meta flake and
every agent's config repo, so it deploys with hyperhive itself.

The distinction is worth stating rather than deleting — a reader who
sees matrix and authelia listed and the forge absent would reasonably
wonder whether the forge is per-hive. It isn't; it's mandatory.
2026-08-05 18:05:39 +02:00
atlas
8d21a47f42 docs(swarm): the shared-services switch and the SSO provider
Names the one toggle and what derives from it, and the authelia shape:
container where the swarm's services live, client pointer everywhere,
users database written by swarm-controller rather than by hand.
2026-08-05 18:00:14 +02:00
atlas
6ea43c1151 feat(nix): matrix follows the swarm's shared-services toggle
Matrix is a swarm-wide service — one homeserver, not one per hive — so
its `enable` defaults from `swarm.enableRequiredServices` rather than
being flipped on its own. That toggle is off by default, so matrix is
off by default exactly as before; what changes is that a host declaring
"the swarm's services live here" gets the homeserver with it.

The forge needs no equivalent: it is mandatory and deploys with
hyperhive itself, so it has no enable to derive.
2026-08-05 17:58:31 +02:00
atlas
316cef94d1 feat(nix): the swarm's authelia, in a swarm-authelia container
One SSO provider per swarm, and a hive is a client of it by default:
`url` exists everywhere so a hive knows where to send people, while the
container only exists where `swarm.enableRequiredServices` says the
swarm's shared services live.

Operator and agents are both subjects of the same provider,
differentiated by roles/claims rather than by mechanism. The users store
is therefore written by swarm-controller rather than maintained by hand
— agents are created and destroyed continuously, so the subject set is
dynamic. This module only guarantees the file exists and parses, so
authelia starts with nobody in it instead of failing to start; a
placeholder account would be a credential nobody meant to create.

Small-deployment defaults, with scope as the justification rather than
preference: file users backend, local sqlite storage (redis buys shared
session state across replicas and there is one instance), filesystem
notifier (SMTP mails humans; provisioning is programmatic).

Secrets are generated in-container on first boot, sharing the authelia
unit's user/group/StateDirectory so systemd creates the directory owned
by the account that reads them. Nothing outside the container reads
these keys, which is what makes that right rather than merely easier —
hive-matrix generates its token host-side only because hive-c0re has to
read that one.

Per-service integration (authelia's auth_request in front of the
gateway's existing auth_basic locations) is deliberately not here:
standing an SSO provider up is reversible, cutting every operator-facing
vhost over to it is not.
2026-08-05 17:56:51 +02:00
atlas
b94382b815 feat(nix): one switch for an all-on-one-host deployment
`services.hyperhive.enableAllLocalDefaults` is the single "everything
runs on this box" toggle, and the autoconfigurable settings default from
it: `swarm.enableRequiredServices` (new — the swarm's shared services
run here) and `swarm.ca.autoConfigure` (previously an explicit false).

Off by default, unchanged from before: a host cannot tell whether it is
the one meant to hold the swarm's services or its CA, so this stays an
operator saying "this is that box". What it replaces is one toggle per
service for the deployment where the answer is "all of them".

Each derived toggle can still be set on its own, so "all local except X"
needs no further option.
2026-08-05 17:50:46 +02:00
14 changed files with 420 additions and 15 deletions

View file

@ -223,7 +223,7 @@ read them à la carte.
bridge network for?"** → [`docs/network.md`](docs/network.md).
- **"How do I connect two hives into a swarm? How do I declare peer
hives and configure TLS trust?"** →
[`docs/swarm.md`](docs/swarm.md).
[`docs/swarm/`](docs/swarm/README.md).
- **"Where do agent snapshots go? How does the swarm's `btrfs receive`
endpoint authenticate a pushing hive, and what does a snapshot
actually contain?"** →

View file

@ -71,7 +71,7 @@ declarations.
discovery work?** → [`gateway.md`](gateway.md).
- **How does DNS resolution work in agent containers? What's the
bridge network for?** → [`network.md`](network.md).
- **How do I connect two hives into a swarm?** → [`swarm.md`](swarm.md)
- **How do I connect two hives into a swarm?** → [`swarm/`](swarm/README.md)
(peer hives, TLS trust).
- **Where do agent snapshots go? How does the swarm's `btrfs receive`
endpoint authenticate a pushing hive?** →

View file

@ -198,7 +198,7 @@ The issuer is a **host-held hive CA**, not a bare self-signed leaf. A host servi
**Why a CA, not a bare leaf**: a bare self-signed leaf is its own trust anchor, so every regeneration is a new anchor every consumer must re-trust — and a runtime-generated, in-container leaf can't be wired into an agent's build-time trust store at all. With a stable CA, agents and federation peers trust it *once*; leaf rotation never re-breaks them.
**What consumers trust**: `trust-bundle.pem` in the same state dir, not `ca.pem`. The hive CA is itself issued under the swarm root ([`swarm.md`](swarm.md#swarm-ca) has the hierarchy), and an intermediate is not a chain a verifier can terminate at — so the bundle carries the hive CA plus whatever it is rooted at. nginx is handed the leaf with the hive CA appended for the same reason. Everything that trusts the hive's TLS reads the bundle: agents (via `security.pki.certificateFiles`), the CI and forge containers, and a federating peer.
**What consumers trust**: `trust-bundle.pem` in the same state dir, not `ca.pem`. The hive CA is itself issued under the swarm root ([`swarm/`](swarm/README.md#swarm-ca) has the hierarchy), and an intermediate is not a chain a verifier can terminate at — so the bundle carries the hive CA plus whatever it is rooted at. nginx is handed the leaf with the hive CA appended for the same reason. Everything that trusts the hive's TLS reads the bundle: agents (via `security.pki.certificateFiles`), the CI and forge containers, and a federating peer.
**Why on by default**: matrix-dart-sdk (FluffyChat's SDK) hardcodes `https://<host>/.well-known/matrix/client` for homeserver discovery and refuses to fall back to plain http. Without TLS the browser client cannot bootstrap.

View file

@ -180,7 +180,7 @@ at the protocol level from first boot (`allow_federation = true`) but
no remote homeserver is trusted until listed here. For a closed
single-hive deployment the default empty list is correct — add peer
hive domains here when connecting hives into a swarm (see
[`docs/swarm.md`](swarm.md)).
[`docs/swarm/`](swarm/README.md)).
**`maxRequestSize`** (default `20_000_000` bytes = 20 MB) — maximum
size of a single matrix client request body. Matches the matrix-spec

View file

@ -66,7 +66,7 @@ The flows, end to end:
the host forwards + masquerades out its uplink.
- **Swarm** — peer hives connect over the `wg-hive` WireGuard mesh
and reach each other's gateway/forge across it
([`docs/swarm.md`](swarm.md)).
([`docs/swarm/`](swarm/README.md)).
- **Control plane (no network)** — per-agent broker socket
`/run/hive/mcp.sock`, privileged helper `/run/hive/priv.sock`,
operator admin `/run/hyperhive/host.sock`, and the per-agent UI

View file

@ -35,7 +35,7 @@ services.hyperhive.swarm.wireguard = {
The store host is a swarm member like any other: peers declare it, and
it declares them, through `services.hyperhive.swarm.peers`. See
[swarm.md](swarm.md) for the mesh itself.
[swarm/](swarm/README.md) for the mesh itself.
Note that the mesh is gated on `swarm.wireguard.enable`, **not** on
`c0re.enable` --- a store host runs no hive and would otherwise get no

View file

@ -113,6 +113,12 @@ the CA is re-issued under the root and the leaf re-signed. Until then
the hive serves TLS exactly as before and is simply not part of the
swarm's trust hierarchy.
## Running the swarm's shared services
One authelia, one matrix, one forge per swarm — which host runs them,
and what a hive that runs none of them configures instead:
[`services.md`](services.md).
## Declaring peer hives
```nix
@ -310,7 +316,7 @@ port}` tells this hive where the swarm's `btrfs receive` endpoint is, so
It is genuinely swarm-scoped rather than per-peer — a swarm has exactly
one store, because the receiver keys destinations by *agent* so a
migrating agent keeps one unbroken incremental chain. See
[snapshot-store.md](snapshot-store.md).
[snapshot-store.md](../snapshot-store.md).
## Swarm controller
@ -323,7 +329,7 @@ hive additionally runs this (what is true across hives).
What it serves, why it is a unix socket rather than a port, and the
socket-directory constraint that governs where `socketPath` may point:
[`swarm-controller/README.md`](../swarm-controller/README.md).
[`swarm-controller/README.md`](../../swarm-controller/README.md).
## Cross-references

59
docs/swarm/services.md Normal file
View file

@ -0,0 +1,59 @@
# Swarm-wide services
Some things exist once per **swarm**, not once per hive: the forge, the
matrix homeserver, the SSO provider, the CA. Two options say where the
optional ones live, and everything else derives:
```nix
services.hyperhive.enableAllLocalDefaults = true; # everything on this box
# or, for a dedicated services host with hives elsewhere:
services.hyperhive.swarm.enableRequiredServices = true;
```
`enableAllLocalDefaults` is the all-on-one-box switch: it defaults both
`swarm.enableRequiredServices` (the shared services run here) and
`swarm.ca.autoConfigure` (the swarm CA is generated here). Each derived
toggle can still be set on its own, so "all local except X" needs no
further option.
**Both default to off**, and that is deliberate: a host cannot tell
whether it is meant to be the swarm's service host, so this is an
operator saying so rather than something inferred. With them off, a hive
is a *client* of those services — it configures how to reach them and
runs none of them.
The forge is the exception, and not because it is per-hive: it is
swarm-wide but **not optional**, being the canonical store for the meta
flake and every agent's config repo, so it deploys with hyperhive itself
and has no `enable` to derive from anything.
### SSO (authelia)
One authelia per swarm, in a `swarm-authelia` container, at
`auth.<hive-domain>`. Operator and agents are both subjects of the same
provider, differentiated by roles and claims rather than by mechanism —
there is one IdP and one auth path.
- **`swarm.authelia.enable`** — run the container here. Defaults from
`swarm.enableRequiredServices`.
- **`swarm.authelia.url`** — where clients are sent to authenticate.
Present on **every** hive, defaulting to this host's own instance only
when this module is the thing running it; otherwise `null`, and a hive
joining someone else's swarm sets it explicitly. Null means "no SSO
configured", and consumers say so rather than guessing an address.
The users database is written by swarm-controller, not by hand: agents
are created and destroyed continuously, so the subject set is dynamic.
This module only guarantees the file exists and parses, so authelia
starts with nobody in it rather than failing to start — a provider with
no subjects yet is the correct state before anything has provisioned
them. Session and storage keys are generated in the container on first
boot and never rotated automatically; replacing one invalidates data
already written (sessions, the encrypted store), so that is an operator
action.
Storage is local sqlite and the notifier writes to a file. Both are
small-deployment choices, and the scope is the justification: redis
buys shared session state across replicas and there is one instance;
SMTP exists to mail humans, and provisioning here is programmatic.

View file

@ -21,6 +21,7 @@
./hive-priv.nix
./hive-tls.nix
./otel.nix
./swarm-authelia.nix
./swarm-ca.nix
./swarm-controller.nix
./swarm-snapshot-store.nix

View file

@ -99,12 +99,19 @@ in
options.services.hyperhive.swarm.matrix = {
enable = lib.mkOption {
type = lib.types.bool;
default = false;
default = config.services.hyperhive.swarm.enableRequiredServices;
defaultText = lib.literalExpression "services.hyperhive.swarm.enableRequiredServices";
description = ''
Run hive-matrix a private matrix-tuwunel homeserver (in a
nixos-container) for hyperhive agents. Off by default while
the integration phases in; flip to `true` once the operator
has set `services.hyperhive.domain` and is ready to onboard agents.
nixos-container) for hyperhive agents.
Matrix is a swarm-wide service one homeserver, not one per
hive so this defaults from
`services.hyperhive.swarm.enableRequiredServices`, which says
the swarm's shared services live on this host. That is off by
default, so this is off by default, as before. Set it directly
to run the homeserver somewhere other than the host that holds
the rest of the swarm's services.
'';
};

View file

@ -132,6 +132,32 @@ in
'';
};
# The one switch for "everything runs on this box". Every autoconf
# toggle in the tree defaults from it, so an all-local deployment is a
# single line rather than one line per service that grew a toggle.
options.services.hyperhive.enableAllLocalDefaults = lib.mkOption {
type = lib.types.bool;
default = false;
example = true;
description = ''
Run the whole swarm on this host: the swarm-wide services
(`services.hyperhive.swarm.enableRequiredServices`) and the swarm
CA (`services.hyperhive.swarm.ca.autoConfigure`) all default from
this, and anything autoconfigurable added later should too.
**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 derived toggle can still be set explicitly to override this
one, so "all local except X" needs no new option.
'';
};
# Whether this hive runs "ruthless" — with no root/manager agent at
# all. Some hives don't want a root agent — see issue tracker
# "scope concept: special agents".

View file

@ -0,0 +1,268 @@
# The swarm's SSO provider: one authelia for the whole swarm, in a
# `swarm-authelia` nixos-container.
#
# Two halves, and only one of them is conditional:
#
# - the CLIENT pointer (`url`) exists on every hive, because a hive
# that doesn't run authelia still has to know where to send people.
# - the CONTAINER only exists where the swarm's shared services live
# (`swarm.enableRequiredServices`, itself defaulting from
# `enableAllLocalDefaults`). A hive is a client by default.
#
# Operator and agents are both subjects of the same provider,
# differentiated by roles/claims rather than by mechanism — there is one
# IdP and one auth path. The users store is therefore written by a
# program (swarm-controller), not maintained by hand: agents are created
# and destroyed continuously, so the subject set is *dynamic*. That is
# also why the file backend is the right one here and not a placeholder
# for LDAP: what makes a directory necessary is the size of the subject
# set, and this deployment's is bounded by one swarm.
#
# Per-service integration — putting authelia's `auth_request` in front
# of the gateway's existing `auth_basic` locations — is deliberately NOT
# here. Standing an SSO provider up is reversible; cutting every
# operator-facing vhost over to it is not.
{
pkgs,
lib,
config,
...
}:
let
cfg = config.services.hyperhive.swarm.authelia;
hyperhiveCfg = config.services.hyperhive;
swarmCfg = config.services.hyperhive.swarm;
hyperhiveDomain = hyperhiveCfg.domain;
# Upstream's `services.authelia.instances.<name>` derives the unit,
# user, group and StateDirectory from the instance name
# (`authelia` + `-<name>`). Naming them here rather than repeating the
# literal keeps the generator unit below and the module in step.
instance = "swarm";
unitName = "authelia-${instance}";
stateDir = "/var/lib/${unitName}";
# Total on a null hive domain for the same reason the option defaults
# below are: the required-domain assertion in hive-network.nix should
# be what an operator sees, not a coercion error from here.
cookieDomain = if hyperhiveDomain == null then "invalid" else hyperhiveDomain;
in
{
options.services.hyperhive.swarm.authelia = {
enable = lib.mkOption {
type = lib.types.bool;
default = swarmCfg.enableRequiredServices;
defaultText = lib.literalExpression "services.hyperhive.swarm.enableRequiredServices";
example = true;
description = ''
Run the swarm's authelia in a `swarm-authelia` container on this
host. Defaults from
`services.hyperhive.swarm.enableRequiredServices` a swarm has
one SSO provider, and this 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.
'';
};
package = lib.mkOption {
type = lib.types.package;
default = pkgs.authelia;
defaultText = lib.literalExpression "pkgs.authelia";
description = ''
authelia package to run in the container. Defaults to
nixpkgs's; override to pin a specific upstream.
'';
};
port = lib.mkOption {
type = lib.types.port;
default = 9091;
description = ''
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).
'';
};
domain = lib.mkOption {
type = lib.types.str;
# Total on a null hive domain so the required-domain assertion in
# hive-network.nix is the thing that fires; see the comment there.
default = if hyperhiveDomain == null then "auth.invalid" else "auth.${hyperhiveDomain}";
defaultText = lib.literalExpression ''"auth.''${services.hyperhive.domain}"'';
example = "login.example.com";
description = ''
Public hostname for the SSO provider the sub-domain shape the
forge and matrix already use. Doubles as the cookie domain's
host, so it must be the name browsers actually visit.
'';
};
url = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = if cfg.enable then "https://${cfg.domain}" else null;
defaultText = lib.literalExpression ''if enable then "https://''${domain}" else null'';
example = "https://auth.example.com";
description = ''
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.
'';
};
usersFile = lib.mkOption {
type = lib.types.str;
default = "${stateDir}/users.yml";
defaultText = lib.literalExpression ''"/var/lib/authelia-swarm/users.yml"'';
description = ''
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.
'';
};
};
config = lib.mkIf (hyperhiveCfg.enable && cfg.enable) {
containers.swarm-authelia = {
autoStart = true;
ephemeral = false;
# Shared host netns, like the forge and matrix containers: the
# gateway reaches authelia at 127.0.0.1:<port>.
privateNetwork = false;
config =
{ ... }:
{
system.stateVersion = "26.05";
# This container shares the host netns, so its own
# firewall.service would rewrite the HOST ruleset at every
# boot. The host firewall owns all filtering.
networking.firewall.enable = false;
# Keep the host-copied /etc/resolv.conf intact — resolvconf's
# host-tracking would regenerate it to an empty file, since
# the host's copy doesn't cross the boundary after start.
networking.resolvconf.enable = lib.mkForce false;
# authelia's own secrets, generated in-container on first
# boot. They are jwt/session/storage keys — nothing outside
# this container ever reads them, which is what makes
# in-container generation right rather than merely easier.
# (hive-matrix generates its token host-side only because
# hive-c0re has to read that one.)
#
# Same `User`/`Group`/`StateDirectory` as the authelia unit,
# so systemd creates the directory owned by the account that
# has to read the files and this unit can write nowhere else.
# No chown, no mode juggling: authelia opens these paths
# itself, as its own user, under `PrivateUsers=true`.
systemd.services."${unitName}-secrets" = {
description = "Generate authelia's secrets on first boot";
wantedBy = [ "multi-user.target" ];
before = [ "${unitName}.service" ];
requiredBy = [ "${unitName}.service" ];
path = [ pkgs.coreutils ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
User = unitName;
Group = unitName;
StateDirectory = unitName;
StateDirectoryMode = "0700";
UMask = "0077";
SyslogIdentifier = "${unitName}-secrets";
};
script = ''
set -euo pipefail
# Each is generated once and never rotated here: the
# session and storage keys are load-bearing for data
# already written (sessions, the encrypted store), so
# replacing one is an operator action, not a boot action.
for f in jwt session storage-encryption; do
p=${lib.escapeShellArg stateDir}/"$f".key
if [ ! -s "$p" ]; then
head -c 64 /dev/urandom | od -An -tx1 | tr -d ' \n' > "$p"
echo "generated $p"
fi
chmod 0600 "$p"
done
# A users database that exists and parses, with nobody in
# it. authelia refuses to start without one, and the
# alternative to an empty file is a placeholder account —
# which is a credential nobody meant to create.
users=${lib.escapeShellArg cfg.usersFile}
if [ ! -s "$users" ]; then
echo "users: {}" > "$users"
echo "seeded empty users database at $users"
fi
chmod 0600 "$users"
'';
};
services.authelia.instances.${instance} = {
enable = true;
package = cfg.package;
secrets = {
jwtSecretFile = "${stateDir}/jwt.key";
sessionSecretFile = "${stateDir}/session.key";
storageEncryptionKeyFile = "${stateDir}/storage-encryption.key";
};
# Small-deployment defaults, and the scope is the
# justification: one swarm, one authelia, no replicas.
# - file users backend, written by swarm-controller
# - local sqlite storage: redis buys shared session state
# across replicas, and there is one instance
# - filesystem notifier: SMTP is for mailing humans, and
# provisioning is programmatic; a file is honest about
# where those messages go instead of implying a mail path
settings = {
theme = "dark";
server.address = "tcp://127.0.0.1:${toString cfg.port}";
log.level = "info";
authentication_backend.file.path = cfg.usersFile;
access_control.default_policy = "one_factor";
# The cookie domain is the hive's domain, NOT authelia's
# own host: the session cookie has to be sent to the apps
# being protected (`<hive>`, `forge.<hive>`,
# `matrix.<hive>`), and a cookie scoped to `auth.<hive>`
# reaches none of them. authelia enforces the relationship
# from the other side too — `authelia_url` must be a
# sub-domain of `domain`, so setting both to the same host
# fails validation at startup rather than at first login.
session.cookies = [
{
domain = cookieDomain;
authelia_url = "https://${cfg.domain}";
}
];
storage.local.path = "${stateDir}/db.sqlite3";
notifier.filesystem.filename = "${stateDir}/notification.txt";
};
};
};
};
};
}

View file

@ -52,12 +52,19 @@ in
options.services.hyperhive.swarm.ca = {
autoConfigure = lib.mkOption {
type = lib.types.bool;
default = false;
default = hyperhiveCfg.enableAllLocalDefaults;
defaultText = lib.literalExpression "services.hyperhive.enableAllLocalDefaults";
example = true;
description = ''
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.
Defaults from `services.hyperhive.enableAllLocalDefaults`, the
all-on-one-box switch which is off, so this is off, and the
paragraph below still describes what a hive does by default.
Set it 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

View file

@ -33,7 +33,7 @@
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.md for the full recipe.
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 (tuwunel validates against its
@ -62,7 +62,7 @@
inside the hive (agents + the Matrix container), never the
host system trust store. Mutually complementary with
`certFingerprint`; set `caCert` for the federation case. See
docs/swarm.md.
docs/swarm/README.md.
'';
};
@ -126,6 +126,37 @@
'';
};
# "The swarm-wide services run HERE." A swarm has one forge, one
# matrix, one SSO — this says this host is where they live. The
# OPTIONAL ones (matrix, authelia) default their own enable from it,
# rather than the operator enabling them one at a time; the forge has
# no enable to derive because it is not optional.
options.services.hyperhive.swarm.enableRequiredServices = lib.mkOption {
type = lib.types.bool;
default = config.services.hyperhive.enableAllLocalDefaults;
defaultText = lib.literalExpression "services.hyperhive.enableAllLocalDefaults";
example = true;
description = ''
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 default their
`enable` from this, so a swarm's service host is declared in one
place.
The forge is swarm-wide too but has no `enable` to derive: 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.
Defaults from `services.hyperhive.enableAllLocalDefaults` (off),
which is the all-on-one-box switch. 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.
'';
};
options.services.hyperhive.swarm.snapshotStore = {
address = lib.mkOption {
type = lib.types.nullOr lib.types.str;