327 lines
18 KiB
Markdown
327 lines
18 KiB
Markdown
# MISSING_MATRIX_RTC_TRANSPORT in ESS 26.4.0: Ursachenanalyse und vollständiger Fix
|
||
|
||
**Die Ursache ist Hypothese 1: `wellKnownDelegation.enabled: false` ist der Kill-Switch.** In genau dieser Konfiguration schreibt das ESS-Chart den Schlüssel `org.matrix.msc4143.rtc_foci` normalerweise automatisch in das an `https://axion1337.chat/.well-known/matrix/client` ausgelieferte JSON-Dokument – sobald `wellKnownDelegation.enabled: true` **und** `matrixRTC.enabled: true` gesetzt sind. Mit `wellKnownDelegation: false` fällt dieses Dokument komplett weg. Element Web hat damit keinen Discovery-Pfad zur LiveKit-Instanz auf `mrtc.axion1337.chat`, und Matrix-JS-SDK wirft genau deshalb `MISSING_MATRIX_RTC_TRANSPORT`. Hypothese 2 (Synapse-Custom-Keys sind bogus) ist ebenfalls korrekt. Hypothese 4 (Element Web braucht explizites `rtc_foci`) ist **falsch** – Element Web liest `rtc_foci` ausschließlich aus Well-Known, niemals aus `config.json`. Hypothese 5 ist ein *sekundäres* Thema: LiveKit UDP-Ports müssen offen sein, aber der Fehler `MISSING_MATRIX_RTC_TRANSPORT` entsteht ausschließlich aus der Discovery, nicht aus Medien-Erreichbarkeit.
|
||
|
||
## Wie die Fehlerkette konkret abläuft
|
||
|
||
Element Web ruft beim Start eines Anrufs intern zwei Discovery-Pfade auf: `GET /_matrix/client/v1/rtc/transports` gegen Synapse (MSC4143, ab Synapse 1.140) und als Fallback `GET https://<serverName>/.well-known/matrix/client`, wo der Schlüssel `org.matrix.msc4143.rtc_foci` erwartet wird. Das ESS-Chart befüllt **beide** Pfade automatisch, sobald `matrixRTC.enabled: true` ist: In `wellKnownDelegation` wird das `rtc_foci`-Array injiziert, in Synapse wird der `matrix_rtc.transports`-Block in die `homeserver.yaml` gemerged (chart-intern, siehe ess-helm Changelog Eintrag #855: *"Configure experimental MSC4143 advertisement in Synapse when MatrixRTC is enabled. This is in addition to the MSC4143 advertisement on the client well-known endpoint for now, but it is expected to replace it in time."*). **Beide Mechanismen werden durch `wellKnownDelegation.enabled: false` nicht vollständig deaktiviert** – die Synapse-Seite wird zwar weiter gesetzt, aber weil kein Well-Known am Apex existiert, kann Element Web den Homeserver gar nicht über `default_server_name` auflösen und daher auch den Transport-Endpunkt nicht konsistent nutzen. Resultat: leere Transport-Liste, Fehler ausgelöst.
|
||
|
||
Ein korrektes `/.well-known/matrix/client` sieht so aus (das ist exakt, was das Chart ausliefern würde):
|
||
|
||
```json
|
||
{
|
||
"m.homeserver": { "base_url": "https://matrix.axion1337.chat" },
|
||
"m.identity_server": { "base_url": "https://vector.im" },
|
||
"org.matrix.msc4143.rtc_foci": [
|
||
{ "type": "livekit",
|
||
"livekit_service_url": "https://mrtc.axion1337.chat" }
|
||
]
|
||
}
|
||
```
|
||
|
||
Der Schlüsselname ist **`org.matrix.msc4143.rtc_foci`** (MSC-Prefix, kein `m.rtc_foci`). Das Value-Objekt hat zwingend `type: "livekit"` und `livekit_service_url: "https://<matrixRTC.ingress.host>"`.
|
||
|
||
## Warum die Custom-Synapse-Values aktuell wirkungslos sind
|
||
|
||
Die Keys `matrix_rtc_enabled` und `matrix_rtc_uri` **existieren in Synapse nicht**. Synapse kennt für MatrixRTC genau zwei Dinge: einen `experimental_features`-Block (`msc3266_enabled`, `msc4140_enabled`, `msc4222_enabled`, und implementierungsabhängig `msc4143_enabled`) und einen Top-Level-Block `matrix_rtc.transports:` für den neuen `/v1/rtc/transports`-Endpunkt. Das ESS-Chart setzt diese **automatisch**, sobald `matrixRTC.enabled: true` ist. Synapse ignoriert unbekannte Top-Level-Keys in der `homeserver.yaml` stillschweigend; der Container crasht nicht, aber es passiert auch exakt gar nichts. Der ganze `rtc-config`-Block muss raus. Der `url-previews`-Block ist dagegen legitim – die Keys `url_preview_enabled`, `url_preview_ip_range_blacklist`, `max_spider_size` sind echte Synapse-Optionen.
|
||
|
||
## Der eigentliche Grund für die "ACME Race Condition"
|
||
|
||
Das Problem war **nicht** ein Bug, sondern eine vorhersagbare Fehlkonfiguration: Beide Ingresses (Element Web und wellKnownDelegation) forderten über die `cert-manager.io/cluster-issuer`-Annotation **jeweils ein eigenes TLS-Secret** für denselben Host `axion1337.chat` an. Cert-manager erzeugt dann zwei `Certificate`-Objekte mit unterschiedlichen `secretName`s → zwei `Order`-Objekte → HTTP-01-Solver-Ingresses überschreiben sich gegenseitig → Let's-Encrypt-Rate-Limit schlägt zu (5 duplicate certs / 7 Tage). Siehe cert-manager Issue #2342 und Discussion #3511: *"If both ingresses have their own secret to save the certificate tls, cert-manager runs havoc and exhausts the ACME limit very quickly."* Die saubere Lösung ist, dass genau **eine** Zertifikat-Quelle für `axion1337.chat` existiert.
|
||
|
||
Das ESS-Chart selbst erwartet, dass `elementWeb.ingress.host` **nicht** gleich `serverName` ist. Der Standard-Pattern ist: Element Web auf `chat.<apex>` bzw. `app.<apex>`, Well-Known auf dem Apex, und eine optionale `baseDomainRedirect.url` leitet `/` auf dem Apex zu Element Web weiter.
|
||
|
||
## Fix 1 (empfohlen): Element Web auf Subdomain umziehen
|
||
|
||
Das ist die Lösung, für die das Chart designt ist, und erfordert genau eine DNS-Änderung sowie YAML-Anpassungen.
|
||
|
||
### DNS-Voraussetzungen
|
||
|
||
| Record | Typ | Ziel | Zweck |
|
||
|---|---|---|---|
|
||
| `axion1337.chat` | A/AAAA | öffentliche IP des K3s-Nodes | Apex: Well-Known + Cert für Matrix-Server-Delegation |
|
||
| `matrix.axion1337.chat` | A/AAAA | K3s-Node-IP | Synapse |
|
||
| `account.axion1337.chat` | A/AAAA | K3s-Node-IP | Matrix Authentication Service |
|
||
| `mrtc.axion1337.chat` | A/AAAA | K3s-Node-IP | LiveKit-Signaling + lk-jwt-service |
|
||
| `chat.axion1337.chat` | A/AAAA | K3s-Node-IP | **NEU**: Element Web |
|
||
| `admin.axion1337.chat` | A/AAAA | K3s-Node-IP | Element Admin |
|
||
|
||
### Angepasster HelmRelease (`apps/production/element-server-suite.yaml`)
|
||
|
||
```yaml
|
||
apiVersion: helm.toolkit.fluxcd.io/v2
|
||
kind: HelmRelease
|
||
metadata:
|
||
name: matrix-stack
|
||
namespace: matrix
|
||
spec:
|
||
interval: 1h
|
||
chart:
|
||
spec:
|
||
chart: matrix-stack
|
||
version: "26.4.0"
|
||
sourceRef:
|
||
kind: HelmRepository
|
||
name: element-ess-oci
|
||
namespace: flux-system
|
||
valuesFrom:
|
||
- kind: ConfigMap
|
||
name: ess-synapse-custom
|
||
valuesKey: values.yaml
|
||
- kind: ConfigMap
|
||
name: ess-element-custom
|
||
valuesKey: values.yaml
|
||
- kind: Secret
|
||
name: ess-mas-values-secret
|
||
valuesKey: values.yaml
|
||
values:
|
||
serverName: axion1337.chat
|
||
certManager:
|
||
clusterIssuer: letsencrypt-prod
|
||
|
||
postgres:
|
||
enabled: true
|
||
|
||
synapse:
|
||
enabled: true
|
||
ingress:
|
||
host: matrix.axion1337.chat
|
||
|
||
matrixAuthenticationService:
|
||
enabled: true
|
||
ingress:
|
||
host: account.axion1337.chat
|
||
|
||
matrixRTC:
|
||
enabled: true
|
||
ingress:
|
||
host: mrtc.axion1337.chat
|
||
# SFU public-IP override empfohlen, falls der Node hinter NAT steht
|
||
# und STUN die falsche Adresse zurückgibt:
|
||
# sfu:
|
||
# useStunToDiscoverPublicIP: false
|
||
# manualIP: "<public-IP-of-node>"
|
||
|
||
elementWeb:
|
||
enabled: true
|
||
ingress:
|
||
host: chat.axion1337.chat # <- NICHT mehr der Apex
|
||
|
||
elementAdmin:
|
||
enabled: true
|
||
ingress:
|
||
host: admin.axion1337.chat
|
||
|
||
wellKnownDelegation:
|
||
enabled: true # <- zurück auf true (Default)
|
||
# Optional: Redirect vom Apex "/" auf Element Web.
|
||
# Das Chart-Feature kam in 26.x hinzu; falls dein konkreter Wert-Key
|
||
# fehlt, siehe Fix 2 (eigene IngressRoute) als sichere Alternative.
|
||
baseDomainRedirect:
|
||
url: https://chat.axion1337.chat
|
||
```
|
||
|
||
Mit diesem Setup beansprucht **genau ein** Chart-erzeugter Ingress den Apex `axion1337.chat`, nämlich der der `wellKnownDelegation`. Es gibt nur ein Zertifikat, keine Race Condition. Element Web läuft auf `chat.axion1337.chat` und bekommt sein eigenes, konfliktfreies Cert.
|
||
|
||
### Bereinigter Synapse-ConfigMap (`apps/production/custom-configs/synapse-values.yaml`)
|
||
|
||
```yaml
|
||
apiVersion: v1
|
||
kind: ConfigMap
|
||
metadata:
|
||
name: ess-synapse-custom
|
||
namespace: matrix
|
||
data:
|
||
values.yaml: |
|
||
synapse:
|
||
logging:
|
||
rootLevel: INFO
|
||
levelOverrides:
|
||
synapse.media.url_previewer: DEBUG
|
||
additional:
|
||
# RTC-Block ENTFERNT: matrix_rtc_enabled / matrix_rtc_uri sind keine
|
||
# gültigen Synapse-Keys. Das ESS-Chart setzt matrix_rtc.transports und
|
||
# die nötigen experimental_features automatisch, wenn matrixRTC.enabled=true.
|
||
1-url-previews:
|
||
config: |
|
||
url_preview_enabled: true
|
||
url_preview_ip_range_blacklist:
|
||
- '127.0.0.0/8'
|
||
- '10.0.0.0/8'
|
||
- '172.16.0.0/12'
|
||
- '192.168.0.0/16'
|
||
- '100.64.0.0/10'
|
||
- '192.0.0.0/24'
|
||
- '169.254.0.0/16'
|
||
- '192.88.99.0/24'
|
||
- '198.18.0.0/15'
|
||
- '192.0.2.0/24'
|
||
- '198.51.100.0/24'
|
||
- '203.0.113.0/24'
|
||
- '224.0.0.0/4'
|
||
- '::1/128'
|
||
- 'fe80::/10'
|
||
- 'fc00::/7'
|
||
- '2001:db8::/32'
|
||
- 'ff00::/8'
|
||
- 'fec0::/10'
|
||
max_spider_size: 10M
|
||
```
|
||
|
||
Wichtige Details zum `additional`-Mechanismus: die Keys werden **alphabetisch sortiert** gemerged, daher die `1-`/`2-`-Namenskonvention. Arrays werden ersetzt, nicht zusammengeführt.
|
||
|
||
### Element-Web-ConfigMap (`apps/production/custom-configs/element-values.yaml`)
|
||
|
||
Hier ist **keine** RTC-spezifische Änderung nötig. Element Web hat gemäß `element-web/docs/config.md` keinen `rtc_foci`-Key und entdeckt den Transport ausschließlich über Well-Known. Der optionale `element_call`-Block steuert nur Branding und Verhalten:
|
||
|
||
```yaml
|
||
apiVersion: v1
|
||
kind: ConfigMap
|
||
metadata:
|
||
name: ess-element-custom
|
||
namespace: matrix
|
||
data:
|
||
values.yaml: |
|
||
elementWeb:
|
||
additional:
|
||
config.json: |
|
||
{
|
||
"brand": "aXion1337.Chat",
|
||
"element_call": {
|
||
"brand": "aXion1337 Call",
|
||
"use_exclusively": true
|
||
},
|
||
"features": {
|
||
"feature_video_rooms": true,
|
||
"feature_element_call_video_rooms": true
|
||
},
|
||
"show_labs_settings": true
|
||
}
|
||
```
|
||
|
||
`use_exclusively: true` entfernt die Legacy-Jitsi-Option aus dem UI, sodass Anrufe garantiert über MatrixRTC/LiveKit laufen.
|
||
|
||
## Fix 2 (Alternative): Apex manuell splitten per Traefik IngressRoute
|
||
|
||
Falls `baseDomainRedirect` in deiner 26.4.0-Revision nicht greift oder du Element Web auf dem Apex behalten willst, ersetze beide Chart-Ingresses durch eine **einzige Traefik IngressRoute mit einem einzigen Certificate**. Dadurch sieht cert-manager nur noch eine Quelle für `axion1337.chat`, und Traefik routet Pfad-basiert.
|
||
|
||
```yaml
|
||
# apps/production/apex-ingress.yaml
|
||
apiVersion: cert-manager.io/v1
|
||
kind: Certificate
|
||
metadata:
|
||
name: axion-apex-tls
|
||
namespace: matrix
|
||
spec:
|
||
secretName: axion-apex-tls
|
||
issuerRef:
|
||
name: letsencrypt-prod
|
||
kind: ClusterIssuer
|
||
dnsNames:
|
||
- axion1337.chat
|
||
---
|
||
apiVersion: traefik.io/v1alpha1
|
||
kind: IngressRoute
|
||
metadata:
|
||
name: axion-apex
|
||
namespace: matrix
|
||
spec:
|
||
entryPoints: [websecure]
|
||
tls:
|
||
secretName: axion-apex-tls
|
||
routes:
|
||
# Höchste Priorität: /.well-known/matrix/* -> wellKnownDelegation-Service
|
||
- match: Host(`axion1337.chat`) && PathPrefix(`/.well-known/matrix`)
|
||
kind: Rule
|
||
priority: 100
|
||
services:
|
||
- name: matrix-stack-well-known-delegation # ggf. Service-Namen mit `kubectl get svc -n matrix` verifizieren
|
||
port: 8080
|
||
# Niedrigere Priorität: alles andere -> Element Web
|
||
- match: Host(`axion1337.chat`)
|
||
kind: Rule
|
||
priority: 10
|
||
services:
|
||
- name: matrix-stack-element-web
|
||
port: 8080
|
||
```
|
||
|
||
Dazu muss in der HelmRelease die automatische Chart-Ingress-Erzeugung für diese beiden Komponenten unterbunden werden – setze `wellKnownDelegation.ingress.className: "none"` bzw. `elementWeb.ingress.className: "none"` oder setze die `host`-Werte auf Dummy-Hostnames, sodass die Chart-Ingresses nicht denselben Apex beanspruchen. Fix 1 ist wegen geringerer Komplexität klar zu bevorzugen.
|
||
|
||
## Netzwerk- und Firewall-Anforderungen für LiveKit
|
||
|
||
**Das ist der nächste Stolperstein nach dem Well-Known-Fix.** Die HTTPS-Ingress auf `mrtc.axion1337.chat` terminiert nur **Signaling (WSS auf TCP 443)** und die lk-jwt-service-Endpunkte `/sfu/get`, `/get_token`, `/healthz`. WebRTC-Medien laufen **nicht** über den Ingress, sondern direkt zum Node auf UDP/TCP-NodePorts.
|
||
|
||
Öffne auf deiner K3s-Node-Firewall (und im Router/Cloud-Security-Group):
|
||
|
||
| Port | Protokoll | Standard ESS CE | Zweck |
|
||
|---|---|---|---|
|
||
| 80, 443 | TCP | — | Ingress + ACME HTTP-01 |
|
||
| `rtcMuxedUdp` NodePort | **UDP** | 30002 (CE) / 30882 (Pro) | **Kritisch**: WebRTC-Medien (UDP-Mux). Ohne das: keine Audio/Video-Daten |
|
||
| `rtcTcp` NodePort | TCP | 30881 | ICE/TCP-Fallback für Clients ohne UDP |
|
||
| `turn` NodePort | UDP | 30004 | Nur falls `matrixRTC.sfu.exposedServices.turn.enabled: true` |
|
||
| `turnTLS` Port | TCP | 31443 oder 443 | Nur falls TURN/TLS aktiv; braucht SNI-Passthrough und eigenes Hostname |
|
||
|
||
Ermittle die tatsächlichen NodePorts per `kubectl get svc -n matrix | grep rtc`. Wenn dein Node hinter NAT steht und LiveKits STUN-basierte Public-IP-Erkennung die falsche Adresse zurückliefert, setze zusätzlich:
|
||
|
||
```yaml
|
||
matrixRTC:
|
||
sfu:
|
||
useStunToDiscoverPublicIP: false
|
||
manualIP: "<deine-öffentliche-IP>"
|
||
```
|
||
|
||
## Verifikationsschritte
|
||
|
||
Nach Reconcile (`flux reconcile helmrelease matrix-stack -n matrix`) diese vier Tests durchführen:
|
||
|
||
**1. Well-Known für Matrix-Client liefert rtc_foci:**
|
||
```bash
|
||
curl -sS https://axion1337.chat/.well-known/matrix/client | jq .
|
||
# Erwartung: JSON mit "m.homeserver" UND "org.matrix.msc4143.rtc_foci"
|
||
# das livekit_service_url == "https://mrtc.axion1337.chat" enthält.
|
||
|
||
curl -sS https://axion1337.chat/.well-known/matrix/server | jq .
|
||
# Erwartung: {"m.server": "matrix.axion1337.chat:443"}
|
||
```
|
||
|
||
**2. lk-jwt-service und LiveKit-Signaling sind erreichbar:**
|
||
```bash
|
||
curl -i https://mrtc.axion1337.chat/healthz
|
||
# Erwartung: HTTP/1.1 200 OK
|
||
|
||
curl -i https://mrtc.axion1337.chat/sfu/get
|
||
# Erwartung: HTTP/1.1 405 Method Not Allowed (akzeptiert nur POST)
|
||
|
||
curl -i -H "Connection: Upgrade" -H "Upgrade: websocket" \
|
||
-H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
|
||
-H "Sec-WebSocket-Version: 13" \
|
||
https://mrtc.axion1337.chat/
|
||
# Erwartung: HTTP/1.1 101 Switching Protocols (WebSocket-Upgrade)
|
||
```
|
||
|
||
**3. Synapse bietet den MSC4143-Endpunkt an:**
|
||
```bash
|
||
TOKEN="<gültiges-access-token>"
|
||
curl -sS -H "Authorization: Bearer $TOKEN" \
|
||
https://matrix.axion1337.chat/_matrix/client/v1/rtc/transports | jq .
|
||
# Erwartung: {"transports":[{"type":"livekit","livekit_service_url":"https://mrtc.axion1337.chat"}]}
|
||
```
|
||
|
||
**4. UDP-Medien kommen an:** In Element Web Call starten → im Chrome/Firefox-DevTools unter Netzwerk → WS prüfen, dass `wss://mrtc.axion1337.chat/rtc?access_token=...` einen `101`-Upgrade bekommt; unter `chrome://webrtc-internals` nach ICE-Candidate-Paaren mit Status `succeeded` und Typ `host/srflx` schauen. Falls nur Signaling, aber kein Media: UDP-NodePort auf Firewall prüfen.
|
||
|
||
## ACME-Race dauerhaft vermeiden
|
||
|
||
Merksatz: **Ein Hostname ⇒ genau ein `Certificate`-Objekt ⇒ genau ein `secretName`.** Drei betriebliche Strategien:
|
||
|
||
- **Subdomain-Trennung (Fix 1):** Jede Chart-Komponente bekommt einen eigenen FQDN, jeder FQDN genau ein Cert. Keine Kollision möglich. Das ist die vom Chart vorgesehene Form.
|
||
- **Geteiltes Secret:** Wenn zwei Ingresses denselben Host tragen müssen, trägt nur **einer** die `cert-manager.io/cluster-issuer`-Annotation. Beide referenzieren `spec.tls[].secretName` auf dasselbe Secret. Traefik wählt das Zertifikat korrekt aus.
|
||
- **DNS-01 statt HTTP-01:** Löst den Solver-Ingress-Konflikt grundsätzlich, weil kein zweiter Ingress erzeugt wird. Setzt DNS-Provider-Credentials im ClusterIssuer voraus.
|
||
|
||
## Vom Benutzer übersehene Chart-Werte
|
||
|
||
Außer den oben beschriebenen Korrekturen sind diese Keys erwähnenswert, falls du feintunen willst. `matrixRTC.hostAliases` ist ein dokumentierter Workaround gegen Cluster-interne DNS-Probleme, wenn der lk-jwt-service-Pod den Homeserver-Apex bzw. Synapse über den Ingress-Controller statt direkt erreichen muss. `matrixRTC.sfu.exposedServices.<name>.portType` akzeptiert `NodePort`, `HostPort` oder `LoadBalancer` – für bare-metal K3s ohne externen LB ist `NodePort` richtig. `matrixRTC.sfu.additional."user-config.yaml".config` erlaubt rohes LiveKit-YAML für Spezialfälle (STUN-Server, Codecs). `wellKnownDelegation.additional.client` / `.server` / `.element` erlauben, in die erzeugten JSON-Dokumente zusätzliche Felder einzumischen – normalerweise nicht nötig, aber nützlich für föderative Sonderfälle.
|
||
|
||
## Fazit
|
||
|
||
Der eigentliche Defekt war ein einziger Kippschalter: `wellKnownDelegation.enabled: false` hat den einzig existierenden Discovery-Kanal für den LiveKit-Focus gekappt. Der darum herum gebaute Custom-Synapse-Block ist ein Red Herring – die zugehörigen Schlüssel existieren in Synapse schlicht nicht und werden ignoriert. Die ursprünglich empfundene ACME-Race ist kein Bug des Charts, sondern die vorhersagbare Folge davon, dass zwei Chart-erzeugte Ingresses (Element Web **auf dem Apex** plus Well-Known) jeweils ein eigenes Let's-Encrypt-Zertifikat für denselben Host anforderten. Sobald Element Web nach `chat.axion1337.chat` wandert und Well-Known wieder aktiv ist, produziert das Chart automatisch das korrekte `org.matrix.msc4143.rtc_foci`-Feld, injiziert den `matrix_rtc.transports`-Block in Synapse, und Element Call funktioniert – vorausgesetzt die UDP-NodePort-Firewall ist offen, was nach dem Discovery-Fix der zweite Punkt auf der Prüfliste ist. |