food-market/deploy/prod-rollback.sh
nns 843fc4bd03
Some checks are pending
Auto-tag / Create date-tag (push) Waiting to run
CI / Backend (.NET 8) (push) Waiting to run
CI / Web (React + Vite) (push) Waiting to run
CI / POS (WPF, Windows) (push) Waiting to run
feat(s21): stage→prod migration toolchain (7 скриптов + workflow)
1. deploy/check-prod-readiness.sh — pre-deploy gating: backup<60min,
   disk≥5GB на /opt+/var/lib/docker, /health/ready=Healthy, .env
   required-keys без placeholder'ов. --ssh-host для удалённой проверки.

2. deploy/prod-deploy.sh <api-tag> <web-tag> — blue-green release:
   pull → green-контейнер на :8088 → migrations (auto) → smoke
   (/health/ready + /api/me с тест-токеном) → nginx upstream switch
   → swap → docker compose up -d с обновлённым тэгом. Failure →
   удаление green, blue остаётся. --skip-web флаг.

3. deploy/prod-rollback.sh <to-tag> — docker pull (если нужно) →
   docker compose up -d --force-recreate с указанным tag'ом → wait
   /health/ready до 120с. --dry-run + --skip-web.

4. deploy/post-deploy-smoke.sh — 10 шагов (signup → login →
   /api/me → list products/counterparties/stores/stock → create+delete
   product → logout-via-session). JSON парсится через python3
   (не grep — споткнулись на пробеле перед `:` в access_token).
   Telegram-alert через FM_TG_TOKEN/CHAT при провале. Stage-тест: 10/10 ✓.

5. deploy/db-schema-diff.sh — pg_dump --schema-only с обоих хостов
   через ssh+docker exec, нормализация (sed), diff -u. Exit:
   0=идентичны, 1=разница, 2=ошибка.

6. deploy/generate-release-notes.sh <from-tag> <to-tag> — git log
   group by prefix через awk: feat→, fix→🐛, perf→, docs→📚,
   test/refactor/chore→<details>. Сохраняет docs/release-notes/<tag>.md.

7. .forgejo/workflows/auto-tag.yml — на push в main: если HEAD не
   помечен → создаёт v<YYYYMMDD>.<N> annotated tag, push в origin,
   генерирует release-notes для будущего деплоя.

Все скрипты идемпотентные, поддерживают --dry-run, не трогают прод.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 22:31:10 +05:00

113 lines
4.1 KiB
Bash
Executable file
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
#
# Sprint 21: быстрый rollback на предыдущий tag.
#
# Алгоритм:
# 1. Проверить что image нужного tag'a есть в registry (docker pull)
# 2. Перезапустить api/web с этим tag'ом через docker compose
# (через ENV API_TAG/WEB_TAG → compose pick'ает)
# 3. Дождаться /health/ready на новом контейнере
# 4. Если health OK → выйти 0; если не OK → fail (но контейнер уже
# поднят, ручное вмешательство нужно)
#
# Миграции БД rollback скрипт НЕ откатывает: down-migrations EF Core
# поддерживает, но мы их не пишем (см. CLAUDE.md / Phase19a/b — обе
# имеют Down() для DROP'a, но это для прода опасно — данные потеряются).
# Если откат требует down-миграции — отдельный manual review.
#
# Usage:
# deploy/prod-rollback.sh <to-tag> [--dry-run] [--skip-web]
#
# Example:
# deploy/prod-rollback.sh v20260606.5
# deploy/prod-rollback.sh v20260606.5 --dry-run
set -uo pipefail
TO_TAG="${1:-}"
DRY_RUN=0
SKIP_WEB=0
while [[ $# -gt 0 ]]; do
case "$1" in
--dry-run) DRY_RUN=1; shift ;;
--skip-web) SKIP_WEB=1; shift ;;
--help|-h) grep -E '^#( |$)' "$0" | sed 's/^# \?//'; exit 0 ;;
-*) echo "Unknown: $1" >&2; exit 2 ;;
*) shift ;;
esac
done
if [[ -z "$TO_TAG" ]]; then
echo "Usage: $0 <to-tag> [--dry-run] [--skip-web]" >&2
exit 2
fi
REGISTRY="${REGISTRY:-127.0.0.1:5001}"
COMPOSE_PATH="${COMPOSE_PATH:-/home/nns/food-market-prod/deploy/docker-compose.yml}"
PROD_URL="${PROD_URL:-https://admin.food-market.kz}"
log() { echo "[$(date -Iseconds)] $*"; }
fail() { log "FAIL: $*"; exit 1; }
run() {
if [[ $DRY_RUN -eq 1 ]]; then echo "[dry-run] $*";
else echo "[exec] $*"; "$@"; fi
}
# ── 1. Validate image existence ──────────────────────────────────────
log "=== Step 1/3: validate images ==="
API_IMG="$REGISTRY/food-market-api:$TO_TAG"
WEB_IMG="$REGISTRY/food-market-web:$TO_TAG"
# Сначала пробуем docker image inspect — если уже скачан, не тянем.
if [[ $DRY_RUN -eq 0 ]]; then
if ! docker image inspect "$API_IMG" >/dev/null 2>&1; then
log "api image $API_IMG не скачан, pull'им"
docker pull "$API_IMG" || fail "api image $TO_TAG отсутствует в $REGISTRY"
else
log "api image $TO_TAG уже скачан"
fi
if [[ $SKIP_WEB -eq 0 ]]; then
if ! docker image inspect "$WEB_IMG" >/dev/null 2>&1; then
docker pull "$WEB_IMG" || fail "web image $TO_TAG отсутствует"
fi
fi
else
log "[dry-run] would pull $API_IMG (and $WEB_IMG)"
fi
# ── 2. docker compose up -d с новым tag ─────────────────────────────
log "=== Step 2/3: docker compose up -d ==="
if [[ ! -f "$COMPOSE_PATH" ]]; then
fail "compose не найден: $COMPOSE_PATH"
fi
cd "$(dirname "$COMPOSE_PATH")"
if [[ $DRY_RUN -eq 0 ]]; then
if [[ $SKIP_WEB -eq 1 ]]; then
API_TAG="$TO_TAG" docker compose up -d --force-recreate api
else
API_TAG="$TO_TAG" WEB_TAG="$TO_TAG" docker compose up -d --force-recreate api web
fi
else
log "[dry-run] would run: API_TAG=$TO_TAG WEB_TAG=$TO_TAG docker compose up -d --force-recreate api web"
fi
# ── 3. Wait /health/ready ────────────────────────────────────────────
log "=== Step 3/3: wait /health/ready ==="
if [[ $DRY_RUN -eq 0 ]]; then
for i in $(seq 1 60); do
sleep 2
if curl -fsS --max-time 5 "$PROD_URL/health/ready" 2>/dev/null | grep -q '"status":"Healthy"'; then
log "✓ Rollback complete: $PROD_URL Healthy после $((i*2))s"
exit 0
fi
done
fail "/health/ready не отвечает Healthy за 120с — ручное вмешательство"
else
log "[dry-run] would poll $PROD_URL/health/ready up to 60×2s"
fi
exit 0