mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 02:54:26 +00:00
045ef095f2 taught the generic redirect vhost a Matrix location and let
web-app-matrix include web-opt-rdr-domains for its server name on its
own. The first put one app's contract into a shared role and made every
redirect source (www, aliases, asset, css) advertise the Matrix
delegation; the second bypassed current_play_redirect_domains and could
overwrite a vhost the server name already had, such as the Synapse one.
Every redirect vhost now serves `/.well-known/` from `<well-known>/<domain>/`
and hands a missing file or the bare directory to `location /` via
`error_page 403 404 = /`, so it still answers 301 with the original
request URI. web-app-matrix only writes its delegation file into the
directory of its server name.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
246 lines
15 KiB
Markdown
246 lines
15 KiB
Markdown
# Matrix
|
|
|
|
## Description
|
|
|
|
Step into the future of communication with Matrix, a dynamic and decentralized platform that delivers secure, real-time messaging and collaboration. With robust federation, end-to-end encryption, and versatile bridging support, Matrix enables seamless connections across diverse networks while safeguarding your data.
|
|
|
|
## Overview
|
|
|
|
This role deploys a Matrix homeserver and the Element web client. Two deployment flavors are supported, selected via `services.matrix.flavor`:
|
|
|
|
- `ansible` (default) wraps [matrix-docker-ansible-deploy](https://github.com/spantaleev/matrix-docker-ansible-deploy) (MDAD). MDAD runs ~70 upstream sub-roles to deliver Synapse, Element, every supported bridge, Element Call, and Jitsi. Infinito.Nexus pins MDAD by commit, renders an MDAD inventory from `services.matrix.*` knobs, and disables MDAD's bundled Traefik / Postgres / Redis so the central Infinito services back the stack.
|
|
- `compose` is the in-repo Docker Compose stack. It remains installable but receives no new features.
|
|
|
|
The compose flavor is **deprecated**. New deployments should use the ansible flavor.
|
|
|
|
## Cosmos
|
|
|
|
The diagram places Matrix in the Infinito.Nexus cosmos: the components it deploys (capabilities), the central services it consumes (dependencies), and its outward reach (federation and bridged external networks).
|
|
|
|
```mermaid
|
|
flowchart LR
|
|
subgraph deps [Dependencies]
|
|
dep_svc_ai_litellm["svc-ai-litellm 🐳🐝"]
|
|
dep_svc_bkp_volume_2_local["svc-bkp-volume-2-local 💻"]
|
|
dep_svc_db_openldap["svc-db-openldap 🐳🐝"]
|
|
dep_svc_db_postgres["svc-db-postgres 🐳🐝"]
|
|
dep_svc_net_tor["svc-net-tor 🐳🐝"]
|
|
dep_web_app_dashboard["web-app-dashboard 🐳🐝"]
|
|
dep_web_app_jitsi["web-app-jitsi 🐳🐝"]
|
|
dep_web_app_keycloak["web-app-keycloak 🐳🐝"]
|
|
dep_web_app_matomo["web-app-matomo 🐳🐝"]
|
|
dep_web_app_prometheus["web-app-prometheus 🐳🐝"]
|
|
dep_web_app_seaweedfs["web-app-seaweedfs 🐳🐝"]
|
|
dep_web_app_stalwart["web-app-stalwart 🐳🐝"]
|
|
dep_web_svc_coturn["web-svc-coturn 🐳🐝"]
|
|
dep_web_svc_css["web-svc-css 💻"]
|
|
dep_web_svc_logout["web-svc-logout 🐳🐝"]
|
|
end
|
|
subgraph role [web-app-matrix 🐳🐝]
|
|
svc_litellm["litellm"]
|
|
svc_sso["sso"]
|
|
svc_ldap["ldap"]
|
|
svc_logout["logout"]
|
|
svc_dashboard["dashboard"]
|
|
svc_matomo["matomo"]
|
|
svc_email["email"]
|
|
svc_coturn["coturn"]
|
|
svc_jitsi["jitsi"]
|
|
svc_livekit["livekit"]
|
|
svc_livekit_jwt["livekit_jwt"]
|
|
svc_postgres["postgres"]
|
|
svc_synapse["synapse"]
|
|
svc_element["element"]
|
|
svc_matrix_chatgpt_bot["matrix-chatgpt-bot"]
|
|
svc_seaweedfs["seaweedfs"]
|
|
svc_css["css"]
|
|
svc_prometheus["prometheus"]
|
|
svc_matrix["matrix"]
|
|
svc_tor["tor"]
|
|
svc_container_backup["container_backup"]
|
|
end
|
|
subgraph dependents [Dependents]
|
|
dpt_web_app_nextcloud["web-app-nextcloud 🐳🐝"]
|
|
end
|
|
dep_svc_ai_litellm -. "0..1" .-> svc_litellm
|
|
dep_svc_bkp_volume_2_local -. "0..1" .-> svc_container_backup
|
|
dep_svc_db_openldap -. "0..1" .-> svc_ldap
|
|
dep_svc_db_postgres -. "0..1" .-> svc_postgres
|
|
dep_svc_net_tor -. "0..1" .-> svc_tor
|
|
dep_web_app_dashboard -. "0..1" .-> svc_dashboard
|
|
dep_web_app_jitsi -. "0..1" .-> svc_jitsi
|
|
dep_web_app_keycloak -. "0..1" .-> svc_sso
|
|
dep_web_app_matomo -. "0..1" .-> svc_matomo
|
|
dep_web_app_prometheus -. "0..1" .-> svc_prometheus
|
|
dep_web_app_seaweedfs -. "0..1" .-> svc_seaweedfs
|
|
dep_web_app_stalwart -. "0..1" .-> svc_email
|
|
dep_web_svc_coturn -. "0..1" .-> svc_coturn
|
|
dep_web_svc_css -. "0..1" .-> svc_css
|
|
dep_web_svc_logout -. "0..1" .-> svc_logout
|
|
svc_litellm -. "0..1" .-> dpt_web_app_nextcloud
|
|
```
|
|
|
|
Solid `1:1` edges are fixed relationships; dashed `0..1` edges are conditional (enabled only in matching deployments); red `0..0` edges are turned off in this role. Node markers show the role's deploy modes (💻 host, 🐳 compose, 🐝 swarm); ❌ marks a service that is explicitly turned off, and ⚙️ an Ansible role dependency declared in `meta/main.yml`.
|
|
|
|
## Features
|
|
|
|
- **Decentralized and Federated:** Connect with a global network of Matrix homeservers, ensuring there is no single point of failure.
|
|
- **End-to-End Encryption:** Protect your communications with robust encryption mechanisms to keep your messages private.
|
|
- **Interoperability:** Bridge communications with external platforms (Signal, Telegram, Slack, IRC, Discord, Gitter, Twitter, and more) through MDAD-managed appservices.
|
|
- **Scalable Architecture:** Designed to handle increasing user loads and message volumes with high performance.
|
|
- **Flexible Client Support:** Access Matrix services via modern web clients like Element, plus integrated Element Call and Jitsi video conferencing.
|
|
|
|
## Quick Setup
|
|
|
|
### Development
|
|
|
|
Clone, set up the workstation, and deploy Matrix onto the local stack:
|
|
|
|
```bash
|
|
git clone https://github.com/infinito-nexus/core.git
|
|
cd core
|
|
make onboard
|
|
make compose-deploy mode=reinstall apps=web-app-matrix full_cycle=false
|
|
```
|
|
|
|
### Production
|
|
|
|
Run the published image to provision the inventory and deploy Matrix to a managed server (the mounted volume persists the inventory):
|
|
|
|
```bash
|
|
APP=web-app-matrix
|
|
HOST=<your-server>
|
|
DOMAIN=<your-domain>
|
|
TLS_MODE=self_signed
|
|
SSH_PUBLIC_KEY="<your-ssh-public-key>"
|
|
|
|
docker run --rm -it \
|
|
-v "$PWD/inventories:/etc/infinito.nexus/inventories" \
|
|
-e APP="$APP" -e HOST="$HOST" -e DOMAIN="$DOMAIN" -e TLS_MODE="$TLS_MODE" -e SSH_PUBLIC_KEY="$SSH_PUBLIC_KEY" \
|
|
ghcr.io/infinito-nexus/core/debian bash -c '
|
|
INVENTORY=/etc/infinito.nexus/inventories/production
|
|
infinito administration inventory provision "$INVENTORY" \
|
|
--inventory-file "$INVENTORY/devices.yml" \
|
|
--host "$HOST" \
|
|
--include "$APP" \
|
|
--vars "{\"TLS_MODE\": \"$TLS_MODE\", \"DOMAIN_PRIMARY\": \"$DOMAIN\", \"users\": {\"administrator\": {\"authorized_keys\": [\"$SSH_PUBLIC_KEY\"]}}}" &&
|
|
infinito administration deploy dedicated "$INVENTORY/devices.yml" \
|
|
--password-file "$INVENTORY/.password" \
|
|
--diff -vv'
|
|
```
|
|
|
|
## Flavors
|
|
|
|
### `ansible` (default)
|
|
|
|
Pinned MDAD upstream lives at `services.matrix.upstream.{repo,ref}`. To bump:
|
|
|
|
1. Update `services.matrix.upstream.ref` in `meta/services.yml` to a newer MDAD commit.
|
|
2. Run `make compose-deploy mode=reinstall apps=web-app-matrix full_cycle=true variant=0`.
|
|
3. Iterate `mode=update` for follow-up adjustments.
|
|
|
|
MDAD knobs are rendered from `templates/flavor/ansible/vars.yml.j2`. Central-service consumers (postgres, redis, mailu, keycloak, openldap) are wired automatically when their `services.<name>.enabled` toggles are true.
|
|
|
|
**Runner-container encapsulation.** MDAD's `ansible-playbook setup.yml` invocation runs inside a dedicated sub-container (image: `services.matrix.runner.image`, built from `files/flavor/ansible/runner/Dockerfile`). This isolates MDAD's Ansible runtime from the Infinito-Nexus deploy container's Ansible, so MDAD can pin its own `ansible-core` / `community.docker` / `community.general` versions without colliding with Infinito's. The runner mounts:
|
|
|
|
- `/var/run/docker.sock`, so MDAD-launched containers land in the same Docker daemon as the rest of the stack
|
|
- `/etc/systemd/system`, `/run/systemd`, so MDAD's `systemctl` calls target the deploy container's systemd
|
|
- `/matrix`, MDAD's data and config root
|
|
- the MDAD checkout at `{{ MATRIX_MDAD_DIR }}`, a read-write working copy
|
|
|
|
`/usr/local/bin` is NOT bind-mounted, even though MDAD writes a few helper scripts there. The runner's `ansible-core` lives at `/opt/ansible/bin/` (out of the way) to keep the option open, but mounting the deploy container's `/usr/local/bin` over the runner causes a Python ABI mismatch segfault (host Python is built differently from `python:3.13-slim`). MDAD's host-side helper scripts are not invoked by the deploy flow.
|
|
|
|
A `mount --make-rshared /` runs once in the deploy container before the runner starts, because MDAD's systemd units bind-mount `/matrix/synapse/storage` with `bind-propagation=slave`, which requires shared/slave propagation on the source.
|
|
|
|
**Federation.** Synapse serves the `client` and `federation` resources on one listener. OpenResty serves the Synapse domain on 443 and, as `default_server`, on `services.synapse.ports.public.federation`, and the role writes `/.well-known/matrix/server` into the well-known directory of its server name, which the redirect vhost of that domain (`web-opt-rdr-domains`) serves, so server discovery delegates to the Synapse domain on 443. The `<server_name>:8448` fallback only answers where OpenResty runs in the host network (compose); in swarm OpenResty publishes 80 and 443 only.
|
|
|
|
**Registry proxy.** When the stack's docker daemon pulls through a registry proxy (the local registry cache), the runner's nested daemon uses the same proxy via a `docker.service` drop-in and, when the outer daemon trusts the cache's CA, fetches that CA from the proxy before it starts.
|
|
|
|
**Nested host network.** Synapse, Element, coturn, LiveKit, its JWT service and Heisenbridge run in the host network of the nested Docker daemon, so they address each other over `127.0.0.1`; Heisenbridge's homeserver URL and its appservice URL in the registration are set accordingly. Jitsi keeps MDAD's own `matrix-jitsi` network and publishes its web port to the runner.
|
|
|
|
### `compose` (deprecated)
|
|
|
|
Set `services.matrix.flavor: compose` to opt in. Tasks live under `tasks/flavor/compose/`, templates under `templates/flavor/compose/`. Existing volumes and credentials are reused, so in-place deploys need no migration.
|
|
|
|
## Schema
|
|
|
|
The swarm deploy chain across both flavors:
|
|
|
|
```mermaid
|
|
flowchart TD
|
|
subgraph node["swarm manager node"]
|
|
prx["openresty<br/>(443 host-published)"]
|
|
dnsm["node resolver<br/>*.domain → 127.0.0.1"]
|
|
subgraph runner["MDAD runner container (ansible flavor)<br/>privileged, nested docker"]
|
|
syn["synapse<br/>(host-netted in nested docker)"]
|
|
end
|
|
stack["matrix stack services (compose flavor)<br/>matrix_synapse, matrix_element, mautrix-*"]
|
|
end
|
|
kc["keycloak_keycloak<br/>+ one-shot realm import job"]
|
|
|
|
role["web-app-matrix tasks"] -- "renders config/mautrix/*/{config,registration}.yaml<br/>and compose.yml, notify: swarm deploy" --> flush["meta: flush_handlers<br/>(before the Synapse wait)"]
|
|
flush -- "docker stack deploy<br/>(both files as secrets)" --> stack
|
|
|
|
syn -- "OIDC discovery via<br/>--add-host issuer → subnet .1" --> prx
|
|
prx -- "vhost auth.*" --> kc
|
|
dnsm -. "127.0.0.1 only valid<br/>for host processes" .-> node
|
|
|
|
verify["CI verifier (03_wait_converge.sh)"] -- "workload: node-local →<br/>skip service poll" --> role
|
|
```
|
|
|
|
## Bridge matrix
|
|
|
|
Set per-bridge flags under `services.matrix.plugins.<bridge>: true|false` in `meta/services.yml`. Every bridge defaults to OFF except `heisenbridge`; `chatgpt` follows whether `svc-ai-litellm` is part of the deployment.
|
|
|
|
The ansible flavor maps each true flag to the matching `matrix_<bridge>_enabled` MDAD var; the compose flavor builds the `MATRIX_BRIDGES` loop from the enabled `mechanism: bridge` addons (see Addons below).
|
|
|
|
## Addons
|
|
|
|
The mautrix network bridges are declared in
|
|
[`meta/addons/`](./meta/addons/) as `mechanism: bridge` addons
|
|
(requirement 026, Decision 13). Each is `required: false` and **disabled by default**; its
|
|
per-network DB password and its appservice `as_token`/`hs_token` pair are referenced from
|
|
[`meta/secrets.yml`](./meta/secrets.yml) `credentials:`, never inlined. The role renders each
|
|
bridge's `config.yaml` and the matching Synapse `registration.yaml` from the same tokens.
|
|
|
|
| Addon | Mechanism | Default state | Bridges |
|
|
|-------|-----------|---------------|---------|
|
|
| `mautrix-whatsapp` | `bridge` | disabled | external network |
|
|
| `mautrix-telegram` | `bridge` | disabled | external network |
|
|
| `mautrix-signal` | `bridge` | disabled | external network |
|
|
| `mautrix-slack` | `bridge` | disabled | external network |
|
|
| `mautrix-meta` | `bridge` | disabled | external network |
|
|
|
|
The compose flavor derives `MATRIX_BRIDGES` from the enabled bridge addons'
|
|
`config:` blocks; the enabled/disabled split is exercised by the compose
|
|
variant in [`meta/variants.yml`](./meta/variants.yml). Coverage is via
|
|
`test-bridge-roster.js`.
|
|
|
|
## Conferencing
|
|
|
|
Conferencing applies to the ansible flavor.
|
|
|
|
- `services.matrix.conferencing.element_call` (default true) runs MDAD's Matrix RTC stack. LiveKit and its JWT service answer on the Synapse domain under `/livekit-server/` and `/livekit-jwt-service/`, ICE uses `services.livekit.ports.public.media` over TCP and UDP, and Synapse publishes the LiveKit transport through its MSC4143 RTC transports API. The JWT service checks each OpenID token against the user's homeserver over federation, so it needs the federation path above; with a self-signed CA it mounts the CA bundle as Synapse does.
|
|
- `services.jitsi` selects the Jitsi Element offers; it is enabled in the ansible flavor off tor nodes. With `enabled` and `shared` (the default when `web-app-jitsi` is in the inventory) Element points at that role's domain. With `enabled` and `shared: false` MDAD runs its own Jitsi on the `jitsi` domain of [`meta/domains.yml`](./meta/domains.yml), with its videobridge on `services.jitsi.ports.public.media` over UDP. The role derives the Jicofo and JVB passwords and recreates both Prosody accounts on every run, because Prosody keeps an account's first password and would otherwise lock both components out after a credential rotation.
|
|
|
|
## Playwright specs
|
|
|
|
In addition to the persona and CSP specs:
|
|
|
|
- `test-element-call.js` opens the Element home view and verifies a call-widget control surfaces when `services.matrix.conferencing.element_call=true` (skipped otherwise).
|
|
- `test-bridge-roster.js` probes the Synapse `/_matrix/client/v3/profile/{bot}` endpoint for every enabled bridge and fails on 5xx (skipped when no bridge is enabled).
|
|
- `test-matrix-rtc.js` reads the LiveKit transport from Synapse, exchanges an OpenID token at the JWT service and opens the SFU signalling websocket (ansible flavor, `element_call`).
|
|
- `test-jitsi.js` checks that Element offers the deployment's Jitsi and joins a conference there (`jitsi` enabled).
|
|
- `test-heisenbridge.js` opens a DM with the Heisenbridge bot and waits for its answer to `help` (ansible flavor, `heisenbridge`).
|
|
|
|
## Further Resources
|
|
|
|
- [Matrix Official Website](https://matrix.org/)
|
|
- [Matrix Documentation](https://matrix.org/docs/)
|
|
|
|
## Credits
|
|
|
|
Implemented by **[Kevin Veen-Birkenbach](https://www.veen.world)**.
|
|
Part of the [Infinito.Nexus Project](https://s.infinito.nexus/code) and maintained by [Kevin Veen-Birkenbach](https://www.veen.world).
|
|
Licensed under the [Infinito.Nexus Community License (Non-Commercial)](https://s.infinito.nexus/license).
|