Watch
0
0
Fork
You've already forked hyperhive
0
hyperhive/docs/2-handbook/swarm/matrix.md
iris 0f39bdaaa4 docs: the forge, knowledge, archetypes, matrix and SSO pages move their implementation detail out
Account minting, knowledge sync, the archetype refresh, the matrix appservice
flow and the SSO secret delivery move to pages under 4-implementation/swarm.
Each handbook page keeps what an operator does or sees, with a pointer.
2026-11-11 01:47:20 +01:00

9.7 KiB

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.<swarm-domain>/ (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.

Container shape

Same shape as gateway.md::hive-forge container shape:

  • 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.<swarm-domain> via the gateway, which dials its unix socket (see 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:<server_name> and !room:<server_name> 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.<services.hyperhive.swarm.domain>. 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:

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.<swarm-domain> 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 <gatewayHost>:443 (gateway.md::Discovery flow). Port 8448 isn't used.

The web chat client at chat.<swarm-domain> 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-<hive>:<server_name>, 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.

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:<server_name> it sends !admin users make-user-admin @<name>:<server_name> there as @swarm. <name> 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 @<name>:<server_name> <admin room id> 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:<server_name>, 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).
  • 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 and the implementation notes.

Configuration tuning

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/).

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.