Корпоративные чаты давно перестали быть просто способом обмена шутками между коллегами. Через них летают пароли, тексты договоров, скриншоты с чувствительными данными, ссылки на внутренние документы. И вот тут возникает резонный вопрос - кому именно достаются все эти сокровища, когда они проходят через сторонние сервисы где-то в облаках за океаном. Ответ обычно неутешительный, поэтому всё больше команд возвращаются к идее держать собственный коммуникационный сервер на своих железках или хотя бы на собственной виртуалке.

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 работают по тем же принципам. И именно поэтому подобные развёртывания стоит делать своими руками хотя бы один раз - после этого облачные сервисы перестают казаться магией, а превращаются в понятные инженерные конструкции, которые можно собрать самостоятельно и оставить под полным контролем.