Files
ForbiddenStarsApp/run.sh
T
NotBigGhostandClaude Opus 5 763745a223 Dev-лаунчер: применять миграции перед стартом
Ветка задачи может принести миграцию, а run.ps1/run.sh сразу запускали uvicorn:
приложение поднималось на старой схеме и падало 500 на первом обращении к новой
таблице. Так вышло с 0012_match_finish_draft — создание партии отвечало «no such
table: match_finish_drafts». В test/prod такого нет, там схему накатывает
entrypoint.sh контейнера.

Отдельно разобран случай «база новее ветки»: после возврата с ветки задачи alembic
не находит ревизию, которой в этой ветке ещё нет. Останавливать запуск тут не за
что — лишние таблицы старому коду не мешают, поэтому печатаем предупреждение и
идём дальше. Настоящая ошибка миграции по-прежнему останавливает запуск.

stderr alembic в run.ps1 уводится в файл: в Windows PowerShell 5.1 «2>&1» на
нативном exe заворачивает каждую строку в ErrorRecord, а решение принимается по
коду возврата. run.ps1 остался строго ASCII.

#36

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BoiJK9ux8peeyjLb8TYjFf
2026-09-09 20:00:49 +03:00

98 lines
5.8 KiB
Bash

#!/usr/bin/env bash
# ╔═══════════════════════════════════════════════════════════════════════════╗
# ║ Единый лаунчер dev / test. Что запускать — решает APP_ENV из корневого .env ║
# ╚═══════════════════════════════════════════════════════════════════════════╝
# development → uvicorn --reload (бэк) + vite (фронт), нативно (Ctrl+C останавливает оба)
# test → docker compose прод-клон (порт 8080)
# production → лаунчером НЕ запускается (прод обособлен: docker compose up -d на Pi)
#
# LOCAL_PUBLIC=vps (только dev) поднимает SSH-туннель на VPS → https://forbidden-stars.ru
# test/prod выставляют себя сами через туннель-контейнер (см. docker-compose*.yml).
#
# Запуск: ./run.sh (при необходимости: chmod +x run.sh)
set -euo pipefail
root="$(cd "$(dirname "$0")" && pwd)"
envf="$root/.env"
[ -f "$envf" ] || { echo "Нет .env в корне. Скопируйте: cp .env.example .env"; exit 1; }
# Достаём значение ключа из .env (последнее присвоение, без комментариев/пробелов).
env_val() {
grep -E "^[[:space:]]*$1[[:space:]]*=" "$envf" | tail -n1 \
| sed -E 's/^[^=]*=[[:space:]]*//; s/[[:space:]#].*$//'
}
app_env="$(env_val APP_ENV | tr 'A-Z' 'a-z')"; app_env="${app_env:-development}"
local_public="$(env_val LOCAL_PUBLIC | tr 'A-Z' 'a-z')"; local_public="${local_public:-local}"
vps_host="$(env_val VPS_TUNNEL_HOST)"
vps_user="$(env_val VPS_TUNNEL_USER)"; vps_user="${vps_user:-tunnel}"
vps_port="$(env_val VPS_TUNNEL_PORT)"; vps_port="${vps_port:-9001}"
echo "APP_ENV = $app_env LOCAL_PUBLIC = $local_public"
# Поднимает SSH reverse-tunnel в фоне: VPS:$vps_port → localhost:$1 (если LOCAL_PUBLIC=vps).
start_tunnel() {
local local_port="$1"
if [ -z "$vps_host" ]; then
echo "LOCAL_PUBLIC=vps, но VPS_TUNNEL_HOST не задан — туннель пропущен."; return
fi
echo "SSH-туннель: forbidden-stars.ru (VPS:$vps_port) → localhost:$local_port …"
ssh -N -R "${vps_port}:localhost:${local_port}" \
-o ServerAliveInterval=30 -o ServerAliveCountMax=3 -o ExitOnForwardFailure=yes \
"${vps_user}@${vps_host}" &
tunnel=$!
trap 'kill "$tunnel" 2>/dev/null || true' EXIT INT TERM
echo " Публично: https://forbidden-stars.ru"
}
case "$app_env" in
development)
py="$root/backend/.venv/bin/python"
[ -x "$py" ] || py="$root/backend/.venv/Scripts/python.exe" # Git Bash на Windows
[ -x "$py" ] || { echo "Нет venv. Сначала: cd backend && python -m venv .venv && pip install -e '.[dev]'"; exit 1; }
[ -d "$root/frontend/node_modules" ] || { echo "Нет node_modules. Сначала: cd frontend && npm install"; exit 1; }
# Сначала схема: ветка могла принести миграцию, и без неё бэк поднимется на
# старой схеме и упадёт 500 на первом же обращении к новой таблице.
echo "Применяю миграции (alembic upgrade head)…"
mig_log="$( cd "$root/backend" && "$py" -m alembic upgrade head 2>&1 )" && mig_code=0 || mig_code=$?
if [ "$mig_code" -ne 0 ]; then
echo "$mig_log"
case "$mig_log" in
*"locate revision"*)
# В базе миграция, которой нет в этой ветке (вернулись с ветки задачи).
# Лишние таблицы старому коду не мешают — продолжаем запуск.
echo "База новее ветки — миграции пропущены." ;;
*)
echo "Миграции не применились — не запускаю. Исправьте ошибку выше и повторите."
exit 1 ;;
esac
fi
echo "Запуск бэка (uvicorn --reload) и фронта (vite)… Ctrl+C — остановить всё."
( cd "$root/backend" && exec "$py" -m uvicorn app.main:app --reload ) &
back=$!
trap 'kill "$back" 2>/dev/null || true' EXIT INT TERM
echo " Бэк: http://127.0.0.1:8000 (Swagger: /api/docs)"
echo " Фронт: http://127.0.0.1:5173"
[ "$local_public" = "vps" ] && start_tunnel 5173
( cd "$root/frontend" && npm run dev )
;;
test)
docker info >/dev/null 2>&1 || { echo "Docker-демон недоступен — запусти Docker и повтори ./run.sh"; exit 1; }
echo "Сборка и запуск прод-клона в Docker (app + туннель в контейнере)…"
docker compose -f "$root/docker-compose.test.yml" up --build -d
echo " Публично: https://forbidden-stars.ru (Swagger: /api/docs)"
echo " (портов на хост нет — только через домен; туннель живёт внутри compose)"
echo " Логи: docker compose -f docker-compose.test.yml logs -f"
echo " Стоп: docker compose -f docker-compose.test.yml down -v"
;;
production)
echo "production лаунчером не запускается — прод обособлен."
echo "Деплой на Pi (из ветки main): docker compose up -d --build"
exit 1
;;
*)
echo "Неизвестный APP_ENV='$app_env'. Допустимо: development | test | production."
exit 1
;;
esac