Установка Matrix Synapse + Element + Telegram bridge
Установка Matrix Synapse + Element + Telegram bridge (mautrix-telegram) — Вообще серверов Matrix и клиентов достаточно много, мною было сделано вот так например. Не думаю, что это прям мануал, ну некое рабочее решение скажем так я думаю. Поехали, че.
1. Общая схема
Element (браузер)
→ reverse VPS (nginx)
→ основной сервер (nginx)
→ Synapse (127.0.0.1:8008)
Telegram bridge (mautrix-telegram)
→ docker
→ отдельный postgres
→ цепляется к Synapse как appservice
- Synapse наружу не открыт
- весь доступ через nginx
- Element на отдельном домене
- Telegram bridge отдельно
2. Matrix Synapse
Установка:
apt update
apt install -y matrix-synapse-py3 postgresql
Файл: /etc/matrix-synapse/homeserver.yaml
server_name: "matrix.example.com" # основной домен Matrix-сервера; именно он будет частью user ID и federation
# пример: @user:matrix.example.com
public_baseurl: "https://matrix.example.com" # публичный адрес сервера для клиентов; должен совпадать с тем, куда реально ходит клиент
pid_file: "/var/run/matrix-synapse.pid" # файл PID процесса Synapse; нужен systemd и служебным утилитам
listeners: # список listener'ов, которые поднимает Synapse
- port: 8008 # локальный HTTP-порт Synapse
tls: false # TLS выключен, потому что шифрование делает nginx
type: http # тип listener'а — HTTP
x_forwarded: true # доверять X-Forwarded-* заголовкам от reverse proxy; обязательно при nginx перед Synapse
bind_addresses: # адреса, на которых будет слушать Synapse
- '127.0.0.1' # слушаем только локалхост, наружу Synapse не открыт
resources: # какие API обслуживать на этом listener'е
- names:
- client # клиентский API Matrix
- federation # federation API для общения с другими серверами
compress: false # встроенное сжатие тут выключено; обычно это ок, nginx и так справится
database: # блок настроек БД
name: psycopg2 # драйвер PostgreSQL для Python
args: # параметры подключения к PostgreSQL
user: synapse # пользователь БД
password: YOUR_SYNAPSE_DB_PASSWORD # пароль пользователя БД
database: synapse # имя базы данных
host: 127.0.0.1 # PostgreSQL находится локально
port: 5432 # стандартный порт PostgreSQL
cp_min: 5 # минимальное число соединений в пуле
cp_max: 10 # максимальное число соединений в пуле
log_config: "/etc/matrix-synapse/log.yaml" # путь до конфига логирования Synapse
media_store_path: /var/lib/matrix-synapse/media # каталог, где хранятся все медиафайлы
signing_key_path: "/etc/matrix-synapse/homeserver.signing.key" # ключ подписи сервера Matrix; критически важный файл, терять нельзя
trusted_key_servers: # доверенные key server'ы для federation
- server_name: "matrix.org" # брать публичные ключи federation у matrix.org
macaroon_secret_key: "YOUR_MACAROON_SECRET" # секрет для токенов и ряда внутренних auth-механизмов Synapse
enable_registration: false # отключает открытую регистрацию новых пользователей через клиент
registration_shared_secret: "YOUR_REGISTRATION_SHARED_SECRET" # секрет для register_new_matrix_user и похожих способов админской регистрации
app_service_config_files: # список appservice-конфигов, которые подключаются к Synapse
- /etc/matrix-synapse/mautrix-telegram.yaml # registration-файл Telegram bridge
retention: # политика хранения сообщений
enabled: true # включает retention вообще
default_policy: # политика по умолчанию
min_lifetime: 1d # минимальный срок хранения сообщений
max_lifetime: 180d # максимальный срок хранения сообщений
allowed_lifetime_min: 1d # самый маленький разрешённый срок, который вообще можно указывать
allowed_lifetime_max: 1y # самый большой разрешённый срок
media_retention: # политика хранения медиафайлов
local_media_lifetime: 180d # сколько хранить локально загруженные файлы
remote_media_lifetime: 30d # сколько хранить медиа, пришедшие с других серверов
Что тут важно:
- Synapse слушает только
127.0.0.1 - TLS не в Synapse, а на nginx
x_forwarded: trueобязателен- SQLite даже не рассматривал, сразу PostgreSQL
- retention лучше включать сразу, иначе потом всё разрастается
3. nginx на машине с Synapse
Файл: nginx-конфиг для matrix.example.com на основном серваке
server { # HTTP-виртуалхост для matrix.example.com
listen 80; # слушаем обычный HTTP
server_name matrix.example.com; # домен этого virtual host
location = /.well-known/matrix/client { # точный путь для client well-known
default_type application/json; # отдаём JSON
add_header Access-Control-Allow-Origin *; # разрешаем запросы с любых origin, нужно клиентам
return 200 '{"m.homeserver":{"base_url":"https://matrix.example.com"}}'; # говорим клиенту, где его homeserver
}
location = /.well-known/matrix/server { # точный путь для server well-known
default_type application/json; # отдаём JSON
add_header Access-Control-Allow-Origin *; # CORS-заголовок
return 200 '{"m.server":"matrix.example.com:443"}'; # говорим federation, куда ходить
}
location / { # всё остальное на HTTP
return 301 https://$host$request_uri; # редиректим на HTTPS
}
}
server { # HTTPS-виртуалхост для matrix.example.com
listen 443 ssl http2; # HTTPS + HTTP/2
server_name matrix.example.com; # домен этого virtual host
ssl_certificate /etc/letsencrypt/live/matrix.example.com/fullchain.pem; # сертификат
ssl_certificate_key /etc/letsencrypt/live/matrix.example.com/privkey.pem; # приватный ключ сертификата
ssl_protocols TLSv1.2 TLSv1.3; # разрешённые версии TLS
location = /.well-known/matrix/client { # client well-known и по HTTPS тоже
default_type application/json; # тип ответа
add_header Access-Control-Allow-Origin * always; # CORS даже если ответ нестандартный
return 200 '{"m.homeserver":{"base_url":"https://matrix.example.com"}}'; # база для Matrix-клиентов
}
location = /.well-known/matrix/server { # server well-known и по HTTPS тоже
default_type application/json; # тип ответа
add_header Access-Control-Allow-Origin * always; # CORS
return 200 '{"m.server":"matrix.example.com:443"}'; # federation адрес сервера
}
location /_matrix { # весь Matrix API прокидываем в Synapse
proxy_pass http://127.0.0.1:8008; # отправляем в локальный Synapse
proxy_http_version 1.1; # используем HTTP/1.1 для proxy
proxy_set_header Host $host; # передаём оригинальный Host
proxy_set_header X-Real-IP $remote_addr; # реальный IP клиента
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # цепочка IP через proxy
proxy_set_header X-Forwarded-Proto https; # говорим бэкенду, что снаружи был HTTPS
client_max_body_size 100M; # лимит размера загружаемых файлов
proxy_read_timeout 600s; # сколько ждать ответ от Synapse
proxy_send_timeout 600s; # сколько ждать передачу в сторону Synapse
send_timeout 600s; # таймаут отправки клиенту
proxy_buffering off; # отключаем буферизацию, полезно для real-time API
proxy_request_buffering off; # не буферить тело запроса
}
location /_synapse/client { # служебный client API Synapse тоже прокидываем
proxy_pass http://127.0.0.1:8008; # на локальный Synapse
proxy_http_version 1.1; # HTTP/1.1
proxy_set_header Host $host; # оригинальный host
proxy_set_header X-Real-IP $remote_addr; # IP клиента
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # XFF цепочка
proxy_set_header X-Forwarded-Proto https; # внешний протокол HTTPS
client_max_body_size 100M; # лимит на размер файла/запроса
proxy_read_timeout 600s; # таймаут чтения
proxy_send_timeout 600s; # таймаут отправки в upstream
send_timeout 600s; # таймаут для клиента
proxy_buffering off; # без буферизации
proxy_request_buffering off; # без буферизации запроса
}
location / { # всё остальное на этом домене
return 404; # сразу режем
}
}
Тут главное — наружу отдаётся только то, что реально нужно Matrix. Всё остальное режется в 404.
4. Reverse nginx на VPS для Matrix
Файл: nginx-конфиг на внешнем reverse VPS
server { # HTTP на внешнем reverse VPS
listen 80; # IPv4 HTTP
listen [::]:80; # IPv6 HTTP
server_name matrix.example.com; # домен Matrix
location /.well-known/matrix/server { # server well-known
default_type application/json; # ответ JSON
add_header Access-Control-Allow-Origin *; # CORS
return 200 '{"m.server":"matrix.example.com:443"}'; # указываем адрес federation
}
location /.well-known/matrix/client { # client well-known
default_type application/json; # JSON
add_header Access-Control-Allow-Origin *; # CORS
return 200 '{"m.homeserver":{"base_url":"https://matrix.example.com"}}'; # адрес homeserver для клиентов
}
location / { # всё остальное на 80
return 301 https://$host$request_uri; # редирект на HTTPS
}
}
server { # HTTPS reverse proxy на VPS
listen 443 ssl http2; # IPv4 HTTPS + HTTP/2
listen [::]:443 ssl http2; # IPv6 HTTPS + HTTP/2
server_name matrix.example.com; # домен
ssl_certificate /etc/letsencrypt/live/matrix.example.com/fullchain.pem; # сертификат VPS
ssl_certificate_key /etc/letsencrypt/live/matrix.example.com/privkey.pem; # ключ VPS
ssl_protocols TLSv1.2 TLSv1.3; # разрешённые версии TLS
ssl_prefer_server_ciphers off; # не навязывать серверный выбор шифров
location /.well-known/matrix/server { # server well-known по HTTPS
default_type application/json; # JSON
add_header Access-Control-Allow-Origin *; # CORS
return 200 '{"m.server":"matrix.example.com:443"}'; # federation адрес
}
location /.well-known/matrix/client { # client well-known по HTTPS
default_type application/json; # JSON
add_header Access-Control-Allow-Origin *; # CORS
return 200 '{"m.homeserver":{"base_url":"https://matrix.example.com"}}'; # homeserver URL
}
location ~ ^(/_matrix|/_synapse/client) { # проксируем только Matrix API и нужный Synapse API
proxy_pass https://YOUR_PUBLIC_IP; # прокидываем на основной сервер по HTTPS
proxy_http_version 1.1; # HTTP/1.1 до upstream
proxy_set_header Host $host; # сохраняем оригинальный host
proxy_set_header X-Real-IP $remote_addr; # IP клиента
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # цепочка proxy
proxy_set_header X-Forwarded-Proto https; # говорим, что внешний протокол HTTPS
proxy_ssl_server_name on; # включить SNI при TLS к upstream
proxy_ssl_verify off; # не проверять сертификат домашнего сервера; удобно, но менее строго по безопасности
client_max_body_size 100M; # лимит размера запроса/файла
proxy_read_timeout 600s; # таймаут чтения upstream
proxy_send_timeout 600s; # таймаут отправки в upstream
send_timeout 600s; # таймаут клиенту
proxy_buffering off; # без буферизации ответа
proxy_request_buffering off; # без буферизации тела запроса
}
location / { # всё остальное на этом домене
return 404; # режем
}
}
Тут как раз был один из неприятных моментов. Без .well-known и без нормального reverse клиенты особенно на мобилках хрен авторизовывались. Через такую схему уже всё заработало нормально.
5. Element
Element на отдельном домене.
Файл: config.json
{
"default_server_config": { // сервер по умолчанию, к которому будет цепляться Element
"m.homeserver": { // описание homeserver
"base_url": "https://matrix.example.com", // URL Matrix homeserver
"server_name": "matrix.example.com" // имя сервера Matrix
}
},
"disable_custom_urls": true, // запретить пользователю вручную менять homeserver в интерфейсе
"disable_guests": true, // отключить гостевые входы
"brand": "example Matrix" // название/бренд интерфейса Element
}
nginx на домашней машине для Element:
server { # HTTP virtual host для Element
listen 80; # обычный HTTP
server_name element.example.com; # домен Element
if ($host != "element.example.com") { # дополнительная проверка host
return 444; # молча рвём соединение если host не тот
}
return 301 https://$host$request_uri; # всё валим на HTTPS
}
server { # HTTPS virtual host для Element
listen 443 ssl http2; # HTTPS + HTTP/2
server_name element.example.com; # домен Element
if ($host != "element.example.com") { # ещё раз проверяем host
return 444; # если не тот host — сразу рвём
}
ssl_certificate /etc/letsencrypt/live/element.example.com/fullchain.pem; # сертификат домена Element
ssl_certificate_key /etc/letsencrypt/live/element.example.com/privkey.pem; # приватный ключ
ssl_protocols TLSv1.2 TLSv1.3; # разрешённые версии TLS
ssl_prefer_server_ciphers off; # не форсить шифры сервера
root /var/www/element; # каталог, где лежит собранный Element Web
index index.html; # стартовый файл
location / { # основная раздача интерфейса
try_files $uri $uri/ /index.html; # SPA-логика: всё неизвестное отдаём в index.html
}
location = /config.json { # отдельная обработка config.json
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0"; # не кешировать конфиг
}
location ~* \.(js|css|png|jpg|jpeg|gif|svg|ico|woff2?)$ { # статика
expires 7d; # кешируем 7 дней
access_log off; # без access log для статики
}
}
reverse nginx на VPS для Element:
server { # HTTP virtual host на VPS для Element
listen 80; # HTTP
server_name element.example.com; # домен Element
if ($host != "element.example.com") { # проверка host
return 444; # рвём если не тот домен
}
return 301 https://$host$request_uri; # редирект на HTTPS
}
server { # HTTPS reverse proxy для Element
listen 443 ssl http2; # HTTPS + HTTP/2
server_name element.example.com; # домен Element
if ($host != "element.example.com") { # защита от левых host
return 444; # рвём соединение
}
ssl_certificate /etc/letsencrypt/live/element.example.com/fullchain.pem; # сертификат
ssl_certificate_key /etc/letsencrypt/live/element.example.com/privkey.pem; # ключ
ssl_protocols TLSv1.2 TLSv1.3; # версии TLS
ssl_prefer_server_ciphers off; # не форсить шифры сервера
location / { # весь трафик Element
proxy_pass https://YOUR_PUBLIC_IP; # проксируем на основной по HTTPS
proxy_http_version 1.1; # HTTP/1.1 до upstream
proxy_set_header Host $host; # оригинальный host
proxy_set_header X-Real-IP $remote_addr; # IP клиента
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # цепочка IP
proxy_set_header X-Forwarded-Proto https; # внешний протокол HTTPS
proxy_read_timeout 300; # таймаут чтения ответа
proxy_send_timeout 300; # таймаут отправки в upstream
send_timeout 300; # таймаут клиенту
proxy_buffering off; # не буферить
proxy_request_buffering off; # не буферить тело запроса
proxy_ssl_server_name on; # включить SNI к основному серверу
}
}
Element сам по себе поднимается просто, но если не сделать нормально config.json и reverse, потом будут вопросы в духе “почему тут логинится, а тут нет”.
6. mautrix-telegram
Bridge у меня живёт в docker.
Файл: /opt/mautrix-telegram/docker-compose.yml
services: # список сервисов docker compose
postgres: # контейнер с PostgreSQL для bridge
image: postgres:15 # образ Postgres 15
container_name: mautrix-telegram-db # имя контейнера
restart: unless-stopped # автозапуск, кроме случая явной остановки
environment: # переменные окружения для инициализации БД
POSTGRES_USER: mautrix # пользователь БД
POSTGRES_PASSWORD: YOUR_MAUTRIX_DB_PASSWORD # пароль БД
POSTGRES_DB: mautrix # имя БД
volumes:
- ./postgres:/var/lib/postgresql/data # сохраняем данные PostgreSQL на хосте
mautrix-telegram: # контейнер самого Telegram bridge
image: dock.mau.dev/mautrix/telegram:latest # актуальный образ bridge
container_name: mautrix-telegram # имя контейнера bridge
restart: unless-stopped # перезапуск при сбоях и после reboot
depends_on:
- postgres # сначала должен стартовать Postgres
ports:
- "29317:29317" # проброс порта appservice наружу/на хост
volumes:
- ./data:/data # конфиги, сессии, логи и прочие данные bridge
Запуск:
cd /opt/mautrix-telegram
docker compose up -d
7. Регистрация appservice для Synapse
Файл: /etc/matrix-synapse/mautrix-telegram.yaml
id: telegram # идентификатор appservice внутри Synapse
url: http://BRIDGE_HOST_IP:29317 # URL, по которому Synapse будет обращаться к bridge
as_token: YOUR_AS_TOKEN # токен appservice для bridge
hs_token: YOUR_HS_TOKEN # токен homeserver для проверки запросов от Synapse
sender_localpart: APPSERVICE_SENDER # localpart системного appservice-юзера
rate_limited: false # не применять стандартный rate limit к appservice
namespaces: # какие user ID и namespace принадлежат bridge
users:
- regex: ^@telegrambot:matrix\.example\.com$ # бот bridge
exclusive: true # этот юзер полностью принадлежит appservice
- regex: ^@telegram_.*:matrix\.example\.com$ # ghost-аккаунты Telegram
exclusive: true # тоже эксклюзивно за bridge
de.sorunome.msc2409.push_ephemeral: true # поддержка ephemeral push events
receive_ephemeral: true # принимать ephemeral-события
Этот файл потом указывается в homeserver.yaml через app_service_config_files.
8. Основной конфиг mautrix-telegram
Файл: /opt/mautrix-telegram/data/config.yaml
network: # настройки работы с Telegram API
api_id: YOUR_TELEGRAM_API_ID # Telegram API ID с my.telegram.org
api_hash: YOUR_TELEGRAM_API_HASH # Telegram API hash
device_info: # как bridge будет представляться Telegram
device_model: mautrix-telegram # имя устройства
system_version: # версия системы; пусто — оставить дефолт/авто
app_version: auto # версию приложения определить автоматически
lang_code: en # язык приложения
system_lang_code: en # язык системы
animated_sticker: # как конвертировать анимированные стикеры
target: gif # итоговый формат
convert_from_webm: false # не пытаться конвертировать из webm дополнительно
args: # параметры конвертации
width: 256 # ширина
height: 256 # высота
fps: 25 # кадры в секунду
member_list: # настройки синка участников
max_initial_sync: 100 # сколько участников тянуть при первом sync
sync_broadcast_channels: false # не синкать broadcast channels как member list
skip_deleted: true # пропускать удалённые аккаунты
ping: # пинги к Telegram для проверки связи
interval_seconds: 30 # интервал пингов
timeout_seconds: 10 # таймаут пинга
sync: # общие лимиты синхронизации
update_limit: 100 # лимит обновлений за проход
create_limit: 15 # лимит созданий порталов/комнат за проход
login_sync_limit: 15 # лимит начального sync после логина
direct_chats: true # подтягивать личные чаты
takeout: # настройки takeout-режима Telegram
dialog_sync: false # не использовать takeout для синка диалогов
forward_backfill: false # не тянуть историю вперёд
backward_backfill: false # не тянуть историю назад
max_member_count: -1 # без лимита по размеру чата
contact_avatars: false # не использовать аватары контактов из адресной книги
contact_names: false # не использовать имена контактов из адресной книги
always_custom_emoji_reaction: false # не форсить кастомные emoji reactions
saved_message_avatar: mxc://maunium.net/XhhfHoPejeneOngMyBbtyWDk # аватар для Saved Messages
always_tombstone_on_supergroup_migration: false # не всегда создавать tombstone при миграции супергруппы
image_as_file_pixels: 16777216 # порог пикселей, после которого изображение можно трактовать как файл
disable_view_once: false # не отключать view once контент
displayname_template: "{{ if .Deleted }}Deleted account {{ .UserID }}{{ else }}{{ .FullName }}{{ end }}" # шаблон отображаемого имени ghost-юзеров
bridge: # логика поведения самого bridge
command_prefix: '!tg' # префикс команд bridge
personal_filtering_spaces: true # использовать personal filtering spaces
private_chat_portal_meta: true # тянуть метаданные для приватных чатов
async_events: false # асинхронную обработку событий не использовать
split_portals: false # не разделять порталы дополнительно
resend_bridge_info: false # не пересылать bridge info повторно
no_bridge_info_state_key: false # использовать обычный state key для bridge info
bridge_status_notices: errors # уведомления статуса показывать только при ошибках
unknown_error_auto_reconnect: null # поведение автопереподключения при неизвестных ошибках
unknown_error_max_auto_reconnects: 10 # максимум попыток автопереподключения
bridge_matrix_leave: false # не зеркалить выход Matrix-пользователя в Telegram
bridge_notices: false # не слать лишние bridge notice
tag_only_on_create: true # теги ставить только при создании комнаты
only_bridge_tags: [m.favourite, m.lowpriority] # какие теги bridge может трогать
mute_only_on_create: true # mute применять только при создании
deduplicate_matrix_messages: false # дедупликация Matrix-сообщений выключена
cross_room_replies: false # ответы между комнатами выключены
revert_failed_state_changes: false # не откатывать проваленные state-изменения
kick_matrix_users: true # bridge может кикать Matrix-пользователей при необходимости
enable_send_state_requests: false # отключить send_state requests
cleanup_on_logout: # что делать при logout Telegram-аккаунта
enabled: false # cleanup полностью выключен
manual:
private: nothing # для private ничего не делать
relayed: nothing # для relayed ничего не делать
shared_no_users: nothing # shared без юзеров — ничего
shared_has_users: nothing # shared с юзерами — ничего
bad_credentials:
private: nothing # при битых кредах private не чистить
relayed: nothing # relayed не чистить
shared_no_users: nothing # shared без юзеров не чистить
shared_has_users: nothing # shared с юзерами не чистить
relay: # режим relay bridge
enabled: false # relay выключен
admin_only: true # даже если включить — только админам
allow_bridge: true # bridge в relay-режиме разрешён
default_relays: [] # релеи по умолчанию отсутствуют
message_formats: # шаблоны сообщений relay-режима
m.text: "<b>{{ .Sender.DisambiguatedName }}</b>: {{ .Message }}" # формат обычного текста
m.notice: "<b>{{ .Sender.DisambiguatedName }}</b>: {{ .Message }}" # формат notice
m.emote: "* <b>{{ .Sender.DisambiguatedName }}</b> {{ .Message }}" # формат emote
m.file: "<b>{{ .Sender.DisambiguatedName }}</b> sent a file{{ if .Caption }}: {{ .Caption }}{{ end }}" # формат файла
m.image: "<b>{{ .Sender.DisambiguatedName }}</b> sent an image{{ if .Caption }}: {{ .Caption }}{{ end }}" # формат картинки
m.audio: "<b>{{ .Sender.DisambiguatedName }}</b> sent an audio file{{ if .Caption }}: {{ .Caption }}{{ end }}" # формат аудио
m.video: "<b>{{ .Sender.DisambiguatedName }}</b> sent a video{{ if .Caption }}: {{ .Caption }}{{ end }}" # формат видео
m.location: "<b>{{ .Sender.DisambiguatedName }}</b> sent a location{{ if .Caption }}: {{ .Caption }}{{ end }}" # формат локации
displayname_format: "{{ .DisambiguatedName }}" # шаблон displayname для relay
portal_create_filter: # фильтр создания порталов/комнат
mode: deny # режим deny-list
list: [] # пустой список запретов
always_deny_from_login: [] # логин не создаёт ничего из отдельного deny-списка
permissions: # права bridge
"*": relay # всем остальным минимальные relay-права
"matrix.example.com": user # всем локальным пользователям права user
"@admin:matrix.example.com": admin # этому юзеру права администратора bridge
database: # база bridge
type: postgres # используем PostgreSQL
uri: postgres://mautrix:YOUR_MAUTRIX_DB_PASSWORD@postgres/mautrix?sslmode=disable # строка подключения к БД в контейнере postgres
max_open_conns: 5 # максимум открытых соединений
max_idle_conns: 1 # максимум idle-соединений
max_conn_idle_time: null # время жизни idle-соединения не ограничено отдельно
max_conn_lifetime: null # общее время жизни соединения не ограничено отдельно
homeserver: # параметры Synapse, к которому цепляется bridge
address: https://matrix.example.com # адрес homeserver
domain: matrix.example.com # домен homeserver
software: standard # тип homeserver
status_endpoint: # отдельный endpoint статуса не задан
message_send_checkpoint_endpoint: # checkpoint endpoint не задан
async_media: false # асинхронную загрузку медиа не использовать
websocket: false # websocket-соединение к homeserver не использовать
ping_interval_seconds: 0 # периодические ping к homeserver выключены
appservice: # как bridge поднимает свой appservice endpoint
address: http://BRIDGE_HOST_IP:29317 # внешний адрес appservice
public_address: # публичный адрес отдельно не задан
hostname: 0.0.0.0 # слушать на всех интерфейсах контейнера/хоста
port: 29317 # порт appservice
id: telegram # ID appservice
bot: # настройки bridge-бота
username: telegrambot # localpart бота
displayname: Telegram bridge bot # отображаемое имя бота
avatar: mxc://maunium.net/tJCRmUyJDsgRNgqhOgoiHWbX # аватар бота
ephemeral_events: true # поддержка ephemeral-событий
async_transactions: false # транзакции обрабатывать синхронно
as_token: "YOUR_AS_TOKEN" # appservice token
hs_token: "YOUR_HS_TOKEN" # homeserver token
username_template: telegram_{{.}} # шаблон имени ghost-аккаунтов Telegram
matrix: # Matrix-специфичные настройки bridge
message_status_events: false # не слать отдельные status events
delivery_receipts: false # не синхронизировать delivery receipts
message_error_notices: true # показывать notice при ошибках сообщений
sync_direct_chat_list: true # синкать список direct chats
federate_rooms: true # разрешить федерацию комнат, созданных bridge
upload_file_threshold: 5242880 # порог, после которого вложения отправляются как файл
ghost_extra_profile_info: false # не тянуть дополнительную инфу профиля ghost-юзеров
analytics: # аналитика bridge
token: null # токен аналитики отсутствует
url: https://api.segment.io/v1/track # endpoint аналитики
user_id: null # user ID аналитики отсутствует
provisioning: # provisioning API bridge
shared_secret: YOUR_PROVISIONING_SECRET # общий секрет provisioning API
allow_matrix_auth: true # разрешить аутентификацию через Matrix
debug_endpoints: false # debug endpoints выключены
enable_session_transfers: false # перенос сессий выключен
public_media: # публичная раздача медиа bridge
enabled: false # выключено
signing_key: YOUR_PUBLIC_MEDIA_SIGNING_KEY # ключ подписи public media
expiry: 0 # срок жизни не используется
hash_length: 32 # длина хэша
path_prefix: /_mautrix/publicmedia # префикс пути
use_database: false # БД для public media не использовать
direct_media: # direct media server
enabled: false # выключено
server_name: discord-media.example.com # имя direct media сервера
well_known_response: # well-known ответ не задан
media_id_prefix: # префикс media ID не задан
allow_proxy: true # proxy для direct media разрешён
server_key: YOUR_DIRECT_MEDIA_SERVER_KEY # ключ сервера direct media
backfill: # обратная подгрузка истории
enabled: false # выключено
max_initial_messages: 50 # если включить, тянуть максимум 50 при первом заполнении
max_catchup_messages: 500 # максимум догоняющих сообщений
unread_hours_threshold: 720 # считать unread в пределах 720 часов
threads:
max_initial_messages: 50 # лимит для thread history
queue:
enabled: false # очередь backfill выключена
batch_size: 100 # размер пачки
batch_delay: 20 # задержка между пачками
max_batches: -1 # без лимита по числу пачек
max_batches_override: {} # override-параметры отсутствуют
double_puppet: # режим double puppeting
servers:
anotherserver.example.org: https://matrix.anotherserver.example.org # внешний сервер для double puppet
allow_discovery: false # авто-discovery выключен
secrets:
example.com: as_token:foobar # секрет для домена example.com
encryption: # настройки E2EE в bridge
allow: false # вообще не разрешать encryption
default: false # не включать по умолчанию
require: false # не требовать encryption
appservice: false # appservice encryption выключен
msc4190: false # поддержка MSC4190 выключена
msc4392: false # поддержка MSC4392 выключена
self_sign: false # self-signing выключен
allow_key_sharing: true # разрешить шаринг ключей
pickle_key: YOUR_PICKLE_KEY # ключ для хранения encrypted session state
delete_keys:
delete_outbound_on_ack: false # не удалять outbound ключи по ack
dont_store_outbound: false # outbound ключи хранить
ratchet_on_decrypt: false # ratchet при decrypt не делать
delete_fully_used_on_decrypt: false # полностью использованные ключи не удалять при decrypt
delete_prev_on_new_session: false # старые ключи не удалять при новой сессии
delete_on_device_delete: false # не удалять ключи при удалении устройства
periodically_delete_expired: false # периодическая очистка истёкших ключей выключена
delete_outdated_inbound: false # устаревшие inbound ключи не удалять
verification_levels:
receive: unverified # принимать и от непроверенных
send: unverified # отправлять непроверенным разрешено
share: cross-signed-tofu # ключи шарить при таком уровне доверия
rotation:
enable_custom: false # кастомную ротацию не включать
milliseconds: 604800000 # базовый интервал ротации
messages: 100 # или каждые 100 сообщений
disable_device_change_key_rotation: false # при смене девайса ротацию не отключать
env_config_prefix: null # префикс env-переменных для override отсутствует
logging: # логирование bridge
min_level: debug # минимальный уровень логов
writers:
- type: stdout # вывод логов в stdout контейнера
format: pretty-colored # красивый цветной формат
- type: file # также писать в файл
format: json # формат файла — JSON
filename: ./logs/bridge.log # путь до файла лога
max_size: 100 # максимальный размер файла
max_backups: 10 # хранить до 10 бэкапов логов
compress: false # старые логи не сжимать
Да, конфиг большой.
9. Логин и команды bridge
Бот:
@telegrambot:matrix.example.com
Полезные команды:
login
sync chats
search username
start-chat username
!tg join https://t.me/...
После логина bridge подтягивает чаты и дальше уже можно жить через Matrix.
10. Работа через CLI
Админку я не прикручивал, всё делается через CLI и Admin API.
Создать пользователя:
register_new_matrix_user -c /etc/matrix-synapse/homeserver.yaml http://127.0.0.1:8008
Список комнат:
curl -H "Authorization: Bearer YOUR_ADMIN_TOKEN" \
http://127.0.0.1:8008/_synapse/admin/v1/rooms?limit=1000 | jq
Участники комнаты:
curl -H "Authorization: Bearer YOUR_ADMIN_TOKEN" \
http://127.0.0.1:8008/_synapse/admin/v1/rooms/ROOM_ID/members | jq
11. Очистка истории и базы
Без чистки всё это добро нормально так разрастается.
Массовая очистка сообщений старше 30 дней:
curl -s -H "Authorization: Bearer YOUR_ADMIN_TOKEN" 'http://127.0.0.1:8008/_synapse/admin/v1/rooms?limit=1000' | jq -r '.rooms[].room_id' | while read -r room; do room_enc=$(printf '%s' "$room" | jq -sRr @uri); echo "=== PURGE $room ==="; curl -s -X POST "http://127.0.0.1:8008/_synapse/admin/v1/purge_history/$room_enc" -H "Authorization: Bearer YOUR_ADMIN_TOKEN" -H "Content-Type: application/json" -d "{\"purge_up_to_ts\":$(date -d '30 days ago' +%s000)}"; echo; sleep 2; done
Эта команда удалит сообщения старше 30 дней.
Удаление локального медиа старше 30 дней:
curl -X POST \
"http://127.0.0.1:8008/_synapse/admin/v1/media/delete?before_ts=$(date -d '30 days ago' +%s000)" \
-H "Authorization: Bearer YOUR_ADMIN_TOKEN"
Очистка media cache:
curl -X POST \
"http://127.0.0.1:8008/_synapse/admin/v1/purge_media_cache?before_ts=$(date -d '30 days ago' +%s000)" \
-H "Authorization: Bearer YOUR_ADMIN_TOKEN"
Сжать базу:
su - postgres -c "psql -d synapse -c 'VACUUM FULL;'"
Проверить размер базы:
su - postgres -c "psql -d synapse -c \"SELECT pg_size_pretty(pg_database_size('synapse'));\""
Проверить самые жирные таблицы:
su - postgres -c "psql -d synapse -c \"SELECT relname AS table, pg_size_pretty(pg_total_relation_size(relid)) AS size FROM pg_catalog.pg_statio_user_tables ORDER BY pg_total_relation_size(relid) DESC LIMIT 20;\""
12. Пару заметок например
- Сначала казалось, что хватит просто поднять Synapse и всё. Хуй там. Без нормального nginx и reverse были странности.
.well-known— обязательная штука. Пока не сделал нормально, клиенты вели себя через жопу.- Нельзя писать заметку обрезками конфигов. Через неделю уже не помнишь, что именно там было реально важно.
- SQLite для такого сетапа вообще мимо. Только PostgreSQL.
- Bridge и история очень быстро раздувают базу, если этим не заниматься.
13. Что ставилось по факту и где что лежит
Чтобы потом не вспоминать по кускам, что вообще ставилось и где это всё живёт, вот коротко по установке уже без простыней конфигов.
На сервере с Synapse:
apt update
apt install -y matrix-synapse-py3 postgresql nginx certbot
Если нужен Docker для bridge:
apt update
apt install -y docker.io docker-compose-plugin
systemctl enable docker
systemctl start docker
Что где лежит:
/etc/matrix-synapse/homeserver.yaml— основной конфиг Synapse/etc/matrix-synapse/log.yaml— настройки логов Synapse/etc/matrix-synapse/homeserver.signing.key— ключ подписи сервера, его терять нельзя/var/lib/matrix-synapse/media— локальное хранилище медиа/etc/matrix-synapse/mautrix-telegram.yaml— registration-файл bridge для Synapse/etc/nginx/sites-available/— nginx-конфиги сайтов/etc/nginx/sites-enabled/— активные nginx-конфиги через symlink/opt/mautrix-telegram/docker-compose.yml— compose-файл bridge/opt/mautrix-telegram/data/config.yaml— основной конфиг mautrix-telegram/opt/mautrix-telegram/data/logs/— логи bridge, если включена запись в файл/var/lib/postgresql/— данные PostgreSQL, если это локальная установка из apt/var/lib/docker/— данные docker, если bridge и postgres крутятся в контейнерах
Важно: registration YAML и основной config.yaml — это не одно и то же.
/etc/matrix-synapse/mautrix-telegram.yaml— registration-файл, который читает сам Synapse черезapp_service_config_files/opt/mautrix-telegram/data/config.yaml— основной конфиг самого bridge, где уже лежат токены, адрес homeserver, appservice и прочие настройки
Element Web:
Если ставить его просто как статику без всяких плясок, то достаточно распаковать сборку в отдельную папку, например:
/var/www/element— файлы Element Web/var/www/element/config.json— его конфиг
Что обычно делается после установки:
- создаётся база и пользователь PostgreSQL под Synapse
- правится
homeserver.yaml - подключается nginx перед Synapse
- делаются сертификаты
- отдельно поднимается Element на своём домене
- отдельно поднимается mautrix-telegram через Docker
- registration-файл bridge подключается в
app_service_config_files - после правок Synapse и nginx перезапускаются
Полезно помнить:
- Synapse как сервис обычно живёт как
matrix-synapse - nginx — обычный
systemctl restart nginx - bridge в docker проверяется через
docker compose psиdocker compose logs -f - если что-то не работает, смотреть надо сразу в три места: Synapse, nginx и bridge
14. PostgreSQL: пользователь и база
В статье выше просто указаны параметры БД, но сам момент создания базы — это отдельная история.
Делается это под пользователем postgres:
su - postgres
Создаём пользователя:
createuser synapse
Создаём базу:
createdb -O synapse synapse
И задаём пароль:
psql
ALTER USER synapse WITH PASSWORD 'YOUR_PASSWORD';
\q
После этого эти данные уже прописываются в homeserver.yaml.
15. Токены bridge (as_token / hs_token)
В конфиге bridge и registration-файле есть два токена:
as_tokenhs_token
Их никто тебе не выдаёт — их надо сгенерировать самому.
Самый простой вариант — просто взять случайные строки:
openssl rand -hex 32
Сгенерил два значения — вставил:
- в
/etc/matrix-synapse/mautrix-telegram.yaml - в
/opt/mautrix-telegram/data/config.yaml
значения должны совпадать в обоих файлах, иначе bridge просто не подключится.
16. Telegram API (api_id / api_hash)
Без этого bridge вообще не работает.
Получается это тут:
https://my.telegram.org
Дальше:
- заходишь под своим номером
- идёшь в API development tools
- создаёшь приложение
- получаешь
api_idиapi_hash
И вставляешь их в config.yaml bridge.
Это делается один раз, дальше оно просто работает.
17. Первый логин в bridge
После запуска bridge он сам по себе никуда не подключён.
Нужно написать боту:
@telegrambot:matrix.example.com
И выполнить:
login
Дальше он попросит номер, код и при необходимости пароль (если включён 2FA).
После логина можно:
sync chats
И он подтянет диалоги.
Всё, дальше уже обычная работа через Matrix.
18. systemd и сервис Synapse
Пакет matrix-synapse-py3 уже создаёт systemd-сервис автоматически.
То есть ничего руками писать не надо.
Основные команды:
systemctl status matrix-synapse
systemctl restart matrix-synapse
systemctl enable matrix-synapse
Если Synapse не стартует — смотреть сюда:
journalctl -u matrix-synapse -xe
Обычно там сразу видно, что именно не так (БД, конфиг, токены и т.д.).
19. Сертификаты Let's Encrypt
В конфиге nginx я сразу использовал сертификаты, но не показал, как они делаются.
Самый простой вариант:
certbot --nginx -d matrix.example.com -d element.example.com
Либо через DNS-челлендж, если не хочешь светить сервер напрямую.
Сертификаты после этого лежат тут:
/etc/letsencrypt/live/matrix.example.com//etc/letsencrypt/live/element.example.com/
И nginx уже использует их напрямую.
Итог
Ну вроде все. Если что-то мною было упущено, потом добавлю.
![]()