РазделПеренос между панелями
Перенос между панелями
Проектный документ. Реализованы прямой перенос между двумя 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. Поэтому импорт — не одно действие, а четыре.
- Разбор (dry-run). Цель отвечает отчётом до того, как что-то создаст: что приедет и сколько весит, что конфликтует (логин занят, домен уже обслуживается, база с таким именем есть), чего не хватает (ветка PHP не установлена, MySQL нет, мало места), что не переносится совсем.
- Копия. Задача создаёт аккаунт, кладёт файлы, поднимает базы, применяет состояние. Сайт поднимается сразу, но DNS на него ещё не переключён — панель показывает готовую команду
curl --resolveи строку дляhosts, чтобы посмотреть его глазами. - Досинхронизация.
mp migrate resyncпроходит только по изменившемуся: файлы по времени и размеру, свежие дампы. Делается прямо перед переключением DNS и занимает минуты вместо часов — сайт на старом сервере всё это время работает. - Переключение. Человек меняет 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 работают одинаково для любого источника.
| Адаптер | Откуда берёт | Состояние |
|---|---|---|
monopanel | API исходной панели по токену переезда | работает |
bitrixvm | «1С-Битрикс: Веб-окружение» (bitrix-env 7–9): nginx-конфиги /etc/nginx/bx/site_enabled, bitrix/.settings.php и dbconn.php каждого сайта, /root/.my.cnf | работает |
fastpanel | FASTPANEL 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 могут ссылаться на пути старой ОС.