Skip to content

Центр обновлений

Обновление сайта клиента — штатная операция за минуты, а не проект. Транспорт — 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 — подготовка релиза (центрально)

  1. Тег версии модуля по semver + CHANGELOG.md.
  2. CI прогоняет тесты модуля на матрице совместимых версий ядра.
  3. satis build (по webhook при пуше тега) обновляет приватный репозиторий.

Фаза 1 — деплой на один сайт (атомарно)

  1. Бэкап БД (+ media-манифест) на S3, fail-fast — откат симлинка не спасёт от необратимой data-миграции, это единственная страховка данных.
  2. composer install --dry-run + composer why-not cms/core <target>поймать конфликт до применения.
  3. Свежий чекаут в releases/{ts}/, composer install (точные версии из lock) — атомарность, reproducible build.
  4. php artisan down --render="errors::503" --secret=<token>защита от конкурентных запросов; secret даёт доступ для проверки; --render — чтобы не словить ошибку, пока грузятся зависимости.
  5. php artisan migrate --force (additive-only, expand-фаза; большие таблицы — CREATE INDEX CONCURRENTLY/gh-ost) + php artisan cms:blocks:migrateобратимость; schema-drift JSONB-блоков не ловится SQL-миграциями.
  6. config:clear && config:cache && route:cache && view:cache && event:cache (именно clear перед cache) — кеш поверх стейл-файла = поймать старый .env.
  7. Атомарная смена симлинка ln -sfn releases/{ts} currentмгновенно, без полуразвёрнутого состояния.
  8. cachetool opcache:resetпри validate_timestamps=0 OPcache отдаёт старый байткод по пути current/....
  9. php artisan queue:restart (+ octane:reload / reverb:restart, если используются) — daemon-воркеры держат старый код в памяти. Затем cms:postupgrade: переиндексация поиска (Scout, если модуль включён), прогрев кеша групп настроек и page-cache — после мажора модуля индекс и кеш гарантированно протухли; сайт не должен встречать трафик холодным.
  10. php artisan up.

Фаза 2 — верификация и решение

  1. Smoke-test curl -f /health (spatie/laravel-health, 200/503) — объективный сигнал живости.
  2. Мониторинг error-rate в Sentry N минут (Deployment Gate) — канареечный гейт волны.
  3. При провале — откат всех трёх слоёв: 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.

Внутренняя база знаний студии