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

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

Проектный документ. Реализованы прямой перенос между двумя MonoPanel и адаптеры для BitrixVM и FASTPANEL (mp migrate); пакет-файл и остальные адаптеры описаны здесь как проект. Что работает — в README и отметках [x] в 05-roadmap.md.

Задача звучит просто: «перевезти сайт (аккаунт, сервер) сюда» — и чтобы человек нажал одну кнопку. Внутри это три разные задачи, и путать их нельзя: переезд между двумя MonoPanel, приём сервера с чужой панелью и приём сервера без панели вообще. Общий у них только формат посылки; собирают её разные руки.

1. Что переносим

Единица переноса — аккаунт: пользователь и всё, что ему принадлежит. Сайт — подмножество аккаунта, сервер — набор аккаунтов плюс серверные настройки.

ОбластьЧто внутри
user:<login>аккаунт панели, unix-пользователь, домашний каталог, сайты, базы, cron, app-сервисы, экземпляры Valkey (настройки, без данных), почтовые домены и ящики, сертификаты
site:<domain>один сайт с файлами, базой (если она одна и его), cron-заданиями, которые его упоминают, сертификатом
serverвсе аккаунты + firewall, real-ip, DNS-провайдеры, настройки почты, ветки PHP

Не переносим: сам сервер (ОС, ядро, сеть), правки в /etc мимо панели, метрики и журнал задач, сессии и API-токены (их выпускают заново), бэкап-цели с их паролями от репозиториев.

2. Пакет переезда

Один самоописывающийся архив. Он же — артефакт для «поднять новый сервер из ничего».

manifest.json          формат, версия панели-источника, область, время, sha256 каждого файла
state/
  users.json           логин, роль, argon2id-хеш пароля панели, uid/gid, shell, квота, shadow-хеш unix-пароля
  sites.json           домен, алиасы, режим, PHP, docroot, php_ini, preset, allow_from, где PHP держит сессии, свои nginx-директивы
  databases.json       базы и аккаунты со строкой аутентификации из SHOW CREATE USER
  cron.json            расписания и команды
  apps.json            app-сервисы: команда, workdir, env
  valkey.json          экземпляры Valkey: назначение (cache, sessions) и память — без данных
  mail.json            домены, ящики (bcrypt), алиасы, ключи DKIM
  certificates.json    имена, тип, автопродление
files/                 домашние каталоги
dumps/                 mysqldump на каждую базу
certs/                 fullchain и ключ действующих сертификатов

Пароли переезжают хешами, а не текстом. Панельный пароль — argon2id прямо из строки БД; unix-пароль — хеш из shadow и chpasswd -e на другой стороне; MySQL — IDENTIFIED WITH ... AS '<хеш>', который отдаёт SHOW CREATE USER; почтовый ящик — тот же {BLF-CRYPT}, что лежит в passwd-файле dovecot. Никто не вводит пароли заново, пользователи не замечают переезда, а панель нигде не видит открытый текст.

Исключение — то, что зашифровано ключом исходной панели: приватные ключи DKIM, секреты TOTP, токен репозитория обновлений. Это перешифровывается при сборке пакета, поэтому пакет всегда зашифрован: либо парольной фразой, которую человек переносит сам, либо публичным ключом целевой панели (при прямом переносе он известен).

3. Как пакет попадает на новый сервер

Файлом. mp migrate export --scope user:alex --out alex.mp → скопировать → mp migrate import alex.mp. Работает всегда, в том числе когда исходный сервер уже не поднимается, а остались только бэкапы.

Панель → панель, по требованию. На источнике администратор разрешает переезд и получает одноразовый токен с областью и сроком жизни (mp migrate grant --scope user:alex --ttl 2h). На целевой панели — «Импорт с другой панели»: адрес, токен, что взять. Дальше цель тянет пакет сама: у неё свободнее ресурсы, она знает своё состояние и может докачать прерванное. Источник при этом только отдаёт поток и пишет в audit, кто и что забрал.

Через репозиторий restic. Экспорт кладёт state/ рядом со снимком существующей бэкап-цели, импорт читает оттуда. Почти бесплатно — вся машинерия уже есть — и заодно закрывает «восстановить сервер целиком на новом железе».

4. Порядок переезда

Переезд ломается не на копировании, а на переключении DNS: пока запись смотрит на старый сервер, новый нельзя ни проверить браузером, ни получить для него сертификат по HTTP-01. Поэтому импорт — не одно действие, а четыре.

  1. Разбор (dry-run). Цель отвечает отчётом до того, как что-то создаст: что приедет и сколько весит, что конфликтует (логин занят, домен уже обслуживается, база с таким именем есть), чего не хватает (ветка PHP не установлена, MySQL нет, мало места), что не переносится совсем.
  2. Копия. Задача создаёт аккаунт, кладёт файлы, поднимает базы, применяет состояние. Сайт поднимается сразу, но DNS на него ещё не переключён — панель показывает готовую команду curl --resolve и строку для hosts, чтобы посмотреть его глазами.
  3. Досинхронизация. mp migrate resync проходит только по изменившемуся: файлы по времени и размеру, свежие дампы. Делается прямо перед переключением DNS и занимает минуты вместо часов — сайт на старом сервере всё это время работает.
  4. Переключение. Человек меняет DNS, затем mp migrate finish: панель ждёт, пока имя начнёт резолвиться на неё, заказывает ACME-сертификаты (до этого работает перевезённый сертификат, поэтому HTTPS не рвётся ни на секунду), и по явной команде ставит сайты на источнике в suspend.

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

5. Что уже работает

Прямой перенос между двумя MonoPanel: mp migrate grant на источнике, mp migrate plan и mp migrate run на приёмнике.

Источник только читает: он отдаёт состояние (/migrate/plan без секретов, /migrate/state с хешами и ключами) и два потока — /migrate/files (tar домашнего каталога или Maildir) и /migrate/dump (mysqldump). Ничего не меняется даже в его собственной базе, кроме записи в audit о том, кто и что забрал.

Токен переезда выдаётся с областью migrate:user:<логин> и сроком жизни. Он ограниченный: пускает только на GET под /migrate и только для своего аккаунта; на /users тем же токеном приходит 403. Остальные области панель по-прежнему считает пометками — токены, выпущенные раньше, работают как работали.

Потоки идут насквозь: tar -c на источнике → HTTPS → tar -x здесь, между ними ничего не складывается на диск. Для этого у агента появились две операции (/v1/stream/out, /v1/stream/in) и рекурсивный chown — распаковка идёт от root, а владельцем должен стать клиент.

Приёмник делает всё остальное: создаёт аккаунт с перенесённым хешем пароля панели и unix-пароля, разворачивает домашний каталог, поднимает базы вместе с их аккаунтами (SHOW CREATE USER → CREATE USER ... AS '<хеш>'), создаёт сайты со своими директивами nginx, cron, app-сервисы (выключенными), экземпляры Valkey (пустыми; сайт оставляет PHP-сессии в Valkey, только если здесь Valkey установлен и у его ветки PHP включено расширение redis, иначе переходит на файлы), почтовые домены с ключами DKIM, ящики с их {BLF-CRYPT} и алиасы, ставит перевезённые сертификаты и применяет конфигурацию.

Проверено на двух настоящих серверах площадки 2026-09-09 (Ubuntu 24.04 → Debian 13, make testbed-migrate): аккаунт с сайтом, PHP-файлом, базой и cron приехал целиком — вход в панель и SFTP старыми паролями, сайт отвечает тем же файлом, строки и пароль пользователя MySQL на месте, задание в crontab. Первый живой прогон поймал ошибку, которую двухпанельный тест с поддельным агентом увидеть не мог: хеш caching_sha2_password содержит бинарную соль, и через текстовый вывод клиента SHOW CREATE USER он приезжал испорченным (ERROR 1827). Теперь источник просит сервер печатать хеш hex-литералом (print_identified_with_as_hex), и приёмник воспроизводит его без потерь. Разные семейства ОС переезду не мешают: Debian 12 → AlmaLinux 10 прошёл те же проверки — всю конфигурацию приёмник рендерит заново под свои пути и имена сервисов, а в разборе остаётся предупреждение: свои директивы nginx и команды cron могут ссылаться на пути старой ОС.

Чего пока нет: досинхронизации (resync), завершения переезда (finish), областей site: и server: и пакета-файла.

6. Чужие панели и серверы без панели

Формат один — тот же MigrationBundle, что отдаёт MonoPanel, — собирают его разные адаптеры. Дальше импорт общий: разбор конфликтов, аккаунт, потоки, сайты, сертификаты, cron работают одинаково для любого источника.

АдаптерОткуда берётСостояние
monopanelAPI исходной панели по токену переездаработает
bitrixvm«1С-Битрикс: Веб-окружение» (bitrix-env 7–9): nginx-конфиги /etc/nginx/bx/site_enabled, bitrix/.settings.php и dbconn.php каждого сайта, /root/.my.cnfработает
fastpanelFASTPANEL 2: её SQLite fastpanel2.db (аккаунты, сайты, бэкенды, базы, сертификаты), crontab пользователя, /var/www/httpd-certработает
plainсканирует vhosts nginx/apache, /var/www, список баз MySQL и предлагает сопоставление — сервер без панелиплан
ispmanager, cpanelпозже; у cPanel свой формат cpmove, его проще принять как естьплан

Чужие панели не умеют отдавать аккаунт по API, поэтому адаптеры читают старый сервер по ssh от root — паролем или приватным ключом (--key, по умолчанию ~/.ssh/id_ed25519 того, кто запускает mp). Всё, что уходит на источник, — cat, ls, test, getent, mysql -e SELECT, tar -c, mysqldump: на нём ничего не меняется. Ключ хоста источника не с чем сверить, поэтому его отпечаток печатается в разборе — сравните с тем, что показывает старый сервер. Пароль ssh в задаче лежит зашифрованным ключом панели и стирается вместе с ней. Каждая команда — отдельная ssh-сессия, и если у старого сервера сломан DNS (мёртвый nameserver в resolv.conf), sshd тратит по 5 секунд на обратные запросы на каждую из них: разбор тогда идёт минуты, а не секунды — почините resolv.conf на источнике.

mp migrate plan --from bitrixvm  --source root@old.example.com --domain shop.example.com
mp migrate run  --from bitrixvm  --source root@old.example.com --domain shop.example.com [--as shop]
mp migrate plan --from fastpanel --source root@old.example.com                 # перечислит аккаунты
mp migrate run  --from fastpanel --source root@old.example.com --scope user:shop [--password-stdin]

BitrixVM. Один unix-пользователь bitrix, поэтому область всегда user:bitrix (другой логин здесь — --as). Основной сайт живёт в /home/bitrix/www под server_name _, имя ему даёт --domain; дополнительные — в /home/bitrix/ext_www/<домен>, у них имена есть. Сайты типа link делят ядро основного через абсолютные симлинки — tar на источнике переписывает их цели под новый домашний каталог (--transform только для целей симлинков), а после распаковки те же пути (/home/bitrix/… → /var/www/<логин>/…) правятся в dbconn.php, .settings*.php и командах cron: так BX_TEMPORARY_FILES_DIRECTORY из bitrix-env продолжает работать. Собственные файлы сайтов, где зашит /home/bitrix/ (скрипты экспорта, bitrix/php_interface, local/; ядро bitrix/ и upload/ не смотрятся), разбор находит grep на источнике и перечисляет, а после переноса пути в них переписываются так же — не больше 200 файлов на сайт. Значение root в конфиге nginx bitrix-env пишет в кавычках — они снимаются; сервер с root вне /home/bitrix не переносится, и разбор это говорит. Реквизиты базы берутся из .settings.php — пароль известен открытым текстом, и аккаунт MySQL заводится здесь заново IDENTIFIED WITH … BY с явно названным плагином: mysql_native_password, если среди сайтов есть PHP ниже 7.4 (разбор предупреждает), иначе caching_sha2_password. Cron: кроме crontab пользователя bitrix читается crontab root — на BitrixVM всё делают от root, и задания с путями /home/bitrix/ (экспорт, обмен) переезжают в crontab аккаунта с пометкой «from root's crontab»; остальные задания root не переносятся, разбор их перечисляет. /usr/bin/php в командах становится php — это ~/data/bin/php, ветка сайтов, а не PHP дистрибутива по умолчанию. Задание, запускающее что-то из /tmp, /var/tmp, /dev/shm или скачивающее скрипт прямо в shell, приезжает выключенным с пометкой — так выглядят закладки после взлома. mp migrate plan печатает все переносимые задания. Если в .settings.php или dbconn.php кеш не файловый (memcache, кластерный CPHPCacheMemcacheCluster), разбор об этом пишет: неработающий кеш заставляет шаблоны пересчитывать всё на каждом хите и занимает все процессы php-fpm. Кеш (bitrix/cache, managed_cache, stack_cache) не переносится. Push-сервер, memcached и msmtp окружения не переезжают — заметки об этом есть в разборе. Пароль веб-панели аккаунту не задаётся (mp user set bitrix --generate), unix-пароль едет хешем.

FASTPANEL. Область — user:<логин панели>; без --scope разбор перечисляет, кто есть. Раскладка совпадает с нашей (/var/www/<логин>/data/www/<домен>), едет data/www целиком; index_dir FASTPANEL (абсолютный docroot) становится относительным docroot. Бэкенд php_fpm — наш fpm, fcgi/mod_php — apache, версия PHP из handler_version. Сгенерированный панелью конфиг nginx не переносится (он весь про старый сервер), но его allow-список с deny all превращается в allow_from; о сайтах с manual_changes разбор напоминает. Пароли баз FASTPANEL хранит зашифрованными, поэтому едут хеши из SHOW CREATE USER; старый mysql_native_password при необходимости включается здесь. Хеш caching_sha2_password в mysql_native_password не переделать: если такой аккаунт приезжает к сайту на PHP ниже 7.4, задача переноса пишет, какой ALTER USER выполнить. Сертификаты: загруженные лежат в её базе текстом, Let's Encrypt — файлами в /var/www/httpd-cert; самоподписанные и просроченные остаются. Почта FASTPANEL не переносится (разбор говорит, сколько ящиков). Пароль веб-панели — unix-пароль (PAM), он приезжает хешем и работает для SFTP; в MonoPanel его задают заново.

Старые серверы Битрикса чаще всего живут на bitrix-env 7 и CentOS 7 (обе ветки давно EOL): адаптер их тоже читает — раскладка та же, а дамп MySQL 5.7 по пути очищается от NO_AUTO_CREATE_USER в sql_mode, который MySQL 8 не принимает.

Проверено на площадке 2026-09-12 (scripts/testbed/sources.sh): BitrixVM 9.0 на AlmaLinux 9 → Debian 13 (сайт с ядром 1 ГБ, база sitemanager, пути переписаны, вход в базу и unix-хеш прежние), bitrix-env 7 на CentOS 7 с MySQL 5.7 → Ubuntu 24.04 и FASTPANEL 1.11 на Debian 12 → Rocky Linux 9 (WordPress с настоящим сертификатом Let's Encrypt — сразу по HTTPS, сайт с docroot public/, алиасом и allow-списком, база с прежним хешем, два задания cron с data/bin/php).

7. Границы

  • Поток, а не «собрать в памяти»: домашние каталоги бывают в десятки гигабайт, докачка обязательна.
  • Разные версии PHP и MySQL на источнике и цели — нормальная ситуация; ловит dry-run, ветку PHP панель предлагает поставить сама.
  • Почтовые Maildir'ы переносятся как файлы; синхронизация почты «на живую» (doveadm sync, imapsync) — отдельный шаг, не в первой версии.
  • Токен переезда — отдельная область: только чтение, только экспорт, с TTL, видимый в audit обеих панелей.
  • Перенос между разными семействами ОС (Debian → EL) разрешён: пути PHP и имена сервисов разные, поэтому конфигурацию приёмник рендерит заново, а dry-run предупреждает, что свои директивы nginx и команды cron могут ссылаться на пути старой ОС.
Исходник страницы на GitHub