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
| @@ -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) | | |||
| | [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) | | |||
| | [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) | | |||
| ### Clients | |||
| @@ -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. | |||
| @@ -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 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 | |||
| - Server components: | |||
| @@ -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 | | |||
| | [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. | | |||
| | [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. | | |||
| ## Clients | |||
| @@ -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: | |||
| - 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 | |||
| Also, all instructions below are from an older version of the playbook and may not work anymore. | |||
| @@ -631,6 +631,7 @@ devture_systemd_service_manager_services_list_auto: | | |||
| 'restart_necessary': ( | |||
| (matrix_conduit_restart_necessary | bool) if matrix_homeserver_implementation == 'conduit' | |||
| 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 true | |||
| ), | |||
| @@ -1008,6 +1009,7 @@ matrix_homeserver_container_client_api_endpoint: |- | |||
| 'dendrite': ('matrix-dendrite:' + matrix_dendrite_http_bind_port | default('8008') | string), | |||
| 'conduit': ('matrix-conduit:' + matrix_conduit_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] | |||
| }} | |||
| @@ -1018,6 +1020,7 @@ matrix_homeserver_container_federation_api_endpoint: |- | |||
| 'dendrite': ('matrix-dendrite:' + matrix_dendrite_http_bind_port | default('8008') | string), | |||
| 'conduit': ('matrix-conduit:' + matrix_conduit_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] | |||
| }} | |||
| @@ -5558,6 +5561,7 @@ grafana_default_home_dashboard_path: |- | |||
| '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 ''), | |||
| '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] | |||
| }} | |||
| @@ -5618,6 +5622,7 @@ matrix_registration_shared_secret: |- | |||
| 'dendrite': matrix_dendrite_client_api_registration_shared_secret | default (''), | |||
| 'conduit': '', | |||
| 'continuwuity': '', | |||
| 'tuwunel': '', | |||
| }[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 | |||
| @@ -84,7 +84,7 @@ matrix_monitoring_container_network: matrix-monitoring | |||
| matrix_homeserver_enabled: true | |||
| # 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. | |||
| # | |||
| @@ -13,7 +13,7 @@ | |||
| - name: Fail if invalid homeserver implementation | |||
| ansible.builtin.fail: | |||
| 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 | |||
| ansible.builtin.fail: | |||
| @@ -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 | |||
| @@ -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) | |||
| }} | |||
| @@ -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" | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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" | |||
| @@ -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" | |||
| @@ -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" | |||
| @@ -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 | |||
| @@ -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 }}" | |||
| @@ -0,0 +1 @@ | |||
| {{ matrix_tuwunel_environment_variables_extension }} | |||
| @@ -0,0 +1,4 @@ | |||
| SPDX-FileCopyrightText: 2026 MDAD project contributors | |||
| SPDX-FileCopyrightText: 2026 Slavi Pantaleev | |||
| SPDX-License-Identifier: AGPL-3.0-or-later | |||
| @@ -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 }} | |||
| @@ -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 | |||
| @@ -0,0 +1,4 @@ | |||
| SPDX-FileCopyrightText: 2026 MDAD project contributors | |||
| SPDX-FileCopyrightText: 2026 Slavi Pantaleev | |||
| SPDX-License-Identifier: AGPL-3.0-or-later | |||
| @@ -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 %} | |||
| @@ -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" | |||
| @@ -97,6 +97,7 @@ | |||
| - custom/matrix-dendrite | |||
| - custom/matrix-conduit | |||
| - custom/matrix-continuwuity | |||
| - custom/matrix-tuwunel | |||
| - custom/matrix-ketesa | |||
| - custom/matrix-synapse-usage-exporter | |||
| - galaxy/prometheus_nginxlog_exporter | |||