#!/usr/bin/env bash set -euo pipefail # ============================================================ # LiveServer-M1 — Interactive Setup Script # ============================================================ RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' CYAN='\033[0;36m' NC='\033[0m' BOLD='\033[1m' print_banner() { echo "" echo -e "${CYAN}╔══════════════════════════════════════════════╗${NC}" echo -e "${CYAN}║${NC} ${BOLD}LiveServer-M1${NC} — Setup Wizard ${CYAN}║${NC}" echo -e "${CYAN}║${NC} Образовательная видеоконференц-платформа ${CYAN}║${NC}" echo -e "${CYAN}╚══════════════════════════════════════════════╝${NC}" echo "" } log_step() { echo -e "\n${BLUE}[$(date +%H:%M:%S)]${NC} ${BOLD}$1${NC}"; } log_ok() { echo -e " ${GREEN}✓${NC} $1"; } log_warn() { echo -e " ${YELLOW}⚠${NC} $1"; } log_err() { echo -e " ${RED}✗${NC} $1"; } ask() { local prompt="$1" default="${2:-}" var_name="$3" if [[ -n "$default" ]]; then echo -en " ${CYAN}?${NC} ${prompt} ${YELLOW}[${default}]${NC}: " else echo -en " ${CYAN}?${NC} ${prompt}: " fi read -r input printf -v "$var_name" '%s' "${input:-$default}" } ask_secret() { local prompt="$1" default="${2:-}" var_name="$3" if [[ -n "$default" ]]; then echo -en " ${CYAN}?${NC} ${prompt} ${YELLOW}[${default}]${NC}: " else echo -en " ${CYAN}?${NC} ${prompt}: " fi read -rs input echo "" printf -v "$var_name" '%s' "${input:-$default}" } ask_yn() { local prompt="$1" default="${2:-y}" local hint="Y/n" [[ "$default" == "n" ]] && hint="y/N" echo -en " ${CYAN}?${NC} ${prompt} ${YELLOW}[${hint}]${NC}: " read -r input input="${input:-$default}" [[ "${input,,}" == "y" || "${input,,}" == "yes" ]] } generate_secret() { openssl rand -base64 32 2>/dev/null || head -c 32 /dev/urandom | base64 | tr -d '=/+' | head -c 32 } check_command() { if command -v "$1" &>/dev/null; then log_ok "$1 найден: $(command -v "$1")" return 0 else log_err "$1 не найден" return 1 fi } # ============================================================ # Main # ============================================================ print_banner # --- Step 1: Check dependencies --- log_step "Шаг 1/6 — Проверка зависимостей" MISSING=0 check_command docker || MISSING=1 check_command "docker compose" 2>/dev/null || check_command docker-compose || MISSING=1 check_command node || MISSING=1 check_command npm || MISSING=1 check_command npx || MISSING=1 if [[ "$MISSING" -eq 1 ]]; then log_err "Установи недостающие зависимости и запусти скрипт снова." exit 1 fi NODE_VER=$(node -v) log_ok "Node.js: $NODE_VER" # --- Step 2: Choose mode --- log_step "Шаг 2/6 — Режим запуска" echo "" echo -e " ${BOLD}1)${NC} Локальная разработка (localhost, без SSL)" echo -e " ${BOLD}2)${NC} Production (домен + Traefik + Let's Encrypt SSL)" echo "" ask "Выбери режим" "1" MODE DOMAIN="" ACME_EMAIL="" if [[ "$MODE" == "2" ]]; then echo "" ask "Домен (например live.example.com)" "" DOMAIN ask "Email для Let's Encrypt" "" ACME_EMAIL if [[ -z "$DOMAIN" || -z "$ACME_EMAIL" ]]; then log_err "Домен и email обязательны для production." exit 1 fi log_ok "Production: ${DOMAIN}" log_warn "Убедись что DNS записи указывают на этот сервер:" echo -e " ${DOMAIN} → $(curl -s ifconfig.me 2>/dev/null || echo '')" echo -e " s3.${DOMAIN} → тот же IP" echo -e " minio.${DOMAIN} → тот же IP" echo "" if ! ask_yn "DNS настроен?" "y"; then log_warn "Настрой DNS и запусти скрипт снова." exit 0 fi else log_ok "Локальная разработка" fi # --- Step 3: Configure environment --- log_step "Шаг 3/6 — Настройка окружения" echo "" echo -e " ${BOLD}PostgreSQL${NC}" ask "Пароль PostgreSQL" "postgres" PG_PASSWORD echo "" echo -e " ${BOLD}MinIO (S3 Storage)${NC}" ask "MinIO root user" "minioadmin" MINIO_USER ask_secret "MinIO root password" "minioadmin" MINIO_PASS ask "Название S3 bucket" "liveserver" S3_BUCKET echo "" echo -e " ${BOLD}LiveKit${NC}" ask "LiveKit URL (wss://...)" "" LK_URL ask "LiveKit API Key" "" LK_KEY ask_secret "LiveKit API Secret" "" LK_SECRET ask_secret "LiveKit Webhook Secret (Enter чтобы пропустить)" "" LK_WEBHOOK echo "" echo -e " ${BOLD}AI Agent (Enter чтобы пропустить, настроишь позже)${NC}" ask_secret "Deepgram API Key" "" DG_KEY ask_secret "OpenAI API Key" "" OAI_KEY echo "" echo -e " ${BOLD}Аутентификация${NC}" AUTH_SECRET=$(generate_secret) log_ok "Сгенерирован BETTER_AUTH_SECRET" if [[ "$MODE" == "1" ]]; then DEV_KEY=$(generate_secret | head -c 16) echo "" echo -e " ${BOLD}Защита локалки${NC}" ask "Ключ доступа (DEV_ACCESS_KEY)" "$DEV_KEY" DEV_ACCESS_KEY ask "Разрешённые IP (через запятую, Enter = все)" "" ALLOWED_IPS fi # --- Step 4: Generate .env --- log_step "Шаг 4/6 — Генерация .env" if [[ "$MODE" == "2" ]]; then APP_URL="https://${DOMAIN}" S3_ENDPOINT="http://minio:9000" else APP_URL="http://localhost:3000" S3_ENDPOINT="http://localhost:9000" fi cat > .env << ENVEOF # === Generated by setup.sh at $(date) === # Domain & SSL DOMAIN=${DOMAIN} ACME_EMAIL=${ACME_EMAIL} # Database DATABASE_URL=postgresql://postgres:${PG_PASSWORD}@localhost:5432/liveserver POSTGRES_PASSWORD=${PG_PASSWORD} # LiveKit LIVEKIT_URL=${LK_URL} NEXT_PUBLIC_LIVEKIT_URL=${LK_URL} LIVEKIT_API_KEY=${LK_KEY} LIVEKIT_API_SECRET=${LK_SECRET} # AI Agent DEEPGRAM_API_KEY=${DG_KEY} OPENAI_API_KEY=${OAI_KEY} # Storage (MinIO/S3) S3_ENDPOINT=${S3_ENDPOINT} S3_ACCESS_KEY=${MINIO_USER} S3_SECRET_KEY=${MINIO_PASS} S3_BUCKET=${S3_BUCKET} MINIO_ROOT_USER=${MINIO_USER} MINIO_ROOT_PASSWORD=${MINIO_PASS} # Auth BETTER_AUTH_SECRET=${AUTH_SECRET} BETTER_AUTH_URL=${APP_URL} NEXT_PUBLIC_APP_URL=${APP_URL} # Local dev protection DEV_ACCESS_KEY=${DEV_ACCESS_KEY:-} ALLOWED_IPS=${ALLOWED_IPS:-} ENVEOF log_ok ".env создан" # --- Step 5: Install & setup --- log_step "Шаг 5/6 — Установка и настройка" # npm install if [[ ! -d "node_modules" ]]; then echo -e " Установка npm зависимостей..." npm install --silent 2>&1 | tail -3 log_ok "npm install" else log_ok "node_modules уже существует" fi # Prisma generate echo -e " Генерация Prisma Client..." npx prisma generate 2>&1 | tail -1 log_ok "Prisma Client сгенерирован" # Docker compose up echo -e " Запуск Docker контейнеров..." if [[ "$MODE" == "2" ]]; then docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build 2>&1 | tail -5 else docker compose up -d postgres minio 2>&1 | tail -5 fi log_ok "Docker контейнеры запущены" # Wait for postgres echo -e " Ожидание PostgreSQL..." for i in $(seq 1 30); do if docker compose exec -T postgres pg_isready -U postgres &>/dev/null; then break fi sleep 1 done log_ok "PostgreSQL готов" # Prisma migrate echo -e " Применение миграций..." npx prisma db push --skip-generate 2>&1 | tail -3 log_ok "Схема БД синхронизирована" # Create MinIO bucket echo -e " Создание S3 bucket..." sleep 2 if command -v mc &>/dev/null; then mc alias set local http://localhost:9000 "$MINIO_USER" "$MINIO_PASS" 2>/dev/null || true mc mb "local/${S3_BUCKET}" 2>/dev/null || true log_ok "Bucket '${S3_BUCKET}' создан" else log_warn "MinIO Client (mc) не найден — создай bucket '${S3_BUCKET}' вручную через http://localhost:9001" fi # --- Step 6: Done --- log_step "Шаг 6/6 — Готово!" echo "" if [[ "$MODE" == "2" ]]; then echo -e " ${GREEN}${BOLD}Production запущен!${NC}" echo "" echo -e " Приложение: ${CYAN}https://${DOMAIN}${NC}" echo -e " MinIO Console: ${CYAN}https://minio.${DOMAIN}${NC}" echo -e " MinIO S3 API: ${CYAN}https://s3.${DOMAIN}${NC}" else echo -e " ${GREEN}${BOLD}Локальная среда готова!${NC}" echo "" echo -e " Запусти приложение: ${CYAN}npm run dev${NC}" echo -e " Приложение: ${CYAN}http://localhost:3000${NC}" echo -e " MinIO Console: ${CYAN}http://localhost:9001${NC}" echo -e " Prisma Studio: ${CYAN}npx prisma studio${NC}" if [[ -n "${DEV_ACCESS_KEY:-}" ]]; then echo "" echo -e " ${YELLOW}Для доступа из сети:${NC}" echo -e " ${CYAN}http://:3000?key=${DEV_ACCESS_KEY}${NC}" fi fi if [[ -z "$LK_URL" ]]; then echo "" log_warn "LiveKit не настроен — видеозвонки не будут работать." echo -e " Зарегистрируйся на ${CYAN}https://cloud.livekit.io${NC}" echo -e " и заполни LIVEKIT_URL, LIVEKIT_API_KEY, LIVEKIT_API_SECRET в .env" fi if [[ -z "$DG_KEY" ]]; then echo "" log_warn "Deepgram не настроен — AI транскрипция отключена." echo -e " Получи ключ: ${CYAN}https://console.deepgram.com${NC}" fi if [[ -z "$OAI_KEY" ]]; then echo "" log_warn "OpenAI не настроен — AI суммаризация отключена." echo -e " Получи ключ: ${CYAN}https://platform.openai.com/api-keys${NC}" fi echo "" echo -e "${CYAN}══════════════════════════════════════════════${NC}" echo -e " Документация: ${CYAN}README.md${NC}" echo -e " Спецификация: ${CYAN}PROMPT.md${NC}" echo -e "${CYAN}══════════════════════════════════════════════${NC}" echo ""