`swarm.matrix` (what the homeserver is to every hive: server name, ports, API URL, gateway host, encryption policy, OIDC client id) moves to nix/host-modules/hive-matrix-service.nix. Everything else -- the `imports` block with its two renames and the removed `sso.enable`, the `deploy.matrix` options, the whole `config` block including `containers.hive-matrix`, and every other let binding -- stays in nix/host-modules/hive-matrix.nix, which default.nix now imports alongside the new file. The `swarm.matrix` block reads three let bindings, and the `config` block reads all three too: `cfg` (`apiUrl` defaults from `cfg.httpPort`), `swarmDomain` (`gatewayHost`'s default) and `deployCfg` (`apiUrl` reads `deployCfg.matrix.enable`). All three are option reads, so each file binds them from `config.services.hyperhive.*`. Nothing is duplicated. A pure move: option paths, option definitions and config are unchanged apart from the comment above `deploy.matrix`, which now names the file `swarm.matrix` lives in. Refs #3742
191 lines
8.6 KiB
Nix
191 lines
8.6 KiB
Nix
# The swarm's matrix homeserver as every hive sees it: the name it mints ids
|
||
# under, the ports and URLs it is reached on, its encryption policy and the
|
||
# OIDC client id it is registered under, identical on every host. What the
|
||
# host running it decides, and the container itself, are in ./hive-matrix.nix.
|
||
{
|
||
lib,
|
||
config,
|
||
...
|
||
}:
|
||
let
|
||
cfg = config.services.hyperhive.swarm.matrix;
|
||
swarmDomain = config.services.hyperhive.swarm.domain;
|
||
deployCfg = config.services.hyperhive.deploy;
|
||
in
|
||
{
|
||
options.services.hyperhive.swarm.matrix = {
|
||
serverName = lib.mkOption {
|
||
type = lib.types.nullOr lib.types.str;
|
||
default = null;
|
||
example = "chat.example.org";
|
||
description = ''
|
||
Matrix `server_name` — the host part of every user ID
|
||
(`@argus:<server_name>`) and room ID minted on this
|
||
homeserver. CRITICAL: must be stable from day one because
|
||
it's embedded irrevocably in the identifiers.
|
||
|
||
Defaults to `services.hyperhive.swarm.domain` (the bare swarm
|
||
domain), because **a swarm runs one homeserver** — tying its
|
||
identity to a single hive's domain would make relocating the
|
||
container between hives look like a different homeserver.
|
||
Combined with the `.well-known/matrix/{client,server}` routes
|
||
the gateway serves at that domain, clients auto-discover the
|
||
actual matrix endpoint without needing a subdomain. Override
|
||
here only if you need a different server_name shape (e.g.
|
||
`chat.example.org` for a bespoke hostname).
|
||
|
||
**Breaking change, and the one on this page that cannot be
|
||
undone by rebuilding.** This default has now moved twice — from
|
||
`matrix.''${services.hyperhive.domain}`, then to the bare hive
|
||
domain, and now to the swarm domain. Every existing homeserver
|
||
must pin whichever value it already minted ids under, e.g.
|
||
|
||
```nix
|
||
services.hyperhive.swarm.matrix.serverName =
|
||
config.services.hyperhive.domain; # or "matrix.''${…domain}"
|
||
```
|
||
|
||
before rebuilding. Adopting a new `server_name` does not rename
|
||
the old users and rooms — it strands them, because their ids
|
||
still name a homeserver that no longer answers.
|
||
'';
|
||
};
|
||
|
||
httpPort = lib.mkOption {
|
||
type = lib.types.port;
|
||
default = 8008;
|
||
description = ''
|
||
TCP port tuwunel serves the matrix client-server API on.
|
||
Default 8008 is the matrix-spec well-known port. Sits
|
||
outside hyperhive's claimed ranges (dashboard 7000, every
|
||
agent in 8100..8999 via FNV-1a hash). Federation listens on
|
||
`federationPort` separately.
|
||
'';
|
||
};
|
||
|
||
apiUrl = lib.mkOption {
|
||
type = lib.types.nullOr lib.types.str;
|
||
default = if deployCfg.matrix.enable then "http://127.0.0.1:${toString cfg.httpPort}" else null;
|
||
defaultText = lib.literalExpression ''
|
||
if services.hyperhive.deploy.matrix.enable
|
||
then "http://127.0.0.1:''${toString services.hyperhive.swarm.matrix.httpPort}"
|
||
else null
|
||
'';
|
||
example = "https://matrix.example.com";
|
||
description = ''
|
||
Client-server API base URL **hive-c0re itself** uses to
|
||
provision matrix (register agent users, create the hive space
|
||
and chat room, invite members). Distinct from the agent-facing
|
||
`hyperhive.matrix.url`, which is the gateway vhost handed to
|
||
each agent's `hive-matrix-daemon`.
|
||
|
||
Defaults to the loopback listener **only when this module is the
|
||
thing running tuwunel** — in that case the address is not a
|
||
guess, it is where this module just put the container. Set it
|
||
explicitly (with `enable = false`) when the homeserver runs on
|
||
another machine; "everything on one host" is a special case of
|
||
the full deployment, not the assumption.
|
||
|
||
`null` means hive-c0re has no homeserver to provision against
|
||
and matrix provisioning no-ops. There is deliberately no
|
||
fallback compiled into the daemon: an address baked into the
|
||
binary is one that builds fine and then talks to the wrong
|
||
machine.
|
||
'';
|
||
};
|
||
|
||
gatewayHost = lib.mkOption {
|
||
type = lib.types.nullOr lib.types.str;
|
||
# `chat.` under the SWARM domain — both halves change: a swarm runs
|
||
# one homeserver, and the label follows the service rather than the
|
||
# protocol.
|
||
#
|
||
# Total on a null swarm domain so the required-domain assertion in
|
||
# hive-network.nix is the thing that fires; see the comment there.
|
||
default = if swarmDomain == null then "chat.invalid" else "chat.${swarmDomain}";
|
||
defaultText = lib.literalExpression ''"chat.''${services.hyperhive.swarm.domain}"'';
|
||
example = "matrix.example.com";
|
||
description = ''
|
||
Public hostname for the matrix homeserver behind the gateway.
|
||
Defaults to `chat.''${services.hyperhive.swarm.domain}` — the
|
||
swarm's domain, because a swarm runs **one** homeserver. Set to
|
||
`null` to skip the gateway vhost (tuwunel stays direct on
|
||
`httpPort`). See `docs/networking/gateway.md` for the vhost map + matrix
|
||
discovery flow, and the federation port-8448 caveat at the
|
||
bottom of that doc.
|
||
|
||
⚠️ **`gatewayHost` and `serverName` are different things, and
|
||
they carry very different costs.** `gatewayHost` is the API
|
||
listener hostname (where nginx proxies `/_matrix/*`) and is
|
||
free to change: it is a routing detail clients rediscover
|
||
through `.well-known`. `serverName` is the matrix-identifier
|
||
domain embedded **irrevocably** in every user and room id —
|
||
adopting a new one is a different homeserver, not a rename.
|
||
Both defaults now sit under the swarm domain, but only this
|
||
one is safe to move on a running deployment.
|
||
|
||
A deployment that was running before this moved keeps its
|
||
current name by pinning
|
||
`matrix.''${services.hyperhive.domain}` here — exactly what the
|
||
old default rendered.
|
||
'';
|
||
};
|
||
|
||
allowEncryption = lib.mkOption {
|
||
type = lib.types.bool;
|
||
default = false;
|
||
description = ''
|
||
Server-side switch for matrix end-to-end encryption — sets
|
||
tuwunel's `allow_encryption`. Off by default: on the hive-internal
|
||
homeserver the operator already controls the transport, so server
|
||
E2EE adds key-management overhead (cross-signing, device
|
||
verification, undecryptable-message recovery) without a clear
|
||
threat-model win for the common single-hive case. Turn on when
|
||
agents join encrypted rooms on external / federated homeservers,
|
||
or when the operator wants message contents opaque to the
|
||
homeserver admin. Independent of the agent matrix client, which
|
||
always supports decryption so it can read encrypted rooms it is
|
||
invited to regardless of this flag; this option only governs
|
||
whether THIS homeserver permits room encryption.
|
||
'';
|
||
};
|
||
|
||
# This homeserver always delegates login to the swarm's authelia, as
|
||
# an OIDC relying party — matrix SSO (`m.login.sso`), offered
|
||
# alongside password login. No toggle: a homeserver in a swarm is a
|
||
# client of that swarm's identity provider.
|
||
#
|
||
# ⚠️ Not to be confused with tuwunel's `oidc_*` settings, which point
|
||
# the other way: those make this homeserver an *authorization server*
|
||
# for matrix clients. This family makes it a *client* of an external
|
||
# identity provider. The two share the protocol's name and answer
|
||
# opposite questions.
|
||
#
|
||
# This **adds** a way in. Password login keeps working: an identity
|
||
# provider that can take the homeserver offline when it hiccups is a
|
||
# worse homeserver than one with two ways in — which is also what
|
||
# makes always-on safe. Making authelia the *only* path is a
|
||
# separate, reversible switch (tuwunel's `login_with_password`),
|
||
# deliberately not folded in here.
|
||
#
|
||
# ⚠️ Matrix SSO lives **inside** the homeserver, never behind a
|
||
# forward-auth proxy: the client-server API is spoken by non-browser
|
||
# clients holding matrix access tokens — every agent's own
|
||
# `hive-matrix-daemon` — plus federation, and a proxy in front of
|
||
# `/_matrix/` breaks all of it.
|
||
sso = {
|
||
clientId = lib.mkOption {
|
||
type = lib.types.str;
|
||
default = "tuwunel";
|
||
description = ''
|
||
OAuth2 client id this homeserver identifies itself with. Must
|
||
match the `id` of the corresponding entry in
|
||
`services.hyperhive.swarm.authelia.oidc.clients`.
|
||
|
||
The secret it pairs with is a host path, so it lives at
|
||
`services.hyperhive.deploy.matrix.sso.clientSecretFile`.
|
||
'';
|
||
};
|
||
};
|
||
};
|
||
}
|