# Swarm Matrix homeserver Private Matrix homeserver (matrix-tuwunel — the conduwuit successor) wrapped in a nixos-container (`hive-matrix`), plus optional fluffychat-web client at `chat./` (the `gatewayHost` vhost). A swarm runs one homeserver, on one host. Two namespaces configure it: - `services.hyperhive.swarm.matrix.*` — what the homeserver **is**, as every hive sees it: `serverName`, `gatewayHost`. - `services.hyperhive.deploy.matrix.*` — what the host running it decides: `enable`, `gui.package`, `trustedServers`, `maxRequestSize`, `sso.clientSecretFile`. Vhost routing lives in [`gateway.md`](../../4-implementation/networking/gateway.md). ## Container shape Same shape as [`gateway.md::hive-forge container shape`](../../4-implementation/networking/gateway.md): - Container name `hive-matrix` (not `h-*`) so c0re's lifecycle scanner ignores it; operator manages via the standard `nixos-container` CLI. - Keeps hive-matrix from fighting any `services.matrix-*` the operator already runs on the host — separate systemd namespace, separate state dir. - Container runs in a network namespace of its own (`privateNetwork = true`) on the hive bridge, and binds no TCP port. Agents reach the homeserver at `chat.` via the gateway, which dials its unix socket (see [Reach](#reach)). - Persistent state at `/var/lib/nixos-containers/hive-matrix/var/lib/matrix-tuwunel/` survives container restart / host reboot. To wipe, destroy the container. ## Identity vs API listener: `serverName` vs `gatewayHost` Two distinct hostnames: - **`serverName`** — matrix-spec `server_name`, embedded *irrevocably* in every `@user:` and `!room:` identifier minted on this homeserver. You can't change it later without abandoning every account and chat history. Defaults to the bare `services.hyperhive.swarm.domain`. - **`gatewayHost`** — the API listener hostname, where the gateway's matrix vhost proxies `/_matrix/*` to tuwunel. Defaults to `chat.`. Set to `null` to skip the gateway vhost, which leaves nothing able to reach the homeserver. Both default under the **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. ⚠️ **they're still not interchangeable, and the difference is the cost of changing one.** `gatewayHost` is a routing detail clients rediscover through `.well-known`, so it's safe to move on a running deployment. The controller's host serves that `.well-known` on the swarm domain, and a homeserver pinned to a hive domain gets it on that hive's vhost. The matrix id format bakes `serverName` into every user and room id, so adopting a new one does **not** rename the existing users and rooms — it strands them, because their ids still name a homeserver that no longer answers.
Pinning serverName on a homeserver with existing ids `serverName` defaults to the bare `services.hyperhive.swarm.domain`. A homeserver that changes `serverName` after minting ids strands its existing users and rooms — keep the value it minted them under: ```nix services.hyperhive.swarm.matrix = { # whichever this deployment already uses serverName = config.services.hyperhive.domain; gatewayHost = "matrix.${config.services.hyperhive.domain}"; }; ``` A rebuild on a host that already has a homeserver prints a `hive-matrix: WARNING — … serverName is unset` line when this is missing, naming the value it's about to default to. It never fails the rebuild, so pin the value before the homeserver mints any ids under the default. A pinned hive domain answers `.well-known/matrix/*` itself, built from `gatewayHost`, on any host that runs that hive's gateway. It doesn't need the homeserver on the same host.
## Reach tuwunel binds no TCP port. It listens on the unix socket `/run/hive-matrix/tuwunel.sock`, in a host directory (`root:nginx`, `0770`) bind-mounted into the container, and the gateway's `/_matrix/` location proxies to that socket. Clients, agents, hive-c0re and federation peers all reach the homeserver at `chat.` through the gateway; from outside the host that takes `gateway.openFirewall`. hive-c0re doesn't talk to the homeserver: matrix is swarm-level, and the hive has no matrix API URL to set. Federation peers find the homeserver through `.well-known/matrix/server` at the server name, which names `:443` ([`gateway.md::Discovery flow`](../../4-implementation/networking/gateway.md#discovery-flow-matrix)). Port 8448 isn't used. The web chat client at `chat.` signs in the same way: its login dialog preselects the server name and looks the homeserver up through that discovery answer. That's the swarm domain by default, and the hive domain on a homeserver that pinned `serverName` to it. Typing the server name into any other Matrix client uses the same value. Outbound, the container sits on the hive bridge like the CI runner: it leases an address from the gateway's DHCP, resolves through its dnsmasq, and leaves through the bridge's NAT. It reaches the secret store, authelia and federation peers by name. ## Accounts The homeserver refuses registration. **The swarm's appservice** creates every account: agents never see an appservice token, and an agent only ever receives its own `access_token`. `swarm-controller` creates each agent's accounts and one sender account per hive, `@hive-:`, an ordinary account that nothing promotes. How the registration gets rendered, where its tokens live and how the controller keeps an agent's token: [`matrix-provisioning.md`](../../4-implementation/swarm/matrix-provisioning.md). ### Operators are homeserver admins Every member of authelia's `admins` group who has a matrix account is a homeserver admin. `swarm-controller` reads the group through `swarm-authelia-bridge` at start and every five minutes after, and for each member not yet in `#admins:` it sends `!admin users make-user-admin @:` there as `@swarm`. `` is the authelia username lowercased, the account an SSO login lands on. - **Leaving `admins` revokes what the sync granted.** The controller records each account it sends a grant for in `/var/lib/swarm-controller/operator-matrix-admins.json`. When one of them is no longer in `admins` but still in `#admins`, it sends `!admin users force-leave-room @: ` there as `@swarm`; leaving that room is what ends homeserver admin. - **Admins the sync didn't make keep admin.** An admin missing from that file, `@swarm`, the server user `@conduit:`, every agent's account, and every user id in `services.hyperhive.deploy.swarm-controller.matrixAdminSync.ignoreUsers` are never revoked. - **The last admin stays.** A revoke that would leave no admin in `#admins` besides `@swarm` and the server user isn't sent; the controller logs a warning on each pass instead. - **No account, no grant.** A member who has never logged in to matrix has no account to promote. The first pass after their first SSO login promotes them. - **One command per account per controller run.** A member still missing from `#admins` after the controller sends its grant gets a `make-user-admin … failed to take` warning in the controller log on each later pass, and no second command until the controller restarts; a revoke that leaves its account in `#admins` gets `force-leave-room … failed to take` the same way. - Runs only on a controller with both `services.hyperhive.deploy.swarm-controller.authBridgeUrl` and `services.hyperhive.deploy.swarm-controller.matrixHomeserverUrl` set. ### Settings - **Federation** is on at the protocol level, but `trusted_servers = []` keeps it closed until you list peers in `trustedServers` (see [Configuration tuning](#configuration-tuning)). - **Registration** is off. The swarm's appservice still creates accounts, and the homeserver refuses everyone else. - **Encryption**: `services.hyperhive.deploy.matrix.allowEncryption` (default `false`) lets rooms on this homeserver use encryption. Turn it on for encrypted rooms with external homeservers, or to keep contents opaque to the homeserver admin. The agents' matrix client reads encrypted rooms either way. - **Key backup.** Every matrix account an agent has gets a recovery passphrase in the swarm secret store, so a crypto store lost to `--purge` or a token reset comes back with its history. The details, and the retired hive-side code, are in [`matrix-provisioning.md`](../../4-implementation/swarm/matrix-provisioning.md) and the [implementation notes](../../4-implementation/networking/matrix-homeserver.md). ## Configuration tuning ```nix services.hyperhive.deploy.matrix = { trustedServers = [ "matrix.org" "example.com" ]; # default: [] maxRequestSize = 20000000; # default: 20 MB }; ``` **`trustedServers`** (default `[]`) — list of peer homeserver names whose signing keys tuwunel fetches and trusts. tuwunel enables federation at the protocol level from first boot (`allow_federation = true`) but trusts no remote homeserver until you list it 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/2-handbook/swarm/`](README.md)). **`maxRequestSize`** (default `20_000_000` bytes = 20 MB) — maximum size of a single matrix client request body. Matches the matrix-spec recommendation for media uploads and the upstream tuwunel default. Raise for deployments that need large file transfers; lower for resource-constrained hosts where a 20 MB request is unexpectedly large.