Ставим matrix+LiveKit на FreeBSD

matrix — это такой протокол для мессенджера, есть несколько его реализаций, но, как водится, полноценная только одна. Протокол ругают за перетяжеленность, но matrix сегодня один из самых зрелых opensource‑вариантов для self‑hosted мессенджера.

Домен возьмем, естественно, example.com. Контейнер назовем matrix. Дадим ему реальный адрес: можно и с пробросом портов, но это отдельная возня. Предполагается, что вы уже умеете создавать контейнеры. Веб‑сервер nginx, но можно поставить любой другой.

Решение на видеосвязь

Передо мной стояла задача «в первую очередь звонки мобильник — мобильник».

Так что, LiveKit, только он. Все клиенты умеют в LiveKit, не все умеют в Jitsi. Возня с Jitsi оправдана, когда нам нужны интеграция с sip и запись аудиовидео «из коробки». Но, в принципе, можно интегрировать звонки с десктопа в телефонную сеть через jitsi.

Ставим зависимости

Предполагается, что example.com у нас развернут хотя бы заглушкой, там нужны будут.well‑known для информации о подключении

Из портов/пакетов

  1. net‑im/py‑matrix‑synapse но есть нюанс (ниже)

  2. www/npm‑node22 или более новый четный (LTS)

  3. devel/git@lite клонировать репозитории

  4. Свежий go из lang/goXXX

  5. databases/py‑psycopg2 именно 2, если вы будете использовать postgres

  6. www/nginx с обязательной поддержкой http2

Если вы собираете python без IPv6, то это вы зря. Соберите с IPv6. Даже если не слушать на IPv6-адресе, все равно поддержка нужна.

Дополнительно

Везде указана актуальная на 2026.07.12 версия. Проверяйте перед установкой текущую актуальную.

Аналогично с версией go, на момент написания это go126.

Вопросы запуска будут рассмотрены ниже.

matrix‑user‑verification‑service

git clone --branch v3.0.0 --depth 1  https://github.com/matrix-org/matrix-user-verification-service.git /usr/local/uvs &&\
cd /usr/local/uvs && npm install --omit=dev

Чттобы не тянуть кучу ненужных зависимостей.

livekit‑server

git clone --branch v1.13.3 --depth 1 \
  https://github.com/livekit/livekit-server.git /usr/local/livekit && \
cd /usr/local/livekit && go126 build ./cmd/server

lk‑jwt‑service

git clone --branch v0.5.0 --depth 1 \
  https://github.com/element-hq/lk-jwt-service.git /usr/local/lk-jwt-service && \
cd /usr/local/lk-jwt-service && \
go126 build -o lk-jwt-service .

synapse‑admin

С https://github.com/Awesome‑Technologies/synapse‑admin/releases/tag/0.11.4 (опять‑таки проверьте актуальную версию) берем synapse‑admin-0.11.4.tar.gz

Развертываем в /usr/local/www/synapse‑admin

Нюанс с версией synapse

В моем случае, Synapse 1.151 не давал работать с LiveKit (или это я где‑то запутался), так что поверх matrix‑из‑портов я сделал pip install --upgrade matrix-synapse==1.155.0 (ну или какая версия будет актуальна для вас). Так я получил rc‑script и актуальную версию кода.

Сертификаты

  1. matrix.example.com

  2. element.example.com если хотим свой веб‑клиент на отдельном поддомене.

не будем выделять livekit в отдельную сущность, он не настолько велик.

Клиенты

Element на десктопе и Element X на мобильном. Это вполне функционально. С LiveKit работает все. Настройка других клиентов оставляется читателю в качестве несложного самостоятельного упражнения.

Настройка nginx

Element Web

Берем релиз из https://github.com/element‑hq/element‑web/releases — ставим, например, в /usr/local/www/element

Настраиваем config.json

{
    "default_server_config": {
        "m.homeserver": {
            "base_url": "https://matrix.example.com"
        }
    },
    "disable_custom_urls": false,
    "disable_guests": true,
    "disable_login_language_selector": false,
    "disable_3pid_login": false,
    "force_verification": false,
    "brand": "Element",
    "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"
    ],
    "default_widget_container_height": 280,
    "default_country_code": "GB",
    "show_labs_settings": false,
    "default_federate": true,
    "default_theme": "light",
    "room_directory": {
        "servers": ["matrix.org"]
    },
    "enable_presence_by_hs_url": {
        "https://matrix.org": false,
        "https://matrix-client.matrix.org": false
    },
    "setting_defaults": {
        "breadcrumbs": true
    },

    "features": {
		"feature_group_calls": true
    },
    "element_call": {
        "use_exclusively": true,
        "participant_limit": 8,
        "brand": "Element Call"
    },
    "map_style_url": "https://api.maptiler.com/maps/streets/style.json?key=fU3vlMsMn4Jb6dnEIFsx"
}

element_call.use_exclusively — значит, отключить старые звонки через jitsi. Совсем.

well‑known на example.com

Там у нас хранятся те же самые настройки но для внешних клиентов. Проще всего не делать отдельный файл, а прямо положить все в nginx.config

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

    location = /.well-known/matrix/client {
        default_type application/json;
        add_header Access-Control-Allow-Origin *;
        add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS';
        add_header Access-Control-Allow-Headers 'X-Requested-With, Content-Type, Authorization';
        return 200
        '{
            "m.homeserver": {
                "base_url": "https://matrix.example.com"
            },
            "org.matrix.msc4143.rtc_foci": [
            {
                "type": "livekit",
                "livekit_url": "wss://matrix.example.com",
                "livekit_service_url": "https://matrix.example.com/\\_lk-jwt-service"
                }
                ]
        }';
    }

Обработка путей на matrix.example.com

Сервисов и путей у нас много.

        # livekit
        location /twirp/ {
            proxy_pass http://127.0.0.1:7880;
            proxy_set_header Host $host;
            proxy_set_header X-Forwarded-For $remote_addr;
        }
        location /rtc/ {
            proxy_pass http://127.0.0.1:7880;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_set_header Host $host;
            proxy_set_header X-Forwarded-For $remote_addr;
        }
		# lk-jwt-service
        location /_lk-jwt-service/ {
            proxy_pass http://127.0.0.1:8080/;
            proxy_set_header Host $host;
            proxy_set_header X-Forwarded-For $remote_addr;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header X-Real-IP $remote_addr;
        }
		# letsencrypt
		location ^~ /.well-known/acme-challenge/ {
            root /usr/local/www/nginx;
            default_type "text/plain";
        }
		# matrix
		location / {
            proxy_pass http://127.0.0.1:8008;
            proxy_set_header X-Forwarded-For $remote_addr;
            proxy_set_header Host $host;
        }

        # matrix admin api 
        location /_synapse/admin {
            proxy_pass http://127.0.0.1:8008;
            proxy_set_header X-Forwarded-For $remote_addr;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header Host $host;
            proxy_http_version 1.1;
        }
		# admin
		location /synapse-admin {
            alias /usr/local/www/synapse-admin;
			index index.html;
        }

После создания сертификатов, добавляем сервер для федерации.

    server {
        listen 8448 ssl;
        http2 on;
        server_name matrix.example.com:8448;

        ssl_certificate /usr/local/etc/letsencrypt/live/matrix.example.com/fullchain.pem; # managed by Certbot
        ssl_certificate_key /usr/local/etc/letsencrypt/live/matrix.example.com/privkey.pem; # managed by Certbot
        include /usr/local/etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
        ssl_dhparam /usr/local/etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

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

    }

Настройка matrix

/usr/local/etc/matrix‑synapse/homeserver.yaml генерируется командой

python3.12 -m synapse.app.homeserver \
  --server-name example.com \
  --config-path /usr/local/etc/matrix-synapse/homeserver.yaml \
  --generate-config \
  --report-stats=no

При этом генерируется ключ для федерации. Потом к файлу нужно добавить, что у нас есть livekit и где его найти

experimental_features:
  msc3266_enabled: true
  msc4143_enabled: true
  msc4140_enabled: true
max_event_delay_duration: 24h
rc_message:
  per_second: 0.5
  burst_count: 30
matrix_rtc:
  transports:
    - type: livekit
      livekit_service_url: "https://matrix.example.com/\\_lk-jwt-service"

Правите настройки логов в файле /usr/local/etc/matrix‑synapse/example.org.log.config

handlers:
    file:
        class: logging.handlers.TimedRotatingFileHandler
        formatter: precise
        filename: /var/log/synapse/homeserver.log

Создаете каталог, даете права.

Создаете базу, даете доступы, прописываете в конфиг.

database:
  name: psycopg2
  args:
    user: matrix
    password: ***
    database: matrix
    host: host
    port: 5432
    cp_min: 5
    cp_max: 10
  # если у вас локаль в базе русская
  allow_unsafe_locale: true

pid_file выставляете в /var/run/matrix-synapse/homeserver.pid

После этого проверяем пути, доступность для юзера synapse всего в /usr/local/etc/matrix‑synapse, правильность путей к медиахранилищу и собственно matrix у вас работает. Но в нем еще нет пользователей.

synapse‑admin

Нужно создать админа.

register_new_matrix_user -c /usr/local/etc/matrix-synapse/homeserver.yaml -u admin -p YOUR_PASSWORD --admin http://localhost:8008

Зайти под админом на synapse‑admin и создать пользователя.

Запуск дополнительных сервисов

Я делаю по олдскульному. daemontools и простейшие run‑scripts. Написание rc‑скриптов оставляется читателю.

Общая часть

cd /var/spool
# это для bash
 for s in uvs livekit lk-jwt-service; do mkdir -p ${s}/log/main && chown nobody ${s}/log/main
cat > ${s}/log/run << EOF
#!/bin/sh
exec setuidgid nobody multilog t ./main
EOF
 chmod a+x ${s}/log/run; done

и у нас есть три отличных логгера.

Настройка UVS

run для uvs

#!/bin/sh
cd /usr/local/uvs && exec npm start

Потом, естественно, chmod a+x для него

Настройка lk‑jwt‑service

Для начала, нужно сгенерировать ключи

/usr/local/livekit/server generate-keys

Потом создать run

#!/bin/sh
exec env \
  LIVEKIT_URL="wss://matrix.example.com" \
  LIVEKIT_KEY="API..." \
  LIVEKIT_SECRET="Secret..." \
  LIVEKIT_JWT_BIND=":8080" \
  LIVEKIT_FULL_ACCESS_HOMESERVERS="example.com" \
  /usr/local/lk-jwt-service/lk-jwt-service 2>&1

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

Настройка livekit

run:

#!/bin/sh
exec /usr/local/livekit/server --config /usr/local/livekit/config.yaml 2>&1

config.yaml

port: 7880
rtc:
    tcp_port: 7881
    port_range_start: 50000
    port_range_end: 50200
    use_external_ip: false
    node_ip: your_ip_here
keys:
    API: Secret 
logging:
    level: info

Зацепляем за svscan

ln -s /var/spool/livekit /var/spool/lk-jwt-service /var/spool/uvs /var/service

Запускаем svscan, проверяем, что все поднялось.

Результат

Как писали в журнале «Радио», схема, аккуратно собранная из исправных компонентов, начинает работать без наладки.

К этому надо бы добавить логин по QR, сервер идентичности и, наверное, что‑то еще, но это уже другая история.