# The swarm's matrix homeserver as every hive sees it: the name it mints ids # under, the 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; 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:`) 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). **Changing this cannot be undone by rebuilding.** The default is the swarm domain, so a homeserver that minted ids under another value (`matrix.''${services.hyperhive.domain}` or the bare hive domain) must pin that value, 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. ''; }; apiUrl = lib.mkOption { type = lib.types.nullOr lib.types.str; default = if cfg.gatewayHost == null then null else "https://${cfg.gatewayHost}"; defaultText = lib.literalExpression '' if services.hyperhive.swarm.matrix.gatewayHost == null then null else "https://''${services.hyperhive.swarm.matrix.gatewayHost}" ''; 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 `services.hyperhive.agent.matrix.url`, which is the gateway vhost handed to each agent's `hive-matrix-daemon`. Defaults to `https://` on the homeserver's gateway vhost (`gatewayHost`), the same name on every hive whichever host runs tuwunel, and `null` when there is no vhost. hive-c0re logs in with the hive's matrix sender token over this URL, so it crosses the network whenever hive-c0re is not on the homeserver's host: an `http://` value sends that token in cleartext. hive-c0re verifies the gateway's certificate against the hive trust bundle (system CAs plus the swarm and services roots). `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, which leaves nothing able to reach the homeserver. 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 sit under the swarm domain, but only this one is safe to change on a running deployment. To use the per-hive name `matrix.''${services.hyperhive.domain}` instead, pin it here. ''; }; 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. ''; }; adminSync.ignoreUsers = lib.mkOption { type = lib.types.listOf (lib.types.strMatching "@[^:[:space:]]+:[^[:space:]]+"); default = [ ]; example = [ "@mara:example.org" ]; description = '' Full matrix user ids `swarm-controller`'s operator admin sync never revokes homeserver admin from. The sync revokes admin only from accounts it made admin itself, once their owner leaves authelia's `admins` group; an account listed here keeps admin regardless. ''; }; # 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. The host running authelia registers it, whichever host runs the homeserver. The homeserver's host reads the secret it pairs with out of the swarm secret store, into `services.hyperhive.deploy.matrix.sso.clientSecretFile`. ''; }; redirectUri = lib.mkOption { type = lib.types.nullOr lib.types.str; readOnly = true; default = if cfg.gatewayHost == null then null else "https://${cfg.gatewayHost}/_matrix/client/unstable/login/sso/callback/${cfg.sso.clientId}"; defaultText = lib.literalExpression ''"https://''${services.hyperhive.swarm.matrix.gatewayHost}/_matrix/client/unstable/login/sso/callback/''${services.hyperhive.swarm.matrix.sso.clientId}"''; description = '' SSO callback authelia sends the browser back to, and the URI it matches **exactly**. Format-locked by tuwunel: the callback host must point directly at the homeserver, and the path is fixed. Read-only: the homeserver's config and its authelia client entry both read it from here. `null` when `gatewayHost` is, because the homeserver then has no name a browser can reach. ''; }; }; otel.clientId = lib.mkOption { type = lib.types.str; readOnly = true; # Not `hive-matrix-collector`: `hive-` is a hive's own client id, # and `matrix-collector` is a name a hive may take. default = "swarm-matrix-collector"; description = '' OAuth2 client id the journal forwarder inside the homeserver's container authenticates as, and the audience it asks its token for. Its own, not {option}`services.hyperhive.swarm.matrix.sso.clientId`, which is the browser login's. Read-only because three hosts have to agree on it: authelia registers the client (`glue-matrix-otel-oidc-client.nix`), the swarm collector admits it on the container's receiver (`swarm.otel.serviceProducers`), and the secret publisher carries its secret to the store. ''; }; }; }