food-market/deploy/deploy-prod.sh
nns b49abfdbd7
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(prod): запуск admin.food-market.kz — бэкапы (hourly/daily/офсайт), restore-verify, мониторинг, deploy-prod
- prod-backup.sh (tier hourly 48ч / daily 30д + uploads), prod-backup-offsite-pull.sh (DR на dev-vm)
- prod-restore-verify.sh: restore во временную БД + сверка числа строк, дроп
- prod-monitor.sh: api/web/public/БД/диск/5xx → Telegram владельцу (+ --test)
- deploy-prod.sh: guard'ы (prod-postgres, .env chmod600, не dev-пароль, quick-login off,
  SEED_DEMO_DATA=false, issuer), бэкап ДО миграций, только --yes, НЕ в ночных
- systemd таймеры (backup-hourly/daily, monitor, offsite-pull); docs/prod-runbook.md

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 19:31:55 +05:00

87 lines
5.3 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
#
# ПРОД food-market — деплой ТОЛЬКО по явной команде (--yes). Делает БЭКАП ДО миграций.
# НЕ встраивать в ночные задачи: прод обновляется вручную.
#
# Запуск (с dev-vm):
# ~/deploy-prod.sh <api-tag> <web-tag> --yes # сборка+push+деплой
# ~/deploy-prod.sh --yes # взять теги из прод .env (не пересобирать)
#
# Guards: прод-compose указывает на food-market-prod-postgres (не shared-postgres),
# .env chmod 600, quick-login выкл, демо-данные выкл, issuer = admin.food-market.kz.
# Публичный сайт (public) НЕ трогается.
set -uo pipefail
PROD_HOST="${PROD_HOST:-nns@192.168.1.190}"
PROD_DIR="${PROD_DIR:-~/food-market/deploy}"
REGISTRY="${REGISTRY:-192.168.1.193:5001}"
REPO="${REPO:-/home/nns/food-market}"
API_TAG=""; WEB_TAG=""; YES=0; BUILD=0
for a in "$@"; do
case "$a" in
--yes) YES=1 ;;
--build) BUILD=1 ;;
-*) echo "Unknown flag: $a" >&2; exit 2 ;;
*) if [[ -z "$API_TAG" ]]; then API_TAG="$a"; elif [[ -z "$WEB_TAG" ]]; then WEB_TAG="$a"; fi ;;
esac
done
if [[ $YES -ne 1 ]]; then
echo "ОТКАЗ: прод-деплой требует явного --yes. Это боевой сервер."
echo "Usage: $0 [<api-tag> <web-tag>] --yes [--build]"
exit 2
fi
log() { echo "[$(date -Is)] [deploy-prod] $*"; }
fail() { echo "[deploy-prod] FAIL: $*" >&2; exit 1; }
# ── Guard 1: прод-compose не ссылается на чужой shared-postgres ──
REMOTE_COMPOSE="$(ssh -o StrictHostKeyChecking=no "$PROD_HOST" "cat $PROD_DIR/docker-compose.yml" 2>/dev/null)" || fail "не прочитать прод-compose"
echo "$REMOTE_COMPOSE" | grep -q 'container_name: food-market-prod-postgres' || fail "в прод-compose нет food-market-prod-postgres"
if echo "$REMOTE_COMPOSE" | grep -qE 'container_name:[[:space:]]*shared-postgres|name:[[:space:]]*food-market-postgres-data[[:space:]]*$|127\.0\.0\.1:5434:5432'; then
fail "прод-compose ссылается на shared-postgres/чужой том/порт 5434 — СТОП"
fi
# ── Guard 2: прод .env санитария ──
REMOTE_ENV="$(ssh -o StrictHostKeyChecking=no "$PROD_HOST" "cat $PROD_DIR/.env" 2>/dev/null)" || fail "не прочитать прод .env"
PERM="$(ssh -o StrictHostKeyChecking=no "$PROD_HOST" "stat -c %a $PROD_DIR/.env" 2>/dev/null)"
[[ "$PERM" == "600" ]] || fail "прод .env права $PERM (должно 600)"
echo "$REMOTE_ENV" | grep -qiE '^POSTGRES_PASSWORD=food_market_dev$' && fail "прод использует dev-пароль БД — СТОП"
echo "$REMOTE_ENV" | grep -qiE '^QUICK_LOGIN_ENABLED=true' && fail "на проде включён quick-login — СТОП"
echo "$REMOTE_ENV" | grep -qiE '^SEED_DEMO_DATA=false' || fail "на проде не выставлен SEED_DEMO_DATA=false — СТОП"
echo "$REMOTE_ENV" | grep -qiE '^OPENIDDICT_ISSUER=https://admin\.food-market\.kz' || fail "issuer не admin.food-market.kz — СТОП"
log "guards OK"
# ── (опц.) сборка + push релизных образов ──
if [[ $BUILD -eq 1 ]]; then
[[ -n "$API_TAG" && -n "$WEB_TAG" ]] || { API_TAG="$(cd "$REPO" && git rev-parse --short HEAD)"; WEB_TAG="$API_TAG"; }
log "build+push api=$API_TAG web=$WEB_TAG"
( cd "$REPO" && docker build --build-arg LOCAL_REGISTRY=$REGISTRY --build-arg GIT_SHA="$API_TAG" -f deploy/Dockerfile.api -t $REGISTRY/food-market-api:$API_TAG . ) || fail "build api"
( cd "$REPO" && docker build --build-arg LOCAL_REGISTRY=$REGISTRY -f deploy/Dockerfile.web -t $REGISTRY/food-market-web:$WEB_TAG . ) || fail "build web"
docker push $REGISTRY/food-market-api:$API_TAG || fail "push api"
docker push $REGISTRY/food-market-web:$WEB_TAG || fail "push web"
# обновить теги в прод .env
ssh -o StrictHostKeyChecking=no "$PROD_HOST" "cd $PROD_DIR && sed -i 's/^API_TAG=.*/API_TAG=$API_TAG/; s/^WEB_TAG=.*/WEB_TAG=$WEB_TAG/' .env"
fi
# ── БЭКАП ДО миграций (обязательно) ──
log "бэкап БД ДО деплоя…"
ssh -o StrictHostKeyChecking=no "$PROD_HOST" "FM_BACKUP_TIER=daily $PROD_DIR/prod-backup.sh" 2>&1 | tail -4 || fail "бэкап не прошёл — деплой остановлен"
# ── деплой: pull + up postgres+api+web (НЕ public) ──
log "pull + up (миграции применяются при старте api)…"
ssh -o StrictHostKeyChecking=no "$PROD_HOST" "cd $PROD_DIR && docker compose pull postgres api web && docker compose up -d postgres api web" 2>&1 | tail -10 || fail "compose up"
# ── ждём healthy + smoke ──
log "жду api healthy…"
OK=0
for i in $(seq 1 30); do
if ssh -o StrictHostKeyChecking=no "$PROD_HOST" "curl -fsS http://localhost:8080/health/ready 2>/dev/null | grep -q Healthy"; then OK=1; break; fi
sleep 3
done
[[ $OK -eq 1 ]] || fail "api не стал Healthy — проверь логи food-market-api"
PCODE=$(curl -s -o /dev/null -w '%{http_code}' https://admin.food-market.kz/health/ready 2>/dev/null || echo 000)
log "публичный /health/ready → HTTP $PCODE"
log "✓ прод-деплой завершён (api=$(ssh $PROD_HOST "grep ^API_TAG= $PROD_DIR/.env | cut -d= -f2"))"