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

В свете небезызвестных событий в законотворческой области, столкнулся с необходимостью организовать канал для общения внутри семьи, т.к. пользоваться звонками в популярных мессенджерах – значит быть подверженным угрозам со стороны мошенников и спонсировать терроризм, а звонить по мобильной сети с ее ужасным качеством связи (несмотря на все потуги операторов в 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/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:

Certificate #142
Certificate #142

В нашем случае 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.

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


  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. inkelyad
        01.09.2025 11:16

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

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


        1. aborouhin
          01.09.2025 11:16

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


  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. 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. JBFW
        01.09.2025 11:16

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

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

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

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


        1. ddr5
          01.09.2025 11:16

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


          1. K0styan
            01.09.2025 11:16

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

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


          1. nv13
            01.09.2025 11:16

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


    1. zurabob
      01.09.2025 11:16

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

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

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

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

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

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


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

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


  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

        -- Вы из КГБ?

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

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