axion1337.chat-gitops/docs/oldwiki/fix report mrtc.md
Scrublord MacBad 0ff598e8e0 Add documentation to wiki branch
- Deployment guides for TURN, Authentik, Monitoring, Element, Policies
- Task tracking (TASKS.md)
- Element desktop setup scripts for all platforms
- Installation guide

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-05-14 23:38:28 +02:00

18 KiB
Raw Permalink Blame History

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):

{
  "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 secretNames → 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)

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)

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:

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.

# 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:

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:

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:

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:

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.