mirror of
https://github.com/spantaleev/matrix-docker-ansible-deploy.git
synced 2026-07-21 22:49:04 +03:00
The /_tuwunel namespace carries Tuwunel's ad-hoc routes (server_version, local_user_count) and its native OpenID Connect provider endpoints (/_tuwunel/oidc/...), which a reverse proxy must route for OIDC login to work. Add public (enabled by default, like the client API) and internal Traefik label groups.
270 lines
17 KiB
Markdown
270 lines
17 KiB
Markdown
<!--
|
|
SPDX-FileCopyrightText: 2026 MDAD project contributors
|
|
SPDX-FileCopyrightText: 2026 Jason Volk
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-or-later
|
|
-->
|
|
|
|
# Configuring Tuwunel (optional)
|
|
|
|
The playbook can install and configure the [Tuwunel](https://matrix-construct.github.io/tuwunel/) Matrix homeserver for you.
|
|
|
|
Tuwunel is a featureful homeserver written entirely in Rust, intended as a scalable, low-cost, enterprise-ready alternative to Synapse that fully implements the [Matrix specification](https://spec.matrix.org/latest/) for all but the most niche uses. It is the official successor to [conduwuit](configuring-playbook-conduwuit.md), is now sponsored by the government of Switzerland 🇨🇭 (where it is currently deployed for citizens), and is used by a number of organisations with a vested interest in its continued development. See the project's [documentation](https://matrix-construct.github.io/tuwunel/) for further background.
|
|
|
|
By default, the playbook installs [Synapse](https://github.com/element-hq/synapse) as it's the only full-featured Matrix server at the moment. If that's okay, you can skip this document.
|
|
|
|
> [!WARNING]
|
|
> - **You can't switch an existing Matrix server's implementation** (e.g. Synapse → Tuwunel). Proceed below only if you're OK with starting over, or you're dealing with a server on a new domain name which hasn't participated in the Matrix federation yet. The one exception is migrating from conduwuit; see [Migrating from conduwuit](#migrating-from-conduwuit).
|
|
> - **Homeserver implementations other than Synapse may not be fully functional** with every part of this playbook. Make yourself familiar with the trade-offs before proceeding.
|
|
|
|
## Adjusting the playbook configuration
|
|
|
|
To use Tuwunel, set the following on `inventory/host_vars/matrix.example.com/vars.yml`:
|
|
|
|
```yaml
|
|
matrix_homeserver_implementation: tuwunel
|
|
|
|
# Open the registration endpoint long enough to create your first user.
|
|
# After signing up, set this back to false.
|
|
matrix_tuwunel_config_allow_registration: true
|
|
|
|
# A registration token to protect the endpoint from abuse.
|
|
# Generate one with `pwgen -s 64 1` or similar.
|
|
matrix_tuwunel_config_registration_token: ''
|
|
```
|
|
|
|
The first user account that registers becomes a server admin and is automatically invited to the admin room. See [Creating the first user account](#creating-the-first-user-account) below for the bootstrap procedure.
|
|
|
|
## Wiring done for you
|
|
|
|
When `matrix_homeserver_implementation: tuwunel` is set, the playbook automatically integrates Tuwunel with the rest of your stack:
|
|
|
|
- **Federation.** Toggled by `matrix_homeserver_federation_enabled`. The federation virtual host (port 8448 in the default setup) is wired up via Traefik labels.
|
|
- **Well-known.** `matrix_tuwunel_config_well_known_client` is set to your public homeserver URL whenever SSL is enabled. Matrix clients use this for delegated-domain server discovery; identity-provider entries below can also omit their `callback_url`, since Tuwunel derives `<well-known>/_matrix/client/unstable/login/sso/callback/<client_id>` automatically.
|
|
- **Element Call / MatrixRTC.** When the [LiveKit JWT service](configuring-playbook-matrix-rtc.md) is enabled, Tuwunel publishes its public URL through `.well-known/matrix/client` per [MSC4143](https://github.com/matrix-org/matrix-spec-proposals/pull/4143).
|
|
- **Legacy calls (TURN).** When [Coturn](configuring-playbook-turn.md) is enabled, its URIs and shared secret (or username/password, depending on `coturn_authentication_method`) are wired automatically.
|
|
|
|
## Extending the configuration
|
|
|
|
Tuwunel exposes a large configuration surface. The role surfaces commonly used options as Ansible variables under `matrix_tuwunel_config_*`. See [`roles/custom/matrix-tuwunel/defaults/main.yml`](../roles/custom/matrix-tuwunel/defaults/main.yml) for the complete list, and [`roles/custom/matrix-tuwunel/templates/tuwunel.toml.j2`](../roles/custom/matrix-tuwunel/templates/tuwunel.toml.j2) for the rendered configuration.
|
|
|
|
For options that aren't surfaced as a dedicated variable, [environment variables](https://matrix-construct.github.io/tuwunel/configuration.html#environment-variables) are the recommended override mechanism. They take priority over the rendered TOML, are scoped to the running container, and require no template patching:
|
|
|
|
```yaml
|
|
matrix_tuwunel_environment_variables_extension: |
|
|
TUWUNEL_REQUEST_TIMEOUT=60
|
|
TUWUNEL_DNS_CACHE_SIZE=131072
|
|
```
|
|
|
|
Keys nested under a TOML section use `__` (double underscore) to descend, e.g. `TUWUNEL_WELL_KNOWN__SERVER`. User-named sections become path segments too: `TUWUNEL_STORAGE_PROVIDER__ARCHIVE__S3__URL` overrides the `url` field of the `archive` storage provider in the example below.
|
|
|
|
If you need wholesale control of the configuration file, copy [`roles/custom/matrix-tuwunel/templates/tuwunel.toml.j2`](../roles/custom/matrix-tuwunel/templates/tuwunel.toml.j2) into your inventory and point `matrix_tuwunel_template_tuwunel_config` at your copy.
|
|
|
|
The container image published as `:latest` is built with `io_uring`, `jemalloc`, LDAP, blurhashing, URL preview, sentry telemetry, and zstd compression all enabled, so most opt-in features are simply a configuration toggle away.
|
|
|
|
### Identity providers (OAuth2 / OIDC)
|
|
|
|
Configure one or more `[[global.identity_provider]]` entries via a list. Each entry maps directly to Tuwunel's [identity-provider fields](https://matrix-construct.github.io/tuwunel/authentication/providers.html); only the fields you set are emitted. GitHub, GitLab, and Google have built-in `issuer_url` defaults so a `client_id` plus `client_secret` is enough; for any other `brand` (Apple, Facebook, Keycloak, MAS, Twitter, etc.) you must supply `issuer_url` explicitly:
|
|
|
|
```yaml
|
|
matrix_tuwunel_config_identity_providers:
|
|
- brand: keycloak
|
|
client_id: matrix
|
|
client_secret: '<provider secret>'
|
|
issuer_url: https://sso.example.com/realms/matrix
|
|
callback_url: https://matrix.example.com/_matrix/client/unstable/login/sso/callback/matrix
|
|
trusted: true
|
|
- brand: github
|
|
client_id: '<github oauth app id>'
|
|
client_secret: '<github oauth app secret>'
|
|
```
|
|
|
|
Self-hosted providers must supply both `client_id` and `issuer_url`. Set `trusted: true` only on providers you operate yourself; trusting a public provider (GitHub, Google, etc.) is an account-takeover risk.
|
|
|
|
### LDAP
|
|
|
|
Tuwunel can authenticate `m.login.password` requests against an LDAP directory and, in search-then-bind mode, keep admin status in sync with directory membership. The shipped image already includes the `ldap` build feature.
|
|
|
|
```yaml
|
|
matrix_tuwunel_config_ldap_enabled: true
|
|
matrix_tuwunel_config_ldap_uri: ldaps://ldap.example.com:636
|
|
matrix_tuwunel_config_ldap_base_dn: ou=users,dc=example,dc=org
|
|
matrix_tuwunel_config_ldap_bind_dn: cn=ldap-reader,dc=example,dc=org
|
|
matrix_tuwunel_config_ldap_bind_password_file: /etc/tuwunel/ldap.pw
|
|
matrix_tuwunel_config_ldap_filter: '(&(objectClass=person)(memberOf=cn=matrix,ou=groups,dc=example,dc=org))'
|
|
```
|
|
|
|
> [!NOTE]
|
|
> `bind_password_file` is read **inside the container**. The role bind-mounts `/matrix/tuwunel/config` to `/etc/tuwunel` (read-only) and `/matrix/tuwunel/data` to `/var/lib/tuwunel`. To make the file available at the path above, drop it on the host at `/matrix/tuwunel/config/ldap.pw` (owned by `matrix:matrix`) before running the playbook; the role does not template secret files for you.
|
|
|
|
For direct-bind, anonymous-search, and admin-sync details, see [LDAP authentication](https://matrix-construct.github.io/tuwunel/authentication/ldap.html).
|
|
|
|
### JWT login
|
|
|
|
Tuwunel can accept signed JSON Web Tokens both as a login flow and as a User-Interactive Authentication step:
|
|
|
|
```yaml
|
|
matrix_tuwunel_config_jwt_enabled: true
|
|
matrix_tuwunel_config_jwt_key: '<shared secret>'
|
|
matrix_tuwunel_config_jwt_format: HMAC # one of HMAC, B64HMAC, ECDSA, EDDSA
|
|
matrix_tuwunel_config_jwt_algorithm: HS256
|
|
matrix_tuwunel_config_jwt_audience: ['matrix']
|
|
matrix_tuwunel_config_jwt_issuer: ['https://issuer.example.com']
|
|
```
|
|
|
|
The defaults match Synapse's `experimental_features.jwt_config` semantics, so a key + algorithm port should authenticate the same set of tokens. See [Enterprise JWT](https://matrix-construct.github.io/tuwunel/authentication/jwt.html) for the full reference, including the asymmetric (ECDSA / EdDSA) formats and the operator-controlled UIAA override flow.
|
|
|
|
### Media storage providers
|
|
|
|
Each entry becomes a `[global.storage_provider.<id>.<kind>]` block. `kind` is `local` or `s3`; the remaining keys map directly to the fields documented in [Storage providers](https://matrix-construct.github.io/tuwunel/media/storage.html):
|
|
|
|
```yaml
|
|
matrix_tuwunel_config_storage_providers:
|
|
- id: primary
|
|
kind: local
|
|
base_path: /var/lib/tuwunel/media
|
|
|
|
- id: archive
|
|
kind: s3
|
|
url: s3://my-bucket/media
|
|
region: us-east-1
|
|
key: AKIA...
|
|
secret: '<aws secret>'
|
|
multipart_threshold: 100 MiB
|
|
```
|
|
|
|
The S3 backend ships with native multipart upload, so no goofys/rclone sidecar is required. MinIO, Cloudflare R2, and DigitalOcean Spaces all work; set `endpoint` and `use_vhost_request: false` as appropriate.
|
|
|
|
> [!NOTE]
|
|
> Local provider paths must live under `/var/lib/tuwunel` (the container's data mount, persisted on the host at `/matrix/tuwunel/data`), or you must mount the target directory into the container yourself via `matrix_tuwunel_container_extra_arguments`. The container otherwise runs read-only.
|
|
|
|
### RocksDB and cache tuning
|
|
|
|
Tuwunel embeds RocksDB. The defaults (`rocksdb_compression_algo: zstd`) suit most deployments. For high-throughput servers you may want to enable direct I/O, raise parallelism, and configure a backup path:
|
|
|
|
```yaml
|
|
matrix_tuwunel_config_rocksdb_direct_io: true
|
|
matrix_tuwunel_config_rocksdb_parallelism_threads: 8
|
|
matrix_tuwunel_config_database_backup_path: /var/lib/tuwunel/backups
|
|
```
|
|
|
|
`matrix_tuwunel_config_cache_capacity_modifier` is left empty by default, so Tuwunel picks a value (`1.0` since v1.7.0, with rebalanced per-cache sizes that already raise memory use). Set it to `2.0` only on small hosts with four or fewer cores; on larger machines the default is recommended.
|
|
|
|
If you run on ZFS, the [Tuwunel maintenance guide](https://matrix-construct.github.io/tuwunel/maintenance.html#zfs) lists the dataset properties (`recordsize`, `primarycache`, `compression`, `atime`, `logbias`) and config flags (`rocksdb_direct_io`, `rocksdb_allow_fallocate`) you need to adjust to avoid severe write amplification.
|
|
|
|
To enable Sentry crash reporting, set `matrix_tuwunel_config_sentry_enabled: true`.
|
|
|
|
### Federation gating
|
|
|
|
Tuwunel accepts regular-expression patterns at every level of remote-server filtering:
|
|
|
|
```yaml
|
|
matrix_tuwunel_config_forbidden_remote_server_names:
|
|
- 'bad\.example\.com$'
|
|
matrix_tuwunel_config_forbidden_remote_room_directory_server_names:
|
|
- 'spam\.example\.com$'
|
|
matrix_tuwunel_config_prevent_media_downloads_from:
|
|
- 'heavy\.example\.com$'
|
|
```
|
|
|
|
Tuwunel additionally implements [MSC4284 policy servers](https://github.com/matrix-org/matrix-spec-proposals/pull/4284) for room-level federation gating. The policy itself lives in room state, but enforcement is opt-in at the server level:
|
|
|
|
```yaml
|
|
matrix_tuwunel_config_enable_policy_servers: true
|
|
matrix_tuwunel_config_policy_server_request_timeout: 5
|
|
```
|
|
|
|
When enabled, rooms with a valid `m.room.policy` state event have outgoing events signed by the configured policy server before federation. Transient network or timeout failures fail open (with a warn log), so a policy-server outage will not silently take the room offline.
|
|
|
|
### Default room version
|
|
|
|
The role sets `default_room_version: '12'`, so newly created rooms default to Matrix [room version 12](https://github.com/matrix-org/matrix-spec-proposals/pull/4289) ("Hydra"). Override `matrix_tuwunel_config_default_room_version` if you need an earlier version for client compatibility.
|
|
|
|
### The `/_tuwunel` API path
|
|
|
|
Besides `/_matrix`, Tuwunel serves its own first-party routes under `/_tuwunel`. This namespace carries ad-hoc endpoints such as `/_tuwunel/server_version` and `/_tuwunel/local_user_count`, and the [native OpenID Connect provider](https://matrix-construct.github.io/tuwunel/authentication/oidc-server.html) endpoints (`/_tuwunel/oidc/...`) that clients use when Tuwunel handles OIDC login itself, rather than delegating to an upstream provider as described above. The role routes `/_tuwunel` on the public entrypoint by default so these features work out of the box.
|
|
|
|
To keep this namespace off the public entrypoint and expose it only on the internal one, set:
|
|
|
|
```yaml
|
|
matrix_tuwunel_container_labels_public_tuwunel_api_enabled: false
|
|
matrix_tuwunel_container_labels_internal_tuwunel_api_enabled: true
|
|
```
|
|
|
|
### Exposing the Administration API
|
|
|
|
Tuwunel serves a Synapse-compatible Administration API under the `/_synapse/admin` path, so administration dashboards (such as synapse-admin and ketesa) and moderation bots (such as Draupnir and Meowlnir) work against it. The served endpoints are listed on the [Tuwunel Synapse Admin API page](https://matrix-construct.github.io/tuwunel/development/compliance/synapse-admin.html).
|
|
|
|
The API is not routed through the reverse proxy by default. Every endpoint requires an administrator access token, but you may still prefer to keep it off the public entrypoint. To reach it only from trusted networks, expose it on the internal Traefik entrypoint:
|
|
|
|
```yaml
|
|
matrix_tuwunel_container_labels_internal_client_synapse_admin_api_enabled: true
|
|
```
|
|
|
|
To expose it publicly instead (for example, when a dashboard runs in the browser), set:
|
|
|
|
```yaml
|
|
matrix_tuwunel_container_labels_public_client_synapse_admin_api_enabled: true
|
|
```
|
|
|
|
## Creating the first user account
|
|
|
|
Unlike Synapse and Dendrite, Tuwunel does not register users from the command line or via the playbook. On first startup it logs a one-time-use registration token to its journal:
|
|
|
|
```sh
|
|
# Adjust the duration if necessary or remove the --since argument.
|
|
journalctl -u matrix-tuwunel.service --since="10 minutes ago"
|
|
```
|
|
|
|
Use the token to create your first account from any client that supports token-gated registration (e.g. [Element Web](configuring-playbook-client-element-web.md)). The account is auto-promoted to admin and invited to the admin room together with the `@conduit:<server_name>` server bot. The bot keeps the legacy `conduit` localpart due to the project's lineage from Conduit.
|
|
|
|
## Configuring bridges and appservices
|
|
|
|
The playbook does not auto-register appservices for Tuwunel. After your bridge has produced its `registration.yaml` (e.g. `/matrix/mautrix-signal/bridge/registration.yaml`), register it manually by sending the contents to the admin room, prefixed with `!admin appservices register` and wrapped in a fenced code block:
|
|
|
|
!admin appservices register
|
|
```
|
|
id: signal
|
|
url: http://matrix-mautrix-signal:29328
|
|
as_token: <token>
|
|
hs_token: <token>
|
|
sender_localpart: _bot_signalbot
|
|
rate_limited: false
|
|
namespaces:
|
|
users:
|
|
- exclusive: true
|
|
regex: '^@signal_.+:example\.org$'
|
|
- exclusive: true
|
|
regex: '^@signalbot:example\.org$'
|
|
aliases:
|
|
- exclusive: true
|
|
regex: '^#signal_.+:example\.org$'
|
|
```
|
|
|
|
Registrations stored this way are persisted in the database and survive restarts. Re-running the command with the same `id` replaces the existing entry. See [Application services](https://matrix-construct.github.io/tuwunel/appservices.html) for the full reference and admin commands.
|
|
|
|
## Migrating from conduwuit
|
|
|
|
Tuwunel is a "binary swap" for conduwuit; it reads conduwuit's RocksDB layout directly, so migration is a data move, not an export/import.
|
|
|
|
1. Set `matrix_homeserver_implementation: tuwunel` on `vars.yml` and remove any `matrix_conduwuit_*` overrides.
|
|
2. Run a full installation so that the new service is created and the old one removed (e.g. `just setup-all`).
|
|
3. Run `just run-tags tuwunel-migrate-from-conduwuit`.
|
|
|
|
The migration stops `matrix-conduwuit.service`, copies `/matrix/conduwuit` into `/matrix/tuwunel`, renames the config file, and starts `matrix-tuwunel.service`. The freshly generated tuwunel data directory is preserved alongside as `/matrix/tuwunel_old` until you remove it manually.
|
|
|
|
> [!CAUTION]
|
|
> Migrating from any other Conduit derivative (Conduit itself, Continuwuity, or any other fork) is **not supported** and will corrupt your database. All Conduit forks share the same linear database version with no awareness of each other; switching between them produces unrecoverable damage. See the [upstream migration table](https://matrix-construct.github.io/tuwunel/#migrating-to-tuwunel).
|
|
|
|
## Troubleshooting
|
|
|
|
As with all other services, the logs are available via [systemd-journald](https://www.freedesktop.org/software/systemd/man/systemd-journald.service.html):
|
|
|
|
```sh
|
|
journalctl -fu matrix-tuwunel
|
|
```
|
|
|
|
Logging verbosity is controlled by `matrix_tuwunel_config_log` in [`tracing-subscriber` env-filter syntax](https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html). The default (`info,state_res=warn`) is reasonable for production; for debugging, try `debug` or scope it tighter, e.g. `info,tuwunel_service::sending=debug`.
|
|
|
|
For RocksDB-level issues, online backups, and offline backup procedures, see the [Tuwunel maintenance guide](https://matrix-construct.github.io/tuwunel/maintenance.html). For protocol-compliance state across MSCs, the spec, and Complement, the project's [compliance dashboard](https://matrix-construct.github.io/tuwunel/development/compliance.html) is the authoritative tracker.
|