Тема
Центр обновлений
Обновление сайта клиента — штатная операция за минуты, а не проект. Транспорт — composer, мозг — процедура, глаза — телеметрия. Три слоя отката всегда синхронны: файлы +
composer.lock+ схема БД.
Транспорт: приватный Satis
- Satis — статичный composer-репозиторий (
composer/satis): генерирует статику, которую раздаёт любой nginx/S3 без нагрузки на PHP — главный плюс для большого парка. - Сборка:
php bin/satis build satis.json web/. Точечно один пакет — добавить его имя аргументом. Satis сам не следит за коммитами — сборка по cron/webhook. - Доступ — токен в
auth.jsonклиентского сервера; HTTPS обязателен. - Токен на каждый сайт — ключ к отзыву доступа при неоплате.
- Composer сам решает версии, конфликты, совместимость (semver-ограничения модулей);
composer.lock— готовый механизм отката. - Не переизобретаем протокол обновлений — composer делает это лучше (урок Winter/Botble).
Для лицензий/премиум-модулей — jeffersongoncalves/laravel-satis (токен видит только оплаченные модули, multi-tenancy, GitHub webhooks) или связка Satis + nginx auth_request
- Laravel-бэкенд лицензий (кейс Spatie).
Совместимость модуля с ядром
- Единая формула совместимости: код модуля зависит от пакета-контрактов —
"cms/core-contracts": "^2.0"вrequire(composer не поднимет контракты до 3.0 без осознанной смены constraint). Совместимость с реализацией ядра декларируетсяminimum-core-versionв манифесте (Statamic-подход, расширенный) — проверяется preflight'ом до установки, даёт человекочитаемую ошибку в UI.requireнаcms/coreмодуль не объявляет — иначе теряется смысл контрактного слоя. - Диагностика конфликтов:
composer why-not cms/core 3.0,composer why cms/core,composer show -t.
Что считать breaking change (мажор): удаление/переименование публичного метода/события; изменение сигнатуры хука; изменение структуры данных, потребляемых модулями (структура таблицы, формат объекта в событии); изменение контракта интерфейса. Deprecation-политика: минимум 1 minor с предупреждением до удаления в следующем major (модель Symfony).
Модель версий парка (из Winter)
Две таблицы — доменная модель, заимствованная у Winter CMS:
| Таблица | Что хранит |
|---|---|
cms_module_versions | текущая версия каждого модуля на этом сайте |
cms_module_history | журнал применённых скриптов — для точного отката «сайт X → версия N модуля Y» |
Плюс декларативный changelog модуля (версия → миграции + описание + флаг important), нагляднее магических имён Laravel-миграций.
UI: страница «Обновления» в Filament
- Пингует Satis: установлено vs доступно, changelog по каждому пакету.
- Кнопка «Обновить» ставит job — обновление выполняет воркер, не HTTP-запрос.
- Отдельный индикатор «доступно обновление безопасности» (severity в changelog-манифесте).
Почему job, а не HTTP: composer install и migrate идут минуты, а nginx→php-fpm обрывает запрос по таймауту (30–60 сек). Обрыв посреди миграции = полу-завершённое состояние БД. Тяжёлый пайплайн — только через CLI/worker.
Процедура обновления (полный пайплайн)
Одна артизан-команда (cms:upgrade), её же дёргает job из админки. Каждый шаг — с обоснованием (половина оплачена инцидентами universal или чужих ядер):
Фаза 0 — подготовка релиза (центрально)
- Тег версии модуля по semver +
CHANGELOG.md. - CI прогоняет тесты модуля на матрице совместимых версий ядра.
satis build(по webhook при пуше тега) обновляет приватный репозиторий.
Фаза 1 — деплой на один сайт (атомарно)
- Бэкап БД (+ media-манифест) на S3, fail-fast — откат симлинка не спасёт от необратимой data-миграции, это единственная страховка данных.
composer install --dry-run+composer why-not cms/core <target>— поймать конфликт до применения.- Свежий чекаут в
releases/{ts}/,composer install(точные версии из lock) — атомарность, reproducible build. php artisan down --render="errors::503" --secret=<token>— защита от конкурентных запросов; secret даёт доступ для проверки;--render— чтобы не словить ошибку, пока грузятся зависимости.php artisan migrate --force(additive-only, expand-фаза; большие таблицы —CREATE INDEX CONCURRENTLY/gh-ost) +php artisan cms:blocks:migrate— обратимость; schema-drift JSONB-блоков не ловится SQL-миграциями.config:clear && config:cache && route:cache && view:cache && event:cache(именно clear перед cache) — кеш поверх стейл-файла = поймать старый.env.- Атомарная смена симлинка
ln -sfn releases/{ts} current— мгновенно, без полуразвёрнутого состояния. cachetool opcache:reset— приvalidate_timestamps=0OPcache отдаёт старый байткод по путиcurrent/....php artisan queue:restart(+octane:reload/reverb:restart, если используются) — daemon-воркеры держат старый код в памяти. Затемcms:postupgrade: переиндексация поиска (Scout, если модуль включён), прогрев кеша групп настроек и page-cache — после мажора модуля индекс и кеш гарантированно протухли; сайт не должен встречать трафик холодным.php artisan up.
Фаза 2 — верификация и решение
- Smoke-test
curl -f /health(spatie/laravel-health, 200/503) — объективный сигнал живости. - Мониторинг error-rate в Sentry N минут (Deployment Gate) — канареечный гейт волны.
- При провале — откат всех трёх слоёв:
dep rollback(Deployer: симлинк + пометкаBAD_RELEASE);git checkout HEAD^ -- composer.lock && composer install;php artisan migrate:rollbackИЛИ восстановление БД из бэкапа шага 4;cachetool opcache:reset+php artisan queue:restart(+octane:reload). Откат одного слоя без остальных = рассинхрон схемы и кода.
Каналы и канареечные волны
- Каналы:
stable/beta. Клиенты — только stable; beta — внутренние сайты студии. - Волны: обновление парка не «всем сразу», а ступенями по проценту сайтов: сайт студии → 1–2 лояльных клиента → 25% → 50% → 100%. Флит-дашборд ведёт волну и останавливает её при провале health-check у канарейки. Гейт — по error-rate (Sentry).
Телеметрия и флит-дашборд
Клиентские сайты раз в сутки пингуют домой (подписанный POST): версии ядра/модулей/PHP, канал, дата последнего бэкапа, статус health-check, hash composer.lock.
Флит-дашборд — отдельное приложение на инфре студии:
- карта парка: кто на чём сидит, кому доступны обновления;
- статус волн обновлений, история, провалы;
- контроль бэкапов и мониторинг доступности (внешними точками — урок VPN-петли из proxy);
- операционная база подписки на поддержку: отчёт клиенту генерируется отсюда.
Без телеметрии через год невозможно вспомнить, где какая версия, — а значит, невозможно и безопасно обновлять.
Ключевые инварианты
- Три слоя отката синхронны: симлинк релиза +
composer.lock+ схема БД. - Миграции additive-only + expand-contract — единственный способ безопасного отката без потери данных (старый и новый код работают с БД одновременно).
- Бэкап БД до миграции обязателен —
dep rollbackиmigrate:rollbackне спасают от необратимых data-миграций. - Обновление через CLI/worker, не HTTP — из-за таймаутов php-fpm и блокировок БД.
- Self-updater пакеты не использовать — скачивают zip поверх ФС, конфликтуют с composer + CI (рассинхрон
composer.lock). Уместны только для коробочных копий без CI.
Безопасность обновлений
- Только HTTPS + токены per-сервер (отзыв токена = отключение клиента от канала).
composer audit— шаг процедуры; уязвимость в зависимости видна до обновления.- Целостность — hash-проверка через
composer.lock; командаcms:verifyсверяет хеши файлов ядра с манифестом (обнаруживает ручные правкиvendor/cms/core). - Форс-применение security-патчей ядра независимо от feature-flags (как форс minor WP).
- Телеметрия не содержит данных клиента — только версии и статусы. Пункт о телеметрии — в договоре; при расторжении подписки (Q10) телеметрия отключается вместе с токеном Satis.