Matrix and mautrix bridges
How DNF implements the Matrix server (Synapse) and mautrix bridges. For day-to-day operation, see Matrix et les ponts.
Overview
Section titled “Overview”Everything is handled by dnf/modules/service/matrix.nix, with two satellite
modules : element.nix (static web client) and turn.nix (coturn relay
for audio/video).
- Caddy routes
/_matrix/*and/_synapse/client/*to Synapse and serves/.well-known/matrix/{client,server}files (client discovery and federation). - Synapse listens on
dnfConfig.network.ports.matrix, local PostgreSQL database (created withCcollation by amatrix-db-initunit). - Kanidm provides authentication : OIDC client provisioned via
darkone.service.idm.oauth2.matrix, local part derived frompreferred_username(see Authentication & IDM). - coturn is wired automatically if the
turnservice is declared (turn_uris, shared secret via sops).
Module anatomy
Section titled “Module anatomy”matrix.nix is a lib.mkMerge of independent blocks:
| Block | Condition | Content |
|---|---|---|
| Base | always | OIDC client, reverse proxy, persistence |
| Server | matrix.enable | Synapse, PostgreSQL, common secrets, doublepuppet appservice |
| One block per bridge | matrix.enable && bridges.<x>.enable | sops secrets + bridge’s mautrix service |
Each bridge can thus be disabled individually
(darkone.service.matrix.bridges.{whatsapp,signal,telegram,messenger,discord}.enable),
its sops secrets only being declared if the bridge is active.
Mautrix bridges
Section titled “Mautrix bridges”DNF relies on the nixpkgs services.mautrix-* modules, which generate the
appservice registration and register it with Synapse on startup.
Two generations of bridges coexist, with different configuration keys:
| Bridge | Generation | Double puppeting | Level |
|---|---|---|---|
| bridgev2 (Go) | double_puppet.secrets | user | |
| signal | bridgev2 (Go) | double_puppet.secrets | user |
| messenger (meta) | bridgev2 (Go) | double_puppet.secrets | user |
| telegram | legacy (Python) | bridge.login_shared_secret_map | full |
| discord | legacy (Go) | bridge.login_shared_secret_map | user |
Permissions
Section titled “Permissions”The mkBridgePermissions helper (in matrix.nix) builds the same
policy for all bridges:
permissions: "domain.tld": user # tout compte local, avec son propre compte distant "@alice:domain.tld": admin # le matrix.admin déclaré dans etc/config.yamlDistant sessions are isolated per Matrix user: generalizing to all domain accounts carries no risk of interference.
Double puppeting
Section titled “Double puppeting”Official appservice 🡕 method:
a doublepuppet appservice is registered in Synapse
(app_service_config_files) with a namespace covering @.*:domain.tld.
Its as_token allows each bridge to emit events on behalf of the
user’s real account.
The token is unique for all bridges (mautrix-doublepuppet-as-token) and
injected into each config by environment variable substitution
(as_token:$MAUTRIX_DOUBLEPUPPET_AS_TOKEN), as nixpkgs modules pass the
configuration through envsubst with the service’s environmentFile.
Appservice tokens
Section titled “Appservice tokens”The registration of an appservice is the contract between Synapse and the bridge. It contains two tokens, one per authentication direction :
| Token | Direction | Usage |
|---|---|---|
as_token | bridge → Synapse | the bridge authenticates on the client API (sending messages, sync…) |
hs_token | Synapse → bridge | Synapse authenticates when pushing events to the bridge (/transactions) |
By default, nixpkgs modules generate these tokens randomly on
first start and store them in /var/lib/mautrix-*. Consequence :
any reset of the state directory changes the token pair, while
Synapse — which reads registrations only at startup — keeps
the old one in memory ; the bridge is then rejected
(« The as_token was not accepted »).
DNF therefore fixes both tokens of each bridge via sops : the registration becomes a pure function of secrets. Resetting a bridge’s state no longer invalidates anything on Synapse’s side, and the whole setup is reproducible (redeployment, migration, backup restoration).
Secrets and ports
Section titled “Secrets and ports”- Secrets : declared by block in
matrix.nix(sops.secrets.*), and assembled into environment files bysops.templates.*owned by the bridge’s system user. Full list in the operations page. - Ports :
matrix,matrixTelegramandmatrixDiscordare set by thednf/config/network.nixregistry ; the default appservice ports of other bridges (29318, 29319, 29328) are listed there asreservedto prevent any future collision. - WhatsApp statuses :
network.enable_status_broadcast = falsein the bridge config, otherwise the bridge endlessly recreates the “WhatsApp Status Broadcast” room for each user.
See also
Section titled “See also”- Matrix messaging : user guide : bridge connections for users
- Matrix : operations guide : enable, configure and manage bridges