Свой сервер: подробное руководство по установке ejabberd: различия между версиями
Rain (обсуждение | вклад) м (Поправил отступ) |
Rain (обсуждение | вклад) |
||
(не показано 17 промежуточных версий этого же участника) | |||
Строка 1: | Строка 1: | ||
В этой статье приводится пример настройки ejabberd с поддержкой актуальных и популярных функций. В качестве основы взят конфиг-файл с работающего сервера версии 21.12, а в качестве сертификатов используется Let's Encrypt - и все это установлено на Debian GNU/Linux. Такой сервер соответствует современным нормам безопасности, поддерживает разнообразные варианты подключения к нему, имеет веб-клиент для тех, кто по каким-то причинам не может использовать обычный [[Клиенты Jabber|jabber-клиент]], умеет вести логи популярных [[Конференции|каналов]], доступные через браузер - и многое другое. | В этой статье приводится пример настройки [[ejabberd]] с поддержкой актуальных и популярных функций. В качестве основы взят конфиг-файл с работающего сервера версии 21.12, а в качестве сертификатов используется Let's Encrypt - и все это установлено на Debian GNU/Linux. Такой сервер соответствует современным нормам безопасности, поддерживает разнообразные варианты подключения к нему, имеет веб-клиент для тех, кто по каким-то причинам не может использовать обычный [[Клиенты Jabber|jabber-клиент]], умеет вести логи популярных [[Конференции|каналов]], доступные через браузер - и многое другое. | ||
Рассмотрим особенности данной конфигурации. | Рассмотрим особенности данной конфигурации. | ||
Строка 12: | Строка 12: | ||
# Для удобства, чтобы в случае миграции сервера с одного IP на другой не приходилось переписывать все записи, стоит завести для него субдомен, который будет ссылаться на нужный IP с помощью А-записи (или 2 таких субдомена в случае использования IPv4+IPv6 - один с A-записью, а второй с AAAA), а все остальные записи сделать CNAME-алиасами для него. Например, для данного сервера ('''jabberworld.info''') сделаны 2 субдомена '''xmpp.jabberworld.info''', ссылающиеся на IPv4 и IPv6-адреса. | # Для удобства, чтобы в случае миграции сервера с одного IP на другой не приходилось переписывать все записи, стоит завести для него субдомен, который будет ссылаться на нужный IP с помощью А-записи (или 2 таких субдомена в случае использования IPv4+IPv6 - один с A-записью, а второй с AAAA), а все остальные записи сделать CNAME-алиасами для него. Например, для данного сервера ('''jabberworld.info''') сделаны 2 субдомена '''xmpp.jabberworld.info''', ссылающиеся на IPv4 и IPv6-адреса. | ||
# Создаем субдомены для необходимых сервисов сервера - '''conference''' (для [[Конференции|конференций]]), '''proxy''' (прокси для прямой [[Передача файлов|передачи файлов]]), '''pubsub''' (сервисы типа "Публикация/подписка", где может сохраняться самая разная информация) и '''upload''' (сервис для [[Передача файлов|передачи файлов]] через HTTP Upload). Все эти записи создаем как CNAME на наш субдомен из 1-го пункта. | # Создаем субдомены для необходимых сервисов сервера<ref>Да, если планируется использовать, например, конференции исключительно локально, то можно обойтись и без DNS-записей - сервер и так знает, куда ему подключаться, так как он этот сервис и предоставляет. Но если хочется полноценный сервис, с которым смогут взаимодействовать и другие сервера - то все же потребуются указанные записи в DNS.</ref> - '''conference''' (для [[Конференции|конференций]]), '''proxy''' (прокси для прямой [[Передача файлов|передачи файлов]]), '''pubsub''' (сервисы типа "Публикация/подписка", где может сохраняться самая разная информация) и '''upload''' (сервис для [[Передача файлов|передачи файлов]] через HTTP Upload). Все эти записи создаем как CNAME на наш субдомен из 1-го пункта. | ||
# Создаем ряд SRV-записей для нашего основного домена - они показывают, где именно какой сервис находится. Конечно, если у вас все на одном адресе, то будет работать и так, но мы ведь решили все делать правильно, верно? Итак, нужны такие SRV-записи: | # Создаем ряд SRV-записей для нашего основного домена - они показывают, где именно какой сервис находится. Конечно, если у вас все на одном адресе, то будет работать и так, но мы ведь решили все делать правильно, верно? Итак, нужны такие SRV-записи: | ||
:: '''_xmpp-client._tcp.EXAMPLE.COM''' (порт '''5222''') - чтобы указать, куда подключаться [[Клиенты Jabber|jabber-клиенту]] | :: '''_xmpp-client._tcp.EXAMPLE.COM''' (порт '''5222''') - чтобы указать, куда подключаться [[Клиенты Jabber|jabber-клиенту]] | ||
:: '''_xmpps-client._tcp.EXAMPLE.COM''' (порт '''5223''') - аналогичная запись для TLS-подключений клиентов | :: '''_xmpps-client._tcp.EXAMPLE.COM''' (порт '''5223''') - аналогичная запись для [https://xmpp.org/extensions/xep-0368.html TLS-подключений клиентов] | ||
:: '''_xmpp-server._tcp.EXAMPLE.COM''' (порт '''5269''') - чтобы указать, куда подключаться [[Публичные серверы Jabber|jabber-серверу]] | :: '''_xmpp-server._tcp.EXAMPLE.COM''' (порт '''5269''') - чтобы указать, куда подключаться [[Публичные серверы Jabber|jabber-серверу]] | ||
:: '''_xmpps-server._tcp.EXAMPLE.COM''' (порт '''5270''') - аналогичная запись для TLS-подключений серверов. | :: '''_xmpps-server._tcp.EXAMPLE.COM''' (порт '''5270''') - аналогичная запись для [https://xmpp.org/extensions/xep-0368.html TLS-подключений серверов]. | ||
:: Записи ниже служат для работы сервиса аудио/видеозвонков через jabber<ref name="avcalls">https://www.process-one.net/blog/how-to-set-up-ejabberd-video-voice-calling/</ref>: | :: Записи ниже служат для работы сервиса аудио/видеозвонков через jabber<ref name="avcalls">https://www.process-one.net/blog/how-to-set-up-ejabberd-video-voice-calling/</ref>. Для максимальной совместимости будем использовать как [[ru_wikipedia:STUN|STUN]], так и [[ru_wikipedia:Traversal Using Relay NAT|TURN]] - все возможные варианты: | ||
:: '''_stun._tcp.EXAMPLE.COM''' (порт '''3478''') - [[ru_wikipedia:STUN|STUN]] через [[ru_wikipedia:TCP|TCP]] | :: '''_stun._tcp.EXAMPLE.COM''' (порт '''3478''') - [[ru_wikipedia:STUN|STUN]] через [[ru_wikipedia:TCP|TCP]] | ||
Строка 30: | Строка 30: | ||
:: Во всех случаях target'ом для записей служит наш алиас из первого пункта. | :: Во всех случаях target'ом для записей служит наш алиас из первого пункта. | ||
{{Hider hiding| | |||
|title=Для продвинутых | |||
|content= | |||
:: Самый полный вариант подразумевает в том числе создание SRV-записей для каждого из сервисов-субдоменов - тех, к которым будут подключаться сторонние серверы. Например, для конференций это может выглядеть так: | |||
:: '''_xmpp-server._tcp.conference.EXAMPLE.COM''' (порт '''5269''') | |||
:: '''_xmpps-server._tcp.conference.EXAMPLE.COM''' (порт '''5270''' для TLS) | |||
:: Но, как уже говорилось выше, это для совсем уж необычных конфигураций - когда сам домен указывает на один адрес (например, там может работать веб-сервер, предоставляющий чат-логи на сайте https://conference.example.com), а сам jabber-сервер при этом находится совершенно на другом. | |||
}} | |||
Вот как выглядят записи для данного сервера в админке бесплатного DNS-провайдера [https://dns.he.net Hurricane Electric]: | Вот как выглядят записи для данного сервера в админке бесплатного DNS-провайдера [https://dns.he.net Hurricane Electric]: | ||
Строка 82: | Строка 91: | ||
В каталогах сервера не обязательно должен быть какой-то контент, хотя, например, на основном домене можно разместить какую-то страничку, посвященную jabber-серверу, а на поддомене '''conference''' разместить чат-логи [[Конференции|конференций]]. Субдомен '''upload''' можно приспособить под загружаемые через HTTP Upload файлы, чтобы в дальнейшем их раздавал веб-сервер<ref name="http_upload"></ref>. | В каталогах сервера не обязательно должен быть какой-то контент, хотя, например, на основном домене можно разместить какую-то страничку, посвященную jabber-серверу, а на поддомене '''conference''' разместить чат-логи [[Конференции|конференций]]. Субдомен '''upload''' можно приспособить под загружаемые через HTTP Upload файлы, чтобы в дальнейшем их раздавал веб-сервер<ref name="http_upload"></ref>. | ||
После всех приготовлений создайте все необходимые сертификаты. Для этого воспользуйтесь следующими командами (выполнять надо от пользователя '''root'''): | После всех приготовлений<ref>Более подробно можно почитать тут: [[Свой сервер: подготовка веб-сервера и сертификатов]]</ref> создайте все необходимые сертификаты. Для этого воспользуйтесь следующими командами (выполнять надо от пользователя '''root'''): | ||
certbot certonly --webroot --webroot-path /var/www/EXAMPLE.COM/htdocs/ -d EXAMPLE.COM | certbot certonly --webroot --webroot-path /var/www/EXAMPLE.COM/htdocs/ -d EXAMPLE.COM --rsa-key-size 4096 | ||
certbot certonly --webroot --webroot-path /var/www/conference.EXAMPLE.COM/htdocs/ -d conference.EXAMPLE.COM | certbot certonly --webroot --webroot-path /var/www/conference.EXAMPLE.COM/htdocs/ -d conference.EXAMPLE.COM --rsa-key-size 4096 | ||
certbot certonly --webroot --webroot-path /var/www/upload.EXAMPLE.COM/htdocs/ -d upload.EXAMPLE.COM | certbot certonly --webroot --webroot-path /var/www/upload.EXAMPLE.COM/htdocs/ -d upload.EXAMPLE.COM --rsa-key-size 4096 | ||
certbot certonly --webroot --webroot-path /var/www/pubsub.EXAMPLE.COM/htdocs/ -d pubsub.EXAMPLE.COM | certbot certonly --webroot --webroot-path /var/www/pubsub.EXAMPLE.COM/htdocs/ -d pubsub.EXAMPLE.COM --rsa-key-size 4096 | ||
certbot certonly --webroot --webroot-path /var/www/proxy.EXAMPLE.COM/htdocs/ -d proxy.EXAMPLE.COM | certbot certonly --webroot --webroot-path /var/www/proxy.EXAMPLE.COM/htdocs/ -d proxy.EXAMPLE.COM --rsa-key-size 4096 | ||
В параметрах после '''--webroot-path''' указывается каталог веб-сервера для данного домена, а после '''-d''' - сам домен. | В параметрах после '''--webroot-path''' указывается каталог веб-сервера для данного домена, а после '''-d''' - сам домен. | ||
Строка 125: | Строка 134: | ||
sudo apt-get install erlang-p1-mysql | sudo apt-get install erlang-p1-mysql | ||
Создайте базу для jabber-сервера, после чего импортируйте туда схему базы из '''/usr/share/ejabberd/sql/mysql.sql''' <ref name="ejabberd_mysql"></ref>. | [[Свой сервер: подготовка базы данных|Создайте базу для jabber-сервера]], после чего импортируйте туда схему базы из '''/usr/share/ejabberd/sql/mysql.sql''' <ref name="ejabberd_mysql"></ref><ref>В последних версиях ejabberd (24 и новее) этот шаг не обязателен - схема создастся автоматически</ref>. | ||
Далее приводится пример конфигурации jabber-сервера. Рассмотрим его подробнее. | Далее приводится пример конфигурации jabber-сервера. Рассмотрим его подробнее. | ||
Строка 202: | Строка 211: | ||
'TLS_OPTIONS': | 'TLS_OPTIONS': | ||
- "no_sslv3" | - "no_sslv3" | ||
- "no_tlsv1" | |||
- "no_tlsv1_1" | |||
- "cipher_server_preference" | - "cipher_server_preference" | ||
- "no_compression" | - "no_compression" | ||
Строка 417: | Строка 426: | ||
- normal | - normal | ||
s2s_shaper: fast | s2s_shaper: fast | ||
soft_upload_quota: | |||
- 1000: admin | |||
- 250 | |||
hard_upload_quota: | |||
- 1000: admin | |||
- 250 | |||
modules: | modules: | ||
Строка 468: | Строка 483: | ||
db_type: sql | db_type: sql | ||
assume_mam_usage: true | assume_mam_usage: true | ||
default: | default: always | ||
compress_xml: true | compress_xml: true | ||
use_cache: true | use_cache: true | ||
Строка 526: | Строка 541: | ||
welcome_message: | welcome_message: | ||
subject: "Добро пожаловать на Jabber-сервер!" | subject: "Добро пожаловать на Jabber-сервер!" | ||
body: "Приветствую. Ведите себя хорошо. Доступен веб-клиент ConverseJS по адресу https://xmpp.EXAMPLE.COM. Лимит на загружаемые файлы - 100 МБ. Срок хранения - 180 дней." | body: "Приветствую. Ведите себя хорошо. Доступен веб-клиент ConverseJS по адресу https://xmpp.EXAMPLE.COM. Лимит на загружаемые файлы - 100 МБ, общий размер - 250 МБ. Срок хранения - 180 дней." | ||
mod_roster: | mod_roster: | ||
versioning: true | versioning: true | ||
Строка 624: | Строка 639: | ||
host_config: | host_config: | ||
"EXAMPLE.COM": | "EXAMPLE.COM": | ||
sql_database: " | sql_database: "jabber_example_com" | ||
sql_username: " | sql_username: "jabberuser" | ||
sql_password: " | sql_password: "MYSECRETPASSWORD" | ||
sql_port: 3306 | sql_port: 3306 | ||
Строка 646: | Строка 661: | ||
* После настроек шейпера идет довольно объемное описание различных модулей. Из интересного: | * После настроек шейпера идет довольно объемное описание различных модулей. Из интересного: | ||
** В '''server-info''' для '''mod_disco''' можно указать контакты администратора сервера, техподдержки или просто дополнительную информацию. | ** В '''server-info''' для '''mod_disco''' можно указать контакты администратора сервера, техподдержки или просто дополнительную информацию. | ||
** В '''mod_http_upload''' можно указать адрес, с которого будут отдаваться загруженные на сервер через HTTP Upload файлы. При желании можно использовать не встроенный веб-сервер, а тот, что был настроен для получения сертификатов<ref name="http_upload">В таком случае ему нужно обеспечить доступ к каталогу '''/var/lib/ejabberd/upload/''' - а лучше указать опцию '''docroot''' с путем за пределами домашнего каталога ejabberd, плюс воспользоваться опцией '''file_mode''', чтобы файлы, созданные ejabberd, могли быть доступны для веб-сервера</ref>. Чуть ниже этой опции есть настройка времени устаревания файлов - в примере выше это 180 дней. Если у вас мало места на сервере, то можно поставить это значение поменьше. | ** В '''mod_http_upload''' можно указать адрес, с которого будут отдаваться загруженные на сервер через HTTP Upload файлы. При желании можно использовать не встроенный веб-сервер, а тот, что был настроен для получения сертификатов<ref name="http_upload">В таком случае ему нужно обеспечить доступ к каталогу '''/var/lib/ejabberd/upload/''' - а лучше указать опцию '''docroot''' с путем за пределами домашнего каталога ejabberd, плюс воспользоваться опцией '''file_mode''', чтобы файлы, созданные ejabberd, могли быть доступны для веб-сервера. Такое решение имеет еще одно преимущество: разнообразные клиенты (не только Jabber) будут гораздо охотнее показывать превью для обычных HTTP/HTTPS-ссылок (т.е., на 80 или 443-м порту), а не тех, где контент отдается на "необычном" порту - вроде 5283 в данном примере.</ref>. Чуть ниже этой опции есть настройка времени устаревания файлов - в примере выше это 180 дней. Если у вас мало места на сервере, то можно поставить это значение поменьше. Кроме того, в разделе '''shaper_rules''' дополнительно можно указать '''*_upload_quota'''<ref name="quota"></ref> - лимит на общий объем файлов пользователя<ref>При необходимости можно добавить нужные категории пользователей в разделе '''acl''' по аналогии с теми, что уже там есть (admin, local и так далее). Например, можно добавить группу VIP, которой выделить больше места для загрузки файлов, чем обычным пользователям, но меньше, чем админу</ref>, выше которого старые файлы начнут удаляться<ref name="quota">Удаляются они при достижении '''hard quota''', а перестают удаляться при снижении до '''soft quota'''. Т.е., если хочется сохранить максимум файлов на доступном месте - просто поставьте оба значения одинаковыми</ref>. | ||
** Аналогичным образом можно поступить с '''mod_muc_log''' - модулем, позволяющим вести логи конференций (при включении соответствующей опции в настройках этой конференции). В примере выше файлы просто складываются в определенный каталог, а дальше их уже раздает веб-сервер. | ** Аналогичным образом можно поступить с '''mod_muc_log''' - модулем, позволяющим вести логи конференций (при включении соответствующей опции в настройках этой конференции). В примере выше файлы просто складываются в определенный каталог, а дальше их уже раздает веб-сервер. | ||
** Для модуля proxy65 не забудьте указать внешний адрес сервера. | ** Для модуля '''proxy65''' не забудьте указать внешний адрес сервера. | ||
** Если хотите получать уведомления о новых регистрациях - обратите внимание на параметр '''registration_watchers''' в секции '''mod_register'''. Тут в том числе можно запретить регистрацию пользователей вообще или ограничить ее определенными адресами (например, локальной сетью), а также задать приветственное сообщение, отправляемое каждому новому пользователю. | ** Если хотите получать уведомления о новых регистрациях - обратите внимание на параметр '''registration_watchers''' в секции '''mod_register'''. Тут в том числе можно запретить регистрацию пользователей вообще или ограничить ее определенными адресами (например, локальной сетью), а также задать приветственное сообщение, отправляемое каждому новому пользователю. | ||
** Ниже идет большой блок параметров STUN/TURN-сервера. Укажите внешние IP-адреса сервера и обслуживаемый домен; если у сервера нет IPv6-адреса - закомментируйте соответствующие блоки. | ** Ниже идет большой блок параметров STUN/TURN-сервера. Укажите внешние IP-адреса сервера и обслуживаемый домен; если у сервера нет IPv6-адреса - закомментируйте соответствующие блоки. | ||
Строка 655: | Строка 670: | ||
Фактически, после указания домена и IP-адресов, сертификатов, [[Jabber ID]] админа и параметров подключения к базе сервер готов к использованию. Запустите сервер, после чего создайте учетную запись администратора - сделать это можно либо в [[Клиенты Jabber|jabber-клиенте]], либо в консоли с использованием '''ejabberdctl'''. В дальнейшем управлять сервером можно по адресу [https://EXAMPLE.COM:5281/admin https://EXAMPLE.COM:5281/admin] - само собой, подставьте в данном случае свой домен. | Фактически, после указания домена и IP-адресов, сертификатов, [[Jabber ID]] админа и параметров подключения к базе сервер готов к использованию. Запустите сервер, после чего создайте учетную запись администратора - сделать это можно либо в [[Клиенты Jabber|jabber-клиенте]], либо в консоли с использованием '''ejabberdctl'''. В дальнейшем управлять сервером можно по адресу [https://EXAMPLE.COM:5281/admin https://EXAMPLE.COM:5281/admin] - само собой, подставьте в данном случае свой домен. | ||
Если вы хотите удалять старые сообщения из базы данных сервера - воспользуйтесь следующей командой, помещенной в cron: | |||
/usr/sbin/ejabberdctl delete_old_mam_messages all 30 | |||
где 30 - число дней, за которые надо хранить историю. | |||
На этом все :) | На этом все :) |
Текущая версия на 22:59, 30 октября 2024
В этой статье приводится пример настройки ejabberd с поддержкой актуальных и популярных функций. В качестве основы взят конфиг-файл с работающего сервера версии 21.12, а в качестве сертификатов используется Let's Encrypt - и все это установлено на Debian GNU/Linux. Такой сервер соответствует современным нормам безопасности, поддерживает разнообразные варианты подключения к нему, имеет веб-клиент для тех, кто по каким-то причинам не может использовать обычный jabber-клиент, умеет вести логи популярных каналов, доступные через браузер - и многое другое.
Рассмотрим особенности данной конфигурации.
DNS-записи[править]
Хотя для базового варианта своего jabber-сервера вполне достаточно бесплатного DynDNS-домена, для полноценной конфигурации все же нужно обзавестись собственным доменом, где можно будет добавить все необходимые DNS-записи и сделать субдомены для сервисов. Выбор регистратора и покупка домена остается за рамками данной статьи, тут только приводятся шаги, которые надо сделать после покупки домена в его админке.
Всего понадобится около 15 записей в DNS:
- Для удобства, чтобы в случае миграции сервера с одного IP на другой не приходилось переписывать все записи, стоит завести для него субдомен, который будет ссылаться на нужный IP с помощью А-записи (или 2 таких субдомена в случае использования IPv4+IPv6 - один с A-записью, а второй с AAAA), а все остальные записи сделать CNAME-алиасами для него. Например, для данного сервера (jabberworld.info) сделаны 2 субдомена xmpp.jabberworld.info, ссылающиеся на IPv4 и IPv6-адреса.
- Создаем субдомены для необходимых сервисов сервера[1] - conference (для конференций), proxy (прокси для прямой передачи файлов), pubsub (сервисы типа "Публикация/подписка", где может сохраняться самая разная информация) и upload (сервис для передачи файлов через HTTP Upload). Все эти записи создаем как CNAME на наш субдомен из 1-го пункта.
- Создаем ряд SRV-записей для нашего основного домена - они показывают, где именно какой сервис находится. Конечно, если у вас все на одном адресе, то будет работать и так, но мы ведь решили все делать правильно, верно? Итак, нужны такие SRV-записи:
- _xmpp-client._tcp.EXAMPLE.COM (порт 5222) - чтобы указать, куда подключаться jabber-клиенту
- _xmpps-client._tcp.EXAMPLE.COM (порт 5223) - аналогичная запись для TLS-подключений клиентов
- _xmpp-server._tcp.EXAMPLE.COM (порт 5269) - чтобы указать, куда подключаться jabber-серверу
- _xmpps-server._tcp.EXAMPLE.COM (порт 5270) - аналогичная запись для TLS-подключений серверов.
- _stun._tcp.EXAMPLE.COM (порт 3478) - STUN через TCP
- _stun._udp.EXAMPLE.COM (порт 3478) - STUN через UDP
- _stuns._tcp.EXAMPLE.COM (порт 5349) - шифрованный STUN через TCP
- _turn._tcp.EXAMPLE.COM (порт 3478) - TURN через TCP
- _turn._udp.EXAMPLE.COM (порт 3478) - TURN через UDP
- _turns._tcp.EXAMPLE.COM (порт 5349) - шифрованный TURN через TCP
- Во всех случаях target'ом для записей служит наш алиас из первого пункта.
- Самый полный вариант подразумевает в том числе создание SRV-записей для каждого из сервисов-субдоменов - тех, к которым будут подключаться сторонние серверы. Например, для конференций это может выглядеть так:
- _xmpp-server._tcp.conference.EXAMPLE.COM (порт 5269)
- _xmpps-server._tcp.conference.EXAMPLE.COM (порт 5270 для TLS)
- Но, как уже говорилось выше, это для совсем уж необычных конфигураций - когда сам домен указывает на один адрес (например, там может работать веб-сервер, предоставляющий чат-логи на сайте https://conference.example.com), а сам jabber-сервер при этом находится совершенно на другом.
Вот как выглядят записи для данного сервера в админке бесплатного DNS-провайдера Hurricane Electric:
Сертификаты[править]
В современном мире стандартной практикой является шифрование соединений, а для этого, в свою очередь, требуются сертификаты от доверенных центров сертификации. Можно, конечно, использовать самоподписанный сертификат, но, во-первых, не все серверы будут их принимать - а значит, к ним не получится подключиться и общаться с их пользователями, а во-вторых, в подключающихся клиентах будут появляться сообщения о недоверенном сертификате, что тоже будет доставлять определенные неудобства. Поэтому стоит воспользоваться услугами одного из популярных бесплатных центров сертификации - Let's Encrypt.
Для работы с сертификатами от Let's Encrypt в Debian есть специальный пакет - certbot, поэтому установите его следующей командой:
sudo apt-get install certbot
Сертификаты от Let's Encrypt выдаются сроком на 3 месяца, поэтому в конце этого срока их нужно обновлять. Для подтверждения владения доменом, а также для автоматизации обновления сертификатов в дальнейшем со стороны Let's Encrypt делается запрос на веб-сервер для указанного домена, поэтому для полноценной работы стоит установить какой-нибудь популярный вариант и прописать там виртуальные хосты для нашего jabber-сервера, а также сервисов на нем (сертификаты будут делаться в том числе для сервисов). Пример конфигурации веб-сервера Apache для данного сервера:
<VirtualHost 185.161.208.229:80 [2a07:c801:0:5::]:80> ServerAdmin webmaster@jabberworld.info DocumentRoot /var/www/jabberworld.info/htdocs ServerName jabberworld.info </VirtualHost> <VirtualHost 185.161.208.229:80 [2a07:c801:0:5::]:80> ServerAdmin webmaster@upload.jabberworld.info DocumentRoot /var/www/upload.jabberworld.info/htdocs ServerName upload.jabberworld.info </VirtualHost> <VirtualHost 185.161.208.229:80 [2a07:c801:0:5::]:80> ServerAdmin webmaster@pubsub.jabberworld.info DocumentRoot /var/www/pubsub.jabberworld.info/htdocs ServerName pubsub.jabberworld.info </VirtualHost> <VirtualHost 185.161.208.229:80 [2a07:c801:0:5::]:80> ServerAdmin webmaster@conference.jabberworld.info DocumentRoot /var/www/conference.jabberworld.info/htdocs ServerName conference.jabberworld.info </VirtualHost> <VirtualHost 185.161.208.229:80 [2a07:c801:0:5::]:80> ServerAdmin webmaster@proxy.jabberworld.info DocumentRoot /var/www/proxy.jabberworld.info/htdocs ServerName proxy.jabberworld.info </VirtualHost>
В каталогах сервера не обязательно должен быть какой-то контент, хотя, например, на основном домене можно разместить какую-то страничку, посвященную jabber-серверу, а на поддомене conference разместить чат-логи конференций. Субдомен upload можно приспособить под загружаемые через HTTP Upload файлы, чтобы в дальнейшем их раздавал веб-сервер[3].
После всех приготовлений[4] создайте все необходимые сертификаты. Для этого воспользуйтесь следующими командами (выполнять надо от пользователя root):
certbot certonly --webroot --webroot-path /var/www/EXAMPLE.COM/htdocs/ -d EXAMPLE.COM --rsa-key-size 4096 certbot certonly --webroot --webroot-path /var/www/conference.EXAMPLE.COM/htdocs/ -d conference.EXAMPLE.COM --rsa-key-size 4096 certbot certonly --webroot --webroot-path /var/www/upload.EXAMPLE.COM/htdocs/ -d upload.EXAMPLE.COM --rsa-key-size 4096 certbot certonly --webroot --webroot-path /var/www/pubsub.EXAMPLE.COM/htdocs/ -d pubsub.EXAMPLE.COM --rsa-key-size 4096 certbot certonly --webroot --webroot-path /var/www/proxy.EXAMPLE.COM/htdocs/ -d proxy.EXAMPLE.COM --rsa-key-size 4096
В параметрах после --webroot-path указывается каталог веб-сервера для данного домена, а после -d - сам домен.
После успешного выполнения команд будет выдана информация о созданном сертификате, в том числе 2 файла - цепочка ключей и приватный ключ - сохраните эти пути для дальнейшего использования, они нам еще пригодятся. Всего получится 10 файлов. certbot обновляет сертификаты автоматически, если срок их истечения составляет менее 30 дней - т.е., обновление происходит раз в 2 месяца.
Так как ejabberd работает от своего пользователя и не имеет доступа к сертификатам, созданным certbot от рута, то надо каким-то образом предоставить доступ ejabberd'у к сертификатам. Делать это можно по-разному - кто-то, например, меняет права на созданные сертификаты и добавляет возможность ejabberd'у получать доступ к нужным файлам - правда, эти права сбрасываются после каждого обновления сертификатов. В моем случае я создал для ejabberd отдельный каталог - /etc/ejabberd/certs, куда копируются созданные сертификаты и уже там даются права для jabber-сервера.
#!/bin/bash for i in {proxy.,upload.,pubsub.,conference.,}EXAMPLE.COM do cp /etc/letsencrypt/live/$i/fullchain.pem /etc/ejabberd/certs/${i}.fullchain.pem cp /etc/letsencrypt/live/$i/privkey.pem /etc/ejabberd/certs/${i}.privkey.pem done chown root:ejabberd -R /etc/ejabberd/certs/ chmod 640 /etc/ejabberd/certs/*.pem /usr/sbin/ejabberdctl reload_config
Настройка ejabberd[править]
Все подготовительные процедуры завершены, теперь можно приступать непосредственно к установке и настройке своего jabber-сервера. В Debian установить ejabberd можно командой
sudo apt-get install ejabberd
Зачастую самая свежая версия может находиться в backports, поэтому при необходимости можно указать конкретный репозиторий из вашего /etc/apt/sources.list с помощью ключа -t - например, -t bullseye-backports. Кроме того, с недавних пор у ejabberd появился официальный DEB и RPM-репозиторий - подробнее об этом можно почитать по этой ссылке.
Штатно ejabberd использует собственную базу данных - Mnesia. Ее объем ограничен 2 ГБ, поэтому если планируется большое количество пользователей, большие объемы сообщений или просто хочется использовать удобные популярные инструменты для работы с привычной вам базой - я бы рекомендовал использовать внешнюю базу данных - например, MySQL или MariaDB. Я так и поступил, тем более сделать это весьма просто - кроме ejabberd просто установите еще пакет erlang-p1-mysql командой
sudo apt-get install erlang-p1-mysql
Создайте базу для jabber-сервера, после чего импортируйте туда схему базы из /usr/share/ejabberd/sql/mysql.sql [5][6].
Далее приводится пример конфигурации jabber-сервера. Рассмотрим его подробнее.
### ### ejabberd configuration file ### ### The parameters used in this configuration file are explained at ### ### https://docs.ejabberd.im/admin/configuration ### ### The configuration file is written in YAML. ### ******************************************************* ### ******* !!! WARNING !!! ******* ### ******* YAML IS INDENTATION SENSITIVE ******* ### ******* MAKE SURE YOU INDENT SECTIONS CORRECTLY ******* ### ******************************************************* ### Refer to http://en.wikipedia.org/wiki/YAML for the brief description. ### However, ejabberd treats different literals as different types: ### ### - unquoted or single-quoted strings. They are called "atoms". ### Example: dog, 'Jupiter', '3.14159', YELLOW ### ### - numeric literals. Example: 3, -45.0, .0 ### ### - quoted or folded strings. ### Examples of quoted string: "Lizzard", "orange". ### Example of folded string: ### > Art thou not Romeo, ### and a Montague? ### # --- ## loglevel: Verbosity of log files generated by ejabberd ## 0: No ejabberd log at all (not recommended) ## 1: Critical ## 2: Error ## 3: Warning ## 4: Info ## 5: Debug loglevel: 4 ## rotation: Disable ejabberd's internal log rotation, as the Debian package ## uses logrotate(8). log_rotate_count: 0 #log_rotate_date: "" ## hosts: Domains served by ejabberd. ## You can define one or several, for example: ## hosts: ## - "example.net" ## - "example.com" ## - "example.org" hosts: - "EXAMPLE.COM" certfiles: - "/etc/ejabberd/certs/conference.EXAMPLE.COM.fullchain.pem" - "/etc/ejabberd/certs/conference.EXAMPLE.COM.privkey.pem" - "/etc/ejabberd/certs/EXAMPLE.COM.fullchain.pem" - "/etc/ejabberd/certs/EXAMPLE.COM.privkey.pem" - "/etc/ejabberd/certs/pubsub.EXAMPLE.COM.fullchain.pem" - "/etc/ejabberd/certs/pubsub.EXAMPLE.COM.privkey.pem" - "/etc/ejabberd/certs/upload.EXAMPLE.COM.fullchain.pem" - "/etc/ejabberd/certs/upload.EXAMPLE.COM.privkey.pem" - "/etc/ejabberd/certs/proxy.EXAMPLE.COM.fullchain.pem" - "/etc/ejabberd/certs/proxy.EXAMPLE.COM.privkey.pem" ## TLS configuration define_macro: 'TLS_CIPHERS': "HIGH:!aNULL:!eNULL:!3DES:@STRENGTH" 'TLS_OPTIONS': - "no_sslv3" - "no_tlsv1" - "no_tlsv1_1" - "cipher_server_preference" - "no_compression" ## generated with: openssl dhparam -out dhparams.pem 2048 c2s_ciphers: 'TLS_CIPHERS' s2s_ciphers: 'TLS_CIPHERS' c2s_protocol_options: 'TLS_OPTIONS' s2s_protocol_options: 'TLS_OPTIONS' ## c2s_dhfile: 'DH_FILE' s2s_dhfile: "/etc/ejabberd/dhparams.pem" listen: - port: 3478 ip: "::" transport: udp module: ejabberd_stun auth_realm: "@HOST@" use_turn: true ## The server's public IPv4 address: turn_ipv4_address: "123.123.123.123" ## The server's public IPv6 address: turn_ipv6_address: "2a01:0123:0123:0123::" - port: 3478 ip: "::" transport: tcp module: ejabberd_stun auth_realm: "@HOST@" use_turn: true ## The server's public IPv4 address: turn_ipv4_address: "123.123.123.123" ## The server's public IPv6 address: turn_ipv6_address: "2a01:0123:0123:0123::" - port: 5349 transport: tcp module: ejabberd_stun use_turn: true tls: true ip: "::" ## The server's public IPv4 address: turn_ipv4_address: "123.123.123.123" ## The server's public IPv6 address: turn_ipv6_address: "2a01:0123:0123:0123::" - port: 5000 ip: "::" module: ejabberd_http tls: true request_handlers: /conversejs: mod_conversejs /: mod_http_fileserver - port: 5222 ip: "::" module: ejabberd_c2s max_stanza_size: 262144 shaper: c2s_shaper access: c2s zlib: true starttls_required: true protocol_options: 'TLS_OPTIONS' - port: 5223 ip: "::" module: ejabberd_c2s max_stanza_size: 262144 shaper: c2s_shaper access: c2s tls: true zlib: true starttls_required: true protocol_options: 'TLS_OPTIONS' - port: 5269 ip: "::" module: ejabberd_s2s_in max_stanza_size: 524288 - port: 5270 ip: "::" tls: true module: ejabberd_s2s_in max_stanza_size: 524288 - port: 5280 ip: "::" module: ejabberd_http request_handlers: "/captcha": ejabberd_captcha tls: true - port: 5281 ip: "::" module: ejabberd_http request_handlers: "/admin": ejabberd_web_admin tls: true - port: 5282 ip: "::" module: ejabberd_http request_handlers: "/captcha": ejabberd_captcha "/register": mod_register_web tls: true - port: 5283 ip: "::" module: ejabberd_http request_handlers: "/api": mod_http_api "/bosh": mod_bosh "/upload": mod_http_upload "/ws": ejabberd_http_ws "/captcha": ejabberd_captcha tls: true ## Disabling digest-md5 SASL authentication. digest-md5 requires plain-text ## password storage (see auth_password_format option). disable_sasl_mechanisms: - "digest-md5" - "X-OAUTH2" s2s_use_starttls: required ## Store the plain passwords or hashed for SCRAM: auth_password_format: scram ## Full path to a script that generates the image. captcha_cmd: "/usr/share/ejabberd/captcha.sh" #captcha_url: "https://xmpp.EXAMPLE.COM:5282" captcha_host: "https://EXAMPLE.COM:5282" acl: admin: user: - "ADMIN": "EXAMPLE.COM" local: user_regexp: "" loopback: ip: - "127.0.0.0/8" - "::1/128" - "::FFFF:127.0.0.1/128" access_rules: local: - allow: local c2s: - deny: blocked - allow announce: - allow: admin configure: - allow: admin muc_create: - allow: local muc: - allow pubsub_createnode: - allow: local register: - allow trusted_network: - allow: loopback #webadmin_view: # - viewers: allow api_permissions: "console commands": from: - ejabberd_ctl who: all what: "*" "admin access": who: - access: - allow: - acl: loopback - acl: admin - oauth: - scope: "ejabberd:admin" - access: - allow: - acl: loopback - acl: admin what: - "*" - "!stop" - "!start" "public commands": who: - ip: "127.0.0.1/8" what: - "status" - "connected_users_number" shaper: normal: 2500 fast: 50000 shaper_rules: max_user_sessions: 25 max_user_offline_messages: - 5000: admin - 200 c2s_shaper: - none: admin - normal s2s_shaper: fast soft_upload_quota: - 1000: admin - 250 hard_upload_quota: - 1000: admin - 250 modules: mod_adhoc: {} mod_admin_extra: {} mod_announce: access: announce mod_avatar: {} mod_blocking: {} mod_bosh: {} mod_caps: {} mod_carboncopy: {} mod_client_state: {} mod_configure: {} ## mod_delegation: {} # for xep0356 mod_disco: server_info: - modules: all name: "abuse-addresses" urls: - "xmpp:ADMIN@EXAMPLE.COM" - "mailto:ADMIN@EXAMPLE.COM" - modules: [mod_muc] name: "Web chatroom logs" urls: ["https://chatlogs.EXAMPLE.COM"] - modules: all name: "support-addresses" urls: - "xmpp:ADMIN@EXAMPLE.COM" - "xmpp:support@conference.EXAMPLE.COM?join" - "https://my.cool.site" mod_fail2ban: {} mod_http_api: {} mod_http_upload: put_url: "https://@HOST@:5283/upload" thumbnail: false jid_in_url: sha1 custom_headers: "Access-Control-Allow-Origin": "*" "Access-Control-Allow-Methods": "GET,HEAD,PUT,OPTIONS" "Access-Control-Allow-Headers": "Content-Type" mod_http_upload_quota: max_days: 180 mod_mam: ## ## Mnesia is limited to 2GB, better to use an SQL backend ## ## For small servers SQLite is a good fit and is very easy ## ## to configure. Uncomment this when you have SQL configured: db_type: sql assume_mam_usage: true default: always compress_xml: true use_cache: true cache_life_time: 86400 mod_muc: access: - allow access_admin: - allow: admin access_create: muc_create access_persistent: muc_create default_room_options: mam: true mod_muc_admin: {} mod_offline: access_max_user_messages: max_user_offline_messages mod_muc_log: outdir: "/var/www/chatlogs" access_log: muc cssfile: /var/www/chatlogs.EXAMPLE.COM/htdocs/muc.css #cssfile: http://chatlogs.EXAMPLE.COM/muc.css mod_ping: {} mod_pres_counter: count: 5 interval: 60 mod_privacy: {} mod_private: {} mod_proxy65: # remove ip? ip: 123.123.123.123 access: local max_connections: 10 mod_pubsub: access_createnode: pubsub_createnode plugins: - "flat" - "pep" force_node_config: "eu.siacs.conversations.axolotl.*": access_model: open ## Avoid buggy clients to make their bookmarks public "storage:bookmarks": access_model: whitelist mod_push: {} mod_push_keepalive: {} mod_register: ## Only accept registration requests from the "trusted" ## network (see access_rules section above). ## Think twice before enabling registration from any ## address. See the Jabber SPAM Manifesto for details: ## https://github.com/ge0rg/jabber-spam-fighting-manifesto #ip_access: trusted_network ip_access: all captcha_protected: true registration_watchers: - "ADMIN@EXAMPLE.COM" welcome_message: subject: "Добро пожаловать на Jabber-сервер!" body: "Приветствую. Ведите себя хорошо. Доступен веб-клиент ConverseJS по адресу https://xmpp.EXAMPLE.COM. Лимит на загружаемые файлы - 100 МБ, общий размер - 250 МБ. Срок хранения - 180 дней." mod_roster: versioning: true mod_s2s_dialback: {} mod_shared_roster: {} mod_sic: {} mod_stream_mgmt: resend_on_timeout: if_offline mod_vcard: search: false mod_stun_disco: services: - host: 123.123.123.123 port: 3478 type: stun transport: udp restricted: false - host: 123.123.123.123 port: 3478 type: turn transport: udp restricted: true - host: 123.123.123.123 port: 3478 type: stun transport: tcp restricted: false - host: 123.123.123.123 port: 3478 type: turn transport: tcp restricted: true - host: "2a01:0123:0123:0123::" port: 3478 type: stun transport: udp restricted: false - host: "2a01:0123:0123:0123::" port: 3478 type: turn transport: udp restricted: true - host: "2a01:0123:0123:0123::" port: 3478 type: stun transport: tcp restricted: false - host: "2a01:0123:0123:0123::" port: 3478 type: turn transport: tcp restricted: true - host: EXAMPLE.COM port: 5349 type: stuns transport: tcp restricted: false - host: EXAMPLE.COM port: 5349 type: turns transport: tcp restricted: true mod_vcard_xupdate: {} mod_version: show_os: false mod_stats: {} mod_last: {} # mod_time: {} mod_conversejs: websocket_url: "wss://EXAMPLE.COM:5283/ws" conversejs_script: "https://EXAMPLE.COM:5000/converse.min.js" conversejs_css: "https://EXAMPLE.COM:5000/converse.min.css" # bosh_service_url: "https://EXAMPLE.COM:5283/bosh" default_domain: "EXAMPLE.COM" mod_http_fileserver: docroot: "/var/www/ejabberd/package/dist" accesslog: "/var/log/ejabberd/fileserver-access.log" custom_headers: "Access-Control-Allow-Origin": "*" "Access-Control-Allow-Methods": "GET,HEAD,OPTIONS" "Access-Control-Allow-Headers": "Content-Type" default_db: sql sql_type: mysql sql_server: "localhost" host_config: "EXAMPLE.COM": sql_database: "jabber_example_com" sql_username: "jabberuser" sql_password: "MYSECRETPASSWORD" sql_port: 3306 language: "ru" ### Local Variables: ### mode: yaml ### End: ### vim: set filetype=yaml tabstop=8
- Первым делом указывается один или несколько хостов, которые будет обслуживать сервер - в конфигурационном файле выше он указан как EXAMPLE.COM.
- Далее надо указать перечень файлов сертификатов, которые мы создали раньше.
- Секция Listen описывает порты, которые слушает сервер. Тут перечисляются порты для STUN/TURN (3478) и их шифрованных аналогов (5349); порты для подключения клиентов (5222 и 5223) и серверов (5269 и 5270), а также ряд дополнительных портов. К ним относятся порты для админ-панели сервера (5281), веб-страницы для регистрации пользователей (5282), а также подключений к серверу через Websocket или BOSH и загрузка файлов через HTTP Upload (5283) - я постарался разнести различные сервисы по разным портам, чтобы в дальнейшем было проще, например, ограничить доступ к админ-панели определенными адресами, не блокируя при этом доступ к остальным функциям. Еще одним полезным сервисом является веб-клиент ConverseJS - под него выделен порт 5000 - о нем будет написано чуть ниже. Для некоторых описанных портов необходимо указывать внешний IP-адрес сервера - это нужно, чтобы сервис знал, какой именно адрес отдавать клиентам - это относится к настройкам STUN/TURN и Proxy. В качестве шаблона в показанном конфиге указаны адреса 123.123.123.123 для IPv4 и 2a01:0123:0123:0123:: для IPv6 - если не используете IPv6, то просто закомментируйте соответствующие строки.
- Далее необходимо указать хост, с которого будет отдаваться картинка CAPTCHA.
- Далее указывается один или несколько администраторских аккаунтов - с помощью них можно будет попасть в веб-интерфейс администрирования, зайти в любую конференцию, а также в браузере сервисов будут доступны дополнительные административные пункты.
- После настроек шейпера идет довольно объемное описание различных модулей. Из интересного:
- В server-info для mod_disco можно указать контакты администратора сервера, техподдержки или просто дополнительную информацию.
- В mod_http_upload можно указать адрес, с которого будут отдаваться загруженные на сервер через HTTP Upload файлы. При желании можно использовать не встроенный веб-сервер, а тот, что был настроен для получения сертификатов[3]. Чуть ниже этой опции есть настройка времени устаревания файлов - в примере выше это 180 дней. Если у вас мало места на сервере, то можно поставить это значение поменьше. Кроме того, в разделе shaper_rules дополнительно можно указать *_upload_quota[7] - лимит на общий объем файлов пользователя[8], выше которого старые файлы начнут удаляться[7].
- Аналогичным образом можно поступить с mod_muc_log - модулем, позволяющим вести логи конференций (при включении соответствующей опции в настройках этой конференции). В примере выше файлы просто складываются в определенный каталог, а дальше их уже раздает веб-сервер.
- Для модуля proxy65 не забудьте указать внешний адрес сервера.
- Если хотите получать уведомления о новых регистрациях - обратите внимание на параметр registration_watchers в секции mod_register. Тут в том числе можно запретить регистрацию пользователей вообще или ограничить ее определенными адресами (например, локальной сетью), а также задать приветственное сообщение, отправляемое каждому новому пользователю.
- Ниже идет большой блок параметров STUN/TURN-сервера. Укажите внешние IP-адреса сервера и обслуживаемый домен; если у сервера нет IPv6-адреса - закомментируйте соответствующие блоки.
- Предпоследней настройкой в секции модулей идет настройка веб-клиента ConverseJS. В ejabberd 21.12 настройки весьма базовые, но над этим модулем активно идет работа[9]. В минимальном варианте конфигурации можно просто указать Websocket-адрес для подключения к серверу (мы его объявляли в секции Listen). Можно даже не указывать файлы ConverseJS - по умолчанию они могут загружаться напрямую с сайта ConverseJS. Но если хочется большей автономности, то можно сделать так, чтобы их отдавал свой сервер. В свою очередь не обязательно их отдавать через встроенный в ejabberd веб-сервер - для этого можно использовать тот, что настроен для сертификатов - в этом есть даже определенные преимущества: например, файлы могут прозрачно сжиматься перед передачей клиенту, что обеспечит более быструю загрузку интерфейса ConverseJS. Если же хочется более продвинутой конфигурации ConverseJS (например, анонимный чат на сайте для работы техподдержки), то это делается передачей опций через специальную индексную страницу, но это уже выходит за рамки данного руководства.
- Ну и последними опциями, требующими вмешательства, являются параметры подключения к созданной ранее базе данных. Если используется несколько виртуальных хостов для ejabberd - можно указать несколько баз через опцию host_config[5].
Фактически, после указания домена и IP-адресов, сертификатов, Jabber ID админа и параметров подключения к базе сервер готов к использованию. Запустите сервер, после чего создайте учетную запись администратора - сделать это можно либо в jabber-клиенте, либо в консоли с использованием ejabberdctl. В дальнейшем управлять сервером можно по адресу https://EXAMPLE.COM:5281/admin - само собой, подставьте в данном случае свой домен.
Если вы хотите удалять старые сообщения из базы данных сервера - воспользуйтесь следующей командой, помещенной в cron:
/usr/sbin/ejabberdctl delete_old_mam_messages all 30
где 30 - число дней, за которые надо хранить историю.
На этом все :)
Сноски[править]
- ↑ Да, если планируется использовать, например, конференции исключительно локально, то можно обойтись и без DNS-записей - сервер и так знает, куда ему подключаться, так как он этот сервис и предоставляет. Но если хочется полноценный сервис, с которым смогут взаимодействовать и другие сервера - то все же потребуются указанные записи в DNS.
- ↑ https://www.process-one.net/blog/how-to-set-up-ejabberd-video-voice-calling/
- ↑ 3,0 3,1 В таком случае ему нужно обеспечить доступ к каталогу /var/lib/ejabberd/upload/ - а лучше указать опцию docroot с путем за пределами домашнего каталога ejabberd, плюс воспользоваться опцией file_mode, чтобы файлы, созданные ejabberd, могли быть доступны для веб-сервера. Такое решение имеет еще одно преимущество: разнообразные клиенты (не только Jabber) будут гораздо охотнее показывать превью для обычных HTTP/HTTPS-ссылок (т.е., на 80 или 443-м порту), а не тех, где контент отдается на "необычном" порту - вроде 5283 в данном примере.
- ↑ Более подробно можно почитать тут: Свой сервер: подготовка веб-сервера и сертификатов
- ↑ 5,0 5,1 У ejabberd есть 2 варианта конфигурации с внешней базой: с указанием хоста внутри базы для всех значений (и таким образом повышенным расходом места для больших серверов) и без указания (и требующим отдельные базы для каждого виртуального хоста - этот вариант и рассмотрен в данном руководстве)
- ↑ В последних версиях ejabberd (24 и новее) этот шаг не обязателен - схема создастся автоматически
- ↑ 7,0 7,1 Удаляются они при достижении hard quota, а перестают удаляться при снижении до soft quota. Т.е., если хочется сохранить максимум файлов на доступном месте - просто поставьте оба значения одинаковыми
- ↑ При необходимости можно добавить нужные категории пользователей в разделе acl по аналогии с теми, что уже там есть (admin, local и так далее). Например, можно добавить группу VIP, которой выделить больше места для загрузки файлов, чем обычным пользователям, но меньше, чем админу
- ↑ Если вам важна данная функция - рекомендую обратить внимание на более новую версию сервера - например, на 22.05
Ссылки[править]
- https://www.ejabberd.im - домашняя страница ejabberd