# Sprint 21 — stage→prod migration toolchain Цель: набор скриптов и workflow'ов чтобы первый прод-деплой был не импровизацией, а одной командой с понятным rollback'ом и автоматическими проверками. Старт: 2026-06-07 (после Sprint 20). Исполнитель: Claude Opus 4.7. ## Принципы - Все mutating-скрипты поддерживают `--dry-run` — печатают что бы сделали. - Прод НЕ трогается из этого спринта (только инструментарий). - Скрипты — идемпотентные: повторный запуск не ломает уже сделанное. - Failure-режим: оставляем старый контейнер, не каскадим ошибку. - НЕ трогать: `global.json`, prod admin.food-market.kz, POS WPF. ## Чек-лист - [x] **1. Pre-deploy check** — `deploy/check-prod-readiness.sh`: backup<60min, disk ≥5GB на `/opt` + `/var/lib/docker`, `/health/ready` Healthy, .env содержит required keys без placeholder'ов (CHANGEME/ REPLACE_ME/TODO/dev). Поддерживает `--ssh-host` для удалённой проверки. Опциональная CI-проверка через `FM_CHECK_CI=1`. - [x] **2. Blue-green deploy** — `deploy/prod-deploy.sh `: pull → green-контейнер `food-market-api-next` на :8088 → migrations (auto через `Database.Migrate()` в Program.cs) → smoke (`/health/ready` + `/api/me`) → nginx upstream switch → swap green→blue. Failure → удаляем green, оставляем blue. `--skip-web` для api-only. - [x] **3. Rollback** — `deploy/prod-rollback.sh `: `docker image inspect` + fallback `docker pull` → `docker compose up -d --force-recreate api web` с указанным tag'ом → wait /health/ready до 120с. `--skip-web` поддерживается. - [x] **4. Post-deploy smoke** — `deploy/post-deploy-smoke.sh`: 10 сценариев (signup → login → /api/me → list 5 endpoints → create product → delete → logout-via-session). Парсит JSON через `python3`, не grep/cut (после первого фейла на токене с пробелом перед `:`). Telegram-alert через `FM_TG_TOKEN+FM_TG_CHAT` при провале. **На stage прогнал 10/10 ✓** (тестовая прогонка). - [x] **5. Stage-prod schema diff** — `deploy/db-schema-diff.sh`: `pg_dump --schema-only --no-owner --no-privileges --no-comments` с обоих хостов через `ssh + docker exec`, нормализация (sed убирает SET/SELECT pg_catalog.set_config/пустые строки/комментарии), `diff -u`. Exit 0 = идентичны, 1 = разница, 2 = ошибка получения. - [x] **6. Release notes generator** — `deploy/generate-release-notes.sh `: `git log` группирует по prefix через `awk`: feat → ✨ Новые возможности, fix → 🐛 Исправления, perf → ⚡, docs → 📚, test/refactor/chore → `
`-свёрнутые. Сохраняет в `docs/release-notes/.md`. **Прогнал на HEAD~3..HEAD** — markdown сгенерирован корректно. - [x] **7. Auto-tag workflow** — `.forgejo/workflows/auto-tag.yml`: на push в main → если HEAD ещё не помечен → создаёт `v.` где N — порядковый счётчик в дне. Annotated tag (`git tag -a`), push в origin, дополнительно генерирует release-notes для будущего деплоя (через `deploy/generate-release-notes.sh`). ## Журнал ### 2026-06-07 старт Sprint 20 закрыт (7/7 ✓). Поехали по prod-toolchain. ### 2026-06-07 итог Все 7 пунктов ✓. Прод не трогали (по правилам спринта). **Тестовые прогоны на stage** (тест.admin.food-market.kz): - `post-deploy-smoke.sh`: **10/10 ✓** (signup, login, /api/me, 5×list, create+delete product, logout-via-session). Первая попытка упала на парсинге access_token (пробел перед `:` в JSON), починил через `python3 -c 'json.load(...)'`. Вторая — на отсутствующем `/connect/revocation` (OpenIddict не сконфигурирован для revoke), заменил step 10 на DELETE сессии через `/api/me/sessions/{id}` с fallback'ом на token-valid sanity-check. - `check-prod-readiness.sh`: на stage backup-dir `/opt/food-market-data/ backups` не существует (это нормально для stage'а) — FAIL, как и ожидалось. Скрипт фиксирует это с конкретной причиной. - `generate-release-notes.sh HEAD~3..HEAD`: markdown с группировкой по prefix сгенерирован, файл сохранён в `docs/release-notes/`. - `db-schema-diff.sh` и `prod-deploy.sh` / `prod-rollback.sh` запускать на dev-vm против prod невозможно без SSH-настройки — синтаксис проверен `bash -n`. Реальный прогон только когда user разрешит. ## Итог Все 7 пунктов ✓. Инструментарий для прод-деплоя готов: | Скрипт | Назначение | --dry-run | Прогон | |---|---|---|---| | `check-prod-readiness.sh` | Pre-deploy gating | ✓ | partial (на stage) | | `prod-deploy.sh` | Blue-green release | ✓ | синтаксис | | `prod-rollback.sh` | Fast rollback | ✓ | синтаксис | | `post-deploy-smoke.sh` | Smoke-suite 10 шагов | ✓ | 10/10 ✓ (stage) | | `db-schema-diff.sh` | Stage↔prod schema | ✓ | синтаксис | | `generate-release-notes.sh` | Markdown release | ✓ | ✓ (HEAD~3..HEAD) | | `.forgejo/workflows/auto-tag.yml` | Auto-tag CI | — | синтаксис (yaml) | Что нужно от пользователя для реального прода: - `/etc/nginx/conf.d/food-market-upstream.conf` на prod-vm с `upstream food_market_api { server 127.0.0.1:8080; }` (создать пустым, скрипт `prod-deploy.sh` его перепишет). - SSH-ключ от dev-vm на prod-vm + alias `prod` в `~/.ssh/config`. - `FM_TG_TOKEN` / `FM_TG_CHAT` env-vars для notify (опц.). - Решение поднять prod-БД и сделать первый backup (`food-market-backup.sh`). - Решение о dns/sertbot для admin.food-market.kz.