diff --git a/docs/RUNBOOK.md b/docs/RUNBOOK.md new file mode 100644 index 0000000..895f9be --- /dev/null +++ b/docs/RUNBOOK.md @@ -0,0 +1,152 @@ +# Раннбук: автосинк и деплой кабинета + +Как устроено скрытие блока «Дополнительные опции» на странице Подписки и вся +автоматика вокруг него: приватный форк, ежедневный синк с апстримом, сборка +статики и выкладка на VPS. + +Репозиторий: `chillpadclub/bedolaga-cabinet` (приватный форк +`BEDOLAGA-DEV/bedolaga-cabinet`). + +## Суть задачи + +На странице `Подписка` нужно было скрыть блок «Дополнительные опции» (докупка +устройств, трафика, управление серверами), не потеряв возможность и дальше +получать обновления апстрима — проект активно развивается, жить на статичном +форке было бы риском самим по себе. + +Решение: приватный форк со своим патчем поверх + GitHub Action, который каждый +день сам подтягивает апстрим, накатывает патч и собирает готовую статику для +VPS. + +## Патч: как спрятан блок + +Файл — `src/pages/Subscription.tsx`. Условие рендера обёрнуто в `false &&`, но +не правкой существующей строки, а **обёрткой снаружи** — так, чтобы ни одна +оригинальная строка апстрима не менялась, только добавлены две строки до и две +после: + +```tsx +{/* Additional Options — disabled on purpose */} +{false && ( + <> +{subscription && // ← строка апстрима не тронута + (subscription.is_active || subscription.is_limited) && + ... +
...
+ )} + +)} +``` + +Благодаря этому будущий `git merge` с апстримом конфликтует, только если +апстрим правит саму первую или последнюю строку блока — а не любую строку +внутри (докупка устройств, трафика и т.д.), которую вы всё равно не +используете. + +Компоненты `DeviceTopupSheet`, `DeviceReductionSheet`, `TrafficTopupSheet`, +`ServerManagementSheet` остаются в коде и в сборке — просто не рендерятся. Это +осознанный выбор в пользу простоты патча, а не баг. + +## Как работает автоматика + +Один workflow — `.github/workflows/sync-and-build.yml`. Запускается по +расписанию (ежедневно, 06:00 UTC) и вручную кнопкой `Run workflow`. + +1. **Merge апстрима** — `git fetch` + `merge upstream/main` поверх форка. +2. **Чистка CI** — удаляет `dependabot.yml` и чужие workflow, если merge их + принёс. +3. **Push в форк** — только если merge реально что-то изменил. +4. **Сборка** — `npm ci` + `npm run build:docker` (те же build-args, что и в + официальном образе). +5. **Релиз** — `tar.gz` статики → GitHub Release с тегом `vX.Y.Z`. +6. **Уведомление** — сообщение в Telegram: готово или упало. + +Если апстрим правит ровно ту же строку, что и патч, — шаг merge падает +**намеренно**. Чужой код не должен домёрживаться в обход человека. Приходит +сообщение в Telegram, конфликт разрешается руками локально, затем push — синк +продолжается. + +Сборка и релиз происходят, только если merge реально что-то поменял (или при +ручном запуске) — вхолостую по расписанию ничего не собирается и не +публикуется. + +## Файлы + +| Путь | Назначение | +|---|---| +| `.github/workflows/sync-and-build.yml` | Весь пайплайн: синк, чистка, сборка, релиз, уведомления | +| `scripts/deploy-cabinet.sh` | Деплой на VPS: скачивает релиз, бэкапит текущую статику, раскатывает | +| `src/pages/Subscription.tsx` | Патч — скрытый блок «Дополнительные опции» | +| `docs/RUNBOOK.md` | Этот файл | + +Удалено из форка сознательно (было в апстриме, плодило шум): + +| Файл | Почему убран | +|---|---| +| `.github/dependabot.yml` | Открывал десятки PR на бамп версий — оставили только Dependabot alerts (без авто-PR) | +| `ci.yml`, `lint.yml` | Тесты/линт апстрима — код уже проверен в апстриме до мёржа | +| `codeql.yml`, `security-audit.yml` | Security-сканы апстрима, дублируют Dependabot alerts | +| `docker.yml` | Пуш официального образа в GHCR — не нужен, мы публикуем статику | +| `release-please.yml`, `release.yml` | Автобамп версии апстрима — версию читаем из `package.json` | + +## Секреты и токены + +| Что | Где хранится | Права | +|---|---|---| +| `TG_BOT_TOKEN` | GitHub → Settings → Secrets and variables → Actions | Отправка сообщений ботом | +| `TG_CHAT_ID` | Там же | — | +| Fine-grained PAT | VPS: `~/.cabinet-deploy-token`, права файла `600` | Только `Contents: Read-only` на этот репозиторий | + +Токен для VPS специально ограничен одним репозиторием и правом только на +чтение — даже если файл утечёт, им нельзя ничего запушить или изменить. + +## Как выкатить на прод + +На VPS: + +```bash +./deploy-cabinet.sh # последний релиз +./deploy-cabinet.sh v1.63.0 # откат/установка конкретной версии +``` + +Скрипт сам: +1. скачивает `cabinet-dist.tar.gz` через `gh release download`; +2. бэкапит текущую статику в `cabinet-dist.bak`; +3. раскатывает новую через `rsync -a --delete` (чистит устаревшие + хэшированные JS-чанки, не трогает `50x.html`). + +Бэкап один, перезаписывается при каждом запуске. Откатиться на конкретную +версию надёжнее через `./deploy-cabinet.sh vX.Y.Z` (GitHub хранит все +релизы), а не полагаться только на `.bak`. + +## Если что-то пошло не так + +**Пришло Telegram-сообщение «⚠️ упал»** — почти всегда конфликт merge с +апстримом. Локально: +```bash +git fetch upstream main && git merge upstream/main +# разрешить конфликт (скорее всего в месте патча Subscription.tsx) +git add -A && git commit && git push origin main +``` +Следующий прогон workflow снова пойдёт сам. + +**Деплой на VPS выкатил битую сборку**: +```bash +rsync -a --delete cabinet-dist.bak/ cabinet-dist/ # мгновенный откат +# либо +./deploy-cabinet.sh vX.Y.Z # конкретный рабочий релиз +``` + +**Security-алерт в GitHub про уязвимость** — это Dependabot alerts (без +авто-PR). Апстрим обычно фиксит сам, фикс прилетит следующим синком. Если +критично и срочно — можно поправить зависимость руками отдельным коммитом. + +## Что стоит проверять периодически + +- **Раз в 1–2 месяца — вкладка Actions.** GitHub отключает scheduled-триггер + после ~60 дней без активности в репозитории. Один ручной `Run workflow` + включает обратно. +- **Бэк и фронт бота обновлять вместе.** Синк фронта и апдейт бэкенда бота — + по-прежнему ручной процесс, специально не автоматизировали дальше деплоя. +- **CHANGELOG апстрима перед выкаткой** — на случай breaking changes в API + между бэкендом и фронтом.