deploy: выкат стека на прод 23.06 + фикс redeploy.sh composer-прав + снимок ПИЛОТ
Accessibility (Pa11y live) / a11y (push) Has been cancelled

Выкачен недеплоенный стек b11e1d97 балансовый блок плюс F/J плюс source-edit-lock
плюс ЮKassa спит флаг off. Миграция add_balance_transaction_id применена вручную
под postgres таблица crm_migrator-owned. Деньги клиента целы tenant 2
1836400 руб 1013 сделок эталон до==после. Квирк-107 ок очередь active HTTP 200.

Фикс redeploy.sh композер install от root COMPOSER_ALLOW_SUPERUSER плюс chown
vendor www-data баг положил прод в 500 на окно наката. README дополнен граблями
composer-прав opcache-рестарт и crm_migrator-миграций. Снимок ПИЛОТ обновлён.
cspell-words пополнен проектным жаргоном.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Дмитрий
2026-06-23 19:50:40 +03:00
parent b11e1d971c
commit b808a24f78
4 changed files with 34 additions and 1 deletions
+7
View File
@@ -2251,3 +2251,10 @@ lpimp
ретеншеном
роуте
хэш
синкать
синкнут
недеплоенный
дедлока
мерджа
застейджено
хардненинг
+16
View File
@@ -11,10 +11,12 @@ Server-side половина деплоя. На боевом лежит в `/var
**Workflow деплоя:**
1. **Локально** — собрать архив кода + Vite-сборку:
```bash
git archive HEAD app/ db/ | gzip > /tmp/deploy-code.tgz
tar czf /tmp/deploy-build.tgz -C app/public build/
```
2. **scp** обоих архивов на сервер.
3. **На сервере** — распаковать в `/var/www/liderra/app/`, выставить владельца
`www-data:www-data`, запустить `bash /var/www/liderra/redeploy.sh`.
@@ -28,6 +30,20 @@ Server-side половина деплоя. На боевом лежит в `/var
с владельцем `ubuntu` → php-fpm (под `www-data`) не мог прочитать → 503 на всём
портале. Инцидент 24.05.2026 03:46 UTC, портал лежал 18 минут.
**Грабли composer-прав (фикс встроен, инцидент 23.06.2026):** `vendor/` принадлежит
`www-data`, а `redeploy.sh` бежит от `ubuntu`. Голый `composer install` падал
`autoload_classmap.php: Permission denied`, и из-за `set -e` скрипт рвался ДО `optimize`
и рестарта → новый код на диске, classmap новых классов НЕ пересобран → **прод 500**.
Фикс: `sudo env COMPOSER_ALLOW_SUPERUSER=1 composer install …` + `sudo chown -R www-data:www-data vendor`.
**Также:** при ручном восстановлении кэши надо пересобирать ДО рестарта php-fpm —
opcache держит старые до перезапуска (затяжной 500, пока fpm не рестартнут ПОСЛЕ кэшей).
**Грабли миграций crm_migrator (инцидент 19.06 + 23.06):** таблицы, принадлежащие
`crm_migrator` (напр. `saas_transactions`), штатной `.env`-ролью НЕ альтерятся
(`must be owner`). Применять миграцию ВРУЧНУЮ под `sudo -u postgres psql` ДО запуска
`redeploy.sh` + `INSERT INTO migrations (migration, batch) VALUES ('<имя_без_php>', <batch>)`.
Тогда `php artisan migrate --force` в скрипте = no-op. Иначе `set -e` порвёт деплой.
**Расхождение с боевым:** если правится этот файл — синкать на боевой
(scp + проверка хеша). Боевой = source of truth для исполнения, репо =
source of truth для рецепта.
+9 -1
View File
@@ -4,7 +4,15 @@
# (app/public/build) через scp. Затем на сервере: bash /var/www/liderra/redeploy.sh
set -euo pipefail
cd /var/www/liderra/app
composer install --optimize-autoloader --no-interaction --no-scripts --ignore-platform-req=ext-redis
# vendor/ принадлежит www-data, а скрипт бежит от ubuntu → composer install от
# ubuntu падает "autoload_classmap.php: Permission denied" и (из-за set -e) рвёт
# деплой ДО кэшей/рестарта → прод 500. Инцидент 23.06.2026. Поэтому composer
# запускаем от root (COMPOSER_ALLOW_SUPERUSER) и возвращаем владельца vendor www-data.
sudo env COMPOSER_ALLOW_SUPERUSER=1 composer install --optimize-autoloader --no-interaction --no-scripts --ignore-platform-req=ext-redis
sudo chown -R www-data:www-data vendor
# NB: миграции crm_migrator-owned таблиц (напр. saas_transactions) штатной .env-ролью
# НЕ применяются ("must be owner") — применять ВРУЧНУЮ под `sudo -u postgres` ДО запуска
# скрипта + INSERT в migrations, тогда строка ниже = no-op. Иначе set -e порвёт деплой.
php artisan migrate --force
sudo -u www-data php artisan optimize
chmod -R a+rX public/build
+2
View File
@@ -8,6 +8,8 @@
- Волатильную часть (доступ, версии, что развёрнуто) перед рискованными действиями **перепроверять реальной командой по SSH**, не доверять снимку вслепую.
- Обновляется по команде заказчика **«обнови пилот»**.
**Снимок снят:** 23.06.2026 (~16:45 МСК) — **🚀 ВЫКАЧЕН НА ПРОД недеплоенный стек (gitea/main `b11e1d97`).** Что теперь живёт: **балансовый блок** N/G/E/H/M/O + **F/J** (единый расчёт замков = полный лимит, пополнение/пересчёт 18:00 снимают оба замка «всё-или-ничего», фикс межсессионного дедлока `balance_freeze_log`) + **source-edit-lock UX** + **онлайн-ЮKassa** (СПИТ — флаг `billing_yookassa_enabled` отсутствует в `system_settings` → код по умолчанию OFF). **Миграция** `2026_06_22_170000_add_balance_transaction_id_to_saas_transactions` применена ВРУЧНУЮ под `sudo -u postgres` (таблица `crm_migrator`-owned, штатная роль не альтерит) + запись в `migrations` batch 21; `migrate:status` чисто, schema-канон **v8.52** (RLS-политик 44 == прод). **💰 Деньги клиента ЦЕЛЫ:** tenant 2 = **1 836 400.00 ₽ / 1013 сделок** (эталон до==после), 3 тенанта. Квирк-107 OK (`config.php` владелец `www-data`), очередь active, HTTP 200 (главная+`/login`), свежих ошибок нет. **⚠️ Был провал ~500 в окно наката:** `redeploy.sh` упал на `composer install` (`vendor/` принадлежит www-data, скрипт от ubuntu → Permission denied; `set -e` оборвал до кэшей/рестарта); затяжной 500 — opcache держал старые кэши, вылечил пересборкой classmap от root + кэши + **повторный** рестарт php-fpm. Клиенты денег не потеряли (БД цела); лиды и так не шли (проекты выключены намеренно владельцем). **`redeploy.sh` ИСПРАВЛЕН** (composer от root + chown vendor; синкнут на прод) — см. `deploy/README.md`. **Бэкап отката на проде:** `/home/ubuntu/pre-deploy-20260623-162331.dump` (БД 13M) + `pre-deploy-env-*.bak` + `pre-deploy-code-*.tar.gz`. Сверка перед накатом (побайтово прод==git, 0 правок мимо git, ничего не потеряно): `docs/superpowers/findings/2026-06-23-balance-block/2026-06-23-PRE-DEPLOY-drift-and-loss-audit.md`. **🔴 L (по-прежнему ОТКРЫТО):** омега tenant 25/проект 188 под блоком (баланс 300₽) — включать только по решению владельца. Кодовая фраза стены — «роутер-наставник».
**Снимок снят:** 23.06.2026 (~11:50 МСК) — **прод НЕ менялся** (деплоя в эту сессию НЕ было; боевой код тот же). **Закрыт балансовый блок проектов (`preflight_blocked_at`) — 6 находок, по TDD + визуал, на gitea/main `2deaf207..de106a20` (НЕ на проде):** **N** массовое «изменить лимит» теперь уважает баланс (skip `balance_insufficient`) + видимый тост (фикс: snackbar жил в размонтируемом `BulkActionsBar``v-if``v-show`); **G** правка/toggle/ручная синхронизация не уводят заблокированный проект к поставщику; **E** авто-снятие блока при пополнении по политике «всё-или-ничего» (`ProjectBlockReleaseService` в единой точке `BillingTopupService::topup` — покрывает и ручное, и онлайн через webhook) — **подтверждено живым demo: реальный заказ создан и убран в кабинете поставщика crm.bp-gr.ru**; **H** метка «Приостановлен — не хватает баланса» на карточке (`ProjectResource.balance_blocked`); **M** правка site/call больше не падает молчаливым 422 (фронт не шлёт `sms_senders` для не-sms); **O** боковая панель показывает блок при 409 (раньше молчала). Локально зелено (N 32/32, billing 114/114, фронт-спеки). Push в gitea fast-forward (gitleaks/lychee чисто). **🔴 L (ОТКРЫТО, требует решения+GO):** разовая сверка реальной омеги — **tenant 25, проект 188** на проде стоит `preflight_blocked_at=2026-06-22 15:05:34`, но УЖЕ заказан у поставщика (`supplier_b1/b2/b3=494/495/496`) — рассинхрон класса, который фиксы предотвращают вперёд; существующую омегу нужно разово привести в согласие после выката. **Долг (не из этой задачи):** lefthook `larastan` краснит локально (~80) — **локальный env-косяк** (ide-helper на L13/Windows генерит битый `_ide_helper_models.php`; на CI/Linux зелено), коммиты делались с точечным `LEFTHOOK_EXCLUDE=larastan,deptrac`; `deptrac` — реальное архнарушение `ProjectResource→SupplierSnapshotGuard` (из source-edit-lock мерджа). Кодовая фраза стены — «роутер-наставник».
**Снимок снят:** 23.06.2026 (~04:00 МСК) — **прод НЕ менялся** (боевой код тот же, что после выката 22.06; в эту сессию деплоя НЕ было). **Подтверждено вживую на проде (read-only) при триаже авто-security-ревью:** админ-зона `/api/admin/*` закрыта двумя слоями — nginx HTTP Basic Auth (`location ^~ /api/admin`, `/etc/nginx/.htpasswd-admin`) + M-1 app-fail-closed гейт (`EnsureSaasAdmin` по `REMOTE_USER`∈allowlist; прод `.env`: `ADMIN_GATE_ENFORCED=true`, `ADMIN_ALLOWED_USERS=admin`). → автонаходка «`/api/admin/payment-gateways` без auth» = **ложная**: эндпоинт записи ключей ЮKassa реально закрыт. **Застейджено на gitea/main `3b142f93` (НЕ на проде):** (1) блокировка смены источника проекта в UI (drawer: поля источника disabled + подсказка с датой разблокировки; баннер «первые лиды с DD» в новом проекте) — спека/план `2026-06-22-project-source-edit-lock-ux`; (2) онлайн-пополнение ЮKassa (параллельная сессия, за флагом `billing_yookassa_enabled`=**off**); (3) хардненинг webhook ЮKassa (строгий `paymentId` + валюта RUB + IP-allowlist `YOOKASSA_WEBHOOK_IPS`, fail-open при пустом). Локально зелено: бэкенд+billing 118/118, фронт-спеки. **Перед включением `billing_yookassa_enabled` на проде:** заполнить `YOOKASSA_WEBHOOK_IPS` опубликованными подсетями ЮKassa + апгрейд до настоящего `auth:saas-admin` (Б-1). gitea-push был fast-forward (17 коммитов), GitHub-`origin` мёртв (suspended) — бэкап только gitea. Кодовая фраза стены — «роутер-наставник».