#!/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; } 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