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

UVdesk относится к категории SaaS-решений с открытым исходным кодом для построения корпоративной службы поддержки. Среди возможностей платформы выделяются управление тикетами, ведение базы знаний, готовые шаблоны ответов и автоматическое создание заявок из входящей почты. Функциональность расширяется через внешние модули, а триггеры позволяют настраивать автоматические действия по событиям. Ниже подробно разобран процесс установки UVdesk на свежий сервер Ubuntu 22.04 с использованием Nginx в роли веб-сервера, MySQL для хранения данных и PHP 8.0 как рантайма приложения.

Подготовка чистого сервера и базовые требования перед началом развёртывания UVdesk

Для работы понадобится сервер с операционной системой Ubuntu 22.04 LTS. Минимальные требования к ресурсам стартуют от 2 гигабайт оперативной памяти, хотя для комфортной эксплуатации с десятком одновременных операторов лучше выделить 4 гигабайта и пару процессорных ядер. Дисковое пространство расходуется в основном на вложения к тикетам, и здесь стоит закладывать запас исходя из ожидаемого объёма переписки.

Понадобится полноценное доменное имя в формате FQDN, направленное на IP-адрес сервера через A-запись в DNS. В рамках туториала используется условный домен uvdesk.example.com - подставляйте свой реальный адрес во всех командах и конфигах. Без живого домена не получится оформить бесплатный SSL-сертификат через Let's Encrypt, а без шифрования держать систему поддержки в публичной сети откровенно небезопасно.

Работа ведётся от имени обычного пользователя с правами sudo, без прямого использования root-сессии. Это правильная практика безопасности, которой стоит держаться даже на тестовых стендах. Перед стартом стандартный ритуал обновления системы.

$ sudo apt update && sudo apt upgrade

Двойной амперсанд означает последовательное выполнение - вторая команда стартует только если первая завершилась успешно. Это удобный паттерн для связки зависимых операций.

Далее ставится набор вспомогательных утилит, без которых дальнейшие шаги будут спотыкаться. Часть пакетов может уже присутствовать в системе, но повторная установка ничего не сломает.

$ sudo apt install wget curl nano unzip -y

Утилита wget пригодится для скачивания файлов по прямым ссылкам, curl делает то же самое плюс умеет работать с REST-API и сложными HTTP-запросами. Редактор nano удобен своей простотой для правки конфигов, а unzip понадобится для распаковки архивов. Флаг -y подтверждает установку без интерактивных вопросов.

Настройка брандмауэра UFW и открытие портов HTTP и HTTPS для входящих соединений

До установки веб-сервера разумно сразу разрулить вопрос с файрволом. На свежей Ubuntu обычно активен UFW, и если просто поставить Nginx, не открыв порты, браузер не достучится до сервера снаружи. Проверка текущего состояния файрвола делается короткой командой.

$ sudo ufw status

На типичном свежем сервере вывод выглядит так.

Status: active

To                         Action      From
--                         ------      ----
OpenSSH                    ALLOW       Anywhere
OpenSSH (v6)               ALLOW       Anywhere (v6)

Видно, что разрешён только SSH-доступ - этого достаточно для самого факта подключения к серверу, но веб-трафику путь закрыт. Открываем порты для незашифрованного и зашифрованного HTTP.

$ sudo ufw allow http
$ sudo ufw allow https

UFW понимает символьные имена сервисов из файла /etc/services, поэтому http автоматически разворачивается в 80/tcp, а https в 443/tcp. Можно было бы написать явно sudo ufw allow 80/tcp, результат тот же.

Повторная проверка статуса покажет обновлённую картину.

$ sudo ufw status

Теперь в списке появляются разрешения на 80-й и 443-й порты для обоих стеков протоколов.

Status: active

To                         Action      From
--                         ------      ----
OpenSSH                    ALLOW       Anywhere
80/tcp                     ALLOW       Anywhere
443                        ALLOW       Anywhere
OpenSSH (v6)               ALLOW       Anywhere (v6)
80/tcp (v6)                ALLOW       Anywhere (v6)
443 (v6)                   ALLOW       Anywhere (v6)

Анализ дублирования записей IPv4 и IPv6 показывает, что современные системы давно работают с двумя стеками параллельно. Если IPv6 отключён на уровне сети, эти строки безвредны - просто не используются.

Установка свежей версии Nginx из официального репозитория компании-разработчика

В стандартных репозиториях Ubuntu 22.04 лежит относительно старая версия Nginx. Для production-сервера разумнее тянуть пакет из официального источника, где регулярно выходят свежие сборки с патчами безопасности. Сначала импортируется GPG-ключ для проверки подлинности пакетов.

$ curl https://nginx.org/keys/nginx_signing.key | gpg --dearmor \
| sudo tee /usr/share/keyrings/nginx-archive-keyring.gpg >/dev/null

Команда выстраивает конвейер из трёх программ. Утилита curl скачивает текстовый ASCII-armored ключ, gpg --dearmor конвертирует его в бинарный формат, и tee записывает результат в нужный каталог с правами root. Перенаправление в /dev/null нужно, чтобы tee не дублировал вывод в терминал. Обратный слэш в конце строки экранирует перенос - команда читается как одна логическая инструкция, разбитая на две строки для удобства чтения.

Затем добавляется сам репозиторий в список источников APT.

$ echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg arch=amd64] \
http://nginx.org/packages/ubuntu `lsb_release -cs` nginx" \
| sudo tee /etc/apt/sources.list.d/nginx.list

В строке репозитория указано связывание с конкретным ключом через signed-by - современный подход, заменивший старую глобальную связку доверенных ключей. Архитектура жёстко задана как amd64, для серверов на ARM-процессорах её придётся поменять на arm64. Обрамлённое обратными кавычками выражение lsb_release -cs подставит кодовое имя релиза Ubuntu - для 22.04 это jammy.

Обновляем кэш и ставим Nginx.

$ sudo apt update
$ sudo apt install nginx

Проверка установленной версии.

$ nginx -v

В выводе появится номер ветки.

nginx version: nginx/1.22.0

Запуск веб-сервера через systemd.

$ sudo systemctl start nginx

После старта Nginx сразу начинает слушать 80-й порт и отдаёт стандартную приветственную страницу.

Установка PHP 8.0 из репозитория Ondrej Sury и подключение всех необходимых для UVdesk расширений

Ubuntu 22.04 поставляется с PHP версии 8.1 по умолчанию, но UVdesk пока стабильнее работает на ветке 8.0. Чтобы получить нужную версию, используется неофициальный, но очень популярный репозиторий Ondrej Sury - того самого мейнтейнера, который много лет собирает свежие сборки PHP для Debian и Ubuntu.

$ sudo add-apt-repository ppa:ondrej/php

Команда автоматически добавит PPA-репозиторий, импортирует ключ и обновит индексы. После этого можно ставить PHP 8.0 и комплект расширений, без которых UVdesk не запустится.

$ sudo apt update
$ sudo apt install php8.0 php8.0-curl php8.0-intl php8.0-gd php8.0-xsl php8.0-mbstring php8.0-zip php8.0-xml php8.0-bz2 php8.0-mysql php8.0-soap php8.0-mysql php8.0-fpm php8.0-gmp php8.0-bcmath php8.0-apcu php8.0-redis php8.0-imagick php8.0-imap php8.0-xdebug php8.0-tidy php8.0-ldap php8.0-opcache php8.0-mailparse

Перечень модулей выглядит угрожающе длинным, но каждый из них решает свою задачу. Расширение php8.0-curl обеспечивает работу с внешними HTTP-запросами, php8.0-intl отвечает за локализацию и работу с unicode, php8.0-gd рисует превью картинок и вложений, php8.0-mbstring необходимо для корректной обработки многобайтных строк включая кириллицу, php8.0-mysql обеспечивает связь с базой, php8.0-fpm запускает PHP-обработчик в режиме FastCGI Process Manager, php8.0-imap нужно для забора писем из почтовых ящиков, php8.0-redis ускоряет кэширование, php8.0-imagick работает с графикой через ImageMagick, php8.0-opcache кеширует скомпилированный байт-код PHP для ускорения работы, php8.0-mailparse разбирает структуру входящих писем для автоматического создания тикетов.

Проверка установленной версии PHP.

$ php --version

Вывод подтвердит активную ветку.

PHP 8.0.23 (cli) (built: Sep 18 2022 10:25:06) ( NTS )
Copyright (c) The PHP Group
Zend Engine v4.0.23, Copyright (c) Zend Technologies
    with Zend OPcache v8.0.23, Copyright (c), by Zend Technologies
    with Xdebug v3.1.5, Copyright (c) 2002-2022, by Derick Rethans

Аббревиатура NTS расшифровывается как Non Thread Safe - сборка без поддержки потоковой безопасности, что нормально для FPM-режима, где параллелизм обеспечивается на уровне процессов, а не потоков.

Установка MySQL и первичная защита базы через скрипт mysql_secure_installation

База данных MySQL ставится одним пакетом, поскольку в стандартных репозиториях Ubuntu 22.04 уже лежит свежая 8-я ветка.

$ sudo apt install mysql-server

Проверка версии.

$ mysql --version
mysql  Ver 8.0.30-0ubuntu0.22.04.1 for Linux on x86_64 ((Ubuntu))

Для MySQL версий 8.0.28 и выше есть особенность, которую обязательно нужно учесть перед запуском скрипта безопасной установки. Дело в том, что новые сборки используют плагин аутентификации caching_sha2_password по умолчанию, а скрипт mysql_secure_installation ожидает совместимый с паролем плагин. Поэтому сначала заходим в shell MySQL.

$ sudo mysql

И переключаем плагин аутентификации для root-пользователя на классический mysql_native_password с задаваемым паролем. Пароль должен включать цифры, заглавные и строчные буквы плюс спецсимволы.

mysql> ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'YourPassword12!';

Выходим из shell.

mysql> exit

Теперь запускаем скрипт укрепления безопасности.

$ sudo mysql_secure_installation

Скрипт проведёт через ряд интерактивных вопросов. Сначала спросит текущий пароль root - вводится тот, что только что был задан. Дальше предложит установить компонент проверки силы паролей VALIDATE PASSWORD COMPONENT. На этот вопрос отвечаем Y, и затем выбираем уровень 2 - самый строгий.

Securing the MySQL server deployment.

Enter password for user root:

VALIDATE PASSWORD COMPONENT can be used to test passwords
and improve security. It checks the strength of password
and allows the users to set only those passwords which are
secure enough. Would you like to setup VALIDATE PASSWORD component?

Press y|Y for Yes, any other key for No: Y

There are three levels of password validation policy:

LOW    Length >= 8
MEDIUM Length >= 8, numeric, mixed case, and special characters
STRONG Length >= 8, numeric, mixed case, special characters and dictionary                  file

Please enter 0 = LOW, 1 = MEDIUM and 2 = STRONG: 2
Using existing password for root.

Estimated strength of the password: 100

На следующих шагах отказываемся менять root-пароль (нажимаем N), затем соглашаемся со всеми остальными предложениями скрипта - удалить анонимных пользователей, запретить удалённый вход для root, удалить тестовую базу и перезагрузить таблицы привилегий.

Change the password for root ? ((Press y|Y for Yes, any other key for No) : N

 ... skipping.
By default, a MySQL installation has an anonymous user,
allowing anyone to log into MySQL without having to have
a user account created for them. This is intended only for
testing, and to make the installation go a bit smoother.
You should remove them before moving into a production
environment.

Remove anonymous users? (Press y|Y for Yes, any other key for No) : Y
Success.


Normally, root should only be allowed to connect from
'localhost'. This ensures that someone cannot guess at
the root password from the network.

Disallow root login remotely? (Press y|Y for Yes, any other key for No) : Y
Success.

By default, MySQL comes with a database named 'test' that
anyone can access. This is also intended only for testing,
and should be removed before moving into a production
environment.


Remove test database and access to it? (Press y|Y for Yes, any other key for No) : Y
 - Dropping test database...
Success.

 - Removing privileges on test database...
Success.

Reloading the privilege tables will ensure that all changes
made so far will take effect immediately.

Reload privilege tables now? (Press y|Y for Yes, any other key for No) : Y
Success.

All done!

Каждое из этих действий закрывает потенциальный вектор атаки. Анонимные учётки позволили бы кому угодно подключиться к базе без пароля. Удалённый root-доступ дал бы шанс перебирать пароль из любой точки сети. Тестовая база служила бы лазейкой для запуска чужих запросов на сервере.

Создание выделенной базы данных и отдельной учётки MySQL для нужд UVdesk

Подключаемся к shell MySQL уже с паролем.

$ sudo mysql -u root -p

Создаём базу данных для UVdesk. Имена объектов в SQL принято писать строчными, чтобы не зависеть от регистрозависимости конкретной ОС.

mysql> CREATE DATABASE uvdeskdb;

Заводим отдельного SQL-пользователя. Привязка через @localhost означает, что подключаться этой учёткой можно только с самого сервера, что разумно для локальной связки приложения с базой.

mysql> CREATE USER 'uvdesk'@'localhost' IDENTIFIED BY 'Your_password2';

Выдаём этому пользователю полные права исключительно на свежесозданную базу - не на всю СУБД целиком, что было бы избыточно.

mysql> GRANT ALL PRIVILEGES ON uvdeskdb.* TO 'uvdesk'@'localhost';

Применяем изменения прав немедленно.

mysql> FLUSH PRIVILEGES;

Выходим.

mysql> exit

Принцип минимальных привилегий - один из краеугольных камней безопасности. Если бы UVdesk использовал root-учётку, любая компрометация веб-приложения через SQL-инъекцию открывала бы атакующему доступ ко всем базам на сервере. С отдельной учёткой максимум, что можно получить, это данные одной базы UVdesk.

Установка менеджера зависимостей Composer для управления PHP-пакетами UVdesk

UVdesk построен на Symfony и использует Composer для управления своими PHP-зависимостями. Без него развернуть проект не получится. Установка Composer выполняется через официальный установщик с проверкой контрольной суммы.

$ php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
$ php -r "if (hash_file('sha384', 'composer-setup.php') === '55ce33d7678c5a611085589f1f3ddf8b3c52d662cd01d4ba75c0ee0459970c2200a51f492d557530c71c15d8dba01eae') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
$ php composer-setup.php
$ php -r "unlink('composer-setup.php');"
$ sudo mv composer.phar /usr/local/bin/composer

Логика последовательности следующая. Первая команда скачивает скрипт установщика. Вторая проверяет SHA-384 хеш файла на совпадение с эталонным - так гарантируется, что в загруженном файле нет вредоносных модификаций. Третья запускает сам установщик, который собирает phar-архив Composer. Четвёртая удаляет промежуточный установочный файл. Пятая перемещает готовый бинарник в системный каталог /usr/local/bin, делая его доступным из любой точки системы по короткой команде composer.

Хеш для проверки нужно брать актуальный с официального сайта Composer - значение в команде меняется при выходе новых версий установщика. Если хеш не совпадёт, скрипт сам удалит подозрительный файл и сообщит о проблеме.

Проверка работоспособности.

$ composer -V
Composer version 2.4.2 2022-09-14 16:11:15

Загрузка проекта UVdesk через Composer и настройка прав доступа через списки контроля ACL

Создаём каталог под веб-приложения и переключаемся в него.

$ sudo mkdir /var/www
$ cd /var/www

Меняем владельца этого каталога на текущего пользователя, чтобы Composer мог писать сюда без sudo. Переменная $USER автоматически разворачивается в имя залогиненного пользователя.

$ sudo chown $USER:$USER /var/www/

Чистим кеш Composer на всякий случай, чтобы исключить проблемы с устаревшими пакетами.

$ composer clear-cache

Создаём проект UVdesk на основе официального скелета. Команда скачает все нужные пакеты и развернёт структуру приложения в указанной папке.

$ composer create-project uvdesk/community-skeleton uvdesk.example.com

Процесс может занять несколько минут в зависимости от скорости интернета - Composer тянет десятки зависимостей и их транзитивных зависимостей.

Ставим утилиту для работы со списками контроля доступа (ACL). Она нужна для тонкой раздачи прав одновременно нескольким пользователям на одни и те же файлы.

$ sudo apt install acl

Теперь самое тонкое - выдача прав. UVdesk должен иметь возможность писать в определённые каталоги от имени Nginx-пользователя для работы с кешем, логами и загружаемыми файлами. Одновременно владелец-разработчик тоже должен иметь возможность редактировать конфиги. Классический chown не позволил бы такого двойного владения, а ACL умеют это легко.

$ sudo setfacl -dR -m u:nginx:rwX -m u:$USER:rwX /var/www/uvdesk.example.com/var
$ sudo setfacl -R -m u:nginx:rwX -m u:$USER:rwX /var/www/uvdesk.example.com/var
$ sudo setfacl -dR -m u:nginx:rwX -m u:$USER:rwX /var/www/uvdesk.example.com/public
$ sudo setfacl -R -m u:nginx:rwX -m u:$USER:rwX /var/www/uvdesk.example.com/public
$ sudo setfacl -dR -m u:nginx:rwX -m u:$USER:rwX /var/www/uvdesk.example.com/config
$ sudo setfacl -R -m u:nginx:rwX -m u:$USER:rwX /var/www/uvdesk.example.com/config
$ sudo setfacl -dR -m u:nginx:rwX -m u:$USER:rwX /var/www/uvdesk.example.com/migrations
$ sudo setfacl -R -m u:nginx:rwX -m u:$USER:rwX /var/www/uvdesk.example.com/migrations
$ sudo setfacl -m u:nginx:rwX -m u:$USER:rwX /var/www/uvdesk.example.com/.env

Флаг -d устанавливает дефолтные ACL, которые наследуются вновь создаваемыми файлами в каталоге. Флаг -R применяет правила рекурсивно ко всему содержимому. Параметр -m добавляет конкретное правило вида u:имя_пользователя:права. Заглавная X в правах означает право на исполнение только для каталогов и уже исполняемых файлов - такой умный режим, который не делает все подряд файлы исполняемыми.

Получение бесплатного SSL-сертификата Let's Encrypt через утилиту Certbot и настройка автообновления

Запускать UVdesk без HTTPS нельзя категорически - через систему ходят данные клиентов и переписка. Для бесплатного сертификата используется Certbot, рекомендованный способ установки которого через Snap.

$ sudo snap install core
$ sudo snap install --classic certbot

Флаг --classic нужен потому, что Certbot требует полного доступа к системе, выходящего за рамки песочницы обычных snap-пакетов.

Создаём символическую ссылку, чтобы команда certbot была доступна из стандартных путей системы.

$ sudo ln -s /snap/bin/certbot /usr/bin/certbot

Получаем сертификат. Команда длинная, но каждый параметр важен.

$ sudo certbot certonly --nginx --agree-tos --no-eff-email --staple-ocsp --preferred-challenges http -m Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript. -d uvdesk.example.com

Подкоманда certonly означает только выпуск сертификата без автоматической правки конфигов Nginx - конфигурацию настроим вручную. Флаг --nginx использует плагин Nginx для прохождения проверки домена. Параметр --agree-tos соглашается с условиями использования Let's Encrypt. Опция --no-eff-email отключает подписку на рассылку фонда EFF. Флаг --staple-ocsp включает OCSP-stapling для ускорения проверки отзыва сертификата. Параметр -m задаёт email для уведомлений об истечении срока, а -d указывает домен, для которого выпускается сертификат.

После успешного выполнения сертификаты лягут в каталог /etc/letsencrypt/live/uvdesk.example.com/.

Дополнительно генерируем параметры Diffie-Hellman для усиления безопасности обмена ключами. Размер 4096 бит даёт высокий уровень криптостойкости, но команда выполняется долго - на слабом сервере процесс может занять полчаса и больше.

$ sudo openssl dhparam -dsaparam -out /etc/ssl/certs/dhparam.pem 4096

Флаг -dsaparam ускоряет генерацию за счёт использования параметров DSA, что считается приемлемым компромиссом для современных систем.

Сертификаты Let's Encrypt живут 90 дней, поэтому Certbot ставит таймер автообновления через systemd. Проверяем его наличие.

$ sudo systemctl list-timers

В списке должна найтись строка snap.certbot.renew.service.

NEXT                        LEFT          LAST                        PASSED      UNIT                      ACTIVATES
Mon 2022-09-19 00:28:48 UTC 13min left    Sun 2022-09-18 23:31:55 UTC 43min ago   fstrim.timer              fstrim.service
Mon 2022-09-19 00:39:00 UTC 23min left    Mon 2022-09-19 00:09:00 UTC 6min ago    phpsessionclean.timer     phpsessionclean.service
Mon 2022-09-19 00:40:00 UTC 24min left    n/a                         n/a         snap.certbot.renew.timer  snap.certbot.renew.service
.......

Проверяем работу обновления через тестовый прогон без реального продления.

$ sudo certbot renew --dry-run

Если команда отработала без ошибок, продление будет происходить автоматически в фоне.

Тонкая настройка PHP-FPM под Nginx и подъём лимитов памяти и времени выполнения

Открываем основной конфигурационный файл пула PHP-FPM.

$ sudo nano /etc/php/8.0/fpm/pool.d/www.conf

По умолчанию PHP-FPM работает под пользователем www-data, который используется Apache. Поскольку у нас Nginx работает под пользователем nginx (из официального репозитория), нужно подкорректировать настройки пула. Находим строки user и group и меняем значения.

...
; Unix user/group of processes
; Note: The user is mandatory. If the group is not set, the default user's group
;       will be used.
; RPM: apache user chosen to provide access to the same directories as httpd
user = nginx
; RPM: Keep a group allowed to write in log dir.
group = nginx
...

Аналогично корректируем владельца и группу для unix-сокета, через который Nginx общается с FPM.

; Set permissions for unix socket, if one is used. In Linux, read/write
; permissions must be set in order to allow connections from a web server. Many
; BSD-derived systems allow connections regardless of permissions. The owner
; and group can be specified either by name or by their numeric IDs.
; Default Values: user and group are set as the running user
;                 mode is set to 0660
listen.owner = nginx
listen.group = nginx

Сохраняем файл через Ctrl + X и подтверждение Y.

Увеличиваем время выполнения PHP-скриптов с 30 до 60 секунд - UVdesk при работе с миграциями и массовыми операциями может упереться в дефолтный лимит.

$ sudo sed -i 's/max_execution_time = 30/max_execution_time = 60/' /etc/php/8.0/fpm/php.ini
$ sudo sed -i 's/max_execution_time = 30/max_execution_time = 60/' /etc/php/8.0/cli/php.ini

Утилита sed с флагом -i правит файлы на месте без создания резервных копий. Подмена идёт по шаблону s/что/на_что/. Меняем лимит и для FPM-режима, и для CLI - последний используется при ручном запуске Composer и команд Symfony Console.

Поднимаем лимит памяти со 128 до 256 мегабайт, что соответствует рекомендациям UVdesk.

$ sudo sed -i 's/memory_limit = 128M/memory_limit = 256M/' /etc/php/8.0/fpm/php.ini

Перезапускаем PHP-FPM, чтобы изменения вступили в силу.

$ sudo systemctl restart php8.0-fpm

Меняем группу каталога с PHP-сессиями на nginx, иначе сессии не будут сохраняться.

$ sudo chgrp -R nginx /var/lib/php/session

Создание конфигурации виртуального хоста Nginx для UVdesk с настройками SSL и проксированием в PHP-FPM

Создаём файл конфигурации виртуального хоста.

$ sudo nano /etc/nginx/conf.d/uvdesk.conf

Содержимое файла довольно объёмное, но каждый блок осмысленный.

server {
    listen       443 ssl http2;
    listen       [::]:443 ssl http2;
    server_name  uvdesk.example.com;

    access_log  /var/log/nginx/uvdesk.access.log;
    error_log   /var/log/nginx/uvdesk.error.log;
    
	# SSL
    ssl_certificate      /etc/letsencrypt/live/uvdesk.example.com/fullchain.pem;
    ssl_certificate_key  /etc/letsencrypt/live/uvdesk.example.com/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/uvdesk.example.com/chain.pem;
    ssl_session_timeout  5m;
    ssl_session_cache shared:MozSSL:10m;
    ssl_session_tickets off;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
    ssl_ecdh_curve X25519:prime256v1:secp384r1:secp521r1;
    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_dhparam /etc/ssl/certs/dhparam.pem;
    resolver 8.8.8.8;

    root /var/www/uvdesk/public;
    index index.php;
    
    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    # Pass PHP Scripts To FastCGI Server
    location ~ \.php$ {
        fastcgi_split_path_info ^(.+\.php)(.*)$;
        fastcgi_pass unix:/run/php/php8.0-fpm.sock; # Depends On The PHP Version
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
        include fastcgi_params;
        try_files $uri =404;
    }    
}

# enforce HTTPS
server {
    listen       80;
    listen       [::]:80;
    server_name  uvdesk.example.com;
    return 301   https://$host$request_uri;
}

Разберём ключевые директивы по группам. Блок listen 443 ssl http2 включает HTTPS с поддержкой второй версии HTTP-протокола, что заметно ускоряет загрузку страниц за счёт мультиплексирования запросов. Дублирование с [::]:443 добавляет прослушивание на IPv6.

Группа ssl_ настроек задаёт параметры шифрования. Современный список cipher suites исключает устаревшие алгоритмы, оставляя только надёжные ECDHE и DHE с AES-GCM или ChaCha20-Poly1305. Поддерживаются протоколы TLS 1.2 и 1.3, более старые SSL и TLS 1.0/1.1 отключены как небезопасные. Включён OCSP-stapling для ускорения проверки статуса сертификата.

Директива root указывает корневой каталог сайта - обратите внимание, это не корень проекта /var/www/uvdesk, а вложенный каталог public, где лежит точка входа в Symfony-приложение.

Блок location / с try_files реализует классический паттерн фронт-контроллера. Сначала Nginx пытается отдать запрошенный URI как реальный файл, затем как каталог, и если ничего не нашлось, передаёт управление на index.php с сохранением query-параметров.

Блок location ~ .php$ обрабатывает все запросы к PHP-файлам через FastCGI-соединение с FPM по unix-сокету. Использование сокета вместо TCP-порта быстрее на локальных соединениях за счёт меньших накладных расходов.

Второй server-блок ловит весь HTTP-трафик на 80-м порту и редиректит его на HTTPS через 301-й код. Это стандартный приём принудительного включения шифрования - даже если клиент ввёл адрес без https://, он будет перенаправлен на защищённое соединение.

Сохраняем файл через Ctrl + X и подтверждение Y.

Правим основной конфиг Nginx, чтобы избежать предупреждения о размере хеш-таблицы для имён серверов.

$ sudo nano /etc/nginx/nginx.conf

Перед строкой include /etc/nginx/conf.d/*.conf; добавляем директиву.

server_names_hash_bucket_size  64;

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

Проверяем синтаксис конфигурации перед применением.

$ sudo nginx -t

При успехе получим подтверждение.

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Перезапускаем сервис.

$ sudo systemctl restart nginx

Прохождение веб-инсталлятора UVdesk через мастер настройки в браузере

Финальная стадия - запуск веб-инсталлятора UVdesk через браузер. Открываем адрес https://uvdesk.example.com и видим приветственное окно мастера. На стартовом экране отображается приветствие и краткое описание процесса, который будет проходить через пять шагов: проверку системных требований, настройку базы данных, создание учётки администратора и непосредственную установку. Кнопка LET'S BEGIN запускает процесс.

На следующей странице мастер проверяет соответствие сервера системным требованиям. UVdesk анализирует наличие PHP 8.0, нужных расширений, прав на файлы конфигурации, доступность .env-файла на запись и достаточность времени выполнения скриптов. Если на предыдущих шагах всё было настроено правильно, по всем пунктам появятся зелёные галочки. Нажатие на PROCEED ведёт дальше.

Третий экран запрашивает параметры подключения к базе данных. Адрес сервера 127.0.0.1 указывает на локальную машину, порт 3306 стандартный для MySQL. В поля имени пользователя и пароля вводятся те значения, что задавались на этапе настройки SQL - имя uvdesk и тот пароль, что был выбран. Имя базы данных uvdeskdb. Галочка автоматического создания базы, если её ещё нет, оставлена включённой - она пригождается, если по какой-то причине база ещё не была создана вручную.

Четвёртый шаг создаёт учётку супер-администратора системы. Указываются полное имя оператора, рабочий email, пароль и его подтверждение. Эта учётка получит полные права на администрирование платформы, включая управление другими операторами, настройками тикетов и шаблонами ответов. Пароль стоит выбирать не короче 12 символов с цифрами и спецсимволами - супер-админ держит все ключи от системы.

Пятый экран задаёт префиксы URL для двух разделов системы. Префикс member используется для входа операторов поддержки в их рабочую панель, customer - для портала клиентов, где они могут отслеживать свои обращения. Префиксы можно поменять под свои нужды, например на staff и support, или оставить дефолтные значения, которые вполне понятны.

После настройки префиксов появляется финальный экран с кнопкой INSTALL NOW. Нажатие запускает создание таблиц в базе данных, наполнение начальными данными и финальную сборку. Процесс занимает обычно от нескольких секунд до пары минут.

Завершающий экран сообщает Congratulations! и показывает две ссылки - на админ-панель и на фронтенд базы знаний. По кнопке Admin Panel открывается рабочее место оператора, где можно начинать работу с тикетами. По кнопке Knowledgebase виден публичный раздел для клиентов с возможностью создания обращений и просмотром статей справки.

Типичные проблемы при установке UVdesk и команды для устранения ошибки с миграциями базы данных

Иногда финальный шаг установки спотыкается на ошибке вида SQLSTATE[42S02]: Base table or view not found: 1146 Table 'uvdesk.uv_support_role' doesn't exist. Это означает, что миграции базы данных не отработали полностью. Лечится ручным запуском миграций через Symfony Console.

$ cd /var/www/helpdesk.example.com/
$ php bin/console doctrine:migrations:diff
$ php bin/console doctrine:migrations:migrate
$ php bin/console c:c
$ sudo shutdown -r now

Команда doctrine:migrations:diff сравнивает текущую схему базы с описанием в коде и генерирует недостающие миграции. Команда doctrine:migrations:migrate применяет все ожидающие миграции к базе. Сокращение c:c расшифровывается как cache:clear - очистка кеша приложения, без которой новые изменения могут не подхватиться. Финальная перезагрузка сервера сбрасывает все промежуточные состояния процессов.

Если установка прошла гладко, эти команды не нужны. Но стоит держать их в закладках на случай неприятностей.

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

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