diff --git a/.env.sample b/.env.sample index 4f4aa85..b559c3e 100644 --- a/.env.sample +++ b/.env.sample @@ -99,9 +99,15 @@ ENABLE_REGISTRATION=false #DISABLE_FEDERATION=1 +# SERVE_SERVER_WELLKNOWN only works if SERVER_NAME and DOMAIN are the same +# if they are different, then a different federation method is needed (like compose.wellknown.yml) # Set "true" to enable federation endpoint on $DOMAIN/.well-known/matrix/server SERVE_SERVER_WELLKNOWN=false +# Serve /.well-known/matrix/{server,client} on SERVER_NAME via Traefik. +# Can be used when SERVER_NAME != DOMAIN and SERVER_NAME is served by Traefik. +#COMPOSE_FILE="$COMPOSE_FILE:compose.wellknown.yml" + ALLOW_PUBLIC_ROOMS_FEDERATION=false ## Room auto-join diff --git a/README.md b/README.md index bb7e781..6cf71b6 100644 --- a/README.md +++ b/README.md @@ -39,11 +39,100 @@ ### Enabling federation -See [`#27`](https://git.coopcloud.tech/coop-cloud/matrix-synapse/pulls/27) for more. Depending on your setup, using `SERVE_SERVER_WELLKNOWN=true` might work to start federating. Make sure you don't leave `DISABLE_FEDERATION=1` set! +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 -You'll need to deploy something like [this](https://git.autonomic.zone/ruangrupa/well-known-uris). This could be implemented in this recipe but we haven't merged it in yet. Change sets are welcome. +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) diff --git a/compose.wellknown.yml b/compose.wellknown.yml new file mode 100644 index 0000000..8ae966c --- /dev/null +++ b/compose.wellknown.yml @@ -0,0 +1,24 @@ +--- +version: "3.8" + +# Serve /.well-known/matrix/{server,client} on SERVER_NAME via Traefik, routed to +# the matrix nginx (`web`) — so server/client delegation works without hand-placing +# files on whatever else hosts SERVER_NAME. +# +# Enable when SERVER_NAME != DOMAIN (users are @alice:example.com, Synapse runs at +# matrix.example.com). The PathPrefix rule is more specific than a bare Host() +# router, and the explicit high priority guarantees it wins over any apex website +# that also serves Host(SERVER_NAME) — so the two coexist, the apex site keeps +# serving everything except /.well-known/matrix. +# +# Requires SERVER_NAME to resolve to this Traefik so ACME can issue its cert. +services: + web: + deploy: + labels: + - "traefik.http.routers.${STACK_NAME}-wellknown.rule=Host(`${SERVER_NAME}`) && PathPrefix(`/.well-known/matrix`)" + - "traefik.http.routers.${STACK_NAME}-wellknown.entrypoints=web-secure" + - "traefik.http.routers.${STACK_NAME}-wellknown.tls=true" + - "traefik.http.routers.${STACK_NAME}-wellknown.tls.certresolver=${LETS_ENCRYPT_ENV}" + - "traefik.http.routers.${STACK_NAME}-wellknown.service=${STACK_NAME}" + - "traefik.http.routers.${STACK_NAME}-wellknown.priority=1000" diff --git a/compose.yml b/compose.yml index decd73a..54c2d63 100644 --- a/compose.yml +++ b/compose.yml @@ -30,6 +30,11 @@ services: - "traefik.http.routers.${STACK_NAME}.rule=Host(`${DOMAIN}`)" - "traefik.http.routers.${STACK_NAME}.entrypoints=web-secure" - "traefik.http.routers.${STACK_NAME}.tls.certresolver=${LETS_ENCRYPT_ENV}" + - "traefik.http.routers.${STACK_NAME}-federation.rule=Host(`${SERVER_NAME}`)" + - "traefik.http.routers.${STACK_NAME}-federation.entrypoints=matrix-federation" + - "traefik.http.routers.${STACK_NAME}-federation.tls=true" + - "traefik.http.routers.${STACK_NAME}-federation.tls.certresolver=${LETS_ENCRYPT_ENV}" + - "traefik.http.routers.${STACK_NAME}-federation.service=${STACK_NAME}" healthcheck: test: curl -f http://${STACK_NAME}_app:8008/health || exit 1 interval: 30s