РазделТестовая площадка
ДокументацияУстройство

Тестовая площадка

Площадка — это Proxmox-хост, на котором по одной виртуальной машине на каждый дистрибутив из матрицы платформ: Debian 12 и 13, Ubuntu 22.04, 24.04 и 26.04, AlmaLinux 9 и 10, Rocky Linux 9 и 10, Oracle Linux 9 и 10. Машины собираются из официальных облачных образов через cloud-init, у каждой сразу после первой загрузки снимается снимок clean, так что прогон всегда начинается с чистой ОС. Всё, что относится к конкретному хосту — адрес, сеть, ключ, — лежит в git-игнорируемом .dev/testbed.env; в репозитории только скрипты.

Как устроено

ФайлГде выполняетсяЧто делает
scripts/testbed/pve.shна хосте Proxmox, от rootскачивает образы, создаёт машины (q35, OVMF, cpu: host, virtio-scsi, cloud-init на scsi1), ждёт cloud-init и гостевой агент, снимает и откатывает снимок clean
scripts/testbed/testbed.shлокальногонит pve.sh по ssh с настройками из .dev/testbed.env, пишет ssh-алиасы mp-<имя> в ~/.ssh/config.d/monopanel-testbed, собирает пакет, разворачивает панель и запускает e2e
scripts/testbed/bootstrap.shна машине, от rootставит .deb/.rpm (или голый бинарник), mp setup, затем через панель — nginx, ветку PHP, СУБД, дополнительно apache/fail2ban/firewall/mail; заканчивает mp doctor

Машина n получает vmid 900+n и адрес TB_PREFIX.(TB_IP_BASE+n), имя хоста mp-<дистрибутив>. cloud-init-диск подключён к virtio-scsi, а не к ide2: у ядра cloud в Debian нет драйвера AHCI, и на q35 диск на ide2 гость не видит. Готовность машины хост видит только по ответу гостевого агента, а закончил ли cloud-init, обёртка спрашивает по ssh — guest-exec в EL заблокирован фильтром агента и SELinux, а образы Oracle поставляются с уже запущенным агентом. Образы Oracle именуются по обновлению и сборке (OL9U8_x86_64-kvm-b293), новые появляются на yum.oracle.com/oracle-linux-templates.html. Прошивка — OVMF без предустановленных ключей Secure Boot, потому что образы EL10 требуют x86-64-v3 и всем удобнее загружаться одинаково.

Настройка

# .dev/testbed.env
PVE_HOST=root@pve.example.com
TB_PREFIX=192.0.2          # первые три октета сети машин
TB_GATEWAY=192.0.2.1
TB_IP_BASE=200             # машина n → 192.0.2.(200+n)
TB_SSH_KEY_FILE=~/.ssh/id_ed25519

Команды

make testbed-up                    # создать недостающие машины, загрузить, снять clean
make testbed-status                # таблица: vmid, имя, адрес, состояние, снимок
make testbed-deploy VM=debian13    # собрать пакет, поставить панель и стек
make testbed-e2e VM=debian13       # e2e-тесты против этой машины
make testbed-reset VM=debian13     # откатить к clean
make testbed-matrix                # reset + deploy + e2e на всех машинах параллельно, сводная таблица
make testbed-migrate SRC=ubuntu2404 DST=debian13   # перенос аккаунта между двумя машинами с проверкой
make testbed-sources ARGS="migrate bitrixvm debian13"   # переезд с BitrixVM / FASTPANEL (машины-источники)
make testbed-full                  # всё для релиза: матрица, переносы, CMS, чужие источники, doctor — с итоговой сводкой
make testbed-down                  # удалить машины (образы остаются в кэше)

VM= можно опустить там, где команда имеет смысл для всех машин. Логи параллельного прогона — в .dev/testbed/logs/<имя>.log. Стек по умолчанию: nginx, PHP 8.4, Percona; переопределяется переменными TB_PHP, TB_DB (percona, mysql, none) и TB_EXTRA (apache fail2ban firewall mail).

Панель на машине слушает самоподписанный сертификат на адрес машины, поэтому e2e идёт с MONOPANEL_INSECURE=1 — обёртка ставит его сама. Токен для тестов выпускается через root-сокет (mp token create) и отзывается после прогона, пароль администратора нигде не сохраняется: он печатается один раз при первом bootstrap.sh.

Полный прогон

make testbed-full (он же scripts/testbed/full-run.sh, STAGES="matrix cms" — часть этапов) делает всё, что проверяется перед релизом, и печатает сводку: матрица на всех машинах, три переезда между панелями, четыре CMS на каждой машине (упавшая на сетевой ошибке CMS переустанавливается ещё раз), три чужих источника, mp doctor везде с числом отказов и предупреждений. Логи — в .dev/testbed/logs/, пароли панелей и админок CMS — в .dev/panels.txt и .dev/cms.txt. Итог exit 1, если хоть что-то упало. Установщики CMS теперь пишут в лог и строки note — повторы и пропуски шагов мастера Битрикса, которые раньше терялись на успешной установке.

Сертификат панели каждой машины выпускается один раз: у Let's Encrypt лимит пять сертификатов на имя в неделю, а откат к снимку выпускал новый. testbed.sh deploy хранит выпущенный сертификат в .dev/testbed/certs/<машина>/ и ставит его обратно (mp ssl panel import), пока до конца срока больше месяца.

Имена в DNS

Машины сидят в частной сети, поэтому HTTP-01 до них не дотянется, а вот имена и DNS-01 — работают. Если в .dev/testbed.env заданы TB_ZONE (зона в Cloudflare) и TB_CF_TOKEN (API-токен с правом Zone:DNS:Edit на неё), make testbed-dns (он же testbed.sh dns up) создаёт для каждой машины A-записи <имя>.tb.<зона> и *.<имя>.tb.<зона> (метка tb меняется через TB_DNS_LABEL), без проксирования; dns down убирает их, dns list показывает. С заданной зоной deploy называет панель её именем, а не адресом, регистрирует в ней Cloudflare как DNS-провайдера cf — токен уезжает на машину по stdin и нигде не печатается — и сразу выпускает панели боевой сертификат (mp ssl panel issue --dns cf; неудача не роняет деплой, панель остаётся на самоподписанном). Сертификат для сайта выпускается через mp ssl issue <имя> --dns cf; на площадке разумно брать --staging, чтобы не упереться в лимит Let's Encrypt на повторные сертификаты для одних и тех же имён при частых откатах к clean. Проверено 2026-09-09: боевой сертификат панели alma9 через DNS-01 доверен с рабочего места, сайт site1.rocky10.tb.<зона> со staging-сертификатом отдаётся nginx по HTTPS (сайт создаётся с --ssl none, затем mp ssl issue … --dns cf и mp site set … --ssl auto — панель подхватывает уже выпущенный сертификат вместо HTTP-01). Учтите отрицательный кеш резолвера: имя под wildcard, спрошенное до появления записи, публичный резолвер может помнить как несуществующее до получаса.

Перенос между панелями

Две любые машины площадки — готовая пара для переноса. scripts/testbed/migrate.sh (он же make testbed-migrate) создаёт на источнике аккаунт с сайтом, PHP-файлом, базой с данными и заданием cron, выдаёт токен (mp migrate grant), на приёмнике делает mp migrate plan и mp migrate run с --insecure (сертификаты самоподписанные) и проверяет, что всё приехало: unix-аккаунт с тем же хешем пароля, вход в панель старым паролем, сайт отвечает тем же PHP-файлом, строки в базе и пароль её пользователя, задание в crontab, mp doctor без ошибок. Аккаунт остаётся на обеих машинах для осмотра; make testbed-reset убирает его вместе со всем остальным.

Чужие панели как источники

Кроме матрицы на площадке есть три машины-источника для переезда с чужих панелей: bitrixvm (AlmaLinux 9, vmid 913, .213), bitrixvm7 (CentOS 7 с bitrix-env 7 — оба давно EOL, так до сих пор живут старые серверы Битрикса; vmid 914, .214, SeaBIOS, потому что у образа CentOS 7 нет EFI-раздела, репозитории cloud-init переводит на vault.centos.org) и fastpanel (Debian 12, vmid 912, .212). В матрицу они не входят (pve.sh держит их в отдельной таблице SOURCES), но up, reset, status и dns их знают. scripts/testbed/sources.sh (он же make testbed-sources ARGS=…):

  • install bitrixvm ставит bitrix-env 9 официальным bitrix-env-9.sh (он требует выключить SELinux и перезагрузиться — скрипт делает это сам, ~15 минут) и создаёт management-пул, без которого bx-sites не работает; install bitrixvm7 — старый bitrix-env.sh на CentOS 7, перед ним EPEL берётся из архива, Remi и Percona — с их ещё живых деревьев EL7 (ссылка на epel-release-latest-7 в установщике мертва, а найденные репозитории он пропускает); install fastpanel — install_fastpanel.sh (пароль fastuser попадает в .dev/sources.txt).
  • seed bitrixvm|bitrixvm7 [FROM] кладёт в /home/bitrix/www сайт Битрикса, который testbed.sh cms FROM bitrix поставил на машину матрицы (по умолчанию alma9), с его базой в sitemanager и реквизитами bitrix-env в .settings.php; главный сайт остаётся под server_name _, как у людей. seed fastpanel [FROM] заводит аккаунт shop с WordPress оттуда же, сайтом с docroot public/, алиасом и allow-списком, базой, crontab с data/bin/php, настоящим сертификатом Let's Encrypt (DNS-01 через панель FROM) и ящиком. FASTPANEL без лицензии из её биллинга не пускает ни в API, ни в интерфейс, поэтому строки аккаунта, сайтов, бэкендов, баз, сертификатов и cron пишутся прямо в fastpanel2.db — так, как их пишет живая панель (сверено с рабочим FASTPANEL 1.11); файлы, nginx, php-fpm и MySQL настоящие.
  • Образ CentOS 7 оставляет в resolv.conf первым мёртвый nameserver libvirt (192.168.122.1): каждый запрос ждёт 5 секунд, вход по ssh — 40, а разбор переезда — минуты. pve.sh вычищает его при каждой загрузке, prepare_centos7 — ещё раз перед установкой.
  • migrate bitrixvm|bitrixvm7 DST и migrate fastpanel DST дают root машины DST ключ на источник, ставят там ветку PHP источника, делают mp migrate plan и run и проверяют: сайт отвечает по новому адресу (у Битрикса — X-Powered-CMS, у WordPress — по HTTPS с перевезённым сертификатом), пути /home/bitrix переписаны, вход в базу прежним паролем или хешем, unix-хеш совпадает с источником, cron и allow-список на месте.

Пароли, которые придумывает seed, лежат в .dev/sources.txt (git-ignored). Прогон 2026-09-12: BitrixVM → debian13 и FASTPANEL → rocky9 без замечаний.

CMS поверх пресетов

make testbed-cms NAME=<машина> [CMS="wordpress joomla opencart bitrix"] (он же testbed.sh cms) ставит на машину CMS средствами самой панели — mp cms install — в сайты с соответствующими пресетами и проверяет, что сайт работает, а правила пресета действуют. Нужны имена в DNS (dns up): сайты называются <cms>.<машина>.tb.<зона> и живут по HTTP — HTTP-01 до частной сети не дотянется, а проверять пресеты TLS не нужен. Пользователь панели cms; сайт с файлами переустанавливается с --force.

  • WordPress: главная и вход, /wp-admin/ уводит на логин, xmlrpc.php закрыт (403), PHP в wp-content/uploads/ не исполняется, ЧПУ-ссылка отдаёт 200.
  • Joomla: главная, /administrator/, configuration.php и cache/ закрыты, /api/ отвечает 401, PHP в images/ не исполняется, SEF-маршрут работает, installation/ удалён.
  • OpenCart: витрина, /admin/, system/ и storage/ закрыты, .twig не отдаётся, SEO-URL уходит в _route_, install/ удалён.
  • 1С-Битрикс (пробная «Старт»): X-Powered-CMS: Bitrix Site Manager на главной, форма входа /bitrix/admin/, закрытые php_interface/dbconn.php, modules/, .settings.php, cache/, PHP в upload/ не исполняется, несуществующий адрес обрабатывает urlrewrite.php, mp site php показывает short_open_tag On, max_input_vars 20000, memory_limit 512M.

Доступы к админкам печатаются строками CREDS один раз. Что пришлось учесть, пока сценарий был ещё ручным: на EL Percona идёт с validate_password (пароль базы должен содержать все классы символов, панель их генерирует так); файлы, скопированные cp -a из /root, на EL сохраняют метку SELinux admin_home_t — теперь это чинит mp site fix, а панель после распаковки метит файлы сама; mp db create для существующей базы не ошибается, а переустанавливает пароль.

Что нашёл первый прогон (2026-09-09)

До площадки панель проверялась только на Ubuntu 24.04. Первый же круг по всей матрице дал шесть ошибок, первый переезд — седьмую, и каждая из них воспроизводилась только на настоящей ОС — тест с поддельным агентом их увидеть не мог:

ГдеЧто былоЧто сделано
Ubuntu 26.04у ppa:ondrej/php нет сборок для resolute, apt update падал на 404панель проверяет dists/<codename>/Release перед подключением, ставит PHP из самой Ubuntu (8.5), в списке веток помечает недоступные с причиной, при следующей установке проверяет PPA снова и убирает за собой негодный источник
EL9, EL10nginx не стартовал: nginx -t, которым агент проверяет конфигурацию, создавал /run/nginx.pid с меткой var_run_t, домен httpd_t его не открывалу набора конфигов появилось поле restore; агент после записи файлов и создания каталогов делает restorecon; при первой установке nginx/PHP панель настраивает fcontext и булевы SELinux под хостинг (02)
EL9semanage fcontext отвергает /run/... из-за правила эквивалентности /run ↔ /var/run (на EL10 — наоборот)панель следует подсказке semanage
EL9, EL10Percona ставится с временным просроченным паролем root в /var/log/mysqld.log, плагин auth_socket не загруженпанель читает пароль из лога, задаёт свежий (просроченный пускает только ALTER USER), грузит плагин и переводит root на сокет; пароль на время трёх запросов живёт только в памяти и передаётся через окружение
EL (Percona)validate_password MEDIUM отвергал примерно половину сгенерированных паролей баз — случайная строка без гарантии спецсимвола; Rocky прошла случайногенератор auth.NewPassword гарантирует все классы символов; используется для паролей баз, ящиков и Roundcube
Ubuntu 24.04repo.percona.com ответил таймаутом TLS, установка упала целикомзагрузки ключей и пакетов репозиториев повторяются при сетевых ошибках
переносхеш caching_sha2_password с бинарной солью портился в текстовом выводе SHOW CREATE USER (ERROR 1827)источник просит сервер печатать хеш hex-литералом

После исправлений полный прогон make testbed-matrix с чистых снимков зелёный на всех машинах (Ubuntu 26.04 — с PHP 8.5, остальные — 8.4; nginx 1.30, Percona 8.4), переносы Ubuntu 24.04 → Debian 13 и Debian 12 → AlmaLinux 10 проходят все проверки.

Oracle Linux 9 и 10 добавлены 2026-09-10 и нашли ещё две особенности: образы Oracle идут с включённым firewalld (только ssh) — теперь mp setup открывает в нём порт панели, установка nginx — 80 и 443, почты — её порты, а mp firewall enable его выключает; и remi-release-10 требует epel-release = 10, которого пакет oracle-epel-release-el10 не предоставляет — на OL10 панель ставит официальный epel-release. После этого e2e на обеих зелёный, перенос Ubuntu 24.04 → Oracle Linux 9 прошёл все проверки.

Сама площадка тоже кое-чему научила: cloud-init-диск на ide2 невидим для ядра cloud Debian; в EL гостевой агент запрещает guest-exec (снимается в /etc/sysconfig/qemu-ga), а SELinux всё равно не даёт ему запускать команды — готовность машины на EL определяется по ответу агента; pkill -f внутри одной ssh-команды убивает и её саму; параллельные deploy не должны собирать пакет каждый сам — matrix собирает один раз.

Исходник страницы на GitHub