Просмотр исходного кода

matrix-tuwunel: add Tuwunel homeserver role (#5200)

Tuwunel is a Matrix homeserver maintained by the matrix-construct
organisation. See https://matrix-construct.github.io/tuwunel/.

The rendered TOML emits only keys exposed as Ansible variables; the
rest fall back to tuwunel's upstream defaults. Anything not surfaced
can be set via the TUWUNEL_* env extension or by overriding the
template path.

Popular features Tuwunel adds variables for:

- OAuth2/OIDC identity providers (a list of `[[global.identity_provider]]`
  blocks; brand-aware defaults for Google, GitHub, Keycloak, MAS, etc)
- LDAP and JWT authentication
- Media storage providers (native local and S3 with multipart upload)
- RocksDB tuning (compression, direct_io, parallelism, online backups)
- Native TLS dual-protocol mode
- Blurhashing, Sentry crash reporting

Auto-wired from existing playbook globals: well-known client URL,
TURN/coturn, MatrixRTC LiveKit URL, federation.

The `tuwunel-migrate-from-conduwuit` tag performs a binary-swap
migration. Migration from any other Conduit derivative is unsupported
and would corrupt the database.

Signed-off-by: Jason Volk <jason@zemos.net>
pull/5201/head
Jason Volk 2 дней назад
committed by GitHub
Родитель
Сommit
c111008d25
Не найден GPG ключ соответствующий данной подписи Идентификатор GPG ключа: B5690EEEBB952194
26 измененных файлов: 1419 добавлений и 3 удалений
  1. +1
    -0
      README.md
  2. +234
    -0
      docs/configuring-playbook-tuwunel.md
  3. +2
    -0
      docs/configuring-playbook.md
  4. +1
    -0
      docs/container-images.md
  5. +1
    -1
      docs/howto-srv-server-delegation.md
  6. +66
    -0
      group_vars/matrix_servers
  7. +1
    -1
      roles/custom/matrix-base/defaults/main.yml
  8. +1
    -1
      roles/custom/matrix-base/tasks/validate_config.yml
  9. +315
    -0
      roles/custom/matrix-tuwunel/defaults/main.yml
  10. +76
    -0
      roles/custom/matrix-tuwunel/tasks/install.yml
  11. +40
    -0
      roles/custom/matrix-tuwunel/tasks/main.yml
  12. +83
    -0
      roles/custom/matrix-tuwunel/tasks/migrate_from_conduwuit.yml
  13. +28
    -0
      roles/custom/matrix-tuwunel/tasks/self_check_client_api.yml
  14. +33
    -0
      roles/custom/matrix-tuwunel/tasks/self_check_federation_api.yml
  15. +8
    -0
      roles/custom/matrix-tuwunel/tasks/setup_install.yml
  16. +8
    -0
      roles/custom/matrix-tuwunel/tasks/setup_uninstall.yml
  17. +24
    -0
      roles/custom/matrix-tuwunel/tasks/uninstall.yml
  18. +44
    -0
      roles/custom/matrix-tuwunel/tasks/validate_config.yml
  19. +1
    -0
      roles/custom/matrix-tuwunel/templates/env.j2
  20. +4
    -0
      roles/custom/matrix-tuwunel/templates/env.j2.license
  21. +141
    -0
      roles/custom/matrix-tuwunel/templates/labels.j2
  22. +55
    -0
      roles/custom/matrix-tuwunel/templates/systemd/matrix-tuwunel.service.j2
  23. +4
    -0
      roles/custom/matrix-tuwunel/templates/systemd/matrix-tuwunel.service.j2.license
  24. +238
    -0
      roles/custom/matrix-tuwunel/templates/tuwunel.toml.j2
  25. +9
    -0
      roles/custom/matrix-tuwunel/vars/main.yml
  26. +1
    -0
      setup.yml

+ 1
- 0
README.md Просмотреть файл

@@ -53,6 +53,7 @@ The homeserver is the backbone of your Matrix system. Choose one from the follow
| [Synapse](https://github.com/element-hq/synapse) | ✅ | Storing your data and managing your presence in the [Matrix](http://matrix.org/) network | [Link](docs/configuring-playbook-synapse.md) | | [Synapse](https://github.com/element-hq/synapse) | ✅ | Storing your data and managing your presence in the [Matrix](http://matrix.org/) network | [Link](docs/configuring-playbook-synapse.md) |
| [Conduit](https://conduit.rs) | ❌ | Storing your data and managing your presence in the [Matrix](http://matrix.org/) network. Conduit is a lightweight open-source server implementation of the Matrix Specification with a focus on easy setup and low system requirements | [Link](docs/configuring-playbook-conduit.md) | | [Conduit](https://conduit.rs) | ❌ | Storing your data and managing your presence in the [Matrix](http://matrix.org/) network. Conduit is a lightweight open-source server implementation of the Matrix Specification with a focus on easy setup and low system requirements | [Link](docs/configuring-playbook-conduit.md) |
| [continuwuity](https://continuwuity.org) | ❌ | Storing your data and managing your presence in the [Matrix](http://matrix.org/) network. | [Link](docs/configuring-playbook-continuwuity.md) | | [continuwuity](https://continuwuity.org) | ❌ | Storing your data and managing your presence in the [Matrix](http://matrix.org/) network. | [Link](docs/configuring-playbook-continuwuity.md) |
| [Tuwunel](https://matrix-construct.github.io/tuwunel/) | ❌ | Storing your data and managing your presence in the [Matrix](http://matrix.org/) network. Tuwunel is the official successor to conduwuit. | [Link](docs/configuring-playbook-tuwunel.md) |
| [Dendrite](https://github.com/element-hq/dendrite) | ❌ | Storing your data and managing your presence in the [Matrix](http://matrix.org/) network. Dendrite is a second-generation Matrix homeserver written in Go, an alternative to Synapse. | [Link](docs/configuring-playbook-dendrite.md) | | [Dendrite](https://github.com/element-hq/dendrite) | ❌ | Storing your data and managing your presence in the [Matrix](http://matrix.org/) network. Dendrite is a second-generation Matrix homeserver written in Go, an alternative to Synapse. | [Link](docs/configuring-playbook-dendrite.md) |


### Clients ### Clients


+ 234
- 0
docs/configuring-playbook-tuwunel.md Просмотреть файл

@@ -0,0 +1,234 @@
<!--
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 bump the cache modifier:

```yaml
matrix_tuwunel_config_rocksdb_direct_io: true
matrix_tuwunel_config_rocksdb_parallelism_threads: 8
matrix_tuwunel_config_cache_capacity_modifier: 2.0
matrix_tuwunel_config_database_backup_path: /var/lib/tuwunel/backups
```

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; that lives in room state and needs no playbook configuration.

### 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.

## 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.

+ 2
- 0
docs/configuring-playbook.md Просмотреть файл

@@ -53,6 +53,8 @@ For a more custom setup, see the [Other configuration options](#other-configurat


- [Configuring continuwuity](configuring-playbook-continuwuity.md), if you've switched to the [continuwuity](https://continuwuity.org) homeserver implementation - [Configuring continuwuity](configuring-playbook-continuwuity.md), if you've switched to the [continuwuity](https://continuwuity.org) homeserver implementation


- [Configuring Tuwunel](configuring-playbook-tuwunel.md), if you've switched to the [Tuwunel](https://matrix-construct.github.io/tuwunel/) homeserver implementation

- [Configuring Dendrite](configuring-playbook-dendrite.md), if you've switched to the [Dendrite](https://matrix-org.github.io/dendrite) homeserver implementation - [Configuring Dendrite](configuring-playbook-dendrite.md), if you've switched to the [Dendrite](https://matrix-org.github.io/dendrite) homeserver implementation


- Server components: - Server components:


+ 1
- 0
docs/container-images.md Просмотреть файл

@@ -28,6 +28,7 @@ We try to stick to official images (provided by their respective projects) as mu
| [Synapse](configuring-playbook-synapse.md) | [element-hq/synapse](https://ghcr.io/element-hq/synapse) | ✅ | Storing your data and managing your presence in the [Matrix](http://matrix.org/) network | | [Synapse](configuring-playbook-synapse.md) | [element-hq/synapse](https://ghcr.io/element-hq/synapse) | ✅ | Storing your data and managing your presence in the [Matrix](http://matrix.org/) network |
| [Conduit](configuring-playbook-conduit.md) | [matrixconduit/matrix-conduit](https://hub.docker.com/r/matrixconduit/matrix-conduit) | ❌ | Storing your data and managing your presence in the [Matrix](http://matrix.org/) network. Conduit is a lightweight open-source server implementation of the Matrix Specification with a focus on easy setup and low system requirements | | [Conduit](configuring-playbook-conduit.md) | [matrixconduit/matrix-conduit](https://hub.docker.com/r/matrixconduit/matrix-conduit) | ❌ | Storing your data and managing your presence in the [Matrix](http://matrix.org/) network. Conduit is a lightweight open-source server implementation of the Matrix Specification with a focus on easy setup and low system requirements |
| [continuwuity](configuring-playbook-continuwuity.md) | [continuwuation/continuwuity](https://forgejo.ellis.link/continuwuation/continuwuity) | ❌ | Storing your data and managing your presence in the [Matrix](http://matrix.org/) network. continuwuity is a continuation of conduwuit. | | [continuwuity](configuring-playbook-continuwuity.md) | [continuwuation/continuwuity](https://forgejo.ellis.link/continuwuation/continuwuity) | ❌ | Storing your data and managing your presence in the [Matrix](http://matrix.org/) network. continuwuity is a continuation of conduwuit. |
| [Tuwunel](configuring-playbook-tuwunel.md) | [matrix-construct/tuwunel](https://ghcr.io/matrix-construct/tuwunel) | ❌ | Storing your data and managing your presence in the [Matrix](http://matrix.org/) network. Tuwunel is the official successor to conduwuit. |
| [Dendrite](configuring-playbook-dendrite.md) | [matrixdotorg/dendrite-monolith](https://hub.docker.com/r/matrixdotorg/dendrite-monolith/) | ❌ | Storing your data and managing your presence in the [Matrix](http://matrix.org/) network. Dendrite is a second-generation Matrix homeserver written in Go, an alternative to Synapse. | | [Dendrite](configuring-playbook-dendrite.md) | [matrixdotorg/dendrite-monolith](https://hub.docker.com/r/matrixdotorg/dendrite-monolith/) | ❌ | Storing your data and managing your presence in the [Matrix](http://matrix.org/) network. Dendrite is a second-generation Matrix homeserver written in Go, an alternative to Synapse. |


## Clients ## Clients


+ 1
- 1
docs/howto-srv-server-delegation.md Просмотреть файл

@@ -26,7 +26,7 @@ The up-to-date list can be accessed on [traefik's documentation](https://doc.tra


**Note**: the changes below instruct you how to do this for a basic Synapse installation. You will need to adapt the variable name and the content of the labels: **Note**: the changes below instruct you how to do this for a basic Synapse installation. You will need to adapt the variable name and the content of the labels:


- if you're using another homeserver implementation (e.g. [Conduit](./configuring-playbook-conduit.md), [continuwuity](./configuring-playbook-continuwuity.md) or [Dendrite](./configuring-playbook-dendrite.md))
- if you're using another homeserver implementation (e.g. [Conduit](./configuring-playbook-conduit.md), [continuwuity](./configuring-playbook-continuwuity.md), [Tuwunel](./configuring-playbook-tuwunel.md) or [Dendrite](./configuring-playbook-dendrite.md))
- if you're using [Synapse with workers enabled](./configuring-playbook-synapse.md#load-balancing-with-workers) (`matrix_synapse_workers_enabled: true`). In that case, it's actually the `matrix-synapse-reverse-proxy-companion` service which has Traefik labels attached - if you're using [Synapse with workers enabled](./configuring-playbook-synapse.md#load-balancing-with-workers) (`matrix_synapse_workers_enabled: true`). In that case, it's actually the `matrix-synapse-reverse-proxy-companion` service which has Traefik labels attached


Also, all instructions below are from an older version of the playbook and may not work anymore. Also, all instructions below are from an older version of the playbook and may not work anymore.


+ 66
- 0
group_vars/matrix_servers Просмотреть файл

@@ -631,6 +631,7 @@ devture_systemd_service_manager_services_list_auto: |
'restart_necessary': ( 'restart_necessary': (
(matrix_conduit_restart_necessary | bool) if matrix_homeserver_implementation == 'conduit' (matrix_conduit_restart_necessary | bool) if matrix_homeserver_implementation == 'conduit'
else (matrix_continuwuity_restart_necessary | bool) if matrix_homeserver_implementation == 'continuwuity' else (matrix_continuwuity_restart_necessary | bool) if matrix_homeserver_implementation == 'continuwuity'
else (matrix_tuwunel_restart_necessary | bool) if matrix_homeserver_implementation == 'tuwunel'
else (matrix_dendrite_restart_necessary | bool) if matrix_homeserver_implementation == 'dendrite' else (matrix_dendrite_restart_necessary | bool) if matrix_homeserver_implementation == 'dendrite'
else true else true
), ),
@@ -1008,6 +1009,7 @@ matrix_homeserver_container_client_api_endpoint: |-
'dendrite': ('matrix-dendrite:' + matrix_dendrite_http_bind_port | default('8008') | string), 'dendrite': ('matrix-dendrite:' + matrix_dendrite_http_bind_port | default('8008') | string),
'conduit': ('matrix-conduit:' + matrix_conduit_port_number | default('8008') | string), 'conduit': ('matrix-conduit:' + matrix_conduit_port_number | default('8008') | string),
'continuwuity': ('matrix-continuwuity:' + matrix_continuwuity_config_port_number | default('8008') | string), 'continuwuity': ('matrix-continuwuity:' + matrix_continuwuity_config_port_number | default('8008') | string),
'tuwunel': ('matrix-tuwunel:' + matrix_tuwunel_config_port_number | default('8008') | string),
}[matrix_homeserver_implementation] }[matrix_homeserver_implementation]
}} }}


@@ -1018,6 +1020,7 @@ matrix_homeserver_container_federation_api_endpoint: |-
'dendrite': ('matrix-dendrite:' + matrix_dendrite_http_bind_port | default('8008') | string), 'dendrite': ('matrix-dendrite:' + matrix_dendrite_http_bind_port | default('8008') | string),
'conduit': ('matrix-conduit:' + matrix_conduit_port_number | default('8008') | string), 'conduit': ('matrix-conduit:' + matrix_conduit_port_number | default('8008') | string),
'continuwuity': ('matrix-continuwuity:' + matrix_continuwuity_config_port_number | default('8008') | string), 'continuwuity': ('matrix-continuwuity:' + matrix_continuwuity_config_port_number | default('8008') | string),
'tuwunel': ('matrix-tuwunel:' + matrix_tuwunel_config_port_number | default('8008') | string),
}[matrix_homeserver_implementation] }[matrix_homeserver_implementation]
}} }}


@@ -5558,6 +5561,7 @@ grafana_default_home_dashboard_path: |-
'dendrite': ('/etc/grafana/dashboards/node-exporter-full.json' if prometheus_node_exporter_enabled else ''), 'dendrite': ('/etc/grafana/dashboards/node-exporter-full.json' if prometheus_node_exporter_enabled else ''),
'conduit': ('/etc/grafana/dashboards/node-exporter-full.json' if prometheus_node_exporter_enabled else ''), 'conduit': ('/etc/grafana/dashboards/node-exporter-full.json' if prometheus_node_exporter_enabled else ''),
'continuwuity': ('/etc/grafana/dashboards/node-exporter-full.json' if prometheus_node_exporter_enabled else ''), 'continuwuity': ('/etc/grafana/dashboards/node-exporter-full.json' if prometheus_node_exporter_enabled else ''),
'tuwunel': ('/etc/grafana/dashboards/node-exporter-full.json' if prometheus_node_exporter_enabled else ''),
}[matrix_homeserver_implementation] }[matrix_homeserver_implementation]
}} }}


@@ -5618,6 +5622,7 @@ matrix_registration_shared_secret: |-
'dendrite': matrix_dendrite_client_api_registration_shared_secret | default (''), 'dendrite': matrix_dendrite_client_api_registration_shared_secret | default (''),
'conduit': '', 'conduit': '',
'continuwuity': '', 'continuwuity': '',
'tuwunel': '',
}[matrix_homeserver_implementation] }[matrix_homeserver_implementation]
}} }}


@@ -5843,6 +5848,67 @@ matrix_continuwuity_systemd_wanted_services_list_auto: |
###################################################################### ######################################################################




######################################################################
#
# matrix-tuwunel
#
######################################################################

matrix_tuwunel_enabled: "{{ matrix_homeserver_implementation == 'tuwunel' }}"

matrix_tuwunel_hostname: "{{ matrix_server_fqn_matrix }}"

matrix_tuwunel_config_allow_federation: "{{ matrix_homeserver_federation_enabled }}"

matrix_tuwunel_config_well_known_client: "{{ matrix_homeserver_url if matrix_playbook_ssl_enabled else '' }}"

matrix_tuwunel_container_image_registry_prefix_upstream: "{{ matrix_container_global_registry_prefix_override if matrix_container_global_registry_prefix_override else matrix_tuwunel_container_image_registry_prefix_upstream_default }}"

matrix_tuwunel_container_network: "{{ matrix_homeserver_container_network }}"

matrix_tuwunel_container_additional_networks_auto: |
{{
(
([matrix_playbook_reverse_proxyable_services_additional_network] if matrix_tuwunel_container_labels_traefik_enabled and matrix_playbook_reverse_proxyable_services_additional_network else [])
) | unique
}}

matrix_tuwunel_container_labels_traefik_enabled: "{{ matrix_playbook_reverse_proxy_type in ['playbook-managed-traefik', 'other-traefik-container'] and not matrix_synapse_workers_enabled }}"
matrix_tuwunel_container_labels_traefik_docker_network: "{{ matrix_playbook_reverse_proxyable_services_additional_network }}"
matrix_tuwunel_container_labels_traefik_entrypoints: "{{ traefik_entrypoint_primary }}"
matrix_tuwunel_container_labels_traefik_tls_certResolver: "{{ traefik_certResolver_primary }}"

matrix_tuwunel_container_labels_public_client_root_redirection_enabled: "{{ matrix_tuwunel_container_labels_public_client_root_redirection_url != '' }}"
matrix_tuwunel_container_labels_public_client_root_redirection_url: "{{ (('https://' if matrix_playbook_ssl_enabled else 'http://') + matrix_server_fqn_element) if matrix_client_element_enabled else '' }}"

matrix_tuwunel_container_labels_public_federation_api_traefik_hostname: "{{ matrix_server_fqn_matrix_federation }}"
matrix_tuwunel_container_labels_public_federation_api_traefik_entrypoints: "{{ matrix_federation_traefik_entrypoint_name }}"
matrix_tuwunel_container_labels_public_federation_api_traefik_tls: "{{ matrix_federation_traefik_entrypoint_tls }}"

matrix_tuwunel_container_labels_internal_client_api_enabled: "{{ matrix_playbook_internal_matrix_client_api_traefik_entrypoint_enabled }}"
matrix_tuwunel_container_labels_internal_client_api_traefik_entrypoints: "{{ matrix_playbook_internal_matrix_client_api_traefik_entrypoint_name }}"

matrix_tuwunel_config_well_known_livekit_url: "{{ matrix_livekit_jwt_service_public_url if matrix_livekit_jwt_service_enabled else '' }}"

matrix_tuwunel_config_turn_uris: "{{ coturn_turn_uris if coturn_enabled else [] }}"
matrix_tuwunel_config_turn_secret: "{{ coturn_turn_static_auth_secret if (coturn_enabled and coturn_authentication_method == 'auth-secret') else '' }}"
matrix_tuwunel_config_turn_username: "{{ coturn_lt_cred_mech_username if (coturn_enabled and coturn_authentication_method == 'lt-cred-mech') else '' }}"
matrix_tuwunel_config_turn_password: "{{ coturn_lt_cred_mech_password if (coturn_enabled and coturn_authentication_method == 'lt-cred-mech') else '' }}"

matrix_tuwunel_self_check_validate_certificates: "{{ matrix_playbook_ssl_enabled }}"

matrix_tuwunel_systemd_wanted_services_list_auto: |
{{
([coturn_identifier ~ '.service'] if coturn_enabled else [])
}}

######################################################################
#
# /matrix-tuwunel
#
######################################################################


###################################################################### ######################################################################
# #
# matrix-user-creator # matrix-user-creator


+ 1
- 1
roles/custom/matrix-base/defaults/main.yml Просмотреть файл

@@ -84,7 +84,7 @@ matrix_monitoring_container_network: matrix-monitoring
matrix_homeserver_enabled: true matrix_homeserver_enabled: true


# This will contain the homeserver implementation that is in use. # This will contain the homeserver implementation that is in use.
# Valid values: synapse, dendrite, conduit, continuwuity
# Valid values: synapse, dendrite, conduit, continuwuity, tuwunel
# #
# By default, we use Synapse, because it's the only full-featured Matrix server at the moment. # By default, we use Synapse, because it's the only full-featured Matrix server at the moment.
# #


+ 1
- 1
roles/custom/matrix-base/tasks/validate_config.yml Просмотреть файл

@@ -13,7 +13,7 @@
- name: Fail if invalid homeserver implementation - name: Fail if invalid homeserver implementation
ansible.builtin.fail: ansible.builtin.fail:
msg: "You need to set a valid homeserver implementation in `matrix_homeserver_implementation`" msg: "You need to set a valid homeserver implementation in `matrix_homeserver_implementation`"
when: "matrix_homeserver_implementation not in ['synapse', 'dendrite', 'conduit', 'continuwuity']"
when: "matrix_homeserver_implementation not in ['synapse', 'dendrite', 'conduit', 'continuwuity', 'tuwunel']"


- name: (Deprecation) Catch and report renamed settings - name: (Deprecation) Catch and report renamed settings
ansible.builtin.fail: ansible.builtin.fail:


+ 315
- 0
roles/custom/matrix-tuwunel/defaults/main.yml Просмотреть файл

@@ -0,0 +1,315 @@
# SPDX-FileCopyrightText: 2026 MDAD project contributors
# SPDX-FileCopyrightText: 2026 Slavi Pantaleev
#
# SPDX-License-Identifier: AGPL-3.0-or-later

---
# Tuwunel is a Matrix homeserver, the official successor to conduwuit.
# Project source code URL: https://github.com/matrix-construct/tuwunel
# See: https://matrix-construct.github.io/tuwunel/

matrix_tuwunel_enabled: true

matrix_tuwunel_hostname: ''

# renovate: datasource=docker depName=ghcr.io/matrix-construct/tuwunel
matrix_tuwunel_version: latest

matrix_tuwunel_container_image: "{{ matrix_tuwunel_container_image_registry_prefix }}matrix-construct/tuwunel:{{ matrix_tuwunel_container_image_tag }}"
matrix_tuwunel_container_image_tag: "{{ matrix_tuwunel_version }}"
matrix_tuwunel_container_image_registry_prefix: "{{ matrix_tuwunel_container_image_registry_prefix_upstream }}"
matrix_tuwunel_container_image_registry_prefix_upstream: "{{ matrix_tuwunel_container_image_registry_prefix_upstream_default }}"
matrix_tuwunel_container_image_registry_prefix_upstream_default: ghcr.io/

matrix_tuwunel_base_path: "{{ matrix_base_data_path }}/tuwunel"
matrix_tuwunel_config_path: "{{ matrix_tuwunel_base_path }}/config"
matrix_tuwunel_data_path: "{{ matrix_tuwunel_base_path }}/data"

matrix_tuwunel_config_port_number: 6167

matrix_tuwunel_tmp_directory_size_mb: 500

# List of systemd services that matrix-tuwunel.service depends on
matrix_tuwunel_systemd_required_services_list: "{{ matrix_tuwunel_systemd_required_services_list_default + matrix_tuwunel_systemd_required_services_list_auto + matrix_tuwunel_systemd_required_services_list_custom }}"
matrix_tuwunel_systemd_required_services_list_default: "{{ [devture_systemd_docker_base_docker_service_name] if devture_systemd_docker_base_docker_service_name else [] }}"
matrix_tuwunel_systemd_required_services_list_auto: []
matrix_tuwunel_systemd_required_services_list_custom: []

# List of systemd services that matrix-tuwunel.service wants
matrix_tuwunel_systemd_wanted_services_list: "{{ matrix_tuwunel_systemd_wanted_services_list_default + matrix_tuwunel_systemd_wanted_services_list_auto + matrix_tuwunel_systemd_wanted_services_list_custom }}"
matrix_tuwunel_systemd_wanted_services_list_default: []
matrix_tuwunel_systemd_wanted_services_list_auto: []
matrix_tuwunel_systemd_wanted_services_list_custom: []

# Controls how long to sleep for after starting the matrix-tuwunel container,
# so that subsequent services that depend on it can start after the homeserver
# is fully up.
#
# Set to 0 to remove the delay.
matrix_tuwunel_systemd_service_post_start_delay_seconds: 3

# The base container network. It will be auto-created by this role if it doesn't exist already.
matrix_tuwunel_container_network: ""

# A list of additional container networks that the container would be connected to.
# The role does not create these networks, so make sure they already exist.
# Use this to expose this container to another reverse proxy, which runs in a different container network.
matrix_tuwunel_container_additional_networks: "{{ matrix_tuwunel_container_additional_networks_auto + matrix_tuwunel_container_additional_networks_custom }}"
matrix_tuwunel_container_additional_networks_auto: []
matrix_tuwunel_container_additional_networks_custom: []

# matrix_tuwunel_container_labels_traefik_enabled controls whether labels to assist a Traefik reverse-proxy will be attached to the container.
# See `../templates/labels.j2` for details.
#
# To inject your own other container labels, see `matrix_tuwunel_container_labels_additional_labels`.
matrix_tuwunel_container_labels_traefik_enabled: true
matrix_tuwunel_container_labels_traefik_docker_network: "{{ matrix_tuwunel_container_network }}"
matrix_tuwunel_container_labels_traefik_entrypoints: web-secure
matrix_tuwunel_container_labels_traefik_tls_certResolver: default # noqa var-naming

# Controls whether labels will be added for handling the root (/) path on a public Traefik entrypoint.
matrix_tuwunel_container_labels_public_client_root_enabled: true
matrix_tuwunel_container_labels_public_client_root_traefik_hostname: "{{ matrix_tuwunel_hostname }}"
matrix_tuwunel_container_labels_public_client_root_traefik_rule: "Host(`{{ matrix_tuwunel_container_labels_public_client_root_traefik_hostname }}`) && Path(`/`)"
matrix_tuwunel_container_labels_public_client_root_traefik_priority: 0
matrix_tuwunel_container_labels_public_client_root_traefik_entrypoints: "{{ matrix_tuwunel_container_labels_traefik_entrypoints }}"
matrix_tuwunel_container_labels_public_client_root_traefik_tls: "{{ matrix_tuwunel_container_labels_public_client_root_traefik_entrypoints != 'web' }}"
matrix_tuwunel_container_labels_public_client_root_traefik_tls_certResolver: "{{ matrix_tuwunel_container_labels_traefik_tls_certResolver }}" # noqa var-naming
matrix_tuwunel_container_labels_public_client_root_redirection_enabled: false
matrix_tuwunel_container_labels_public_client_root_redirection_url: ""

# Controls whether labels will be added that expose the Client-Server API on a public Traefik entrypoint.
matrix_tuwunel_container_labels_public_client_api_enabled: true
matrix_tuwunel_container_labels_public_client_api_traefik_hostname: "{{ matrix_tuwunel_hostname }}"
matrix_tuwunel_container_labels_public_client_api_traefik_path_prefix: /_matrix
matrix_tuwunel_container_labels_public_client_api_traefik_rule: "Host(`{{ matrix_tuwunel_container_labels_public_client_api_traefik_hostname }}`) && PathPrefix(`{{ matrix_tuwunel_container_labels_public_client_api_traefik_path_prefix }}`)"
matrix_tuwunel_container_labels_public_client_api_traefik_priority: 0
matrix_tuwunel_container_labels_public_client_api_traefik_entrypoints: "{{ matrix_tuwunel_container_labels_traefik_entrypoints }}"
matrix_tuwunel_container_labels_public_client_api_traefik_tls: "{{ matrix_tuwunel_container_labels_public_client_api_traefik_entrypoints != 'web' }}"
matrix_tuwunel_container_labels_public_client_api_traefik_tls_certResolver: "{{ matrix_tuwunel_container_labels_traefik_tls_certResolver }}" # noqa var-naming

# Controls whether labels will be added that expose the Client-Server API on the internal Traefik entrypoint.
matrix_tuwunel_container_labels_internal_client_api_enabled: false
matrix_tuwunel_container_labels_internal_client_api_traefik_path_prefix: "{{ matrix_tuwunel_container_labels_public_client_api_traefik_path_prefix }}"
matrix_tuwunel_container_labels_internal_client_api_traefik_rule: "PathPrefix(`{{ matrix_tuwunel_container_labels_internal_client_api_traefik_path_prefix }}`)"
matrix_tuwunel_container_labels_internal_client_api_traefik_priority: "{{ matrix_tuwunel_container_labels_public_client_api_traefik_priority }}"
matrix_tuwunel_container_labels_internal_client_api_traefik_entrypoints: ""

# Controls whether labels will be added that expose the Server-Server (Federation) API on a public Traefik entrypoint.
matrix_tuwunel_container_labels_public_federation_api_enabled: "{{ matrix_tuwunel_config_allow_federation }}"
matrix_tuwunel_container_labels_public_federation_api_traefik_hostname: "{{ matrix_tuwunel_hostname }}"
matrix_tuwunel_container_labels_public_federation_api_traefik_path_prefix: /_matrix
matrix_tuwunel_container_labels_public_federation_api_traefik_rule: "Host(`{{ matrix_tuwunel_container_labels_public_federation_api_traefik_hostname }}`) && PathPrefix(`{{ matrix_tuwunel_container_labels_public_federation_api_traefik_path_prefix }}`)"
matrix_tuwunel_container_labels_public_federation_api_traefik_priority: 0
matrix_tuwunel_container_labels_public_federation_api_traefik_entrypoints: ''
# TLS is force-enabled because the spec (https://spec.matrix.org/latest/server-server-api/#tls) requires the federation API use HTTPS.
matrix_tuwunel_container_labels_public_federation_api_traefik_tls: true
matrix_tuwunel_container_labels_public_federation_api_traefik_tls_certResolver: "{{ matrix_tuwunel_container_labels_traefik_tls_certResolver }}" # noqa var-naming

# Additional Docker container labels (multiline string) appended verbatim to the label file.
# See `../templates/labels.j2`.
matrix_tuwunel_container_labels_additional_labels: ''

# Extra arguments for the Docker container
matrix_tuwunel_container_extra_arguments: []

# Specifies which template files to use when configuring tuwunel.
# To override the rendered config wholesale, copy the template into your inventory and point this at it:
# matrix_tuwunel_template_tuwunel_config: "{{ playbook_dir }}/inventory/host_vars/matrix.example.com/tuwunel.toml.j2"
matrix_tuwunel_template_tuwunel_config: "{{ role_path }}/templates/tuwunel.toml.j2"

# The pretty server name used as a suffix on user/room IDs. Cannot be changed after first start without a database wipe.
matrix_tuwunel_config_server_name: "{{ matrix_domain }}"

# Max size for uploads, in bytes
matrix_tuwunel_config_max_request_size: 20000000

# Enables open registration. If false, no users can register on this server.
matrix_tuwunel_config_allow_registration: false

# When registration is enabled, set a strong token to protect the endpoint from abuse.
# Generate one with e.g. `pwgen -s 64 1`. If left empty AND `allow_registration` is true,
# you must explicitly opt in via the open-registration acknowledgement variable below.
matrix_tuwunel_config_registration_token: ''

# Acknowledgement required to allow registration with no token.
# Maps to tuwunel's `yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse`.
matrix_tuwunel_config_yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse: false

# Suffix appended to new-user displaynames upon registration. Empty disables it.
# Upstream defaults to a flag glyph; we keep MDAD homeserver behavior consistent and leave this empty.
matrix_tuwunel_config_new_user_displayname_suffix: ""

# Emergency password to grant access to the admin user when locked out. Empty disables.
matrix_tuwunel_config_emergency_password: ''

# Trusted notary servers used for key verification.
matrix_tuwunel_config_trusted_servers:
- "matrix.org"

# Logging directives in `tracing-subscriber` env-filter syntax.
matrix_tuwunel_config_log: "info,state_res=warn"

# TURN integration
matrix_tuwunel_config_turn_uris: []
matrix_tuwunel_config_turn_secret: ''
matrix_tuwunel_config_turn_username: ''
matrix_tuwunel_config_turn_password: ''

# Self-check toggles
matrix_tuwunel_self_check_validate_certificates: true

# Encryption / room creation policy
matrix_tuwunel_config_allow_encryption: true
matrix_tuwunel_config_allow_room_creation: true

# Default room version newly created rooms will use.
matrix_tuwunel_config_default_room_version: '12'

# Rooms newly registered users will be auto-joined to.
# Must be rooms this server has joined at least once and that are public.
matrix_tuwunel_config_auto_join_rooms: []

# (De)federation toggles
matrix_tuwunel_config_allow_federation: true
matrix_tuwunel_config_allowed_remote_server_names: []
matrix_tuwunel_config_forbidden_remote_server_names: []
matrix_tuwunel_config_forbidden_remote_room_directory_server_names: []
matrix_tuwunel_config_prevent_media_downloads_from: []

# Outgoing presence is heavy on CPU and network and almost no clients use it. Off by default.
matrix_tuwunel_config_allow_outgoing_presence: false

# URL preview gating
matrix_tuwunel_config_url_preview_domain_contains_allowlist: []
matrix_tuwunel_config_url_preview_domain_explicit_allowlist: []
matrix_tuwunel_config_url_preview_check_root_domain: false

# Well-known overrides
# Maps to `[global.well_known] client = "..."` and `server = "host:port"`.
matrix_tuwunel_config_well_known_client: ''
matrix_tuwunel_config_well_known_server: ''
matrix_tuwunel_config_well_known_support_page: ''
matrix_tuwunel_config_well_known_support_email: ''
matrix_tuwunel_config_well_known_support_mxid: ''

# MatrixRTC foci served via /_matrix/client/v1/rtc/transports (MSC4143)
matrix_tuwunel_config_well_known_livekit_url: ''

# RocksDB tuning. Empty values let tuwunel auto-pick.
matrix_tuwunel_config_rocksdb_compression_algo: 'zstd' # one of: zstd, lz4, bz2, none
matrix_tuwunel_config_rocksdb_compression_level: ''
matrix_tuwunel_config_rocksdb_bottommost_compression_level: ''
matrix_tuwunel_config_rocksdb_direct_io: false
matrix_tuwunel_config_rocksdb_parallelism_threads: 0
matrix_tuwunel_config_rocksdb_max_log_file_size: ''
matrix_tuwunel_config_rocksdb_log_time_to_roll: ''
matrix_tuwunel_config_database_backup_path: ''
matrix_tuwunel_config_database_backups_to_keep: 1

# Cache sizing. Empty values let tuwunel auto-pick (scaled by CPU count).
matrix_tuwunel_config_cache_capacity_modifier: ''
matrix_tuwunel_config_db_cache_capacity_mb: ''
matrix_tuwunel_config_db_write_buffer_capacity_mb: ''

# Admin room
matrix_tuwunel_config_create_admin_room: true
matrix_tuwunel_config_federate_admin_room: false
matrix_tuwunel_config_grant_admin_to_first_user: true

# Sentry crash/error reporting (off by default)
matrix_tuwunel_config_sentry_enabled: false
matrix_tuwunel_config_sentry_endpoint: ''
matrix_tuwunel_config_sentry_send_server_name: false
matrix_tuwunel_config_sentry_traces_sample_rate: 0.15

# Blurhashing for image previews
matrix_tuwunel_config_blurhashing_enabled: true
matrix_tuwunel_config_blurhashing_components_x: 4
matrix_tuwunel_config_blurhashing_components_y: 3
matrix_tuwunel_config_blurhashing_max_raw_size: 33554432

# Native TLS (use only when reverse-proxying is not desired)
matrix_tuwunel_config_tls_certs: ''
matrix_tuwunel_config_tls_key: ''
matrix_tuwunel_config_tls_dual_protocol: false

# LDAP authentication ([global.ldap] in tuwunel.toml).
# See: https://matrix-construct.github.io/tuwunel/authentication/providers.html
matrix_tuwunel_config_ldap_enabled: false
matrix_tuwunel_config_ldap_uri: ''
matrix_tuwunel_config_ldap_base_dn: ''
matrix_tuwunel_config_ldap_bind_dn: ''
matrix_tuwunel_config_ldap_bind_password_file: ''
matrix_tuwunel_config_ldap_filter: '(objectClass=*)'
matrix_tuwunel_config_ldap_uid_attribute: 'uid'
matrix_tuwunel_config_ldap_name_attribute: 'givenName'
matrix_tuwunel_config_ldap_admin_base_dn: ''
matrix_tuwunel_config_ldap_admin_filter: ''

# JWT authentication ([global.jwt] in tuwunel.toml).
matrix_tuwunel_config_jwt_enabled: false
matrix_tuwunel_config_jwt_key: ''
matrix_tuwunel_config_jwt_format: 'HMAC' # one of: HMAC, B64HMAC, ECDSA, EDDSA
matrix_tuwunel_config_jwt_algorithm: 'HS256'
matrix_tuwunel_config_jwt_register_user: true
matrix_tuwunel_config_jwt_audience: []
matrix_tuwunel_config_jwt_issuer: []
matrix_tuwunel_config_jwt_require_exp: false
matrix_tuwunel_config_jwt_require_nbf: false
matrix_tuwunel_config_jwt_validate_exp: true
matrix_tuwunel_config_jwt_validate_nbf: true

# OAuth2/OIDC identity providers.
#
# Each entry becomes a `[[global.identity_provider]]` block. Only fields you set are emitted;
# tuwunel applies brand-aware defaults for known providers (Google, GitHub, Keycloak, MAS, etc).
#
# Example:
# matrix_tuwunel_config_identity_providers:
# - brand: keycloak
# client_id: matrix
# client_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: '...'
# client_secret: '...'
#
# See: https://matrix-construct.github.io/tuwunel/authentication/providers.html
matrix_tuwunel_config_identity_providers: []

# Media storage providers.
#
# Each entry maps an ID to a backend. `kind` is `local` or `s3`; remaining keys map directly
# to fields under `[global.storage_provider.<ID>.<kind>]`.
#
# Examples:
# 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: '...'
#
# See: https://matrix-construct.github.io/tuwunel/media/storage.html
matrix_tuwunel_config_storage_providers: []

# Additional environment variables to pass to the container, one per line.
# Environment variables override the rendered config file.
#
# Example:
# matrix_tuwunel_environment_variables_extension: |
# TUWUNEL_REQUEST_TIMEOUT=60
# TUWUNEL_DNS_CACHE_SIZE=131072
matrix_tuwunel_environment_variables_extension: ''

# matrix_tuwunel_restart_necessary controls whether the service will be restarted (when true)
# or merely started (when false) by the systemd service-manager role when conditional restart
# is enabled. Computed during installation based on whether config / unit / image changed.
matrix_tuwunel_restart_necessary: false

+ 76
- 0
roles/custom/matrix-tuwunel/tasks/install.yml Просмотреть файл

@@ -0,0 +1,76 @@
# SPDX-FileCopyrightText: 2026 MDAD project contributors
# SPDX-FileCopyrightText: 2026 Slavi Pantaleev
#
# SPDX-License-Identifier: AGPL-3.0-or-later

---

- name: Ensure tuwunel config path exists
ansible.builtin.file:
path: "{{ matrix_tuwunel_config_path }}"
state: directory
mode: '0750'
owner: "{{ matrix_user_name }}"
group: "{{ matrix_group_name }}"

- name: Ensure tuwunel data path exists
ansible.builtin.file:
path: "{{ matrix_tuwunel_data_path }}"
state: directory
mode: '0770'
owner: "{{ matrix_user_name }}"
group: "{{ matrix_group_name }}"

- name: Ensure tuwunel configuration installed
ansible.builtin.template:
src: "{{ matrix_tuwunel_template_tuwunel_config }}"
dest: "{{ matrix_tuwunel_config_path }}/tuwunel.toml"
mode: '0644'
owner: "{{ matrix_user_name }}"
group: "{{ matrix_group_name }}"
register: matrix_tuwunel_config_result

- name: Ensure tuwunel support files installed
ansible.builtin.template:
src: "{{ role_path }}/templates/{{ item }}.j2"
dest: "{{ matrix_tuwunel_base_path }}/{{ item }}"
mode: '0640'
owner: "{{ matrix_user_name }}"
group: "{{ matrix_group_name }}"
with_items:
- labels
- env
register: matrix_tuwunel_support_files_result

- name: Ensure tuwunel container network is created
community.general.docker_network:
enable_ipv6: "{{ devture_systemd_docker_base_ipv6_enabled }}"
name: "{{ matrix_tuwunel_container_network }}"
driver: bridge
driver_options: "{{ devture_systemd_docker_base_container_networks_driver_options }}"

- name: Ensure tuwunel container image is pulled
community.docker.docker_image_pull:
name: "{{ matrix_tuwunel_container_image }}"
pull: always
register: matrix_tuwunel_container_image_pull_result
retries: "{{ devture_playbook_help_container_retries_count }}"
delay: "{{ devture_playbook_help_container_retries_delay }}"
until: matrix_tuwunel_container_image_pull_result is not failed

- name: Ensure matrix-tuwunel.service installed
ansible.builtin.template:
src: "{{ role_path }}/templates/systemd/matrix-tuwunel.service.j2"
dest: "{{ devture_systemd_docker_base_systemd_path }}/matrix-tuwunel.service"
mode: '0644'
register: matrix_tuwunel_systemd_service_result

- name: Determine whether tuwunel needs a restart
ansible.builtin.set_fact:
matrix_tuwunel_restart_necessary: >-
{{
matrix_tuwunel_config_result.changed | default(false)
or matrix_tuwunel_support_files_result.changed | default(false)
or matrix_tuwunel_systemd_service_result.changed | default(false)
or matrix_tuwunel_container_image_pull_result.changed | default(false)
}}

+ 40
- 0
roles/custom/matrix-tuwunel/tasks/main.yml Просмотреть файл

@@ -0,0 +1,40 @@
# SPDX-FileCopyrightText: 2026 MDAD project contributors
# SPDX-FileCopyrightText: 2026 Slavi Pantaleev
#
# SPDX-License-Identifier: AGPL-3.0-or-later

---

- tags:
- setup-all
- setup-tuwunel
- install-all
- install-tuwunel
block:
- when: matrix_tuwunel_enabled | bool
ansible.builtin.include_tasks: "{{ role_path }}/tasks/validate_config.yml"

- when: matrix_tuwunel_enabled | bool
ansible.builtin.include_tasks: "{{ role_path }}/tasks/install.yml"

- tags:
- setup-all
- setup-tuwunel
block:
- when: not matrix_tuwunel_enabled | bool
ansible.builtin.include_tasks: "{{ role_path }}/tasks/uninstall.yml"

- tags:
- self-check
block:
- when: matrix_tuwunel_enabled | bool
ansible.builtin.include_tasks: "{{ role_path }}/tasks/self_check_client_api.yml"

- when: matrix_tuwunel_enabled | bool
ansible.builtin.include_tasks: "{{ role_path }}/tasks/self_check_federation_api.yml"

- tags:
- tuwunel-migrate-from-conduwuit
block:
- when: matrix_tuwunel_enabled | bool
ansible.builtin.include_tasks: "{{ role_path }}/tasks/migrate_from_conduwuit.yml"

+ 83
- 0
roles/custom/matrix-tuwunel/tasks/migrate_from_conduwuit.yml Просмотреть файл

@@ -0,0 +1,83 @@
# SPDX-FileCopyrightText: 2026 MDAD project contributors
# SPDX-FileCopyrightText: 2026 Slavi Pantaleev
#
# SPDX-License-Identifier: AGPL-3.0-or-later

---

# Migrates from the conduwuit server implementation (`/matrix/conduwuit`) to tuwunel (`/matrix/tuwunel`).
# Tuwunel is the official successor to conduwuit and reads conduwuit's RocksDB layout directly.
# We back up the freshly generated tuwunel directory, copy conduwuit's data into it,
# rename the config file, restore tuwunel's labels file, and start the new service.

- name: Check existence of conduwuit directory
ansible.builtin.stat:
path: "{{ matrix_base_data_path }}/conduwuit"
register: matrix_removed_conduwuit_directory_stat

- name: Check existence of tuwunel directory
ansible.builtin.stat:
path: "{{ matrix_base_data_path }}/tuwunel"
register: matrix_tuwunel_directory_stat

- when: >
matrix_removed_conduwuit_directory_stat.stat.exists | bool and
matrix_tuwunel_directory_stat.stat.exists | bool
block:
- name: Ensure matrix-tuwunel.service systemd service is stopped
ansible.builtin.systemd:
name: matrix-tuwunel
state: stopped
enabled: false
daemon_reload: true

- name: Ensure tuwunel directory is backed up
ansible.builtin.command:
cmd: "mv {{ matrix_base_data_path }}/tuwunel {{ matrix_base_data_path }}/tuwunel_old"
creates: "{{ matrix_base_data_path }}/tuwunel_old"
removes: "{{ matrix_base_data_path }}/tuwunel"

- name: Ensure conduwuit directory contents are copied to tuwunel
ansible.builtin.copy:
src: "{{ matrix_base_data_path }}/conduwuit/"
dest: "{{ matrix_base_data_path }}/tuwunel"
remote_src: true
mode: preserve

- name: Ensure conduwuit.toml file is renamed
ansible.builtin.command:
cmd: "mv {{ matrix_base_data_path }}/tuwunel/config/conduwuit.toml {{ matrix_base_data_path }}/tuwunel/config/tuwunel.toml"
removes: "{{ matrix_base_data_path }}/tuwunel/config/conduwuit.toml"

- name: Ensure tuwunel labels are restored
ansible.builtin.copy:
src: "{{ matrix_base_data_path }}/tuwunel_old/labels"
dest: "{{ matrix_base_data_path }}/tuwunel/labels"
remote_src: true
force: true
mode: preserve

- name: Ensure directories ownership is set
block:
- name: Set tuwunel ownership
ansible.builtin.file:
path: "{{ matrix_base_data_path }}/tuwunel"
state: directory
owner: "{{ matrix_user_name }}"
group: "{{ matrix_group_name }}"
recurse: true

- name: Set tuwunel_old ownership
ansible.builtin.file:
path: "{{ matrix_base_data_path }}/tuwunel_old"
state: directory
owner: "{{ matrix_user_name }}"
group: "{{ matrix_group_name }}"
recurse: true

- name: Ensure matrix-tuwunel.service systemd service is started
ansible.builtin.systemd:
name: matrix-tuwunel
state: started
enabled: true
daemon_reload: true

+ 28
- 0
roles/custom/matrix-tuwunel/tasks/self_check_client_api.yml Просмотреть файл

@@ -0,0 +1,28 @@
# SPDX-FileCopyrightText: 2026 MDAD project contributors
# SPDX-FileCopyrightText: 2026 Slavi Pantaleev
#
# SPDX-License-Identifier: AGPL-3.0-or-later

---

- name: Check Matrix Client API
ansible.builtin.uri:
url: "{{ matrix_tuwunel_client_api_url_endpoint_public }}"
follow_redirects: none
validate_certs: "{{ matrix_tuwunel_self_check_validate_certificates }}"
register: result_matrix_tuwunel_client_api
ignore_errors: true
check_mode: false
when: matrix_tuwunel_enabled | bool
delegate_to: 127.0.0.1
become: false

- name: Fail if Matrix Client API not working
ansible.builtin.fail:
msg: "Failed checking Matrix Client API is up at `{{ matrix_server_fqn_matrix }}` (checked endpoint: `{{ matrix_tuwunel_client_api_url_endpoint_public }}`). Is tuwunel running? Is port 443 open in your firewall? Full error: {{ result_matrix_tuwunel_client_api }}"
when: "matrix_tuwunel_enabled | bool and (result_matrix_tuwunel_client_api.failed or 'json' not in result_matrix_tuwunel_client_api)"

- name: Report working Matrix Client API
ansible.builtin.debug:
msg: "The Matrix Client API at `{{ matrix_server_fqn_matrix }}` (checked endpoint: `{{ matrix_tuwunel_client_api_url_endpoint_public }}`) is working"
when: matrix_tuwunel_enabled | bool

+ 33
- 0
roles/custom/matrix-tuwunel/tasks/self_check_federation_api.yml Просмотреть файл

@@ -0,0 +1,33 @@
# SPDX-FileCopyrightText: 2026 MDAD project contributors
# SPDX-FileCopyrightText: 2026 Slavi Pantaleev
#
# SPDX-License-Identifier: AGPL-3.0-or-later

---

- name: Check Matrix Federation API
ansible.builtin.uri:
url: "{{ matrix_tuwunel_federation_api_url_endpoint_public }}"
follow_redirects: none
validate_certs: "{{ matrix_tuwunel_self_check_validate_certificates }}"
register: result_matrix_tuwunel_federation_api
ignore_errors: true
check_mode: false
when: matrix_tuwunel_enabled | bool
delegate_to: 127.0.0.1
become: false

- name: Fail if Matrix Federation API not working
ansible.builtin.fail:
msg: "Failed checking Matrix Federation API is up at `{{ matrix_server_fqn_matrix }}` (checked endpoint: `{{ matrix_tuwunel_federation_api_url_endpoint_public }}`). Is tuwunel running? Is port {{ matrix_federation_public_port }} open in your firewall? Full error: {{ result_matrix_tuwunel_federation_api }}"
when: "matrix_tuwunel_enabled | bool and matrix_tuwunel_config_allow_federation | bool and (result_matrix_tuwunel_federation_api.failed or 'json' not in result_matrix_tuwunel_federation_api)"

- name: Fail if Matrix Federation API unexpectedly enabled
ansible.builtin.fail:
msg: "Matrix Federation API is up at `{{ matrix_server_fqn_matrix }}` (checked endpoint: `{{ matrix_tuwunel_federation_api_url_endpoint_public }}`) despite being disabled."
when: "matrix_tuwunel_enabled | bool and not matrix_tuwunel_config_allow_federation | bool and not result_matrix_tuwunel_federation_api.failed"

- name: Report working Matrix Federation API
ansible.builtin.debug:
msg: "The Matrix Federation API at `{{ matrix_server_fqn_matrix }}` (checked endpoint: `{{ matrix_tuwunel_federation_api_url_endpoint_public }}`) is working"
when: "matrix_tuwunel_enabled | bool and matrix_tuwunel_config_allow_federation | bool"

+ 8
- 0
roles/custom/matrix-tuwunel/tasks/setup_install.yml Просмотреть файл

@@ -0,0 +1,8 @@
# SPDX-FileCopyrightText: 2026 MDAD project contributors
# SPDX-FileCopyrightText: 2026 Slavi Pantaleev
#
# SPDX-License-Identifier: AGPL-3.0-or-later

---

- ansible.builtin.include_tasks: "{{ role_path }}/tasks/install.yml"

+ 8
- 0
roles/custom/matrix-tuwunel/tasks/setup_uninstall.yml Просмотреть файл

@@ -0,0 +1,8 @@
# SPDX-FileCopyrightText: 2026 MDAD project contributors
# SPDX-FileCopyrightText: 2026 Slavi Pantaleev
#
# SPDX-License-Identifier: AGPL-3.0-or-later

---

- ansible.builtin.include_tasks: "{{ role_path }}/tasks/uninstall.yml"

+ 24
- 0
roles/custom/matrix-tuwunel/tasks/uninstall.yml Просмотреть файл

@@ -0,0 +1,24 @@
# SPDX-FileCopyrightText: 2026 MDAD project contributors
# SPDX-FileCopyrightText: 2026 Slavi Pantaleev
#
# SPDX-License-Identifier: AGPL-3.0-or-later

---

- name: Check existence of matrix-tuwunel service
ansible.builtin.stat:
path: "{{ devture_systemd_docker_base_systemd_path }}/matrix-tuwunel.service"
register: matrix_tuwunel_service_stat

- when: matrix_tuwunel_service_stat.stat.exists | bool
block:
- name: Ensure matrix-tuwunel is stopped
ansible.builtin.systemd:
name: matrix-tuwunel
state: stopped
daemon_reload: true

- name: Ensure matrix-tuwunel.service doesn't exist
ansible.builtin.file:
path: "{{ devture_systemd_docker_base_systemd_path }}/matrix-tuwunel.service"
state: absent

+ 44
- 0
roles/custom/matrix-tuwunel/tasks/validate_config.yml Просмотреть файл

@@ -0,0 +1,44 @@
# SPDX-FileCopyrightText: 2026 MDAD project contributors
# SPDX-FileCopyrightText: 2026 Slavi Pantaleev
#
# SPDX-License-Identifier: AGPL-3.0-or-later

---

- name: Fail if required tuwunel settings not defined
ansible.builtin.fail:
msg: >-
You need to define a required configuration setting (`{{ item.name }}`).
when: "item.when | bool and lookup('vars', item.name, default='') | string | length == 0"
with_items:
- {'name': 'matrix_tuwunel_hostname', when: true}
- {'name': 'matrix_tuwunel_container_network', when: true}
- {'name': 'matrix_tuwunel_container_labels_internal_client_api_traefik_entrypoints', when: "{{ matrix_tuwunel_container_labels_internal_client_api_enabled }}"}

- name: Fail if registration is enabled without a token or explicit acknowledgement
ansible.builtin.fail:
msg: >-
`matrix_tuwunel_config_allow_registration` is true, but neither
`matrix_tuwunel_config_registration_token` nor
`matrix_tuwunel_config_yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse`
is set. Set a registration token (recommended) or explicitly opt in to open registration.
when: >-
matrix_tuwunel_config_allow_registration | bool
and (matrix_tuwunel_config_registration_token | length == 0)
and not (matrix_tuwunel_config_yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse | bool)

- name: Fail if a storage provider is missing required fields
ansible.builtin.fail:
msg: >-
Storage provider `{{ item.id | default('?') }}` is missing required fields.
Each entry must define both `id` and `kind` (one of: local, s3).
when: "(item.id | default('') | length == 0) or (item.kind | default('') not in ['local', 's3'])"
with_items: "{{ matrix_tuwunel_config_storage_providers }}"

- name: Fail if an identity provider is missing required fields
ansible.builtin.fail:
msg: >-
Identity provider entry is missing both `client_id` and `brand`.
At minimum one of these is required for tuwunel to identify the provider.
when: "(item.client_id | default('') | length == 0) and (item.brand | default('') | length == 0)"
with_items: "{{ matrix_tuwunel_config_identity_providers }}"

+ 1
- 0
roles/custom/matrix-tuwunel/templates/env.j2 Просмотреть файл

@@ -0,0 +1 @@
{{ matrix_tuwunel_environment_variables_extension }}

+ 4
- 0
roles/custom/matrix-tuwunel/templates/env.j2.license Просмотреть файл

@@ -0,0 +1,4 @@
SPDX-FileCopyrightText: 2026 MDAD project contributors
SPDX-FileCopyrightText: 2026 Slavi Pantaleev

SPDX-License-Identifier: AGPL-3.0-or-later

+ 141
- 0
roles/custom/matrix-tuwunel/templates/labels.j2 Просмотреть файл

@@ -0,0 +1,141 @@
{#
SPDX-FileCopyrightText: 2026 MDAD project contributors
SPDX-FileCopyrightText: 2026 Slavi Pantaleev

SPDX-License-Identifier: AGPL-3.0-or-later
#}

{% if matrix_tuwunel_container_labels_traefik_enabled %}
traefik.enable=true

{% if matrix_tuwunel_container_labels_traefik_docker_network %}
traefik.docker.network={{ matrix_tuwunel_container_labels_traefik_docker_network }}
{% endif %}

traefik.http.services.matrix-tuwunel.loadbalancer.server.port={{ matrix_tuwunel_config_port_number }}


{% if matrix_tuwunel_container_labels_public_client_root_enabled %}
############################################################
# #
# Public Root path (/) #
# #
############################################################

{% set client_root_middlewares = [] %}

{% if matrix_tuwunel_container_labels_public_client_root_redirection_enabled %}
{% set client_root_middlewares = client_root_middlewares + ['matrix-tuwunel-client-root-redirect'] %}
traefik.http.middlewares.matrix-tuwunel-client-root-redirect.redirectregex.regex=(.*)
traefik.http.middlewares.matrix-tuwunel-client-root-redirect.redirectregex.replacement={{ matrix_tuwunel_container_labels_public_client_root_redirection_url }}
{% endif %}

traefik.http.routers.matrix-tuwunel-public-client-root.rule={{ matrix_tuwunel_container_labels_public_client_root_traefik_rule }}

traefik.http.routers.matrix-tuwunel-public-client-root.middlewares={{ client_root_middlewares | join(',') }}

{% if matrix_tuwunel_container_labels_public_client_root_traefik_priority | int > 0 %}
traefik.http.routers.matrix-tuwunel-public-client-root.priority={{ matrix_tuwunel_container_labels_public_client_root_traefik_priority }}
{% endif %}

traefik.http.routers.matrix-tuwunel-public-client-root.service=matrix-tuwunel
traefik.http.routers.matrix-tuwunel-public-client-root.entrypoints={{ matrix_tuwunel_container_labels_public_client_root_traefik_entrypoints }}
traefik.http.routers.matrix-tuwunel-public-client-root.tls={{ matrix_tuwunel_container_labels_public_client_root_traefik_tls | to_json }}

{% if matrix_tuwunel_container_labels_public_client_root_traefik_tls %}
traefik.http.routers.matrix-tuwunel-public-client-root.tls.certResolver={{ matrix_tuwunel_container_labels_public_client_root_traefik_tls_certResolver }}
{% endif %}

############################################################
# #
# /Public Root path (/) #
# #
############################################################
{% endif %}


{% if matrix_tuwunel_container_labels_public_client_api_enabled %}
############################################################
# #
# Public Client-API (/_matrix) #
# #
############################################################

traefik.http.routers.matrix-tuwunel-public-client-api.rule={{ matrix_tuwunel_container_labels_public_client_api_traefik_rule }}

{% if matrix_tuwunel_container_labels_public_client_api_traefik_priority | int > 0 %}
traefik.http.routers.matrix-tuwunel-public-client-api.priority={{ matrix_tuwunel_container_labels_public_client_api_traefik_priority }}
{% endif %}

traefik.http.routers.matrix-tuwunel-public-client-api.service=matrix-tuwunel
traefik.http.routers.matrix-tuwunel-public-client-api.entrypoints={{ matrix_tuwunel_container_labels_public_client_api_traefik_entrypoints }}

traefik.http.routers.matrix-tuwunel-public-client-api.tls={{ matrix_tuwunel_container_labels_public_client_api_traefik_tls | to_json }}
{% if matrix_tuwunel_container_labels_public_client_api_traefik_tls %}
traefik.http.routers.matrix-tuwunel-public-client-api.tls.certResolver={{ matrix_tuwunel_container_labels_public_client_api_traefik_tls_certResolver }}
{% endif %}

############################################################
# #
# /Public Client-API (/_matrix) #
# #
############################################################
{% endif %}


{% if matrix_tuwunel_container_labels_internal_client_api_enabled %}
############################################################
# #
# Internal Client-API (/_matrix) #
# #
############################################################

traefik.http.routers.matrix-tuwunel-internal-client-api.rule={{ matrix_tuwunel_container_labels_internal_client_api_traefik_rule }}

{% if matrix_tuwunel_container_labels_internal_client_api_traefik_priority | int > 0 %}
traefik.http.routers.matrix-tuwunel-internal-client-api.priority={{ matrix_tuwunel_container_labels_internal_client_api_traefik_priority }}
{% endif %}

traefik.http.routers.matrix-tuwunel-internal-client-api.service=matrix-tuwunel
traefik.http.routers.matrix-tuwunel-internal-client-api.entrypoints={{ matrix_tuwunel_container_labels_internal_client_api_traefik_entrypoints }}

############################################################
# #
# /Internal Client-API (/_matrix) #
# #
############################################################
{% endif %}


{% if matrix_tuwunel_container_labels_public_federation_api_enabled %}
############################################################
# #
# Public Federation-API (/_matrix) #
# #
############################################################

traefik.http.routers.matrix-tuwunel-public-federation-api.rule={{ matrix_tuwunel_container_labels_public_federation_api_traefik_rule }}

{% if matrix_tuwunel_container_labels_public_federation_api_traefik_priority | int > 0 %}
traefik.http.routers.matrix-tuwunel-public-federation-api.priority={{ matrix_tuwunel_container_labels_public_federation_api_traefik_priority }}
{% endif %}

traefik.http.routers.matrix-tuwunel-public-federation-api.service=matrix-tuwunel
traefik.http.routers.matrix-tuwunel-public-federation-api.entrypoints={{ matrix_tuwunel_container_labels_public_federation_api_traefik_entrypoints }}

traefik.http.routers.matrix-tuwunel-public-federation-api.tls={{ matrix_tuwunel_container_labels_public_federation_api_traefik_tls | to_json }}
{% if matrix_tuwunel_container_labels_public_federation_api_traefik_tls %}
traefik.http.routers.matrix-tuwunel-public-federation-api.tls.certResolver={{ matrix_tuwunel_container_labels_public_federation_api_traefik_tls_certResolver }}
{% endif %}

############################################################
# #
# /Public Federation-API (/_matrix) #
# #
############################################################
{% endif %}


{% endif %}

{{ matrix_tuwunel_container_labels_additional_labels }}

+ 55
- 0
roles/custom/matrix-tuwunel/templates/systemd/matrix-tuwunel.service.j2 Просмотреть файл

@@ -0,0 +1,55 @@
#jinja2: lstrip_blocks: True
[Unit]
Description=Tuwunel Matrix homeserver
{% for service in matrix_tuwunel_systemd_required_services_list %}
Requires={{ service }}
After={{ service }}
{% endfor %}
{% for service in matrix_tuwunel_systemd_wanted_services_list %}
Wants={{ service }}
{% endfor %}

[Service]
Type=simple
Environment="HOME={{ devture_systemd_docker_base_systemd_unit_home_path }}"
ExecStartPre=-{{ devture_systemd_docker_base_host_command_sh }} -c '{{ devture_systemd_docker_base_host_command_docker }} stop -t {{ devture_systemd_docker_base_container_stop_grace_time_seconds }} matrix-tuwunel 2>/dev/null || true'
ExecStartPre=-{{ devture_systemd_docker_base_host_command_sh }} -c '{{ devture_systemd_docker_base_host_command_docker }} rm matrix-tuwunel 2>/dev/null || true'

ExecStartPre={{ devture_systemd_docker_base_host_command_docker }} create \
--rm \
--name=matrix-tuwunel \
--log-driver=none \
--user={{ matrix_user_uid }}:{{ matrix_user_gid }} \
--cap-drop=ALL \
--read-only \
--tmpfs=/tmp:rw,noexec,nosuid,size={{ matrix_tuwunel_tmp_directory_size_mb }}m \
--network={{ matrix_tuwunel_container_network }} \
--env-file={{ matrix_tuwunel_base_path }}/env \
--env TUWUNEL_CONFIG=/etc/tuwunel/tuwunel.toml \
--label-file={{ matrix_tuwunel_base_path }}/labels \
--mount type=bind,src={{ matrix_tuwunel_data_path }},dst=/var/lib/tuwunel \
--mount type=bind,src={{ matrix_tuwunel_config_path }},dst=/etc/tuwunel,ro \
{% for arg in matrix_tuwunel_container_extra_arguments %}
{{ arg }} \
{% endfor %}
{{ matrix_tuwunel_container_image }}

{% for network in matrix_tuwunel_container_additional_networks %}
ExecStartPre={{ devture_systemd_docker_base_host_command_docker }} network connect {{ network }} matrix-tuwunel
{% endfor %}

ExecStart={{ devture_systemd_docker_base_host_command_docker }} start --attach matrix-tuwunel

{% if matrix_tuwunel_systemd_service_post_start_delay_seconds != 0 %}
ExecStartPost=-{{ matrix_host_command_sleep }} {{ matrix_tuwunel_systemd_service_post_start_delay_seconds }}
{% endif %}

ExecStop=-{{ devture_systemd_docker_base_host_command_sh }} -c '{{ devture_systemd_docker_base_host_command_docker }} stop -t {{ devture_systemd_docker_base_container_stop_grace_time_seconds }} matrix-tuwunel 2>/dev/null || true'
ExecStop=-{{ devture_systemd_docker_base_host_command_sh }} -c '{{ devture_systemd_docker_base_host_command_docker }} rm matrix-tuwunel 2>/dev/null || true'
ExecReload={{ devture_systemd_docker_base_host_command_docker }} exec matrix-tuwunel /bin/sh -c 'kill -HUP 1'
Restart=always
RestartSec=30
SyslogIdentifier=matrix-tuwunel

[Install]
WantedBy=multi-user.target

+ 4
- 0
roles/custom/matrix-tuwunel/templates/systemd/matrix-tuwunel.service.j2.license Просмотреть файл

@@ -0,0 +1,4 @@
SPDX-FileCopyrightText: 2026 MDAD project contributors
SPDX-FileCopyrightText: 2026 Slavi Pantaleev

SPDX-License-Identifier: AGPL-3.0-or-later

+ 238
- 0
roles/custom/matrix-tuwunel/templates/tuwunel.toml.j2 Просмотреть файл

@@ -0,0 +1,238 @@
{#
SPDX-FileCopyrightText: 2026 MDAD project contributors
SPDX-FileCopyrightText: 2026 Slavi Pantaleev

SPDX-License-Identifier: AGPL-3.0-or-later
#}
### Tuwunel configuration rendered by matrix-docker-ansible-deploy.
###
### This file only emits options exposed as Ansible variables. All other knobs
### keep tuwunel's upstream defaults. To override anything not surfaced here,
### use `matrix_tuwunel_environment_variables_extension` (env vars override TOML)
### or replace the template via `matrix_tuwunel_template_tuwunel_config`.
###
### Reference: https://matrix-construct.github.io/tuwunel/configuration.html

[global]
server_name = {{ matrix_tuwunel_config_server_name | to_json }}
address = "0.0.0.0"
port = {{ matrix_tuwunel_config_port_number }}
database_path = "/var/lib/tuwunel"

max_request_size = {{ matrix_tuwunel_config_max_request_size }}

new_user_displayname_suffix = {{ matrix_tuwunel_config_new_user_displayname_suffix | to_json }}

allow_registration = {{ matrix_tuwunel_config_allow_registration | to_json }}
{% if matrix_tuwunel_config_registration_token | length > 0 %}
registration_token = {{ matrix_tuwunel_config_registration_token | to_json }}
{% endif %}
{% if matrix_tuwunel_config_yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse | bool %}
yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse = true
{% endif %}

{% if matrix_tuwunel_config_emergency_password | length > 0 %}
emergency_password = {{ matrix_tuwunel_config_emergency_password | to_json }}
{% endif %}

allow_encryption = {{ matrix_tuwunel_config_allow_encryption | to_json }}
allow_room_creation = {{ matrix_tuwunel_config_allow_room_creation | to_json }}
default_room_version = {{ matrix_tuwunel_config_default_room_version | to_json }}
{% if matrix_tuwunel_config_auto_join_rooms | length > 0 %}
auto_join_rooms = {{ matrix_tuwunel_config_auto_join_rooms | to_json }}
{% endif %}

allow_federation = {{ matrix_tuwunel_config_allow_federation | to_json }}
trusted_servers = {{ matrix_tuwunel_config_trusted_servers | to_json }}
{% if matrix_tuwunel_config_allowed_remote_server_names | length > 0 %}
allowed_remote_server_names_experimental = {{ matrix_tuwunel_config_allowed_remote_server_names | to_json }}
{% endif %}
{% if matrix_tuwunel_config_forbidden_remote_server_names | length > 0 %}
forbidden_remote_server_names = {{ matrix_tuwunel_config_forbidden_remote_server_names | to_json }}
{% endif %}
{% if matrix_tuwunel_config_forbidden_remote_room_directory_server_names | length > 0 %}
forbidden_remote_room_directory_server_names = {{ matrix_tuwunel_config_forbidden_remote_room_directory_server_names | to_json }}
{% endif %}
{% if matrix_tuwunel_config_prevent_media_downloads_from | length > 0 %}
prevent_media_downloads_from = {{ matrix_tuwunel_config_prevent_media_downloads_from | to_json }}
{% endif %}

allow_outgoing_presence = {{ matrix_tuwunel_config_allow_outgoing_presence | to_json }}

{% if matrix_tuwunel_config_url_preview_domain_contains_allowlist | length > 0 %}
url_preview_domain_contains_allowlist = {{ matrix_tuwunel_config_url_preview_domain_contains_allowlist | to_json }}
{% endif %}
{% if matrix_tuwunel_config_url_preview_domain_explicit_allowlist | length > 0 %}
url_preview_domain_explicit_allowlist = {{ matrix_tuwunel_config_url_preview_domain_explicit_allowlist | to_json }}
{% endif %}
url_preview_check_root_domain = {{ matrix_tuwunel_config_url_preview_check_root_domain | to_json }}

create_admin_room = {{ matrix_tuwunel_config_create_admin_room | to_json }}
federate_admin_room = {{ matrix_tuwunel_config_federate_admin_room | to_json }}
grant_admin_to_first_user = {{ matrix_tuwunel_config_grant_admin_to_first_user | to_json }}

log = {{ matrix_tuwunel_config_log | to_json }}

{% if matrix_tuwunel_config_turn_uris | length > 0 %}
turn_uris = {{ matrix_tuwunel_config_turn_uris | to_json }}
{% endif %}
{% if matrix_tuwunel_config_turn_secret | length > 0 %}
turn_secret = {{ matrix_tuwunel_config_turn_secret | to_json }}
{% endif %}
{% if matrix_tuwunel_config_turn_username | length > 0 %}
turn_username = {{ matrix_tuwunel_config_turn_username | to_json }}
{% endif %}
{% if matrix_tuwunel_config_turn_password | length > 0 %}
turn_password = {{ matrix_tuwunel_config_turn_password | to_json }}
{% endif %}

{% if matrix_tuwunel_config_rocksdb_compression_algo | length > 0 %}
rocksdb_compression_algo = {{ matrix_tuwunel_config_rocksdb_compression_algo | to_json }}
{% endif %}
{% if matrix_tuwunel_config_rocksdb_compression_level | string | length > 0 %}
rocksdb_compression_level = {{ matrix_tuwunel_config_rocksdb_compression_level }}
{% endif %}
{% if matrix_tuwunel_config_rocksdb_bottommost_compression_level | string | length > 0 %}
rocksdb_bottommost_compression_level = {{ matrix_tuwunel_config_rocksdb_bottommost_compression_level }}
{% endif %}
rocksdb_direct_io = {{ matrix_tuwunel_config_rocksdb_direct_io | to_json }}
{% if matrix_tuwunel_config_rocksdb_parallelism_threads | int > 0 %}
rocksdb_parallelism_threads = {{ matrix_tuwunel_config_rocksdb_parallelism_threads }}
{% endif %}
{% if matrix_tuwunel_config_rocksdb_max_log_file_size | string | length > 0 %}
rocksdb_max_log_file_size = {{ matrix_tuwunel_config_rocksdb_max_log_file_size }}
{% endif %}
{% if matrix_tuwunel_config_rocksdb_log_time_to_roll | string | length > 0 %}
rocksdb_log_time_to_roll = {{ matrix_tuwunel_config_rocksdb_log_time_to_roll }}
{% endif %}
{% if matrix_tuwunel_config_database_backup_path | length > 0 %}
database_backup_path = {{ matrix_tuwunel_config_database_backup_path | to_json }}
database_backups_to_keep = {{ matrix_tuwunel_config_database_backups_to_keep }}
{% endif %}

{% if matrix_tuwunel_config_cache_capacity_modifier | string | length > 0 %}
cache_capacity_modifier = {{ matrix_tuwunel_config_cache_capacity_modifier }}
{% endif %}
{% if matrix_tuwunel_config_db_cache_capacity_mb | string | length > 0 %}
db_cache_capacity_mb = {{ matrix_tuwunel_config_db_cache_capacity_mb }}
{% endif %}
{% if matrix_tuwunel_config_db_write_buffer_capacity_mb | string | length > 0 %}
db_write_buffer_capacity_mb = {{ matrix_tuwunel_config_db_write_buffer_capacity_mb }}
{% endif %}

{% if matrix_tuwunel_config_sentry_enabled | bool %}
sentry = true
{% if matrix_tuwunel_config_sentry_endpoint | length > 0 %}
sentry_endpoint = {{ matrix_tuwunel_config_sentry_endpoint | to_json }}
{% endif %}
sentry_send_server_name = {{ matrix_tuwunel_config_sentry_send_server_name | to_json }}
sentry_traces_sample_rate = {{ matrix_tuwunel_config_sentry_traces_sample_rate }}
{% endif %}

{% if (matrix_tuwunel_config_tls_certs | length > 0) and (matrix_tuwunel_config_tls_key | length > 0) %}

[global.tls]
certs = {{ matrix_tuwunel_config_tls_certs | to_json }}
key = {{ matrix_tuwunel_config_tls_key | to_json }}
dual_protocol = {{ matrix_tuwunel_config_tls_dual_protocol | to_json }}
{% endif %}

{% set well_known_keys = [
matrix_tuwunel_config_well_known_client,
matrix_tuwunel_config_well_known_server,
matrix_tuwunel_config_well_known_support_page,
matrix_tuwunel_config_well_known_support_email,
matrix_tuwunel_config_well_known_support_mxid,
matrix_tuwunel_config_well_known_livekit_url,
] %}
{% if well_known_keys | select | list | length > 0 %}

[global.well_known]
{% if matrix_tuwunel_config_well_known_client | length > 0 %}
client = {{ matrix_tuwunel_config_well_known_client | to_json }}
{% endif %}
{% if matrix_tuwunel_config_well_known_server | length > 0 %}
server = {{ matrix_tuwunel_config_well_known_server | to_json }}
{% endif %}
{% if matrix_tuwunel_config_well_known_support_page | length > 0 %}
support_page = {{ matrix_tuwunel_config_well_known_support_page | to_json }}
{% endif %}
{% if matrix_tuwunel_config_well_known_support_email | length > 0 %}
support_email = {{ matrix_tuwunel_config_well_known_support_email | to_json }}
{% endif %}
{% if matrix_tuwunel_config_well_known_support_mxid | length > 0 %}
support_mxid = {{ matrix_tuwunel_config_well_known_support_mxid | to_json }}
{% endif %}
{% if matrix_tuwunel_config_well_known_livekit_url | length > 0 %}
livekit_url = {{ matrix_tuwunel_config_well_known_livekit_url | to_json }}
{% endif %}
{% endif %}

{% if matrix_tuwunel_config_blurhashing_enabled | bool %}

[global.blurhashing]
components_x = {{ matrix_tuwunel_config_blurhashing_components_x }}
components_y = {{ matrix_tuwunel_config_blurhashing_components_y }}
blurhash_max_raw_size = {{ matrix_tuwunel_config_blurhashing_max_raw_size }}
{% endif %}

{% if matrix_tuwunel_config_ldap_enabled | bool %}

[global.ldap]
enable = true
uri = {{ matrix_tuwunel_config_ldap_uri | to_json }}
base_dn = {{ matrix_tuwunel_config_ldap_base_dn | to_json }}
{% if matrix_tuwunel_config_ldap_bind_dn | length > 0 %}
bind_dn = {{ matrix_tuwunel_config_ldap_bind_dn | to_json }}
{% endif %}
{% if matrix_tuwunel_config_ldap_bind_password_file | length > 0 %}
bind_password_file = {{ matrix_tuwunel_config_ldap_bind_password_file | to_json }}
{% endif %}
filter = {{ matrix_tuwunel_config_ldap_filter | to_json }}
uid_attribute = {{ matrix_tuwunel_config_ldap_uid_attribute | to_json }}
name_attribute = {{ matrix_tuwunel_config_ldap_name_attribute | to_json }}
{% if matrix_tuwunel_config_ldap_admin_base_dn | length > 0 %}
admin_base_dn = {{ matrix_tuwunel_config_ldap_admin_base_dn | to_json }}
{% endif %}
{% if matrix_tuwunel_config_ldap_admin_filter | length > 0 %}
admin_filter = {{ matrix_tuwunel_config_ldap_admin_filter | to_json }}
{% endif %}
{% endif %}

{% if matrix_tuwunel_config_jwt_enabled | bool %}

[global.jwt]
enable = true
{% if matrix_tuwunel_config_jwt_key | length > 0 %}
key = {{ matrix_tuwunel_config_jwt_key | to_json }}
{% endif %}
format = {{ matrix_tuwunel_config_jwt_format | to_json }}
algorithm = {{ matrix_tuwunel_config_jwt_algorithm | to_json }}
register_user = {{ matrix_tuwunel_config_jwt_register_user | to_json }}
{% if matrix_tuwunel_config_jwt_audience | length > 0 %}
audience = {{ matrix_tuwunel_config_jwt_audience | to_json }}
{% endif %}
{% if matrix_tuwunel_config_jwt_issuer | length > 0 %}
issuer = {{ matrix_tuwunel_config_jwt_issuer | to_json }}
{% endif %}
require_exp = {{ matrix_tuwunel_config_jwt_require_exp | to_json }}
require_nbf = {{ matrix_tuwunel_config_jwt_require_nbf | to_json }}
validate_exp = {{ matrix_tuwunel_config_jwt_validate_exp | to_json }}
validate_nbf = {{ matrix_tuwunel_config_jwt_validate_nbf | to_json }}
{% endif %}

{% for idp in matrix_tuwunel_config_identity_providers %}

[[global.identity_provider]]
{% for key, value in idp.items() %}
{{ key }} = {{ value | to_json }}
{% endfor %}
{% endfor %}

{% for sp in matrix_tuwunel_config_storage_providers %}

[global.storage_provider.{{ sp.id }}.{{ sp.kind }}]
{% for key, value in sp.items() if key not in ['id', 'kind'] %}
{{ key }} = {{ value | to_json }}
{% endfor %}
{% endfor %}

+ 9
- 0
roles/custom/matrix-tuwunel/vars/main.yml Просмотреть файл

@@ -0,0 +1,9 @@
# SPDX-FileCopyrightText: 2026 MDAD project contributors
# SPDX-FileCopyrightText: 2026 Slavi Pantaleev
#
# SPDX-License-Identifier: AGPL-3.0-or-later

---

matrix_tuwunel_client_api_url_endpoint_public: "{{ 'https' if matrix_playbook_ssl_enabled else 'http' }}://{{ matrix_tuwunel_hostname }}/_matrix/client/versions"
matrix_tuwunel_federation_api_url_endpoint_public: "{{ 'https' if matrix_playbook_ssl_enabled else 'http' }}://{{ matrix_tuwunel_hostname }}:{{ matrix_federation_public_port }}/_matrix/federation/v1/version"

+ 1
- 0
setup.yml Просмотреть файл

@@ -97,6 +97,7 @@
- custom/matrix-dendrite - custom/matrix-dendrite
- custom/matrix-conduit - custom/matrix-conduit
- custom/matrix-continuwuity - custom/matrix-continuwuity
- custom/matrix-tuwunel
- custom/matrix-ketesa - custom/matrix-ketesa
- custom/matrix-synapse-usage-exporter - custom/matrix-synapse-usage-exporter
- galaxy/prometheus_nginxlog_exporter - galaxy/prometheus_nginxlog_exporter


Загрузка…
Отмена
Сохранить