# Matrix (Synapse) * **Category**: Apps * **Status**: 0, work-in-progress * **Image**: [`matrixdotorg/synapse`](https://hub.docker.com/r/matrixdotorg/synapse), 4, upstream * **Healthcheck**: Yes * **Backups**: No * **Email**: Yes * **Tests**: No * **SSO**: Yes ## Basic usage 1. Set up Docker Swarm and [`abra`](https://docs.coopcloud.tech/abra/) 2. Deploy [`coop-cloud/traefik`](https://git.coopcloud.tech/coop-cloud/traefik) 3. `abra app new matrix-synapse --secrets` (optionally with `--pass` if you'd like to save secrets in `pass`) 4. `abra app config YOURAPPDOMAIN` - be sure to change `$DOMAIN` to something that resolves to your Docker swarm box 5. `abra app deploy YOURAPPDOMAIN` 6. Create an initial user: `abra app run YOURAPPDOMAIN app register_new_matrix_user -c /data/homeserver.yaml http://localhost:8008` ## Tips & Tricks ### Create User `register_new_matrix_user -u -k $(cat /var/run/secrets/registration) -p ` ### Set Admin User `abra app cmd YOURAPPDOMAIN db set_admin ` ### Disabling federation - Use `DISABLE_FEDERATION=1` to turn off federation listeners - Don't use [`compose.matrix.yml`](https://git.coopcloud.tech/coop-cloud/traefik/src/branch/master/compose.matrix.yml) in your traefik config to keep the federation ports closed ### Enabling federation Federation is on by default (`DISABLE_FEDERATION=0`). Remote homeservers need a way to discover the host:port that serves your `SERVER_NAME`. There are three supported approaches. At least one needs to be working for federation to work (and matrix will fallback between them). #### Option 1: built-in well-known (`SERVER_NAME` = `DOMAIN`) Set `SERVE_SERVER_WELLKNOWN=true` and leave `SERVER_NAME` unset (defaults to `DOMAIN`). The recipe's nginx serves `/.well-known/matrix/server` and `/.well-known/matrix/client` on `DOMAIN`. Suitable when users are e.g. `@alice:matrix.example.com`. #### Option 2: external well-known on `SERVER_NAME` Use when you want users to be e.g. `@alice:example.com` while Synapse runs at `matrix.example.com` (and SERVER_NAME is served by the same machine that Synapse is running on). Set: ``` SERVER_NAME=example.com DOMAIN=matrix.example.com SERVE_SERVER_WELLKNOWN=false ``` The two paths that must be served on `SERVER_NAME` are: - `https://example.com/.well-known/matrix/server` → `{"m.server": "matrix.example.com:443"}` - `https://example.com/.well-known/matrix/client` → `{"m.homeserver": {"base_url": "https://matrix.example.com"}}` **Recommended — let this recipe serve them via Traefik** by enabling `compose.wellknown.yml`: ``` COMPOSE_FILE="$COMPOSE_FILE:compose.wellknown.yml" ``` This publishes a Traefik router `Host(${SERVER_NAME}) && PathPrefix(/.well-known/matrix)` pointing at the matrix nginx, which already serves both files. The path-scoped, high-priority rule coexists with any apex website that also serves `Host(${SERVER_NAME})` — that site keeps serving everything except `/.well-known/matrix`. `SERVER_NAME` must resolve to this Traefik so ACME can issue its certificate. **Alternative** — serve the two files yourself from whatever already hosts `example.com`. #### Option 3: Traefik `matrix-federation` entrypoint (port 8448) Use when `SERVER_NAME` ≠ `DOMAIN` but you have no separate web service at `SERVER_NAME`. Remote homeservers fall back to `SERVER_NAME:8448` when there's no delegation (also requires SERVER_NAME pointing to same server that matrix is running on). Requirements: - [traefik](https://git.coopcloud.tech/coop-cloud/traefik) `>= 5.1.2+v3.6.15` with `MATRIX_FEDERATION_ENABLED=1` and `compose.matrix.yml` enabled. - `SERVER_NAME` set in your matrix-synapse env (used by the federation router's Host rule). With these in place, the recipe publishes a Traefik router on `Host(${SERVER_NAME})` via the `matrix-federation` entrypoint, reusing the existing matrix nginx → synapse path. #### Option 4: DNS SRV records (usually not viable here) For reasons explained below, I might be confused, but I think SRV records usually don't help with co-op cloud matrix deployments. You should probably prefer Option 2 (well known), but the possibility of SRV is explained below: Federation can also be delegated with a DNS `SRV` record on `SERVER_NAME` instead of well-known: ``` _matrix-fed._tcp.example.com. 3600 IN SRV 10 0 8448 matrix.example.com. # modern _matrix._tcp.example.com. 3600 IN SRV 10 0 8448 matrix.example.com. # deprecated, for older peers ``` The catch is TLS: on the SRV path a remote validates the certificate against **`SERVER_NAME`**, *not* the SRV target. This recipe's Traefik only issues a cert for **`DOMAIN`**, so: - **SRV → `DOMAIN`:443 fails** — the presented cert is for `DOMAIN`, but the peer requires one for `SERVER_NAME`. - **SRV → `SERVER_NAME`:443 collides** — Traefik routes TLS by SNI, and `Host(SERVER_NAME)` on `:443` is already owned by whatever apex site serves `SERVER_NAME`. - **SRV → `SERVER_NAME`:8448 works** — the Option 3 `matrix-federation` router holds a cert for `SERVER_NAME` — but that's just Option 3 made explicit (the `:8448` fallback already works with no SRV record). #### Verifying The canonical test: - https://federationtester.matrix.org/#YOUR_SERVER_NAME Or check the underlying paths directly. They should all return JSON: ```bash # Options 1 & 2 — delegation curl https://SERVER_NAME/.well-known/matrix/server # Option 3 — federation endpoint via 8448 curl https://SERVER_NAME:8448/_matrix/key/v2/server # Confirms Synapse itself is healthy (independent of the path remote servers use) curl https://DOMAIN/_matrix/key/v2/server ``` ### Getting client discovery on a custom domain Enable `compose.wellknown.yml` (see Option 2 above) — it serves `/.well-known/matrix/client` on `SERVER_NAME` too, so clients signing in as `@alice:example.com` auto-discover the homeserver. ### Matrix Authentication Service (MAS) [MAS](https://element-hq.github.io/matrix-authentication-service/) is Element’s OAuth/OIDC-native auth service for Matrix: it handles login, tokens, and upstream IdPs while Synapse delegates authentication via `matrix_authentication_service`. > [!IMPORTANT] > **If you plan to migrate an existing homeserver with `syn2mas`:** deploy and configure MAS as below, but **leave `MAS_ENABLED=1` commented** until migration and cutover are done, so Synapse keeps using your current login path until you intentionally switch. You cannot use Synapse legacy OIDC/Keycloak SSO alongside MAS; plan IdP apps and envs accordingly. **Enable the stack:** - In `.env`, uncomment `compose.mas.yml` (and `compose.mas-upstream.yml` plus upstream envs if you use an external IdP), and uncomment the `SECRET_MAS_*` version lines. - `abra app secret generate YOURAPPDOMAIN` - `abra app cmd -l YOURAPPDOMAIN generate_mas_signing_rsa` — generates and inserts the PEM RSA key for `SECRET_MAS_SIGNING_RSA_VERSION`. Requires `openssl` on the local machine. - `abra app cmd YOURAPPDOMAIN db ensure_mas_database` (once, creates the `mas` database in Postgres) - `abra app deploy YOURAPPDOMAIN`
Migrating an existing server (syn2mas) Requires PostgreSQL on Synapse and a dedicated MAS database. Backup Postgres (and configs) before you start. Official background: [MAS migration guide](https://element-hq.github.io/matrix-authentication-service/setup/migration.html). 1. **Prepare (Synapse still running):** With MAS in `COMPOSE_FILE` but **`MAS_ENABLED` still off**, deploy, then run checks from your machine: ```bash abra app cmd YOURAPPDOMAIN prepare_mas_migration ``` This fetches rendered `homeserver.yaml` into the MAS container, runs `syn2mas check`, then `migrate --dry-run` (the dry run rolls back MAS data at the end). The file stays in the MAS container until next restart, so you can repeat this step to provide the file for the actual migration. 2. **Optional snapshot:** save a copy of the rendered config while `app` is up, e.g. `abra app run -t YOURAPPDOMAIN app cat /data/homeserver.yaml > homeserver.snapshot.yaml`. 3. **Downtime — stop Synapse:** run on the **host** with Docker/Swarm access (not inside a container), e.g.: ```bash docker service scale _app=0 ``` Use the real service name from `docker service ls` (suffix `_app`). 4. **Migration:** with MAS still running and Synapse at zero replicas, run `run_mas_migration` from your machine. The homeserver snapshot at `/tmp/homeserver.yaml` in `mas` must still be present from step 1. ```bash abra app cmd YOURAPPDOMAIN run_mas_migration ``` 5. **Cutover:** in `.env`, set `MAS_ENABLED=1`, `PASSWORD_LOGIN_ENABLED=false`, remove legacy Keycloak/SSO envs, then `abra app deploy YOURAPPDOMAIN` (Synapse comes back with MAS delegation). `syn2mas` does not write to the Synapse database; if you abort before serving traffic through MAS, you can often drop and recreate the MAS DB and revert env.
## Bridges For all Bridges: - Setting it up is a bit of a chicken/egg & chasing cats moment. - Make sure to uncomment `APP_SERVICES_ENABLED`, `HOMESERVER_URL`, `HOMESERVER_DOMAIN`, `compose.shared_secret_auth.yml`, `SHARED_SECRET_AUTH_ENABLED` and `SECRET_SHARED_SECRET_AUTH_VERSION` - include the registration in synapse, e.g. `APP_SERVICE_CONFIGS="[\"/telegram-data/registration.yaml\"]"` - and set yourself as admin, e.g.: `TELEGRAM_BRIDGE_PERMISSIONS="{ \"*\": \"relaybot\", \"@akadmin:example.com\": \"admin\"}"` > [!IMPORTANT] > The shared secret authenticator may break when matrix-synapse uses a newer python version with an error stating something like "module not found". You have to fix the path in the compose.shared_secret_auth.yml like [here](https://git.coopcloud.tech/coop-cloud/matrix-synapse/commit/3d1350533079ce1ad3bea92039fe003684589b95) ### Telegram bridging You need to get your bot setup on the telegram side first by creating a [telegram app](https://my.telegram.org/apps) and a [telegram bot](https://docs.mau.fi/bridges/python/telegram/relay-bot.html#setup) and have these values: ``` api_id: ... api_hash: ... telegram_bot_token: ... ``` Experimental script for a automated token replacement: ``` DOMAIN= abra app secret insert $DOMAIN telegram_api_hash v1 abra app secret insert $DOMAIN telegram_bot_token v1 abra app secret generate -a $DOMAIN abra app deploy $DOMAIN abra app cmd -l $DOMAIN set_bridge_tokens telegram ``` Alternatively a manual guide for the necessary steps: ``` DOMAIN= abra app secret insert $DOMAIN telegram_api_hash v1 abra app secret insert $DOMAIN telegram_bot_token v1 abra app secret generate -a $DOMAIN abra app deploy $DOMAIN abra app run $DOMAIN telegrambridge cat /data/registration.yaml abra app undeploy $DOMAIN abra app secret rm $DOMAIN telegram_as_token abra app secret insert $DOMAIN telegram_as_token v1 abra app secret rm $DOMAIN telegram_hs_token abra app secret insert $DOMAIN telegram_hs_token v1 abra app deploy $DOMAIN ``` Some helpful documentation: - [`docs.mau.fi`](https://docs.mau.fi/bridges/python/setup/docker.html?bridge=telegram) - [`example-config.yaml`](https://mau.dev/mautrix/telegram/-/blob/master/mautrix_telegram/example-config.yaml) ### Discord bridging > WIP docs Just as messy as the Telegram bridging above! Rough guide: - get a local copy of [`config.yaml`](https://github.com/matrix-org/matrix-appservice-discord/blob/develop/config/config.sample.yaml) - fill it out with the values you need, all the discord token stuff, etc. - run `mkdir -p data && cp config.yaml data/` then `docker run --rm -v data:/data halfshot/matrix-appservice-discord:v1.0.0 sh -c "cd /data && node /build/src/discordas.js -r -u "http://discordbridge:9005" -c config.yaml"` - this generates the app service registration configuration you need to feed to the homeserver - run secret generation for the `discord_db_password`, insert your `discord_bot_token` - run `abra app cp discord-registration.yaml app:/discord-data` (it has to be called `discord-registration.yaml`) - deploy the bridge & happy hacking Some helpful documentation: - [`matrix-org/matrix-appservice-discord` docs](https://github.com/matrix-org/matrix-appservice-discord#bridging-a-room) - [`t2bot.io/discord`](https://t2bot.io/discord/) ### Signal bridging Experimental script for a more automated token replacement: ``` DOMAIN= abra app secret generate -a $DOMAIN abra app deploy $DOMAIN abra app cmd -l $DOMAIN set_bridge_tokens signal ``` Alternatively a manual guide for the necessary steps: ``` DOMAIN= abra app secret insert $DOMAIN signal_hs_token v1 foo abra app secret insert $DOMAIN signal_as_token v1 foo abra app secret generate $DOMAIN -a abra app deploy $DOMAIN abra app run $DOMAIN signalbridge cat /data/registration.yaml abra app secret rm $DOMAIN signal_as_token abra app secret insert $DOMAIN signal_as_token v1 abra app secret rm $DOMAIN signal_hs_token abra app secret insert $DOMAIN signal_hs_token v1 abra app deploy $DOMAIN ``` - message `@signalbot:example.com` to test - See the [docs](https://docs.mau.fi/bridges/go/signal/authentication.html) for authentication