Расширяем возможности ispmanager: создаём плагин для поддержки mod_rewrite в nginx

Расширяем возможности ispmanager: создаём плагин для поддержки mod_rewrite в nginx

03 сентября 2026
10 минут
Алексей

Функциональность ispmanager можно самостоятельно расширять с помощью механизма плагинов: добавлять новые пункты меню, формы, отчёты, а также перехватывать события до и после стандартных операций и интегрировать данные из внешних систем в интерфейс. Рассмотрим создание плагина для ispmanager на примере mod_rewrite в nginx. В результате доработки:

  • В nginx подключается модуль-расширение mod_rewrite (ngx_http_apache_rewrite_module), который позволяет сайтам, работающим на связке nginx + PHP-FPM без Apache, корректно обрабатывать правила из .htaccess.
  • Управление модулем становится доступно через интерфейс ispmanager без необходимости ручного редактирования конфигурационных файлов (исходные коды модуля-плагина).

Реализация ориентирована на AlmaLinux 9/10 и совместимые с ними дистрибутивы. В результате мы получим RPM-пакет, который решает две задачи:

  1. Загрузка и сборка модуля расширения.
    Пакет скачивает исходные коды модуля mod_rewrite для nginx, собирает его и отслеживает версии: при обновлении системного nginx пересборка выполняется автоматически.
  2. Интеграция с интерфейсом ispmanager.
    В панели появляются формы управления настройками модуля, позволяющие администратору включать, отключать и конфигурировать расширение без обращения к командной строке.

Проверяем обработку правил .htaccess без плагина

Сначала проверим, как выполняются правила mod_rewrite в nginx через стандартных механизм ЧПУ, без плагина. Создадим в ispmanager тестового пользователя и сайт для него с файлом .htaccess, содержащим набор правил. Сайт настроен как FastCGI (nginx + PHP-FPM) с опцией «Включить обработку ЧПУ».

PHP-обработчик
PHP-обработчик

Структура тестового сайта:

./index.php
    1        <?php
    2        echo "Hello from index.php param=". $_GET["param"];
./test1.php
    1        <?php
    2        echo "hello from test1.php param = " . $_GET["param"];
./test2.php
    1        <?php
    2        echo "Hello from test2.php param=" . $_GET["param"];
    3        
    4        
./test3.php
    1        <?php
    2        echo "test3 should be forbidden";
.htaccess
    1        RewriteEngine On
    2        # --- Rule 1: Add ?param=1 to /index.php (if not already present) ---
    3        RewriteCond %{QUERY_STRING} !^param=1 [NC]
    4        RewriteRule ^index\.php$ /index.php?param=1 [L,R=302]
    5        # --- Rule 2: /test1/* paths redirect to test1.php ---
    6        RewriteRule ^test1/(.*)$ /test1.php [L]
    7        # --- Rule 3: /test-ok/* paths redirect to test1.php ---
    8        RewriteRule ^test-ok/(.*)$ /test1.php [L]
    9        # --- Rule 4: /test6.php redirects to /test2.php ---
   10        RewriteRule ^test6\.php$ /test2.php [L]
   11        # --- Rule 5: /test3.php returns Forbidden (403) via RewriteRule ---
   12        RewriteCond %{REQUEST_URI} ^/test3\.php [NC]
   13        RewriteRule ^ / [F,L]

Ожидаемое поведение правил:

  1. index.php должен перенаправляться на index.php?param=1
  2. Любой URL, начинающийся с test1/, должен вести на test1.php
  3. Любой URL, начинающийся с test-ok/, должен вести на test1.php
  4. test6.php должен внутренне перенаправляться на test2.php
  5. Доступ к test3.php должен быть запрещён (HTTP 403)

Связка Apache + mod_rewrite справляется с этими задачами без проблем. Проверим, как ведёт себя nginx + PHP-FPM с опцией «Включить обработку ЧПУ»:

Правило 1:

bash
# curl -L http://user2.test.domain
Hello from index.php param=

Ожидалось: param=1 — не сработало.

Правило 2:

bash
# curl -L http://user2.test.domain/test1/1.php
<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.30.2</center>
</body>
</html>

Ожидался: вывод test1.php.

Правило 3:

bash
# curl -L http://user2.test.domain/test-ok/m.php?param=1
<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.30.2</center>
</body>
</html>

Ожидался: вывод test1.php.

Правило 4:

bash
# curl -L http://user2.test.domain/test6.php
<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.30.2</center>
</body>
</html>

Ожидался: вывод test2.php.

Правило 5:

bash

# curl -L http://user2.test.domain/test3.php
test3 should be forbidden

Ожидался: HTTP 403 — вместо запрета отдаётся содержимое скрипта.

Результат очевиден: стандартный механизм «ЧПУ» не обеспечивает корректную обработку правил .htaccess. Без модификации конфигурации nginx многие CMS будут работать некорректно. Приступаем к решению проблемы.

Разработка плагина для ispmanager

Согласно официальной документации, создание плагина начинается с подготовки XML-файла, который определяет:

  • структуру форм, отображаемых в интерфейсе;
  • перечень событий, на которые плагин должен реагировать;
  • обработчики этих событий.

Файлы плагина располагаются в следующих каталогах:

  • /usr/local/mgr5/etc/xml — XML-описания структуры плагина;
  • /usr/local/mgr5/addon — скрипты-обработчики логики.

Важно различать понятия модуль и плагин в терминологии ispmanager:

  • Плагин расширяет функциональность панели, добавляя пункты меню и формы.
  • Модуль появляется в разделе «Модули» и служит для первичной настройки — через него можно задать базовые параметры и удалить плагин через графический интерфейс.

В нашей статье фигурируют три сущности:

  1. Плагин ispmanager — добавляет пункты меню и формы управления.
  2. Модуль ispmanager — запись в разделе «Модули» для первичной настройки и удаления.
  3. Модуль mod_rewrite — расширение для nginx, которое собирается и устанавливается в систему.

Описание модуля ispmanager (первичная настройка)

Первичная настройка в нашем случае — это сборка модуля mod_rewrite для nginx.

Создаём описание модуля в файле /usr/local/mgr5/etc/plugins/ispmgr/nginx_mod_rewrite_plugin.xml:

xml
<?xml version="1.0" encoding="UTF-8" ?>
<mgrdata>
  <plugin name="nginx_mod_rewrite_plugin">
    <dist>1</dist>
    <free />
    <settings>nginx_mod_rewrite_plugin.settings</settings>
    <msg name="desc_short" lang="ru">Модуль nginx mod_rewrite</msg>
    <msg
        name="desc_full"
        lang="ru"
    >Модуль добавления поддержки mod_rewrite правил и .htaccess в nginx&lt;br/&gt;&lt;a href="https://github.com/bayrepo/ngx_http_apache_rewrite_module" target="_blank"&gt;Документация по настройкам модуля&lt;/a&gt;</msg>
  </plugin>
</mgrdata>

Здесь мы указываем:

  • через тег <settings> — обработчик настройки модуля;
  • через тег <free /> — тип модуля (бесплатный);
  • краткое и полное описание на русском языке.

После перезапуска панели модуль появляется в разделе «Модули».

Вид нового модуля в интерфейсе ispmanager
Вид нового модуля в интерфейсе ispmanager

Форма настройки модуля

Теперь добавим описание формы настроек. Файл /usr/local/mgr5/etc/xml/ispmgr_mod_nginx_mod_rewrite_plugin.xml:

xml
<metadata name="nginx_mod_rewrite_plugin.settings" type="form" mgr="ispmgr">
    <form>
        <field name="nginx_mod_rewrite_plugin_field" noname="yes" fullwidth="yes">
            <textdata name="nginx_mod_rewrite_plugin_msg" type="banner" status="warning"/>
            <textdata name="nginx_mod_rewrite_plugin_msg_info" type="banner" status="info"/>
        </field>
        <buttons>
            <button name="ok" type="ok" />
            <button name="cancel" type="cancel" />
        </buttons>
    </form>
</metadata>

Эта форма содержит информационный и предупреждающий баннеры, а также кнопки «ОК» и «Отмена». Что и когда отображать — определяет обработчик, прикреплённый к форме.

Обработчик указывается следующим образом:

xml
<handler name="nginx_mod_rewrite_plugin.py" type="xml">
    <func name="nginx_mod_rewrite_plugin.settings" />
</handler>

Таким образом, форма nginx_mod_rewrite_plugin.settings обрабатывается скриптом nginx_mod_rewrite_plugin.py, расположенным в /usr/local/mgr5/addon.

Обработчик модуля

Скрипт nginx_mod_rewrite_plugin.py написан на Python и выполняет системные действия в ответ на события от пользователя в веб-интерфейсе.

Его структура включает:

  • логирование действий;
  • сборку модуля mod_rewrite для nginx;
  • установку флага, что сборка выполнена и можно активировать хук пересборки.

Хук пересборки прописывается в spec-файле:

spec
%triggerin -- nginx
if [ -e /usr/local/ispmanager-mod_rewrite/settings/installed.cfg ]; then
    /usr/local/ispmanager-mod_rewrite/utils/build_mod_rewrite.py --nodeps
fi

При обновлении nginx этот триггер проверяет наличие файла-маркера и при необходимости запускает пересборку модуля.

В обработчике реализованы процедуры main и Handle. Они анализируют переменные окружения (PARAM_func, PARAM_plugin_settings) для распознавания обращения к форме настроек.

Скрипт читает XML из stdin, разбирает его и, если нажата кнопка «ОК»:

python
if os.getenv("PARAM_clicked_button") == "ok":
    Handle(root)
запускает сборку модуля. При этом из XML удаляются ненужные баннеры:
python
for parent in root.iter():
    for child in list(parent):
        if child.tag == 'textdata' and child.get('name') == 'nginx_mod_rewrite_plugin_msg_info':
            parent.remove(child)

После завершения сборки в ответ добавляется элемент <ok>, который сигнализирует о закрытии формы:

python
etree.SubElement(root, 'ok')

Пользовательский сценарий выглядит так:

  1. Переход в настройки модуля ispmanager.
  2. Нажатие кнопки сборки.
  3. Автоматическое закрытие формы после завершения.
Настройка модуля в ispmanager
Настройка модуля в ispmanager

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

Модуль уже собран
Модуль уже собран

Модифицированный XML отправляется в stdout:

python
etree.dump(root)

Описание форм управления mod_rewrite в плагине

После того как модуль mod_rewrite собран, необходимо обеспечить его включение для отдельных сайтов. Здесь есть два подхода.

Вариант 1. Использование include-директив

ispmanager позволяет добавлять собственные настройки для хостов nginx с помощью include. Типичная конфигурация хоста выглядит так:

text
server {
    server_name userX.domain www.userX.domain;
    charset off;
    index index.php index.html;
    disable_symlinks if_not_owner from=$root_path;
    include /etc/nginx/vhosts-includes/*.conf;
    include /etc/nginx/vhosts-resources/userX.domain/*.conf;
    include /etc/nginx/users-resources/userX/*.conf;
    ...
}

Для наших целей подходит путь /etc/nginx/vhosts-resources/userX.domain/*.conf. В этот каталог мы будем подкладывать файл с настройками mod_rewrite:

bash
# cat /etc/nginx/vhosts-resources/userX.domain/mod_rewrite.conf
GlobalLocationRewriteEngine on;
HtaccessEnable on;

Эти директивы включают обработку .htaccess и активируют mod_rewrite для всех Location текущего сайта. Файл создаётся или удаляется при создании/редактировании сайта в зависимости от состояния чекбокса.

Вариант 2. Модификация шаблонов nginx

Этот способ сложнее, но даёт более тонкую настройку. Согласно документации ispmanager, шаблоны хостов хранятся в /usr/local/mgr5/etc/templates/default/nginx-vhosts.template и nginx-vhosts-ssl.template. Их можно скопировать в /usr/local/mgr5/etc/templates/ — там они не будут перезаписаны при обновлении панели.

Модифицируем шаблон для генерации следующей конфигурации:

text
server {
    server_name domain;
    ...
    location / {
        ...
        RewriteEngine on;
    }
    location @php {
        ...
        RewriteEngine on;
    }
    ...
    HtaccessEnable on;
}

Для повышения гибкости статические файлы можно вынести за пределы корневого Location, чтобы исключить применение правил mod_rewrite к изображениям, CSS и JS. Это снижает нагрузку и даёт более предсказуемую маршрутизацию.

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

Форма глобальных настроек плагина

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

  • отключить загрузку mod_rewrite в nginx;
  • выбрать способ интеграции (Вариант 1 или Вариант 2);
  • включить отладку работы плагина.

Описание формы:

xml
<metadata name="nginx_mod_rewrite_plugin_menu_settings" type="form">
    <form needconfirm="yes">
        <field name="nginx_mod_rewrite_plugin_menu_module_enable">
            <input type="checkbox" name="nginx_mod_rewrite_plugin_menu_module_enable"/>
        </field>
        <field name="nginx_mod_rewrite_plugin_menu_global_module_enable">
            <input type="checkbox" name="nginx_mod_rewrite_plugin_menu_global_module_enable"/>
        </field>
        <field name="nginx_mod_rewrite_plugin_menu_debug_enable">
            <input type="checkbox" name="nginx_mod_rewrite_plugin_menu_debug_enable"/>
        </field>
    </form>
</metadata>

Также необходимо добавить чекбокс в форму редактирования сайта:

xml
<metadata name="webdomain.edit" type="form">
    <form>
        <page name="optimization">
            <field name="nginx_mod_rewrite_plugin_site_edit_enable">
                <input type="checkbox" name="nginx_mod_rewrite_plugin_site_edit_enable"/>
            </field>
        </page>
    </form>
</metadata>

Этот флажок появится на вкладке «Оптимизация» при редактировании сайта.

Для навигации создаём пункт меню:

xml
<mainmenu level="admin+">
    <modernmenu>
        <node name="nginx_mod_rewrite_plugin_menu" customicon="/manimg/icons/mod_rewrite_menu.svg">
            <node name="nginx_mod_rewrite_plugin_menu_settings" customicon="/manimg/icons/mod_rewrite_tool.svg"/>
        </node>
    </modernmenu>
</mainmenu>
Новое меню настроек
Новое меню настроек
Глобальные настройки модуля
Глобальные настройки модуля

Теперь добавляем обработчик для новых форм:

xml
<handler name="nginx_form_rewrite_plugin.py" type="xml">
    <event name="webdomain.edit" after="yes"/>
    <func name="nginx_mod_rewrite_plugin_menu_settings"/>
</handler>

Здесь func обрабатывает глобальное меню, а event расширяет стандартную функцию webdomain.edit, срабатывая после неё.

Обработчик форм плагина

Скрипт nginx_form_rewrite_plugin.py, расположенный в /usr/local/mgr5/addon, анализирует переменные окружения и определяет, какое действие нужно выполнить:

  • включить или отключить mod_rewrite;
  • выбрать способ встраивания (шаблон или include);
  • включить/отключить отладку;
  • обработать создание или изменение сайта.

При переключении флажка «Использовать шаблон» происходит копирование и модификация шаблонов nginx.

Форма создания сайта с установленным плагином выглядит так:

Создание сайта с установленным плагином
Создание сайта с установленным плагином

При включении чекбокса для сайта обработчик либо создаёт файл /etc/nginx/vhosts-resources/userX.domain/mod_rewrite.conf, либо устанавливает переменную для шаблона:

python
new_element = etree.Element("nginx_mod_rewrite_plugin_site_edit_enable")
new_element.text = "on"
root.append(new_element)

В шаблоне /usr/local/mgr5/etc/templates/nginx-vhosts-ssl.template появляется условие:

text
location / {
{% if $nginx_MOD_REWRITE_PLUGIN_SITE_EDIT_ENABLE == on %}
                RewriteEngine on;
{% endif %}

 

Сборка пакета плагина

Важное замечание: плагин рекомендуется устанавливать именно через RPM-пакет, а не путём ручного копирования файлов. Пакет устанавливает триггер (хук), который отслеживает обновления nginx и запускает пересборку mod_rewrite автоматически.

Сборка RPM-пакета выполняется с помощью Docker. Достаточно одной команды:

bash
bash build_package.sh almalinux:9

где almalinux:9 может быть заменён на любой образ RPM-совместимой ОС.

После успешной сборки в каталоге _packages появляются rpm-файлы:

text
_packages/ispmanager-plugin-nginx_mod_rewrite_plugin-0.0.1-1.el9.src.rpm
_packages/ispmanager-plugin-nginx_mod_rewrite_plugin-0.0.1-1.el9.x86_64.rpm

На этом этап сборки завершён.

Результат работы mod_rewrite

Активируем mod_rewrite для нашего тестового сайта и проверяем те же правила.

Правило 1:

bash
# curl -L http://user2.test.domain
Hello from index.php param=1

✅ Работает корректно.

Правило 2:

bash
# curl -L http://user2.test.domain/test1/1.php
hello from test1.php param =

✅ Работает корректно.

Правило 3:

bash
# curl -L http://user2.test.domain/test-ok/m.php?param=1
hello from test1.php param = 1

✅ Работает корректно.

Правило 4:

bash

# curl -L http://user2.test.domain/test6.php
Hello from test2.php param=

✅ Работает корректно.

Правило 5:

bash
# curl -L http://user2.test.domain/test3.php
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.30.2</center>
</body>
</html>

✅ Работает корректно.

Все правила отрабатывают так, как и ожидалось.

Теперь сайты на CMS, использующие .htaccess, могут работать на связке nginx + PHP-FPM в окружении ispmanager так же предсказуемо, как и на Apache, а администраторам не приходится вручную править конфигурационные файлы.

Алексей
Алексей