Опыт установки сервера Matrix Synapse

Matrix — открытый протокол мгновенного обмена сообщениями. Synapse - эталонная реализация сервера для протокола. За время использования сервера успел сделать несколько ошибок и перенести пару раз сервер с хоста на хост. Хочется поделиться своим опытом.

Используйте для сервера Matrix отдельное доменное имя

Это даёт гибкость и, возможно, избавит вас от проблем в будущем. Если сегодня ваш сайт и сервер Matrix работают на одном сервере, то в будущем, возможно, понадобится необходимость перенести Synapse на другую машину. Имея отдельное доменное имя можно сократить простой работы Synapse до минимума.

Пример:

@ A 1.1.1.1
www A 1.1.1.1
matrix A 1.1.1.1
_matrix._tcp SRV matrix.example.com.

В случае необходимости вы легко можете изменив только одну запись matrix перенести сервер на другую машину. При этом сайт на основном домене может оставаться на своём месте.

Используйте в качестве фронтэнда nginx

Это позволит упростить работу с сертификатами доменов, т.к. нет необходимости что-то прописывать в настройках synapse, использовать nginx в качестве прокси при переносе сервера synapse на другой IP адрес, т.е. уменьшить время простоя.

Если вы используете на фронтенде nginx, то нет необходимости разбираться с настройками сертификатов непосредственно в synapse, как их получать, куда их размещать. Используя nginx, вы можете использовать имеющийся опыт работы с сертификатами на других веб-серверах, либо получить этот опыт на своём Matrix-сервере и использовать его при настройке других сервисов. Получение сертификата для домена, например, с Let’s Encrypt, легко автоматизируется в nginx. При этом если вы захотите перенести свой synapse на другую машину, то достаточно будет на nginx прописать настройки прокси, а сертификат необходимый у вас уже будет настроен и доступен. Нужно лишь немного изменить настройку виртуального хоста в nginx на вашей старой машине.

Пример конфигурации nginx

server {
    listen 443 ssl http2;
    listen [aaa::aaa]:443 ssl http2 default_server;
    listen 8448 ssl default_server;
    listen [aaa::aaa]:8448 ssl default_server;

    server_name matrix.example.com;

    access_log /var/log/nginx/matrix_access.log;
    error_log /var/log/nginx/matrix_error.log;

    ssl_certificate /etc/letsencrypt/live/matrix.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/matrix.example.com/privkey.pem;

    client_max_body_size 10M;

    root /var/www/matrix/htdocs;
    index index.html;

    location /_matrix {
	proxy_pass http://127.0.0.1:8008;
        proxy_set_header X-Forwarded-For $remote_addr;

	# set_real_ip_from xx.xx.xx.xx;
	# real_ip_header X-Forwarded-For;
	# real_ip_recursive on;
    }
    include acme.conf;
}

В данной конфигурации производится прослушивание двух портов (443 и 8448), а также двух типов IP адресов IPv4 и IPv6. При этом по IPv4 на порту 443 кроме Matrix можут находиться другие веб-сайты. Для IPv6 для сервиса Matrix выделен отдельный адрес [aaa::aaa] (укажите свой) и другие сайты недоступны через этот порт, поэтому указан параметр default_server.

Порт 8448 используется исключительно для synapse. Для этого порта не используется SNI (Server Name Indication, с которым были проблемы в старых версиях synapse). Кроме этого, т.к. порт используется для федерации с дургими серверами Matrix, я не включаю протокол http2, на всякий случай.

В качестве server_name укажите своё доменное имя.

Файлы логов доступа и ошибок размещены в стандартном месте. Если они не нужны, укажите вместо пути значение off. Если просто закомментировать строки, то лог будет писаться в общий лог nginx.

Пути сертификату и ключу указаны стандартные для сертификатов Let’s Encrypt, полученных через certbot. Эти строки необходимы для работы протокола https.

Ключ client_max_body_size указывает на максимальный размер запроса, фактически это ограничение на размер загружаемых на сервер файлов, т.к. эти запросы самые объёмные. В данном случае указано ограничение в 10 мегабайт.

Ключи root и index указывают на папку с содержимым веб-сайта и файл по умолчанию. В данном случае рассчитывается, что по данному пути будет расположен веб-клиент Riot.im. Благодаря этому подключиться к серверу через браузер можно будет просто введя адрес https://matrix.example.com/. При этом будет запущен клиент Riot.im.

Секция location обеспечивает доступ к API matrix-synapse. Закомментированные строки позволяют получить реальный IP адрес пользователя, если он был подключен через доверенный прокси по адресу xx.xx.xx.xx. Адрес клиента в этом случае берётся из заголовка X-Forwarded-For, который добавляет вышестоящий прокси, а не из IP-адреса источника. По этом причине не следует указывать адреса прокси серверов, которым вы не доверяете, поскольку они могут легко указать любой адрес или не указывать заголовок совсем. Эти же настрйки вышестоящего прокси могут быть перенесены непосредственно в секцию server, тогда они будут действовать на любые контексты на сайте matrix.example.com.

При смене IP адреса вашего matrix-synapse пропишите в настройках nginx на старом сервере следующее:

    location / {
        proxy_pass https://yy.yy.yy.yy;
	proxy_set_header Host $host;
	proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
	proxy_set_header X-Real-IP $remote_addr;
    }

В качестве адреса сервера yy.yy.yy.yy необходимо указать IP адрес вашего нового сервера (не доменное имя, т.к. оно может всё ещё указывать на текущий сервер).

Последний пункт конфигурации нужен для прохождения проверки при получении нового сертификата Let’s Encrypt. Содержимое файла /etc/nginx/acme.conf:

location ~ /.well-known/acme-challenge/(.*) {
    default_type "text/plain";
    root /var/letsencrypt;
    allow all;
}

Папка /var/letsencrypt создана вручную и должна быть указана при создании нового сертификата как webroot, например:

certbot certonly --webroot -w /var/letsencrypt -d matrix.example.com

Настройка обнаружения сервера на домене

Сообшить другим серверам о том, что на вашем домене функциониурет сервер Matrix можно двумя способами:

  1. с помощью записей SRV в DNS;
  2. с помощью специальных унифицированных идентификаторов ресурсов (URIs) в папке /.well-known/.

Первый способ не требует вмешательства в работу основного веб-сайта. Для идентификации Matrix сервера создаётся запись SRV _matrix._tcp:

_matrix._tcp.example.com. 3600 IN SRV 10 5 8448 matrix.example.com.

Она содержит номер порта (8448) и адрес сервера, который отвечает за сервер Matrix (matrix.example.com.).

Для использования второго способа требуется настроить вдачу JSON ответа по адресу /.well-known/matrix/server:

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

В ответе должнен содержаться парамер m.server, который указывает на имя домена и порт сервера. Реализовать подобный ответ на сервере nginx можно с помощью следующей вставки в конфигурацию хоста:

    location /.well-known/matrix/server {
        default_type application/json;
        return 200 '{"m.server": "matrix.example.com:8448"}';
    }

Как показала практика, URIs имеют приоритет над записями DNS. В этом можно убедиться с помощью диагностического сайта https://federationtester.matrix.org/, который при наличии корректного ответа /.well-known/matrix/server не обращается к записям SRV.

Обнаружение сервера клиентами можно осуществить через ответ сервера по адресу /.well-known/matrix/client:

{
    "m.homeserver": {
        "base_url":"https://matrix.example.com"
    }
}

Параметр m.homeserver говорит клиентам о местоположении сервера Matrix. В этом же ответе можно указать сервер идентификации с помощью параметра m.indentity_server. Подробнее об этом можно прочитать в документации к протоколу.

Поскольку в качестве клиентов может использоваться веб-приложение, браузеры могут ограничить чтение файла JSON с помощью механизма безопасности CORS. Чтобы этого не происходило, необходимо в ответе добавить заголовки, разрешающие использовать эти данные на других доменах. Всё вышеописанное можно реализовать в nginx с помощью следующего фрагмента конфигурации:

    location /.well-known/matrix/client {
	add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Methods' 'GET';
        default_type application/json;
        return 200 '{"m.homeserver":{"base_url":"https://matrix.example.com"}}';
    }

В таком случае, например, при использовании веб-клиента https://app.element.io/ теперь достаточно ввести имя пользователя @мой_пользователь:мой_домен и клиент автоматически получит данные о сервере для вашего домена. Указав верный пароль вы уже можете присоединиться к общению не выполняя настроек сервера вручную.

Скрипт обновления клиента Element

Для обновления клиента Element на своём сервере использую скрипт:

#!/bin/bash

###################################################################
# Script for check new version of Element from github
# and download new version, if update is avaiable
#
# https://gist.github.com/MurzNN/ee64f98ab2e71b886c41d55594e5dd9e
#
###################################################################

# Directory where Element files must be placed
DIRECTORY_INSTALL=~/public_html
# Directory for temp files - must be different that install directory!
DIRECTORY_TMP=/tmp

VERSION_URL=https://api.github.com/repos/vector-im/element-web/releases/latest

command -v curl >/dev/null 2>&1 || { echo "You need to install "curl" package for this script: sudo apt install curl"; exit 1; }
command -v tar >/dev/null 2>&1 || { echo "You need to install "tar" package for this script: sudo apt install tar"; exit 1; }
command -v jq >/dev/null 2>&1 || { echo "You need to install "jq" package for this script: sudo apt install jq"; exit 1; }

VERSION_INSTALLED=`cat $DIRECTORY_INSTALL/version`
VERSION_LATEST=`curl -s $VERSION_URL | jq -r '.name' | sed s/v//` || { echo "Error checking last Element version!"; exit 1; }

if ( [[ -z "$VERSION_LATEST" ]] || [ "$VERSION_LATEST" == "null" ] ); then
  echo "Error! Received bad version number from $VERSION_URL: $VERSION_LATEST"
  exit
fi

if ( [ "$VERSION_INSTALLED" != "$VERSION_LATEST" ] ); then
  echo "Element installed version is $VERSION_INSTALLED, in GitHub releases found fresher version: $VERSION_LATEST - updating..."
  DL_URL=`curl -s $VERSION_URL | jq -r '.assets[0].browser_download_url'`
  curl -L -o $DIRECTORY_TMP/element-latest.tar.gz $DL_URL || { echo "Error downloading element-latest.tar.gz"; exit 1; }
  mkdir $DIRECTORY_TMP/element-latest/
  tar -xf $DIRECTORY_TMP/element-latest.tar.gz --strip 1 -C $DIRECTORY_TMP/element-latest/
  find $DIRECTORY_INSTALL/* -not -name 'config*.json' -delete
  rm -f $DIRECTORY_INSTALL/config.sample.json
  mv $DIRECTORY_TMP/element-latest/* $DIRECTORY_INSTALL/
  rm -rf $DIRECTORY_TMP/element-latest
  rm $DIRECTORY_TMP/element-latest.tar.gz
  echo "Element succesfully updated from $VERSION_INSTALLED to $VERSION_LATEST";
else
  echo "Installed Element version $VERSION_INSTALLED, last is $VERSION_LATEST - no update found, exiting.";
fi

Полезные ссылки

Оптимизация базы данных (англ.)