Files
LiveServer-M1/setup.sh
joylessorchid 3846e3e00d feat: LiveServer-M1 v1 — educational video conferencing platform
Full MVP implementation:
- Next.js 16 + React 19 + TypeScript + Tailwind CSS v4
- LiveKit integration (rooms, tokens, webhooks, moderation)
- better-auth (email/password, sessions, roles)
- Prisma 7 + PostgreSQL (9 models, 3 enums)
- 15 API routes (auth, rooms, lobby, chat, files, moderation, hand-raise)
- 7 pages (landing, auth, dashboard, join, video room)
- SSE-based waiting room with host approval flow
- Security: PIN rate limiting, session fingerprint bans, chat/files auth
- Python AI Agent (Deepgram STT + OpenAI summarization)
- Docker Compose (local + production with Traefik + Let's Encrypt)
- Interactive setup script (setup.sh)
- Dev protection middleware (DEV_ACCESS_KEY, ALLOWED_IPS)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-22 13:57:53 +03:00

329 lines
10 KiB
Bash

#!/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 '<server-ip>')"
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://<ip>: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 ""