К - конкурентоспособность
К - конкурентоспособность

В свете небезызвестных событий в законотворческой области, столкнулся с необходимостью организовать канал для общения внутри семьи, т.к. пользоваться звонками в популярных мессенджерах – значит быть подверженным угрозам со стороны мошенников и спонсировать терроризм, а звонить по мобильной сети с ее ужасным качеством связи (несмотря на все потуги операторов в VoLTE и прочие VoiceHD)  в 2025 году – какой-то моветон. А MAX на мои устройства устанавливаться отказался, не знаю почему, я даже не пробовал. Может быть потому что я слишком мало времени провожу в лифте и на парковке?

Еще с 2013 года я заразился NAS'остроением, поэтому проблем с выбором VDS/VPS у меня небыло: домашний "NAS" за это время превратился из WD MyCloud с установленным OpenMediaVault в уже вполне себе самостоятельный мини-сервер, но все с тем же OpenMediaVault в качестве хостовой системы (да-да, Debian с веб-мордой, я пробовал и Proxmox и все остальное – не зашло по тем или иным причинам. Если статья зайдет аудитории Хабра - напишу подробно про все этапы превращения домашней файлопомойки во вполне себе функциональный ящичек).

Одиноко скучающий детёныш сервера
стоит-пылится
стоит-пылится

В качестве среды для всего, что мы сегодня будем городить, я предпочел Docker. А разворачивать все это лично мне удобнее через Portainer.

Первым вопросом встал выбор протокола, который будет использоваться для звонков вместо всех этих мошеннических Telegram и террористических WhatsApp.

Судя по форумам, в качестве протоколов самое популярное и доступное для селфхостинга – Matrix и XMPP. Определиться, что же лучше, по описаниям и отзывам я не смог – поэтому решил поставить сразу два сервера, под каждый протокол, и уже по ходу дела выбрать победителя.

В качестве сервера Matrix выбор пал на Synapse, в качестве сервера XMPP - Ejabberd. Возможно, есть что-то лучше, но у этих двоих довольно понятные мануалы и оба без проблем разворачиваются в Docker.

При выборе клиентов я руководствовался по большей части эстетическими соображениями, выбирая на основании юзабельности и симпатичности клиентов, ведь использовать все это будут в том числе родители и супруга, а как известно, женщина – лучший показатель применимости технологии. Если женщина начала чем-то пользоваться – значит оно действительно работает. Больше всего приглянулся Element для Matrix (не X, в Х чтоб поднять звонки нужно было слишком уж заморочаться) –  мультиплатформенный, без лишних рюшечек, и Conversation для XMPP, но так как с мультиплатформенностью у него туго - на iOS в качестве клиента выбор пал на Monal.

Element на Android и iOS соответственно
Клиенты XMPP на Android и iOS соответственно

Итого "стек" технологий выглядел примерно так: разворачивать все будем в Docker с помощью Portainer, для неискушенного командной строкой пользователя это вполне доступный метод.

Протокол Matrix у нас будет обеспечиваться сервером Synapse, XMPP –  Ejjaberd.

Так же для работы аудиозвонков нам потребуется сервер Coturn, который будет пробиваться за NAT (с ним кстати плясок с бубнами оказалось больше всего).

На сервере у меня установлен Nginx Proxy Manager (далее для краткости – NPM), который разрешает доменные имена третьего уровня и получает сертификаты, через него наши клиенты и будут обращаться к серверной части.

Если представить все в виде блок-схемы, то получим такое:

Стильно, модно, молодежно
Стильно, модно, молодежно

В этом руководстве я подразумеваю, что у вас уже установлен Docker, Portainer, NPM, имеется доменное имя и вы +/- представляете, что с этим всем делать. Я не претендую на идеальную правильность приведенных ниже решений – я у мамы инженер-физик, а не программист, поэтому с удовольствием прислушаюсь к вашим советам и рекомендациям. Итак, начинаем.

 Первым что нам необходимо сделать – зарегистрировать записи в DNS. Представим что ваш личный сервер имеет адрес myserver.ru. Идем к регистратору и создаем следующие записи:

  • А‑запись: synapse.myserver.ru — для обращения к серверу Synapse;

  • А‑запись: element.myserver.ru — для обращения к web‑клиенту Element;

  • А‑запись: xmpp.myserver.ru — для обращения к Ejabberd, через нее же будем подключаться к Coturn и возьмем для него сертификаты;

  • SRV‑запись: xmpp‑client.tcp.myserver.ru с указанием target xmpp.myserver.ru и порта 5222 — для подключения клиентов xmpp;

  • SRV‑запись: xmpp‑server.tcp.myserver.ru с указанием target xmpp.myserver.ru и порта 5269 — для подключения федерации серверов xmpp.

Вторым – создадим папки на сервере, где будут храниться конфиги и прочие потроха сервисов. Для упрощения будем считать что вы выделили под все это папку в разделе /opt и назвали ее /myservice.

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

mkdir -p /opt/myservice/ejabberd/{certs,conf,data,logs,upload} /opt/myservice/matrix/{data,element,pg}

Теперь создадим конфиги под наши будущие сервисы:

Сервер Ejabberd:

делаем в терминале

nano /opt/myservice/ejabberd/conf/ejabberd.yml

и вставляем туда следующий конфиг:

ejabberd.yml
loglevel: 4                # Уровень логирования (0 - минимум, 5 - максимум). 4 = подробные логи
log_rotate_size: 10485760  # Максимальный размер файла лога (10 МБ)
log_rotate_count: 1        # Количество сохраняемых старых файлов логов (1 — только один архив)

hosts:                     # Список доменов, обслуживаемых сервером XMPP
  - "xmpp.myserver.ru"     # Основной XMPP-домен сервера

acl:                       # Access Control Lists — списки доступа
  admin:                   # Группа администраторов
    user:
      - "admin@xmpp.myserver.ru"  # Пользователь с правами администратора

acme:
  auto: false              # Автоматическое получение SSL-сертификатов через Let's Encrypt выключено

access_rules:              # Правила доступа для различных сервисов
  local:                   # Локальные подключения (например, скрипты)
    allow: all             # Разрешены всем
  c2s:                     # Client-to-Server (подключение клиентов)
    allow: all             # Разрешено всем пользователям
  s2s:                     # Server-to-Server (федерация XMPP)
    allow: all             # Разрешено соединение с любыми серверами
  configure:               # Доступ к административным функциям
    allow: admin           # Разрешено только администраторам

listen:                    # Конфигурация прослушиваемых портов
  - port: 5222             # TCP-порт для клиентских подключений (c2s)
    module: ejabberd_c2s   # Модуль для Client-to-Server соединений
    starttls: true         # Поддержка STARTTLS
    starttls_required: true# Обязательно использовать TLS
    max_stanza_size: 65536 # Максимальный размер XMPP-сообщения (в байтах)
    shaper: c2s_shaper     # Ограничение скорости для клиентов

  - port: 5269             # TCP-порт для серверных соединений (s2s)
    module: ejabberd_s2s_in# Модуль для Server-to-Server соединений
    max_stanza_size: 131072# Максимальный размер пакета (128 КБ)
    shaper: s2s_shaper     # Ограничение скорости для серверов

  - port: 5280             # HTTP-интерфейс
    module: ejabberd_http  # Модуль веб-доступа
    request_handlers:      # Обработчики путей
      "/admin": ejabberd_web_admin # Веб-интерфейс админики
      "/api": mod_http_api        # HTTP API
      "/bosh": mod_bosh           # BOSH (HTTP-подключение XMPP-клиентов)
      "/ws": ejabberd_http_ws     # WebSocket-подключения
      "/upload": mod_http_upload  # HTTP File Upload
    tls: false             # HTTPS не используется (работает только через HTTP)

  - port: 3478             # UDP-порт для STUN/TURN (VoIP, WebRTC)
    transport: udp
    module: ejabberd_stun  # Модуль STUN/TURN
    use_turn: true         # Включен TURN (ретрансляция медиа-трафика)
    turn_min_port: 49152   # Минимальный порт для медиапотоков
    turn_max_port: 65535   # Максимальный порт для медиапотоков

  - port: 5349             # TCP-порт для STUN/TURN с TLS
    transport: tcp
    module: ejabberd_stun
    use_turn: true
    tls: true              # Поддержка шифрования TLS
    turn_min_port: 49152
    turn_max_port: 65535

certfiles:                 # Пути к SSL-сертификатам
  - "/etc/letsencrypt/fullchain.pem" # Полная цепочка сертификатов
  - "/etc/letsencrypt/privkey.pem"   # Приватный ключ

default_db: internal       # Используется встроенная база данных ejabberd (Mnesia)

modules:                   # Подключенные модули ejabberd
  mod_adhoc: {}            # Ad-hoc команды (XEP-0050)
  mod_admin_extra: {}      # Дополнительные административные функции
  mod_announce:            # Модуль объявлений (broadcast)
    access: admin          # Только администраторы могут рассылать
  mod_avatar: {}           # Поддержка аватаров пользователей
  mod_blocking: {}         # Блокировка контактов (XEP-0191)
  mod_bosh: {}             # Поддержка BOSH
  mod_caps: {}             # Entity Capabilities (XEP-0115)
  mod_carboncopy: {}       # Сообщения копируются на все устройства (XEP-0280)
  mod_client_state: {}     # Состояние клиента (idle/active)
  mod_configure: {}        # Конфигурация через XMPP
  mod_disco: {}            # Service Discovery (XEP-0030)
  mod_http_api: {}         # REST API
  mod_http_upload:         # HTTP File Upload (XEP-0363)
    put_url: "https://xmpp.myserver.ru/upload" # URL для загрузки файлов
    get_url: "https://xmpp.myserver.ru/upload" # URL для скачивания файлов
    docroot: "/home/ejabberd/upload"           # Папка для хранения файлов
    max_size: 104857600    # Максимальный размер файла (100 МБ)
  mod_last: {}             # Последняя активность пользователя (XEP-0012)
  mod_mam:                 # Message Archive Management (XEP-0313)
    default: always        # Всегда сохранять сообщения
  mod_muc:                 # Multi-User Chat (XEP-0045)
    access: all            # Доступен всем
    access_create: all     # Любой может создавать комнаты
    access_persistent: all # Разрешены постоянные комнаты
    access_admin: admin    # Администрирование комнат — только админ
  mod_ping: {}             # Ping (XEP-0199)
  mod_privacy: {}          # Privacy Lists (XEP-0016)
  mod_private: {}          # Хранение приватных данных (XEP-0049)
  mod_pubsub:              # PubSub (XEP-0060)
    access_createnode: all # Разрешено всем создавать узлы
    plugins:               # Подключенные плагины
      - "flat"             # Простая структура
      - "pep"              # Personal Eventing Protocol (XEP-0163)
    force_node_config:     # Настройки по умолчанию для узлов
      "urn:xmpp:microblog:0": # Микроблоги
        deliver_payloads: true # Доставлять данные сразу
        notify_retract: true   # Уведомлять об удалении
        persist_items: true    # Хранить данные
        max_items: 100         # Максимум 100 сообщений
  mod_push: {}             # Push-уведомления
  mod_push_keepalive: {}   # Поддержка keepalive для push
  mod_register:            # Регистрация пользователей
    access: none           # Отключена (запрет регистрации)
    ip_access: none        # Нельзя регистрироваться ни с какого IP
    registration_watchers: # Уведомления о регистрации
      - "admin@xmpp.myserver.ru" # Администратор получает уведомления

Сохраняем и переходим к конфигу Synapse.

Делаем:

nano /opt/myservice/matrix/data/homeserver.yaml

и вставляем в него следующее:

homeserver.yaml
server_name: "synapse.myserver.ru" # Основное имя сервера Matrix 
pid_file: /data/homeserver.pid     # Файл, где будет храниться PID процесса Synapse

# Задаем порты и протоколы для клиентов и федерации
listeners:
  - port: 8008              # Порт, на котором работает Synapse
    tls: false              # TLS выключен 
    type: http              # Тип протокола
    x_forwarded: true       # Разрешает доверять заголовку X-Forwarded-For от прокси
    resources:
      - names: [client, federation]  # Разрешённые ресурсы: клиентские запросы и федерация
        compress: false              # Отключено сжатие (для производительности)

# Настройки базы данных (PostgreSQL)
database:
  name: psycopg2             # Используемый драйвер PostgreSQL
  txn_limit: 10000           # Максимальное число транзакций в одном пуле
  args:
    user: synapse            # Пользователь БД
    password: mypassword     # Пароль
    database: synapse        # Имя базы данных
    host: synapse-db         # Хост базы данных (контейнер/сервер)
    port: 5432               # Порт PostgreSQL
    cp_min: 5                # Минимальное число соединений в пуле
    cp_max: 10               # Максимальное число соединений в пуле

# Настройки логов (раскомментировать при необходимости)
#log_config: "log.yaml"

# Директория для хранения медиа (файлы, изображения и т.п.)
media_store_path: /data/media_store

# Убирает предупреждение о key сервере
suppress_key_server_warning: true

# Максимальный размер загружаемых файлов
max_upload_size: 400M

# Регистрация новых пользователей запрещена (false)
enable_registration: false

# Включена федерация с другими Matrix-серверами
matrix_synapse_federation_enabled: true
matrix_synapse_federation_port_enabled: true

# Общий секрет для регистрации пользователей через API
registration_shared_secret: "mypassword"

# Разрешает поиск по всем пользователям сервера
search_all_users: true

# Предпочитать локальных пользователей (вместо удалённых из федерации)
prefer_local_users: true

# Настройки TURN-сервера (для звонков и WebRTC)
turn_uris:
  - "turn:xmpp.myserver.ru:3478?transport=udp"
  - "turn:xmpp.myserver.ru:3478?transport=tcp"
  - "turns:xmpp.myserver.ru:5349?transport=udp"
  - "turns:xmpp.myserver.ru:5349?transport=tcp"

turn_shared_secret: "mysecret"      # Секретный ключ для генерации учетных данных TURN
turn_user_lifetime: 86400000        # Время жизни учётной записи (мс)
turn_allow_guests: true             # Разрешить гостям использовать TURN
turn_server_name: "synapse.myserver.ru"  # Имя TURN-сервера

# Список администраторов сервера
admin_users:
  - "@admin:synapse.myserver.ru"

# Отправлять анонимную статистику разработчикам Synapse
report_stats: false

# Секретные ключи для подписи токенов
macaroon_secret_key: "mnogobukv"
form_secret: "mnogobukv"

# Ключ для подписи событий (не удалять - сообщения потом не расшифруются)
signing_key_path:  "/data/synapse.myserver.ru.signing.key"

# Экспериментальные функции Matrix (включены новые спецификации)
experimental_features:
  call: true
  msc3266_enabled: true
  msc4222_enabled: true
  msc4140_enabled: true

# Максимальная задержка доставки события
max_event_delay_duration: 24h

# Ограничения скорости отправки сообщений (rate limiting)
rc_message:
  per_second: 0.5   # Средняя скорость (0.5 сообщений в секунду)
  burst_count: 30   # Максимальный "всплеск" сообщений

# Ограничения на управление отложенными событиями
rc_delayed_event_mgmt:
  per_second: 1
  burst_count: 20

# Политика хранения медиа (очистка старых файлов)
media_retention:
    local_media_lifetime: 120d   # Срок хранения локальных файлов
    remote_media_lifetime: 120d  # Срок хранения удалённых файлов

затем

nano /opt/myservice/matrix/config.json

и вставляем:

config.json
{
    "homeserver_url": "https://synapse.myserver.ru",
    "enable_presence_by_hs_url": {
        "https://synapse.myserver.ru": true
    },
    "turn": {
        "urls": [
            "turn:xmpp.myserver.ru:3478?transport=udp",
            "turn:xmpp.myserver.ru:3478?transport=tcp",
            "turns:xmpp.myserver.ru:5349?transport=udp",
            "turns:xmpp.myserver.ru:5349?transport=tcp"
        ],
        "secret": "mysecret",
        "expiry": 864000000,
        "turn_allow_guest": true
    },
    "terms_and_conditions_links": [
        {
            "url": "https://myserver.ru/privacy",
            "text": "Privacy Policy"
        },
        {
            "url": "https://myserver.ru/cookie-policy",
            "text": "Cookie Policy"
        }
    ],
    "privacy_policy_url": "https://myserver.ru/privacy"
}

Теперь настроим клиент Element-web:

снова создаем файл

nano /opt/myservice/matrix/element/config.json

и вставляем

config.json
{
    "default_server_config": {
        "m.homeserver": {
            "base_url": "https://synapse.myserver.ru",
            "server_name": "synapse.myserver.ru"
        },
        "io.element.call": {
            "url": "https://call.element.io"
        },
         "io.element.e2ee": {
             "default": true
        },
        "m.identity_server": {
            "base_url": "https://vector.im"
        }
    },
    "disable_custom_urls": true,
    "disable_guests": true,
    "disable_login_language_selector": true,
    "disable_3pid_login": true,
    "brand": "My Personal Server",
    "enable_element_call": true,
    "integrations_ui_url": "https://scalar.vector.im/",
    "integrations_rest_url": "https://scalar.vector.im/api",
    "integrations_widgets_urls": [
        "https://scalar.vector.im/_matrix/integrations/v1",
        "https://scalar.vector.im/api",
        "https://scalar-staging.vector.im/_matrix/integrations/v1",
        "https://scalar-staging.vector.im/api",
        "https://scalar-staging.riot.im/scalar/api"
    ],
    "bug_report_endpoint_url": "https://element.io/bugreports/submit",
    "uisi_autorageshake_app": "element-auto-uisi",
    "default_country_code": "RU",
    "show_labs_settings": false,
    "features": {},
    "default_federate": false,
    "default_theme": "light",
    "room_directory": {
        "servers": ["synapse.myserver.ru"]
    },
    "element_call": {
        "url": "https://call.element.io",
        "participant_limit": 8,
        "brand": "Element Call"
    },
    "enable_presence_by_hs_url": {
        "https://synapse.myserver.ru": true
    },
    "terms_and_conditions_links": [
        {
            "url": "https://element.io/privacy",
            "text": "Privacy Policy"
        },
        {
            "url": "https://element.io/cookie-policy",
            "text": "Cookie Policy"
        }
    ],
    "privacy_policy_url": "https://element.io/cookie-policy"
}

Не забываем все это сохранять и убедитесь что ваш Docker имеет права на эти папки и файлы!

Идем запускать контейнеры.

Первым пойдет Coturn.

Идем в Portainer, раздел Stacks и нажимаем Add Stack:

Пишем в названии стека coturn, в окно web-editor вставляем следующее: 

coturn
services:
  coturn:
    image: coturn/coturn               # Образ coturn 
    container_name: coturn             # Имя контейнера
    restart: unless-stopped            # Автоперезапуск
    ports:                             # Пробрасываем порты наружу
      - "3478:3478/udp"                # TURN (UDP)
      - "3478:3478/tcp"                # TURN (TCP)
      - "5349:5349/tcp"                # TLS-over-TCP (TURNs)
      - "5349:5349/udp"                # TLS-over-UDP (TURNs)
      - "49152-65535:49152-65535/udp"  # Диапазон портов для передачи медиаданных (нужно открыть на роутере)
    volumes: 
      - /opt/myservices/ejabberd/certs:/etc/letsencrypt:ro  # Подключаем сертификаты Let's Encrypt (только для чтения)
    command: >
      -n                                # Режим запуска
      --turn_allow_guests=true          # Разрешаем гостевые подключения без учётки
      --use-auth-secret                 # Используем динамическую аутентификацию
      --static-auth-secret=mysecret     # Секрет для генерации временных логинов/паролей
      --rearm=xmpp.myserver.ru          # Разрешаем подключение 
      --cert=/etc/letsencrypt/fullchain.pem # Путь к TLS-сертификату
      --pkey=/etc/letsencrypt/privkey.pem   # Путь к приватному ключу

Запускаем все это дело и смотрим в логи. Должен ругнуться на сертификаты, их пока нет, но будут. Останавливаем чтоб не мешал.

 Далее поднимаем Ejabberd:

Аналогично как и с Coturn, открываем окно Web-editor, обзываем стек и вставляем туда следующее:

ejabberd
services:
  ejabberd:
    image: ejabberd/ecs                  # Образ ejabberd 
    container_name: ejabberd             # Имя контейнера
    restart: unless-stopped              # Автоперезапуск контейнера 
    environment:
      - ERLANG_NODE=ejabberd@localhost  # Имя Erlang-ноды, используемой ejabberd
      - XMPP_DOMAIN=xmpp.myserver.ru    # Домен XMPP сервера (Jabber ID будет вида user@xmpp.myserver.ru)
    volumes:
      - /opt/myservice/ejabberd/conf:/home/ejabberd/conf     # Конфиги ejabberd
      - /opt/myservice/ejabberd/data:/home/ejabberd/database # Хранилище данных (базы)
      - /opt/myservice/ejabberd/logs:/home/ejabberd/logs     # Логи сервера
      - /opt/myservice/ejabberd/upload:/home/ejabberd/upload # Файлы, загружаемые пользователями
      - /opt/myservice/ejabberd/certs:/etc/letsencrypt:ro   # Сертификаты Let's Encrypt для TLS (только чтение)
    ports:
      - "5222:5222" # Клиентский порт XMPP (TCP, STARTTLS)
      - "5269:5269" # Порт федерации XMPP (сервер-сервер)
      - "5280:5280" # HTTP(S) порты (веб-админка)

Запускаем, останавливаем.

Далее – стек Synapse:

synapse
services:
  synapse:
    image: matrixdotorg/synapse:latest        # Образ Synapse 
    container_name: matrix-synapse            # Имя контейнера
    restart: unless-stopped                    # Автоперезапуск контейнера
    environment:
      - SYNAPSE_SERVER_NAME=synapse.myserver.ru  # Домен сервера
      - SYNAPSE_REPORT_STATS=false               # Отправка анонимной статистики разработчикам
    volumes:
      - /opt/myservice/matrix/data:/data       # Тут храним конфиги Synapse 
    ports:
      - 8008:8008   # Порт для клиентов 
      - 8448:8448   # Порт для федерации с другими серверами 
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8008/health"] # Проверка что не сдох
      interval: 30s  # Проверять каждые 30 секунд
      timeout: 10s   # Таймаут на выполнение healthcheck
      retries: 5     # Количество повторных попыток

  element-web:
    image: vectorim/element-web:latest         # Образ Element Web 
    container_name: element-web
    restart: unless-stopped
    volumes:
      - /opt/myservice/matrix/element/config.json:/app/config.json  # Конфиги Element Web
    ports:
      - 8009:80   # Внешний порт для доступа к веб-клиенту

  synapse-db:
    image: docker.io/postgres:latest          # Образ PostgreSQL для Synapse
    container_name: synapse-db
    hostname: synapse-db
    restart: unless-stopped
    environment:
      TZ: "Europe/Moscow"                     # Часовой пояс
      POSTGRES_USER: synapse                  # Пользователь БД
      POSTGRES_PASSWORD: password             # Пароль БД
      POSTGRES_INITDB_ARGS: --encoding=UTF-8 --lc-collate=C --lc-ctype=C # Инициализация базы с правильной локалью
    volumes:
      - /opt/myservice/matrix/pg:/var/lib/postgresql/data  # Данные БД сохраняются вне контейнера

  synapse-admin:
    image: awesometechnologies/synapse-admin:latest  # Веб-интерфейс для управления Synapse
    container_name: synapse-admin
    restart: unless-stopped
    ports:
      - 8007:80  # Внешний порт для доступа к веб-админке
    environment:
      - SYNAPSE_ADMIN_API=http://matrix-synapse:8008  # Адрес админки

Запустили, остановили. Идем получать сертификаты.

Открываем NPM и добавляем проксируемые хосты:

Для xmpp.myserver.ru:

Во вкладке SSL не забываем получить сертификаты, их мы будем активно использовать далее.

Важное примечание для xmpp – чтоб клиенты могли обмениваться файлами – ему надо указать куда эти файлы ложить и откуда брать, поэтому в разделе Custom Location делаем так, указывая IP вашего сервера в локалке:

Для synapse.myserver.ru:

Тут тоже есть нюанс, чтоб Element нормально работал – ему нужен .well-known, поэтому в раздел Advanced вставляем следующее:

.well-known
location /.well-known/matrix/server {
    default_type application/json;
    add_header Access-Control-Allow-Origin *;
    return 200 '{"m.server": "synapse.myserver.ru:443"}';
}

location /.well-known/matrix/client {
    default_type application/json;
    add_header Access-Control-Allow-Origin *;
    return 200 '{
        "m.homeserver": {
            "base_url": "https://synapse.myserver.ru"
        },
        "io.element.e2ee": {
            "default": true
        }
    }';
}
proxy_read_timeout 360s;
proxy_buffering off;

Для Element-Web:

Тут тоже надо дописать в Advanced:

location
location /.well-known/matrix/client {
    add_header Access-Control-Allow-Origin "*";
    add_header Content-Type "application/json";
    return 200 '{"m.homeserver":{"base_url":"https://synapse.myserver.ru"}}';
}

По итогу всего этого у вас должно получиться примерно так:

Хосты настроили, теперь идем за сертификатами. Которые вы естественно для каждой записи в NPM получили. Не забыли же? По умолчанию NPM сохраняет сертификаты к себе в папку /live/npm-xxx, где ххх – номер проксируемого хоста. Поэтому идем за ними. Смотрим какой номер у сертификата xmpp.myserver.ru:

В нашем случае 142, копируем их к нам в папку, не забываем менять путь к папке с сертификатами на свой! В моем случае папка NPM расположена по пути /portainer/npm/live/:

cp /portainer/npm/live/npm-142/fullchain.pem /opt/myservice/ejabberd/certs/fullchain.pem
cp /portainer/npm/live/npm-142/privkey.pem /opt/myservice/ejabberd/certs/privkey.pem

Когда сертификаты скопированы, возвращаемся в Portainer и запускаем наши контейнеры.

Для Ejabberd нужно создать админскую учетку, для этого заходим в консоль и выполняем следующее:

docker exec -it ejabberd ejabberdctl register admin myserver.ru password

Для Synapse мы его уже прописали в homeserver.yaml

После всего проделанного

Админка Ejabberd будет доступна по адресу ip:5280/admin
Админка Synapse будет доступна по адресу ip:8007
Web-клиент Element будет находится по адресу element.myserver.ru

Если нигде не ошиблись – можно скачивать понравившиеся вам клиенты и подключаться.

В итоге, попользовавшись всем этим несколько дней, остановился на Synapse и Element. Хотя визуально Conversation и выглядит просто отлично - ничего лишнего, все работает, но XMPP мне не зашел, подвели устройства Apple, на которых ни один из клиентов не захотел работать в фоне. Сообщения можно было получить только открыв приложение и подождав пока они загрузятся. Плюс не понравилось хранение и передача файлов по сути ссылками на открытые папки. Если кто подскажет путь решения этих проблем - буду благодарен.

Одним из жирных плюсов для себя так же выделил разделение потоков информации - новости, мемчики и котики в Telegram, семья - в Element.

Комментарии (124)


  1. Ivnika
    01.09.2025 11:16

    Статью еще не успел прочитать, но заглавная картинка не бровь, а в глаз! ))


  1. PainHedgehog
    01.09.2025 11:16

    Интересная идея


  1. ddr5
    01.09.2025 11:16

     а звонить по мобильной сети с ее ужасным качеством связи

    Еще до 2022 качество звонков в мессенджерах часто было отстойным, потому что нужен стабильный интернет у обоих участников. А, как правило, это не так.

    Ни разу не оправдываю блокировку звонков, но лично они меня всегда люто бесили по причине того, что позвонить по телефону гораздо быстрее. А еще входящие звонки в мессенджер часто прячутся куда-то в шторку, откуда их нужно выковыривать. Но многие почему-то упорно пытались вызвонить меня в мессенджере.

    Так что, как ни прискорбно, но лучше старой доброй мобильной связи еще ничего не придумали. А то что работало, сломали.


    1. unwrecker
      01.09.2025 11:16

      Битрейт у мобильной связи маловат. После мессенджеров - такое себе. Опять же, по мобильной связи нельзя говорить с гарнитуры на компе.


      1. ddr5
        01.09.2025 11:16

        Да пофиг на битрейт, важно чтобы собеседник тебя нормально слышал, а ты его, без постоянного "але але меня слышно ква-ква".

        Опять же, по мобильной связи нельзя говорить с гарнитуры на компе.

        Увы, это так и тут на помощь придут три волшебные буквы.


        1. unwrecker
          01.09.2025 11:16

          Ну тут выбор меджу чистым узнаваемым голосом, но требующим хорошей сети, и маленьким битрейтом с сильной компрессией, который работает везде.

          3 волшебных буквы - это SIP? Но как получить его на тот же мобильный номер?


          1. lifespirit
            01.09.2025 11:16

            Купить любой сип номер у кучи сип провайдеров и настроить безусловную переадресацию на симке мобильного телефона.


            1. unwrecker
              01.09.2025 11:16

              Вариант, но я так понимаю, что переадресация будет с поминутной оплатой?


              1. lifespirit
                01.09.2025 11:16

                Нет. однократное списание за факт переадрескации. голос не замыкается на мобильном номере при переадресации. Просто звонок сигнализацией уходит на сип провайдера сразу.


              1. lifespirit
                01.09.2025 11:16

                Проверил у своего оператора, действительно теперь поминутная оплата. Просто жесть.... Остаётся только открывать ИП и на него открывать sip стык с оператором. Но это уже не так просто и дёшево.

                UPD: ну или забить вообще на номер симки и пользоваться только SIP номером.


                1. furie
                  01.09.2025 11:16

                  только учтите, что с 1 сентября операторы связи обязаны будут определять бизнес-номера, которые принадлежат компаниям и ИП, чтобы пользователи знали, кто им звонит.


                  1. lifespirit
                    01.09.2025 11:16

                    И что? Как подпись под номером "ИП Иванов" повлияет на качество звонка?

                    Бизнес кстати наоборот оттёк на симки физ. лиц. Потому что вместе с маркировкой приехала плата за соединение. Назад в 2007 год. Так что да, скорее выгоднее просто купить номер у SIP провайдера и всё.

                    UPD: Могу сказать что это даже не во всех протоколах работает. На условном 2G никакая маркировка вам не покажется. Так же как не все провайдеры SIP'а её примут. По факту сделали какой костыль под 4Г который и так глушат где попало, ещё и с обязательной оплатой владельцами номеров.


          1. vikarti
            01.09.2025 11:16

            Но как получить его на тот же мобильный номер?

            FMC SIM (но весьма вероятно что надо хотя бы ИП - физику это сложно).

            симка которая с телефонной сетью общается как симка, но с точки зрения IP-АТС - оно SIP-клиент. Интернет к такой симке можно добавить.

            Например могут: simmetria (у этих только SIM а где IP-АТС и номера брать - ваше дело), gravitel (эти - и IP АТС и номера и много что еще продадут с удовольствием).

            Из недостатков:

            • на отрезке телефон<->сервис это голосовой звонок со всеми преимуществами и недостатками

            • очень мало кто из тех кто в такое умеет в России - работают с физиками (не удивлюсь если в 2025 - данное множеств будет вообще нулевым)

            • цены...ну скажем так - это бизнес услуга,


        1. gaussssss
          01.09.2025 11:16

          Ну хз, от локации зависит. Я последние несколько лет по мобильному инету спокойно видеозвонками пользовался, не то что голосовая связь.


        1. hochbar
          01.09.2025 11:16

          Что за три волшебные буквы?


      1. inkelyad
        01.09.2025 11:16

        Опять же, по мобильной связи нельзя говорить с гарнитуры на компе.

        Вряд ли это так. Компьютер с bluetooth адаптером может притворяться 'гарнитурой' для сотового телефона. Собственно, даже Android Auto - вроде бы 'под капотом' так и делает, пользуясь этим костылем для обхода собственных ограничений Android телефонов на захват голоса для программной обработки.


        1. aborouhin
          01.09.2025 11:16

          Для связки Windows+Android это штатная функциональность в приложении Link to Windows от MS.


      1. Sazonov
        01.09.2025 11:16

        Это штатная функция на устройствах Apple. Достаточно устройствам находиться в одной локальной сети и идёт бесшовный проброс вызовов. Причём можно начать отвечать на компе, а потом перейти на телефон (или наоборот).


      1. evstropov_dv
        01.09.2025 11:16

        В винде "связь с телефоном" есть функция, друган мой пользуется, по "блюпупу" она работает


    1. hochbar
      01.09.2025 11:16

      Но многие почему-то упорно пытались вызвонить меня в мессенджере.

      А как же возможность сэкономить полтора рубля на звонке? С десяти звонков гляди 15 рублёв сохранил.


  1. lifespirit
    01.09.2025 11:16

    У Element, если я правильно помню, припроетарный протокол для звонков, основанный на Livekit и lk-jwt-service . + нужно вставить анонс кастомного RFC в .well-known . Иначе нельзя будет в любой группе сделать звонок, а только в специально помеченной как "голосовая". А этот плагин ломает авторизацию на сервере.

    Есть ещё вариант на SIP'е от Belledonne Communications . Flexisip + linphone работает великолепно. Платформа изначально заточена на голос. При желании для андройда можно пересобрать linphone со совоей push подпиской и тогда вообще хорошо.


  1. gridmal
    01.09.2025 11:16

    Я на данный момент тестирую Nextcloud Talk внутри семьи для звонков с видео. Настройка много проще. Качество - лучше WhatsApp


    1. ArtemSmit Автор
      01.09.2025 11:16

      Nextcloud Talk тащит за собой весь монструозный Nextcloud. Ушел от него на SeaFile и с тех пор про Nextcloud вспоминаю с содроганием ))) Плюсом, чтоб пользователь мог звонить в Nextcloud Talk - его нужно пустить в сам Nextcloud. Одно дело когда это внутри семьи, а если как у меня еще коллеги - уже не очень хочется быть общественной файлопомойкой.


      1. AndyCravec
        01.09.2025 11:16

        Пользуюсь Nextcloud, года четыре. Великоват, но он и не для дома, но если развернут - то он твой. Закрыть доступ к файловой системе не проблема - условная группа "звонки" с нулевой квотой на создание файлов и доступом только к каталогу Public


        1. ArtemSmit Автор
          01.09.2025 11:16

          Попробуйте Seafile просто ради интереса. Ничуть не преуменьшаю крутость Nextcloud, но связка из Seafile под файлы и Immich под фото как по мне идеальна. На Nextcloud сидел около двух лет, постоянно что-то приходилось ему помогать, даже где то заметка на несколько страниц осталась со всеми его приколами и путями решения. Плюс тормознутый интерфейс. Тоже думал что хоть и требует к себе внимания, но такой мегакомбайн заслуживает некоторых послаблений в требованиях. Как-то наткнулся на Seafile, поставил ради опыта - и в итоге на нем и остался. Реактивная скорость работы, чистый лог без ошибок, я даже забыл когда последний раз на его контейнеры поглядывал


          1. gridmal
            01.09.2025 11:16

            Я тестирую Nextcloud именно для звонков: чистая пустая установка (snap пакетом для простоты установки) с доустановкой Talk + Coturn для клиентов за nat. Все крутится под debian 13 на старом ноуте 2011 года (i3 2-ого поколения с 4Гб ОЗУ), который давно лежал и пылился. Установка и настройка заняла у меня около часа вечером выходного дня (помогал DeepSeek). Есть клиент под Android.

            Устанавливал Nextcloud и по частям вручную и snap пакетом - второй путь совсем простой. Для установки достаточно ознакомиться с wiki snap пакета на github из нескольких страниц.


          1. AndyCravec
            01.09.2025 11:16

            Спасибо, возможно. Меня напрягает, что на Ubuntu 20 у меня не развертывается php 8.2, а версии Nextcloud зависят от версии PHP. Недельных танцев с бубном вокруг замены PHP 7.4 на 8.1 долго не забуду, до сих какие-то ошметки 7.4 в системе стоят. Но Seafile непрозрачное внутренне файловое хранилище - если упало, то пропало


            1. ArtemSmit Автор
              01.09.2025 11:16

              У Seafile встроенная утилита, которая вытаскивает файлы во внешнюю папку в случае чего, даже если контейнер мёртв.

              Сейчас попробовал ради интереса снова поставить Nextcloud AiO - тянет за собой кучу контейнеров, создаёт свою сеть плюс с десяток volume и перемапить их нельзя, при перезапуске все возвращается в исходное и падает. Может быть это мой загон, но не люблю такого бардака.


            1. Tigr127
              01.09.2025 11:16

              А почему не перейдете на 24 убунту? Ни в коем случае не даю рекомендации, просто как новому пользователю убунты стало интересно, слышал что софт обратно совместим и проблем не должно быть


            1. belyvoron
              01.09.2025 11:16

              Nextcloud разворачивается одной командой через docker (смотреть на nextcloud-aio). И далее все работает из коробки. Более того, это рекомендуемый способ установки для малых инсталляций


          1. Ryder95
            01.09.2025 11:16

            Вставлю свои 5 копеек. Сам пользуюсь Nextcloud года этак 3 и почти всем доволен. Он бывает тормозной и в принципе выглядит немного устаревше, но это реально решение всё в одном. Мне кажется основная фишка что у тебя и хранение файлов, и контакты (с синхронизацией со всеми устройствами, конечно), и календарь (так же с синхронизацией), и музыкальный плеер, и даже почтовый клиент


  1. JBFW
    01.09.2025 11:16

    Годная тема.

    Хотя если честно - вообще не понимаю концепции голосовых звонков, если вы не курьер, которому надо открыть шлагбаум. Уже сотни лет известна письменность!


    1. ddr5
      01.09.2025 11:16

      Ну если охота пальцы ломать, то никто не может запретить. Но от других этого ждать не стоит.


      1. inkelyad
        01.09.2025 11:16

        Ну если охота пальцы ломать, то никто не может запретить.

        Голосовой набор - существует. И работает даже с локальным распознаванием.


        1. ddr5
          01.09.2025 11:16

          Зачем нужны эти костыли, если можно просто набрать номер?


          1. zartarn
            01.09.2025 11:16

            Чтоб осталась история разговора/обсуждения по которой есть нормальный поиск?


            1. K0styan
              01.09.2025 11:16

              Это нужно далеко не во всех сценариях, особенно в семье. Зато есть другие, в которых критична именно синхронность - например, когда нужно встретиться в месте, одному из участников малознакомом.

              Бонус: голос передаёт эмоции.


          1. aborouhin
            01.09.2025 11:16

            Чтобы не отвлекать человека в данный конкретный момент и дать ему возможность ответить когда ему удобно. Переключения - главный враг продуктивности.


            1. ddr5
              01.09.2025 11:16

              Но тогда придется переключиться мне, если я решаю какую-то задачу, которая требует вовлечения другого человека, и без его ответа или участия дальше не продвинуться.


      1. JBFW
        01.09.2025 11:16

        Вы удивитесь, сколько людей терпеть не может голосовухи, звонки и т.п.

        Прежде всего из-за помянутой синхронности: на звонок нужно ответить здесь и сейчас (сидя за рулем/в туалете/на совещании/занимаясь домашними делами - плевать, возьми ответь и разговаривай)

        И не смей отвлекаться, пока тебе не договорят!

        Просто любители голоса с этими людьми мало пересекаются, не могут дозвониться...


        1. ddr5
          01.09.2025 11:16

          Ну так это классический конфликт удобства. Я вот не хочу писать стены текста, а другой человек не хочет слушать. И кому-то придется уступить.


          1. K0styan
            01.09.2025 11:16

            Есть вариант, одинаково неудобный обоим: голосовое сообщение))

            Отправивший не получает ответа в реалтайме, получивший вынужден тратить время на выслушивание)


            1. numark
              01.09.2025 11:16

              Телега с премиумом или ТГ бот всё расшифрует вам на ура.


              1. Mixael-L
                01.09.2025 11:16

                Э-э-э, кхм, ага, это… че хотел-то, в общем, да мы поехали. А уж если использовать жаргонизмы и аббревиатуры в устной речи, то все эти расшифровщики нервно курят.


                1. LittleDuck
                  01.09.2025 11:16

                  небольшая автономная сеточка (whisper) привязанная на тг-бота. Скармливаю ей все голосовухи, нормально распознает. Вот пример, скормил ей мемную голосовуху, довольно точно всё распознал, да еще и знаки препинания расставил


                  1. LittleDuck
                    01.09.2025 11:16

                    Сейчас уже понимаю, что стоило, пожалуй, прикрепить другой пример, поскольку в этом я заметил наличие нескольких ругательств


                1. vikarti
                  01.09.2025 11:16

                  Ну не знаю. Для меня - эта расшифровка - один из главных поводов платить за Telegram Premium. По работе есть желающие голосовые слать. Работает нормально. Понятно что имели ввиду.


            1. InikonI
              01.09.2025 11:16

              Кстати, к synapse/matrix можно написать бота используя whisper (ну или готового поискать) который будет переводить голосовое сообщение в текст.
              Есть у меня родные которые иногда бывает присылают аудио сообщение.
              Аудио сообщения меня быстро утомили и я завел бота, теперь я пересылаю аудио в комнату к боту, и читаю текст.

              Собственно тема ботов для matrix совсем не раскрыта в статье.
              А там много чего можно интересного запустить.
              От банального GPT-бота (включая и генерацию картинок) до ботов которые преобразуют текст в аудио и обратно.
              Так же можно завести бота который смотрит в календарь nextcloud и напоминает по событиям.


              1. questarrow
                01.09.2025 11:16

                один "нюанс" - боты не могут писать в зашифрованные группы:)


                1. InikonI
                  01.09.2025 11:16

                  У меня вроде пишут, хотя надо проверить.
                  То что читают это точно, так как писал код для расшифровки.


                1. InikonI
                  01.09.2025 11:16

                  Посмотрел своих ботов.
                  Те которые пишут в зашифрованные комнаты, пишут без уважения не шифруя сообщения.
                  Но возможность писать шифрованные сообщения боту добавить все же можно.
                  Там правда придется предусмотреть и подтверждение бота, и наверное синхронизацию ключей.
                  Думаю как нибудь даже попробую добавить.
                  Правда мне для семейного использования не так уж и нужно будет, но задача интересная.


            1. urvanov
              01.09.2025 11:16

              Мало кто может в момент записи голосового чётко и понятно сформулировать свою мысль. А над текстом можно и подумать.

              А там ещё и завывания ветра, шум машин и стройки рядом будет. И сквозь это как-то нужно будет разобрать что он там мямлит


          1. nv13
            01.09.2025 11:16

            Неоклассический) Просто многие путают синхронные и асинхронные коммуникации. Ну зачем список покупок на вечер диктовать на звонке утром? И зачем писать текст, а потом возмущаться что его не читают в ту же секунду. дом же горит)


          1. gxcreator
            01.09.2025 11:16

            Есть альтернатива: голосовой ввод текста, работает сейчас отлично.


            1. ddr5
              01.09.2025 11:16

              Никак не решает вопрос синхронности.


          1. JBFW
            01.09.2025 11:16

            Это не вопрос удобства, это вопрос восприятия: знаю людей, которые не могут написать или прочитать - буквы -то они видят, слова понимают, а смысла не улавливают. Тут только голосом.

            И наоборот, когда звонить бесполезно, все сказанное будет частично забыто, частично перепутано примерно через 10 секунд после разговора. Зато письменно - всё решается на ура.

            Люди не одинаковы


            1. inkelyad
              01.09.2025 11:16

              буквы -то они видят, слова понимают, а смысла не улавливают. Тут только голосом.

              Если бы этот тезис был верен, таким было бы достаточно воспользоваться софтом чтения текста. Но, подозреваю, это не сработает. Тут не то что 'голосом' влияет, а что-то другое.


              1. JBFW
                01.09.2025 11:16

                Просто вы не сталкивались


            1. urvanov
              01.09.2025 11:16

              У меня на первом месте работы после университета был коллега, который звонил просто потому что любил говорить. С ним можно было любой даже самый простой вопрос по целому часу обсуждать. А потом он бы ещё раз позвонил и ещё столько же пообсуждал бы.


        1. kenomimi
          01.09.2025 11:16

          Тем звонки и удобны - сообщение прочитал, "ладно потом отвечу", и забыл начисто. Причем проблема с обоих сторон сразу. В результате любая попытка о чем-то договориться выливается в недели переписок. Хотя по телефону всё решается за пять минут плюс пять минут на написание письма-резюме по итогам разговора, чтобы не потерять подробности разговора.

          Всему свое место. Где-то текстом удобнее, где-то голосом, а где-то иной раз и ненавистные голосовухи нужны...


          1. andreymal
            01.09.2025 11:16

            Для «ладно потом отвечу» в любом приличном мессенджере есть функция «Отметить как непрочитанное», достаточно иметь хотя бы капельку дисциплинированности


            1. K0styan
              01.09.2025 11:16

              В почтовых клиентах - да, в мессенджерах ни разу не видел. Буду признателен за пример.


              1. zartarn
                01.09.2025 11:16

                в тг по диалогу ПКМ


                1. K0styan
                  01.09.2025 11:16

                  Ага, спасибо. Неожиданно.


              1. inkelyad
                01.09.2025 11:16

                В почтовых клиентах - да, в мессенджерах ни разу не видел

                Кроме, собственно, кнопки -- есть папки, теги и эквиваленты

                Вот тут же на скриншоте - можно, кроме собственно 'Пометить как непрочитанное', 'Добавить в папку' "потом отвечу"

                И еще можно 'Поделиться' в какой-нибудь заметочник/TODO-шник.


    1. zurabob
      01.09.2025 11:16

      Ловите депутата!

      • объект в Nске, где глючит твое оборудование и дед в ушанке, которому ты смотря его видеозвонок в прямом эфире помогаешь настроить или хоть понять, что делать.

      • старенькая мама далеко, которой хочется поговорить, а тебе ее услышать. А сотовая это дорого, зато она же давно общается со своими подругами по вотсапу часами.

      • Приятель сына, который с родителями уехал в Европу, но не разорвал отношения и интересно поболтать.

      • заказчик, который хз где, но хочет обсудить моменты перед письменным миниТЗ.

      ...................
      И увы, ни один из этих сценариев не попадает под описанное в статье, но все идеально вкладывались в вотсап, который был у всех и в этом его огромное преимущество, даже впн тут мало помогает.


      1. ArtemSmit Автор
        01.09.2025 11:16

        Описанное в статье изначально не задумывалось как решение ваших примеров, это был канал связи для семьи. C подругами мать общается по городскому телефону, за мкадом такие еще остались и на удивление за 100 рублей в месяц. А вот плюс element для меня оказался неожиданно еще в том, что через него спокойно звонится в Дубайск, где находится мой "дед в шапке-ушанке" и "заказчик, который хз где", что через WhatsApp к сожалению невозможно.


        1. zurabob
          01.09.2025 11:16

          Разумеется, каждый решает свои проблемы, это была не критика, а ответ не видящему смысла в звонках. Конечно, все эти и прочие сценарии имеют и другие пути реализации, особенно для опытного пользователя ПК(как писали раньше), еще один путь это интересно и полезно. Хотя обычно одна дорога лучше кучи труднопроходимых тропинок.


        1. Nemoumbra
          01.09.2025 11:16

          Городские и внутри МКАДа остались.


  1. CloudlyNosound
    01.09.2025 11:16

    А секта сетка ещё не заменяет бородатого и прочих?


    1. K0styan
      01.09.2025 11:16

      Которая? Одноимённая соцсеть от одного работного портала?


      1. CloudlyNosound
        01.09.2025 11:16

        Ну а что? Из каждого утюга ведь.


        1. K0styan
          01.09.2025 11:16

          Разные у нас утюги, похоже


  1. coloradobug
    01.09.2025 11:16

    Вы считаете собранный на коленке мессенджер безопаснее?


    1. JBFW
      01.09.2025 11:16

      Если в вашем "семейном" мессенджере вдруг позвонит "сотрудник службы безопасности банка" - это будет очень удивительно.


      1. K0styan
        01.09.2025 11:16

        -- Вы из КГБ?

        -- А как вы догадались?

        -- А вы на отключённый телефон дозвонились


    1. kenomimi
      01.09.2025 11:16

      Зря заминусонили, по сути всё верно заметил. Проблема защиты таких комплексных сервисов действительно серьезная, уязвимости находят пачками... Логи аудита на публичных серверах гигами в день накапливаются, сканирование дыр идет постоянно. Специально руками вас ломать не будут, Джо неуловим потому что никому не нужен, но вот при автоматических атаках на большие подсети отломят элементарно.

      И это не рекомендация не делать свое, больше напоминание про то, что файрволл с геоип обязателен (например, если у нас нет контактов из Китая - дропаем его полностью, ибо там миллиарды зараженных IoT-устройств, с которых идут атаки), и параллельно с публичными сервисами ставить как минимум какой-нибудь мониторинг сетевой активности, чтобы вовремя среагировать на взлом. Включать secure boot со своими сертами, чтобы не работало закрепление в ядре, включать и настраивать selinux (или аналоги), внутри контейнеров запускаться от nobody (а не от рута, как обычно), и так далее...