Корпоративные чаты давно перестали быть просто способом обмена шутками между коллегами. Через них летают пароли, тексты договоров, скриншоты с чувствительными данными, ссылки на внутренние документы. И вот тут возникает резонный вопрос - кому именно достаются все эти сокровища, когда они проходят через сторонние сервисы где-то в облаках за океаном. Ответ обычно неутешительный, поэтому всё больше команд возвращаются к идее держать собственный коммуникационный сервер на своих железках или хотя бы на собственной виртуалке.
Mattermost как раз и создавался с прицелом на самохостинг и приватность. Это бесплатное программное обеспечение с открытым исходным кодом, которое часто называют альтернативой популярным облачным мессенджерам. Платформа умеет личные сообщения, групповые каналы, бесконечную историю с поиском, обмен файлами, двухфакторную аутентификацию, уведомления и интеграции с инструментами разработки. Серверная часть написана на Go, веб-клиент - на React. Получился продукт, специально заточенный под нужды организаций любого размера, от небольших команд до крупных холдингов.
Ниже разобран полный путь установки Mattermost на свежий Debian 11 - от подготовки базы данных до открытия веб-интерфейса в браузере. Параллельно настраивается Nginx в качестве обратного прокси, что даёт возможность работать с мессенджером по стандартному 80-му порту вместо нестандартного 8065 и открывает дорогу к последующему подключению TLS.
Подготовительные требования и состав окружения для развёртывания платформы общения
Чтобы пройти этот путь без сюрпризов, нужен сервер на Debian 11 - желательно свежий, без хвостов от предыдущих экспериментов. Понадобится валидное доменное имя, направленное A-записью на IP сервера. Для тестов сгодится и поддомен внутри корпоративной зоны, но в продакшене лучше использовать полноценный домен с возможностью выпуска TLS-сертификата через Let's Encrypt.
Ресурсы для Mattermost скромны, но не пустячны. Минимально комфортно платформа чувствует себя на 2 ядрах CPU и 4 ГБ оперативной памяти. Дисковое пространство зависит от объёма сообщений и пересылаемых файлов, но 20 ГБ под систему и базу - разумная стартовая точка. Доступ к серверу должен быть с правами root или через пользователя с возможностью повышения привилегий через sudo. Все команды ниже приведены в форме для root - если работать из-под обычного пользователя, перед каждой командой нужно ставить sudo.
Установка MariaDB и подготовка базы данных с отдельным пользователем для нужд Mattermost
Mattermost умеет работать с MySQL и MariaDB. PostgreSQL тоже поддерживается, но в этой инструкции выбран более лёгкий путь через MariaDB - её установка проще и в стандартных репозиториях Debian лежит свежая стабильная версия.
Ставим сервер базы данных:
apt-get install mariadb-server -y
Флаг -y отвечает за автоматическое подтверждение при установке - удобно для скриптов, но в первый раз лучше всё-таки следить, что именно ставится в систему. После завершения процесса запускаем службу и включаем её автозапуск:
systemctl start mariadb
systemctl enable mariadb
Первая команда запускает демон прямо сейчас, вторая прописывает запуск при загрузке системы. Без enable после перезагрузки сервер базы данных просто не поднимется, и Mattermost окажется в подвешенном состоянии. Эту мелочь забывают так часто, что она стала чем-то вроде неофициального обряда посвящения новичков в системные администраторы.
Подключаемся к консоли базы:
mysql
Команда без параметров входит под root по локальному unix-сокету, поскольку свежая установка MariaDB на Debian использует socket-аутентификацию для root. Это безопаснее, чем пароль, поскольку не позволяет подключиться извне.
Создаём базу и пользователя:
MariaDB [(none)]> create database mattermost;
MariaDB [(none)]> create user mattermost@localhost identified by 'password';
Пароль 'password' в боевом окружении нужно заменить на что-то длинное и сложное - 16-20 символов разных регистров с цифрами и спецсимволами. Простота примера здесь только для наглядности, но если оставить такой пароль на реальном сервере, рано или поздно прилетит неприятный сюрприз.
Выдаём пользователю права на работу с базой:
MariaDB [(none)]> grant all privileges on mattermost.* to mattermost@localhost;
Конструкция mattermost.* означает все таблицы внутри базы mattermost. Учётка получает полный набор прав - select, insert, update, delete, create, drop и так далее. Для приложения это нормально, поскольку оно само создаёт схему и управляет своими таблицами.
Применяем изменения и выходим:
MariaDB [(none)]> flush privileges;
MariaDB [(none)]> exit;
Команда flush privileges перечитывает таблицы прав и применяет изменения немедленно, без необходимости перезапуска сервиса.
Загрузка дистрибутива Mattermost и подготовка системного пользователя для работы платформы
Перед скачиванием платформы создаём отдельного системного пользователя, под которым она будет работать:
useradd --system --user-group mattermost
Флаг --system создаёт пользователя без домашнего каталога и с UID из системного диапазона (обычно ниже 1000). Параметр --user-group одновременно создаёт группу с тем же именем. Запуск служб от имени специально созданного пользователя - базовая практика безопасности. Если приложение вдруг будет скомпрометировано через какую-нибудь уязвимость, злоумышленник получит права только этого ограниченного пользователя, а не root.
Скачиваем архив с дистрибутивом:
wget https://releases.mattermost.com/6.0.2/mattermost-6.0.2-linux-amd64.tar.gz
В этой команде стоит обратить внимание на номер версии. На момент актуализации этой статьи на сайте проекта могут быть более свежие выпуски - перед использованием имеет смысл заглянуть на официальный сайт и подставить актуальный номер. Принципы установки от версии к версии меняются редко, но возможности и исправленные ошибки накапливаются.
Распаковываем архив:
tar -xvzf mattermost-6.0.2-linux-amd64.tar.gz
Флаги расшифровываются как extract (извлечь), verbose (подробный вывод), gunzip (через gzip-распаковку), file (имя архива указано следующим параметром). Получится каталог mattermost в текущей директории.
Перемещаем его в стандартное место для дополнительного программного обеспечения:
mv mattermost /opt
Каталог /opt традиционно используется для пакетов, которые ставятся не из репозиториев дистрибутива, а отдельно. Это давняя соглашённость Filesystem Hierarchy Standard, и придерживаться её - хороший тон.
Создаём каталог для пользовательских данных и файлов:
mkdir /opt/mattermost/data
Сюда платформа будет складывать загруженные пользователями вложения, аватарки, экспортированные данные и прочие артефакты работы. На больших инсталляциях этот каталог быстро разрастается, поэтому имеет смысл сразу подумать о его размещении на отдельном томе с запасом места.
Меняем владельца всего каталога на созданного ранее пользователя:
chown -R mattermost:mattermost /opt/mattermost
chmod -R g+w /opt/mattermost
Первая команда рекурсивно переписывает владельца и группу. Вторая добавляет права на запись для группы - это нужно для корректной работы плагинов и автоматических обновлений компонентов внутри платформы.
Связывание приложения с базой данных через правку файла config.json
Mattermost по умолчанию настроен на работу с PostgreSQL, поэтому нужно перевести его на MariaDB через правку основного конфигурационного файла:
nano /opt/mattermost/config/config.json
Внутри файла находим строки, отвечающие за подключение к базе:
"DriverName": "postgres",
"DataSource": "postgres://mmuser:mostest@localhost/mattermost_test?sslmode=disable\u0026connect_timeout=10",
Заменяем их на свои настройки:
"DriverName": "mysql",
"DataSource": "mattermost:password@tcp(127.0.0.1:3306)/mattermost?charset=utf8mb4,utf8\u0026readTimeout=30s\u0026writeTimeout=30s",
Разберём строку подключения по частям. Конструкция mattermost:password - это имя пользователя и пароль базы, тот самый пароль, который задавался при создании учётки в MariaDB. Дальше @tcp(127.0.0.1:3306) указывает на способ подключения и адрес сервера - локальный TCP на стандартном порту MariaDB. После слэша идёт имя базы - mattermost. В параметрах после знака вопроса заданы кодировки (utf8mb4 поддерживает все символы Unicode, включая эмодзи и редкие иероглифы) и таймауты на чтение и запись.
Странная последовательность \u0026 - это просто экранированный амперсанд в JSON. Можно было бы написать обычный &, но JSON-синтаксис не очень любит спецсимволы в строках, и Mattermost страхуется через unicode-эскейп. Разница чисто косметическая.
Сохраняем файл и закрываем редактор. Если в этот момент совершить опечатку и сломать JSON-синтаксис, демон откажется стартовать с малопонятной ошибкой. Поэтому стоит проверить файл через jq:
jq . /opt/mattermost/config/config.json
Если jq не ругается, значит JSON валидный. Если ругается, ищи лишнюю запятую или незакрытую кавычку.
Создание systemd-юнита для автоматического управления службой Mattermost
Чтобы Mattermost запускался как нормальная служба, поднимался при старте системы и корректно перезапускался при сбоях, создаём для него systemd-юнит:
nano /etc/systemd/system/mattermost.service
Содержимое файла:
[Unit]
Description=Mattermost
After=syslog.target network.target mysqld.service
[Service]
Type=notify
WorkingDirectory=/opt/mattermost
User=mattermost
ExecStart=/opt/mattermost/bin/mattermost
PIDFile=/var/spool/mattermost/pid/master.pid
TimeoutStartSec=3600
LimitNOFILE=49152
[Install]
WantedBy=multi-user.target
В секции Unit описано, что и когда должно быть готово до старта Mattermost. Параметр After перечисляет зависимости - syslog для логирования, network для сети и mysqld для базы данных. Пока эти службы не поднимутся, наш мессенджер ждёт.
Секция Service содержит сами параметры запуска. Type=notify означает, что Mattermost сам сообщит systemd о готовности через специальный механизм уведомлений - это точнее, чем гадать по таймауту. WorkingDirectory задаёт рабочий каталог, User указывает, от чьего имени запускать процесс. ExecStart - команда запуска, путь к бинарнику внутри развёрнутого пакета.
Параметр TimeoutStartSec=3600 даёт целый час на старт - это перестраховка для случаев, когда платформа выполняет миграцию большой базы данных при первом запуске или после обновления. LimitNOFILE=49152 поднимает лимит на количество одновременно открытых файловых дескрипторов до почти 50 тысяч. Без этого лимита под нагрузкой Mattermost быстро упирается в системные ограничения и начинает терять соединения.
Сохраняем файл и перечитываем конфигурацию systemd:
systemctl daemon-reload
Эта команда обязательна после любых изменений в юнит-файлах. Без неё systemd работает по старому кешированному набору, и новые правки игнорируются.
Запускаем службу и включаем автозапуск:
systemctl start mattermost
systemctl enable mattermost
Проверяем результат:
systemctl status mattermost
В выводе должна быть строка active (running). Помимо неё видно несколько дочерних процессов - это плагины платформы, в том числе Focalboard для управления задачами, Playbooks для автоматизации процессов и channel-export для выгрузки истории каналов.
? mattermost.service - Mattermost
Loaded: loaded (/etc/systemd/system/mattermost.service; disabled; vendor preset: enabled)
Active: active (running) since Fri 2021-11-12 13:56:25 UTC; 4s ago
Main PID: 2888 (mattermost)
Tasks: 31 (limit: 4679)
Memory: 273.3M
CPU: 12.191s
CGroup: /system.slice/mattermost.service
??2888 /opt/mattermost/bin/mattermost
??2915 plugins/com.mattermost.plugin-channel-export/server/dist/plugin-linux-amd64
??2925 plugins/playbooks/server/dist/plugin-linux-amd64
??2931 plugins/focalboard/server/dist/plugin-linux-amd64
Nov 12 13:56:24 debian11 mattermost[2888]: {"timestamp":"2021-11-12 13:56:24.681 Z","level":"info","msg":"Scheduling next survey for Dec 3, 2>
Nov 12 13:56:25 debian11 mattermost[2888]: {"timestamp":"2021-11-12 13:56:25.064 Z","level":"info","msg":"Post.Message has size restrictions">
Nov 12 13:56:25 debian11 mattermost[2888]: {"timestamp":"2021-11-12 13:56:25.084 Z","level":"info","msg":"info [2021-11-12 13:56:25.083 Z] co>
Nov 12 13:56:25 debian11 mattermost[2888]: {"timestamp":"2021-11-12 13:56:25.131 Z","level":"info","msg":"\n -- collation of mattermost's >
Nov 12 13:56:25 debian11 mattermost[2888]: {"timestamp":"2021-11-12 13:56:25.491 Z","level":"info","msg":"debug [2021-11-12 13:56:25.488 Z] i>
Nov 12 13:56:25 debian11 mattermost[2888]: {"timestamp":"2021-11-12 13:56:25.777 Z","level":"info","msg":"info [2021-11-12 13:56:25.777 Z] Se>
Nov 12 13:56:25 debian11 mattermost[2888]: {"timestamp":"2021-11-12 13:56:25.963 Z","level":"info","msg":"Starting Server...","caller":"app/s>
Nov 12 13:56:25 debian11 mattermost[2888]: {"timestamp":"2021-11-12 13:56:25.964 Z","level":"info","msg":"Server is listening on [::]:8065",">
Nov 12 13:56:25 debian11 mattermost[2888]: {"timestamp":"2021-11-12 13:56:25.964 Z","level":"info","msg":"Sending systemd READY notification.>
Nov 12 13:56:25 debian11 systemd[1]: Started Mattermost.
Строка Server is listening on [::]:8065 говорит о том, что платформа подхватила нужный порт и готова принимать соединения.
Настройка Nginx как обратного прокси с поддержкой WebSocket для корректной работы интерфейса
По умолчанию Mattermost слушает порт 8065. Гонять пользователей на нестандартный порт - дурной тон, к тому же без обратного прокси проблематично прикрутить TLS, балансировку или дополнительное кеширование. Поэтому ставится Nginx, который будет принимать запросы на стандартном 80-м порту и перенаправлять их в платформу.
Ставим веб-сервер:
apt-get install nginx -y
Создаём отдельный файл конфигурации для нашего сайта:
nano /etc/nginx/conf.d/mattermost.conf
Конфигурация целиком:
upstream mattermost {
server localhost:8065;
keepalive 32;
}
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=mattermost_cache:10m max_size=3g inactive=120m use_temp_path=off;
server {
listen 80;
server_name mattermost.example.com;
location ~ /api/v[0-9]+/(users/)?websocket$ {
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
client_max_body_size 50M;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Frame-Options SAMEORIGIN;
proxy_buffers 256 16k;
proxy_buffer_size 16k;
client_body_timeout 60;
send_timeout 300;
lingering_timeout 5;
proxy_connect_timeout 90;
proxy_send_timeout 300;
proxy_read_timeout 90s;
proxy_pass http://mattermost;
}
location / {
client_max_body_size 50M;
proxy_set_header Connection "";
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Frame-Options SAMEORIGIN;
proxy_buffers 256 16k;
proxy_buffer_size 16k;
proxy_read_timeout 600s;
proxy_cache mattermost_cache;
proxy_cache_revalidate on;
proxy_cache_min_uses 2;
proxy_cache_use_stale timeout;
proxy_cache_lock on;
proxy_http_version 1.1;
proxy_pass http://mattermost;
}
}
Эта конфигурация заслуживает подробного разбора, потому что она нетривиальна. Блок upstream объявляет группу бэкендов - в нашем случае один сервер на localhost:8065. Параметр keepalive 32 держит до 32 постоянных соединений к бэкенду, что заметно ускоряет обработку запросов под нагрузкой.
Директива proxy_cache_path задаёт каталог для кеша Nginx с двухуровневой структурой подкаталогов и зоной памяти на 10 мегабайт для метаданных. Лимит на размер кеша - 3 гигабайта, неиспользуемые файлы удаляются через 120 минут простоя.
В блоке server указан стандартный 80-й порт и доменное имя mattermost.example.com - его нужно заменить на свой реальный домен. Дальше идут два location-блока, и тут начинается самое интересное.
Первый location обрабатывает WebSocket-соединения, по которым платформа доставляет сообщения в реальном времени. URL-маска /api/v[0-9]+/(users/)?websocket$ ловит запросы вида /api/v4/websocket или /api/v4/users/websocket. Заголовки Upgrade и Connection: "upgrade" нужны для перехода с HTTP на WebSocket-протокол. Без них чат будет показывать сообщения только после ручного обновления страницы.
Второй location - общий, для всего остального трафика. Здесь работает кеширование через proxy_cache, что снижает нагрузку на бэкенд. Параметр proxy_cache_revalidate on позволяет Nginx проверять актуальность закешированного ответа через If-Modified-Since. Опция proxy_cache_min_uses 2 кеширует ответ только после второго запроса, что отсекает редко запрашиваемые ресурсы. proxy_cache_use_stale timeout позволяет отдавать устаревший кеш, если бэкенд не отвечает - пользователь увидит хоть что-то вместо ошибки.
Заголовки X-Real-IP и X-Forwarded-For передают исходный IP клиента в бэкенд - без них в логах Mattermost все запросы выглядели бы как идущие с localhost. Параметр client_max_body_size 50M разрешает загрузку файлов до 50 мегабайт, что позволяет пересылать в чате презентации, видео и крупные документы.
Проверяем синтаксис конфигурации:
nginx -t
Ожидаемый вывод:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Запускаем Nginx:
systemctl start nginx
Проверяем статус:
systemctl status nginx
Ожидаемый вывод:
? nginx.service - A high performance web server and a reverse proxy server
Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
Active: active (running) since Fri 2021-11-12 13:57:02 UTC; 1min 12s ago
Docs: man:nginx(8)
Process: 3384 ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_process on; (code=exited, status=0/SUCCESS)
Process: 3392 ExecStart=/usr/sbin/nginx -g daemon on; master_process on; (code=exited, status=0/SUCCESS)
Main PID: 3602 (nginx)
Tasks: 3 (limit: 4679)
Memory: 6.6M
CPU: 55ms
CGroup: /system.slice/nginx.service
??3602 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
??3604 nginx: worker process
??3605 nginx: worker process
Nov 12 13:57:01 debian11 systemd[1]: Starting A high performance web server and a reverse proxy server...
Nov 12 13:57:02 debian11 systemd[1]: nginx.service: Failed to parse PID from file /run/nginx.pid: Invalid argument
Nov 12 13:57:02 debian11 systemd[1]: Started A high performance web server and a reverse proxy server.
Сообщение про Failed to parse PID from file /run/nginx.pid - это известная мелочь, не влияющая на работу. Оно появляется при первом старте, когда systemd проверяет PID-файл раньше, чем Nginx успевает его создать.
Первый вход в веб-интерфейс и создание аккаунта администратора со своими настройками организации
Открываем браузер и переходим по адресу http://mattermost.example.com (заменив домен на свой реальный). Появится страница с формой создания первой учётной записи. Этот первый аккаунт автоматически становится системным администратором с полными правами на управление платформой.
Указываем email-адрес администратора, имя пользователя и сложный пароль. Нажимаем кнопку Create Account. Дальше появится страница приветствия с предложением перейти в системную консоль (Go to System Console). Через эту консоль настраиваются параметры всей инсталляции - от лимитов на размер сообщений до интеграций с внешними системами.
В системной консоли стоит сразу обратить внимание на несколько разделов. В Site Configuration задаётся название сервера, описание и URL-адрес. В разделе Notifications настраиваются уведомления и письма. Раздел Authentication открывает доступ к настройкам входа через LDAP, SAML и OAuth - для корпоративных инсталляций это часто становится главной точкой интеграции с существующей инфраструктурой.
Раздел Plugins позволяет управлять расширениями, в том числе встроенными Focalboard и Playbooks. Можно подключать внешние плагины из Mattermost Marketplace или загружать свои собственные.
Распространённые проблемы при эксплуатации и практические рекомендации для боевых развёртываний
В копилку наблюдений из практики стоит добавить несколько моментов, которые редко описываются в стандартных туториалах, но регулярно встречаются в боевой эксплуатации.
Первая частая проблема - сообщение об ошибке при попытке загрузки файла или при работе с большими сообщениями. Чаще всего виноват client_max_body_size либо в Nginx, либо в самой платформе через её внутренние настройки FileSettings.MaxFileSize. Эти два параметра должны быть согласованы, иначе возникнет странное поведение - платформа разрешает что-то, что Nginx режет на подходе, или наоборот.
Вторая популярная история - проблемы с WebSocket за корпоративным прокси. Если Nginx стоит за дополнительным прокси компании, который не поддерживает Upgrade-заголовок, чат потеряет режим реального времени. Сообщения будут приходить только после ручного обновления страницы. Лекарство простое - либо настроить корпоративный прокси на корректную работу с WebSocket, либо обходить его для конкретного домена.
Третья тонкость касается резервного копирования. Сама база MariaDB бэкапится через mysqldump, но многие забывают про каталог /opt/mattermost/data, где хранятся все вложения. Без него восстановление превратится в катастрофу - сообщения вернутся, а файлы окажутся утеряны. Регулярные снимки этого каталога вместе с дампом базы - обязательная гигиена.
Четвёртое наблюдение - не пренебрегай TLS даже на внутреннем сервере. Самоподписанный сертификат лучше отсутствующего, но Let's Encrypt с certbot настраивается за пять минут и решает вопрос на годы вперёд. Без шифрования пароли пользователей пролетают по сети в открытом виде, и любой коллега с доступом к коммутатору может их перехватить.
Где такая инсталляция пригодится в реальной жизни? Сценариев много. Команды разработки, которым важно держать обсуждения архитектуры и кода вне сторонних облаков. Юридические и финансовые отделы, где требования к конфиденциальности переписки прописаны в политиках безопасности. Государственные и около-государственные структуры, где запрет на иностранные сервисы - не каприз, а нормативное требование. Организации, работающие с персональными данными по строгим регламентам. Распределённые проектные команды, которые ценят возможность настраивать платформу под свои процессы, а не подстраиваться под чужие правила.
Освоение Mattermost даёт инженеру не просто навык установки одного приложения, а целый пласт практик - работа с базой данных, настройка systemd-юнитов, тонкая конфигурация обратного прокси с кешированием и WebSocket, грамотное управление правами и пользователями. Эти знания переносимы на десятки других сценариев. Тот, кто разобрался с Mattermost, без труда поднимет любую веб-платформу со схожей архитектурой - GitLab, Nextcloud, Jitsi, Rocket.Chat работают по тем же принципам. И именно поэтому подобные развёртывания стоит делать своими руками хотя бы один раз - после этого облачные сервисы перестают казаться магией, а превращаются в понятные инженерные конструкции, которые можно собрать самостоятельно и оставить под полным контролем.