Skip to content

Instantly share code, notes, and snippets.

@AriesAlex
Last active April 30, 2026 07:54
Show Gist options
  • Select an option

  • Save AriesAlex/767dd99d238bb48e82bdddc35a97d0f7 to your computer and use it in GitHub Desktop.

Select an option

Save AriesAlex/767dd99d238bb48e82bdddc35a97d0f7 to your computer and use it in GitHub Desktop.
Гайд: Matrix (Synapse) + LiveKit + Traefik

Гайд: Matrix (Synapse) + LiveKit + Traefik

Предварительные требования

  1. Домен: В примере используется example.com.
    • matrix.example.com — основной сервер Synapse.
    • matrix.example.com/admin — админка Synapse.
    • chat.example.com — веб-клиент Element.
    • rtc.example.com — сервер LiveKit (сигналинг и медиа).
  2. VPS/Server: Linux, установленный Docker и Docker Compose.
  3. Порты:
    • TCP: 80, 443 (для Traefik)
    • TCP: 7881 (LiveKit RTC)
    • UDP: 50100-50200 (диапазон для медиа-потоков LiveKit, нужно открыть в Firewall)

Структура папок

Создайте следующую структуру проекта:

matrix-server/
├── docker-compose.yml
├── traefik/
│   ├── traefik.yml
│   └── dynamic.yml
├── matrix/
│   ├── synapse/             # Монтируется как /data в контейнер synapse
│   ├── well-known/
│   │   ├── matrix/
│   │   │   ├── client
│   │   │   └── server
│   ├── livekit.yaml
│   ├── web-config.json      # Конфиг для Element
│   └── synapse-admin-config.json

Шаг 1: Генерация ключей

Вам понадобятся случайные строки для секретов. Сгенерируйте их (например, через openssl rand -hex 32) и сохраните где-нибудь. Нам понадобятся:

  1. REGISTRATION_SHARED_SECRET (для регистрации)
  2. MACAROON_SECRET (для Synapse)
  3. LIVEKIT_API_KEY (назовем devkey или как удобно)
  4. LIVEKIT_API_SECRET (длинный хеш)

Шаг 2: Конфигурация Traefik

Файл traefik/traefik.yml (статическая конфигурация):

entryPoints:
  http:
    address: ":80"
  https:
    address: ":443"

http:
  routers:
    http-catchall:
      rule: hostregexp(`{host:.+}`)
      entrypoints:
      - http
      middlewares:
      - redirect-to-https
  middlewares:
    redirect-to-https:
      redirectScheme:
        scheme: https
        permanent: false

providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    watch: true
    exposedByDefault: false
  file:
    filename: /traefik/dynamic.yml
    watch: true

certificatesResolvers:
  letsEncrypt:
    acme:
      email: your-email@example.com  # <-- УКАЖИТЕ СВОЮ ПОЧТУ
      storage: /traefik/acme.json
      caServer: "https://acme-v02.api.letsencrypt.org/directory"
      httpChallenge:
        entryPoint: http

Файл traefik/dynamic.yml можно оставить пустым или добавить туда TLS настройки, но файл должен существовать.

Шаг 3: Конфигурация LiveKit

Файл matrix/livekit.yaml. Здесь важно указать ключи, совпадающие с теми, что будут в Synapse и JWT сервисе.

port: 7880
bind_addresses:
  - '0.0.0.0'
rtc:
  tcp_port: 7881
  port_range_start: 50100
  port_range_end: 50200
  use_external_ip: true
room:
  auto_create: false
logging:
  level: info
turn:
  enabled: false
keys:
  # Пример пары ключ:секрет. Замените на свои!
  devkey: 'ВАШ_СГЕНЕРИРОВАННЫЙ_LIVEKIT_SECRET'

Шаг 4: Конфигурация Well-Known (Discovery)

Это критически важная часть. Именно через эти файлы клиенты узнают, где находится сервер LiveKit.

Файл matrix/well-known/matrix/client:

{
  "m.homeserver": { "base_url": "https://matrix.example.com" },
  "org.matrix.msc4143.rtc_foci": [
    {
      "type": "livekit",
      "livekit_service_url": "https://rtc.example.com/livekit/jwt"
    }
  ]
}

Файл matrix/well-known/matrix/server:

{
  "m.server": "matrix.example.com:443"
}

Шаг 5: Конфигурация клиентов

Файл matrix/web-config.json (для Element Web):

{
  "default_server_config": {
    "m.homeserver": {
      "base_url": "https://matrix.example.com",
      "server_name": "matrix.example.com"
    }
  },
  "disable_identity_server": true,
  "brand": "My Matrix",
  "roomDirectory": { "servers": ["matrix.example.com"] },
  "showLabsSettings": true
}

Файл matrix/synapse-admin-config.json:

{
  "restrictBaseUrl": "https://matrix.example.com"
}

Шаг 6: Docker Compose

Основной файл docker-compose.yml. Обратите внимание на переменные environment для сервиса lk-jwt — ключи должны совпадать.

services:
  traefik:
    image: traefik:v2.11
    container_name: traefik
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    ports:
      - 80:80
      - 443:443
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik:/traefik
      - ./traefik/traefik.yml:/traefik.yml:ro
      - ./traefik/dynamic.yml:/traefik/dynamic.yml:ro

  synapse:
    image: matrixdotorg/synapse:latest
    container_name: synapse
    restart: unless-stopped
    environment:
      - SYNAPSE_SERVER_NAME=matrix.example.com
      - SYNAPSE_REPORT_STATS=no
    volumes:
      - ./matrix/synapse:/data
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.synapse.rule=Host(`matrix.example.com`) && PathPrefix(`/_matrix`)"
      - "traefik.http.routers.synapse.entrypoints=https"
      - "traefik.http.routers.synapse.tls.certresolver=letsEncrypt"
      - "traefik.http.routers.synapse.service=synapse"
      - "traefik.http.services.synapse.loadbalancer.server.port=8008"
      # API Admin (необязательно выставлять наружу, если есть VPN, но вот пример)
      - "traefik.http.routers.synapse_admin_api.rule=Host(`matrix.example.com`) && PathPrefix(`/_synapse`)"
      - "traefik.http.routers.synapse_admin_api.entrypoints=https"
      - "traefik.http.routers.synapse_admin_api.tls.certresolver=letsEncrypt"
      - "traefik.http.routers.synapse_admin_api.service=synapse"

  livekit:
    image: livekit/livekit-server:latest
    container_name: livekit
    command: --config /etc/livekit.yaml
    restart: unless-stopped
    volumes:
      - ./matrix/livekit.yaml:/etc/livekit.yaml:ro
    ports:
      - "7881:7881/tcp"
      - "50100-50200:50100-50200/udp"
    labels:
      - "traefik.enable=true"
      # Роутинг SFU
      - "traefik.http.routers.livekit_sfu.rule=Host(`rtc.example.com`) && PathPrefix(`/livekit/sfu`)"
      - "traefik.http.routers.livekit_sfu.entrypoints=https"
      - "traefik.http.routers.livekit_sfu.tls.certresolver=letsEncrypt"
      - "traefik.http.routers.livekit_sfu.middlewares=lk_sfu_strip"
      - "traefik.http.routers.livekit_sfu.service=livekit_sfu"
      - "traefik.http.services.livekit_sfu.loadbalancer.server.port=7880"
      - "traefik.http.middlewares.lk_sfu_strip.stripprefix.prefixes=/livekit/sfu,/livekit/sfu/"

  lk-jwt:
    image: ghcr.io/element-hq/lk-jwt-service:latest
    container_name: livekit-jwt
    restart: unless-stopped
    environment:
      - PORT=8080
      - LIVEKIT_JWT_PORT=8080
      - LIVEKIT_URL=wss://rtc.example.com/livekit/sfu
      - LIVEKIT_KEY=devkey
      - LIVEKIT_SECRET=ВАШ_СГЕНЕРИРОВАННЫЙ_LIVEKIT_SECRET
      - LIVEKIT_FULL_ACCESS_HOMESERVERS=matrix.example.com
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.lk_jwt.rule=Host(`rtc.example.com`) && PathPrefix(`/livekit/jwt`)"
      - "traefik.http.routers.lk_jwt.entrypoints=https"
      - "traefik.http.routers.lk_jwt.tls.certresolver=letsEncrypt"
      - "traefik.http.routers.lk_jwt.middlewares=lk_jwt_strip,lk_cors"
      - "traefik.http.routers.lk_jwt.service=lk_jwt"
      - "traefik.http.services.lk_jwt.loadbalancer.server.port=8080"
      # CORS Preflight
      - "traefik.http.routers.lk_jwt_preflight.rule=Host(`rtc.example.com`) && PathPrefix(`/livekit/jwt`) && Method(`OPTIONS`)"
      - "traefik.http.routers.lk_jwt_preflight.entrypoints=https"
      - "traefik.http.routers.lk_jwt_preflight.tls.certresolver=letsEncrypt"
      - "traefik.http.routers.lk_jwt_preflight.middlewares=lk_cors"
      - "traefik.http.routers.lk_jwt_preflight.service=noop@internal"
      # Middlewares
      - "traefik.http.middlewares.lk_cors.headers.accesscontrolalloworigin=*"
      - "traefik.http.middlewares.lk_cors.headers.accesscontrolallowmethods=GET,POST,OPTIONS"
      - "traefik.http.middlewares.lk_cors.headers.accesscontrolallowheaders=Content-Type,Authorization"
      - "traefik.http.middlewares.lk_jwt_strip.stripprefix.prefixes=/livekit/jwt,/livekit/jwt/"

  wellknown:
    image: nginx:alpine
    container_name: wellknown
    restart: unless-stopped
    volumes:
      - ./matrix/well-known:/usr/share/nginx/html/.well-known:ro
    labels:
      - "traefik.enable=true"
      # Client discovery
      - "traefik.http.routers.wk_client.rule=Host(`matrix.example.com`) && PathPrefix(`/.well-known/matrix/client`)"
      - "traefik.http.routers.wk_client.priority=1000"
      - "traefik.http.routers.wk_client.entrypoints=https"
      - "traefik.http.routers.wk_client.tls.certresolver=letsEncrypt"
      - "traefik.http.routers.wk_client.middlewares=wk_headers"
      - "traefik.http.routers.wk_client.service=wk"
      # Server discovery
      - "traefik.http.routers.wk_server.rule=Host(`matrix.example.com`) && PathPrefix(`/.well-known/matrix/server`)"
      - "traefik.http.routers.wk_server.priority=1000"
      - "traefik.http.routers.wk_server.entrypoints=https"
      - "traefik.http.routers.wk_server.tls.certresolver=letsEncrypt"
      - "traefik.http.routers.wk_server.middlewares=wk_headers"
      - "traefik.http.routers.wk_server.service=wk"
      - "traefik.http.middlewares.wk_headers.headers.customResponseHeaders.Access-Control-Allow-Origin=*"
      - "traefik.http.middlewares.wk_headers.headers.customResponseHeaders.Content-Type=application/json"
      - "traefik.http.services.wk.loadbalancer.server.port=80"

  element-web:
    image: vectorim/element-web:latest
    container_name: element-web
    restart: unless-stopped
    volumes:
      - ./matrix/web-config.json:/app/config.json:ro
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.element.rule=Host(`chat.example.com`)"
      - "traefik.http.routers.element.entrypoints=https"
      - "traefik.http.routers.element.tls.certresolver=letsEncrypt"
      - "traefik.http.services.element.loadbalancer.server.port=80"

  synapse-admin:
    image: awesometechnologies/synapse-admin:latest
    container_name: synapse-admin
    restart: unless-stopped
    environment:
      - REACT_APP_SERVER=https://matrix.example.com
    volumes:
      - ./matrix/synapse-admin-config.json:/app/config.json:ro
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.synapse_admin_ui.rule=Host(`matrix.example.com`) && PathPrefix(`/admin`)"
      - "traefik.http.routers.synapse_admin_ui.entrypoints=https"
      - "traefik.http.routers.synapse_admin_ui.tls.certresolver=letsEncrypt"
      - "traefik.http.middlewares.admin_redirect.redirectregex.regex=^(.*)/admin/?$"
      - "traefik.http.middlewares.admin_redirect.redirectregex.replacement=$${1}/admin/"
      - "traefik.http.middlewares.admin_strip.stripprefix.prefixes=/admin"
      - "traefik.http.routers.synapse_admin_ui.middlewares=admin_redirect,admin_strip"
      - "traefik.http.services.synapse_admin_ui.loadbalancer.server.port=80"

Шаг 7: Генерация и правка конфига Synapse

  1. Запустите контейнер Synapse один раз, чтобы сгенерировать конфиг (если его еще нет):

    docker run -it --rm \
        -v "$PWD/matrix/synapse:/data" \
        -e SYNAPSE_SERVER_NAME=matrix.example.com \
        -e SYNAPSE_REPORT_STATS=no \
        matrixdotorg/synapse:latest generate
  2. Отредактируйте созданный файл matrix/synapse/homeserver.yaml. В него нужно добавить поддержку экспериментальных функций для LiveKit/WebRTC.

Внесите следующие изменения в homeserver.yaml:

# Основные настройки
server_name: "matrix.example.com"
public_baseurl: "https://matrix.example.com/"

# Включаем экспериментальные фичи (обязательно для нативного WebRTC)
experimental_features:
  msc3266_enabled: true
  msc4222_enabled: true
  msc4140_enabled: true # Native Element Call

# Если используете SQLite (для теста), или настройте Postgres
database:
  name: sqlite3
  args:
    database: /data/homeserver.db

# Настройка интеграции с LiveKit (ключи как в livekit.yaml и docker-compose)
livekit_jwt_sso:
  enabled: true
  # URL к сервису lk-jwt (через внешний адрес или внутреннюю сеть)
  livekit_jwt_sso_url: "https://rtc.example.com/livekit/jwt/token"
  livekit_secret: "ВАШ_СГЕНЕРИРОВАННЫЙ_LIVEKIT_SECRET"

# Секреты (замените на свои сгенерированные)
registration_shared_secret: "ВАШ_SECRET_1"
macaroon_secret_key: "ВАШ_SECRET_2"

Шаг 8: Запуск

Запускаем всё хозяйство:

docker-compose up -d

Проверяем логи, особенно livekit и synapse: docker-compose logs -f livekit

Как это работает?

  1. Пользователь заходит на chat.example.com.
  2. Element запрашивает https://matrix.example.com/.well-known/matrix/client.
  3. Сервер отвечает JSON-ом, в котором сказано: "Для звонков используй rtc.example.com (LiveKit)".
  4. При начале звонка Synapse генерирует токена через ваш lk-jwt сервис.
  5. Element устанавливает соединение с LiveKit по UDP (порты 50100-50200) для медиа и WebSocket для сигналинга.

Регистрация пользователей

Для регистрации нужно создавать пользователям аккаунты на matrix.example.com/admin

@riodevelop
Copy link
Copy Markdown

riodevelop commented Mar 13, 2026

Мануал норм, но есть нюансы из-за которых прилось сломать голову:

  • команаты должны сами создаваться:
    room: auto_create: true

  • надо разрешить MatrixRTC для ElementX:
    experimental_features: msc3266_enabled: true msc4222_enabled: true msc4140_enabled: true # Native Element Call msc4143_enabled: true

  • забыли livekit_key:
    `livekit_jwt_sso:
    enabled: true
    livekit_jwt_sso_url: "https://rtc.example.com/livekit/jwt/token"
    livekit_key: "LIVEKIT_API_KEY"
    livekit_secret: "ВАШ_СГЕНЕРИРОВАННЫЙ_LIVEKIT_SECRET"'

  • для traefik нужно использовать camelCase и плюс ко всему не accessControlAllowOrigin, а accessControlAllowOriginList:
    ' - "traefik.http.middlewares.lk_cors.headers.accessControlAllowOriginList=*"
    - "traefik.http.middlewares.lk_cors.headers.accessControlAllowMethods=GET,POST,OPTIONS"
    - "traefik.http.middlewares.lk_cors.headers.accessControlAllowHeaders=Content-Type,Authorization"'

Но мануал хороший, спасибо!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment