Files
LiveServer-M1/setup.sh
joylessorchid 7f9c521fed fix: remove "type" field from package.json + improve setup.sh
- Remove "type": "commonjs" from package.json — fixes Turbopack
  ESM/CommonJS conflict causing white screen in dev mode
- setup.sh: auto-generate passwords (PostgreSQL, MinIO, auth secret)
- setup.sh: auto-install Docker/Node.js if missing (apt/nvm/brew)
- setup.sh: backup existing .env before overwrite
- setup.sh: create MinIO bucket via curl if mc not installed
- setup.sh: add timeout errors for service readiness checks
- setup.sh: include pgbouncer in local dev docker compose up

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 02:21:40 +03:00

492 lines
16 KiB
Bash
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
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
}
generate_password() {
# 20 символов: буквы + цифры (безопасно для URL и CLI)
openssl rand -base64 24 2>/dev/null | tr -d '=/+' | head -c 20 || \
head -c 24 /dev/urandom | base64 | tr -d '=/+' | head -c 20
}
check_command() {
if command -v "$1" &>/dev/null; then
log_ok "$1 найден: $(command -v "$1")"
return 0
else
log_err "$1 не найден"
return 1
fi
}
install_if_missing() {
local cmd="$1" install_hint="$2"
if command -v "$cmd" &>/dev/null; then
log_ok "$cmd найден: $(command -v "$cmd")"
return 0
fi
log_warn "$cmd не найден"
# Попытка автоустановки через системные менеджеры
if [[ "$cmd" == "node" || "$cmd" == "npm" || "$cmd" == "npx" ]]; then
if command -v nvm &>/dev/null; then
echo -e " Установка Node.js через nvm..."
nvm install --lts 2>&1 | tail -3
if command -v "$cmd" &>/dev/null; then
log_ok "$cmd установлен через nvm"
return 0
fi
fi
fi
# Автоустановка через apt/yum/brew если доступно
case "$cmd" in
docker)
if command -v apt-get &>/dev/null; then
if ask_yn "Установить Docker автоматически (apt)?" "y"; then
echo -e " Установка Docker..."
curl -fsSL https://get.docker.com | sh 2>&1 | tail -5
sudo usermod -aG docker "$USER" 2>/dev/null || true
if command -v docker &>/dev/null; then
log_ok "Docker установлен"
return 0
fi
fi
fi
;;
node|npm|npx)
if command -v apt-get &>/dev/null; then
if ask_yn "Установить Node.js 22 автоматически (apt)?" "y"; then
echo -e " Установка Node.js..."
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - 2>&1 | tail -3
sudo apt-get install -y nodejs 2>&1 | tail -3
if command -v "$cmd" &>/dev/null; then
log_ok "$cmd установлен"
return 0
fi
fi
elif command -v brew &>/dev/null; then
if ask_yn "Установить Node.js через Homebrew?" "y"; then
brew install node 2>&1 | tail -3
if command -v "$cmd" &>/dev/null; then
log_ok "$cmd установлен"
return 0
fi
fi
fi
;;
esac
log_err "$cmd не удалось установить автоматически"
echo -e " ${YELLOW}Установи вручную:${NC} $install_hint"
return 1
}
# ============================================================
# Main
# ============================================================
print_banner
# --- Step 1: Check & install dependencies ---
log_step "Шаг 1/6 — Проверка и установка зависимостей"
MISSING=0
install_if_missing docker "https://docs.docker.com/get-docker/" || MISSING=1
check_command "docker compose" 2>/dev/null || check_command docker-compose 2>/dev/null || {
log_err "docker compose не найден (нужен Docker Compose V2)"
MISSING=1
}
install_if_missing node "https://nodejs.org/" || MISSING=1
install_if_missing npm "https://nodejs.org/" || 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 — Настройка окружения"
# Автогенерация паролей
PG_PASSWORD=$(generate_password)
MINIO_USER="minio-$(head -c 4 /dev/urandom | xxd -p)"
MINIO_PASS=$(generate_password)
AUTH_SECRET=$(generate_secret)
DEV_ACCESS_KEY=$(generate_password | head -c 16)
S3_BUCKET="liveserver"
echo ""
echo -e " ${GREEN}${BOLD}Пароли сгенерированы автоматически:${NC}"
echo -e " ${CYAN}PostgreSQL:${NC} ${PG_PASSWORD}"
echo -e " ${CYAN}MinIO user:${NC} ${MINIO_USER}"
echo -e " ${CYAN}MinIO password:${NC} ${MINIO_PASS}"
echo -e " ${CYAN}Auth secret:${NC} ${AUTH_SECRET:0:16}..."
if [[ "$MODE" == "1" ]]; then
echo -e " ${CYAN}DEV_ACCESS_KEY:${NC} ${DEV_ACCESS_KEY}"
fi
echo ""
echo -e " ${YELLOW}Все пароли сохранены в .env — можешь изменить позже${NC}"
if ask_yn "Хочешь задать свои пароли вместо сгенерированных?" "n"; then
echo ""
echo -e " ${BOLD}PostgreSQL${NC}"
ask "Пароль PostgreSQL" "$PG_PASSWORD" PG_PASSWORD
echo ""
echo -e " ${BOLD}MinIO (S3 Storage)${NC}"
ask "MinIO root user" "$MINIO_USER" MINIO_USER
ask_secret "MinIO root password" "$MINIO_PASS" MINIO_PASS
ask "Название S3 bucket" "$S3_BUCKET" S3_BUCKET
if [[ "$MODE" == "1" ]]; then
echo ""
echo -e " ${BOLD}Защита локалки${NC}"
ask "Ключ доступа (DEV_ACCESS_KEY)" "$DEV_ACCESS_KEY" DEV_ACCESS_KEY
fi
fi
echo ""
echo -e " ${BOLD}LiveKit${NC} (Enter чтобы пропустить, настроишь позже)"
ask "LiveKit URL (wss://...)" "" LK_URL
LK_KEY=""
LK_SECRET=""
LK_WEBHOOK=""
if [[ -n "$LK_URL" ]]; then
ask "LiveKit API Key" "" LK_KEY
ask_secret "LiveKit API Secret" "" LK_SECRET
ask_secret "LiveKit Webhook Secret (Enter = пропустить)" "" LK_WEBHOOK
fi
echo ""
echo -e " ${BOLD}AI Agent${NC} (Enter чтобы пропустить, настроишь позже)"
ask_secret "Deepgram API Key" "" DG_KEY
ask_secret "OpenAI API Key" "" OAI_KEY
if [[ "$MODE" == "1" ]]; then
ALLOWED_IPS=""
if ask_yn "Ограничить доступ по IP? (кроме localhost)" "n"; then
ask "Разрешённые IP (через запятую)" "" ALLOWED_IPS
fi
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
# Бэкап существующего .env
if [[ -f .env ]]; then
cp .env ".env.backup.$(date +%Y%m%d_%H%M%S)"
log_ok "Бэкап старого .env создан"
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}
# Redis
# Для Docker контейнеров: redis://redis:6379
# Для локального npm run dev: redis://localhost:6379
REDIS_URL=redis://localhost:6379
# 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 — автоматически если нет node_modules или package-lock изменился
if [[ ! -d "node_modules" ]]; then
echo -e " Установка npm зависимостей..."
npm install 2>&1 | tail -5
log_ok "npm install завершён"
elif [[ "package.json" -nt "node_modules/.package-lock.json" ]] 2>/dev/null; then
echo -e " package.json обновлён, переустановка зависимостей..."
npm install 2>&1 | tail -5
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 redis pgbouncer 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
if [[ "$i" -eq 30 ]]; then
log_err "PostgreSQL не запустился за 30 секунд"
exit 1
fi
sleep 1
done
log_ok "PostgreSQL готов"
# Wait for Redis
echo -e " Ожидание Redis..."
for i in $(seq 1 15); do
if docker compose exec -T redis redis-cli ping 2>/dev/null | grep -q PONG; then
break
fi
if [[ "$i" -eq 15 ]]; then
log_err "Redis не запустился за 15 секунд"
exit 1
fi
sleep 1
done
log_ok "Redis готов"
# Wait for PgBouncer
echo -e " Ожидание PgBouncer..."
for i in $(seq 1 15); do
if docker compose exec -T pgbouncer pg_isready -h 127.0.0.1 -p 6432 &>/dev/null; then
break
fi
if [[ "$i" -eq 15 ]]; then
log_warn "PgBouncer не отвечает — продолжаем без него"
fi
sleep 1
done
log_ok "PgBouncer готов"
# Prisma migrate
echo -e " Применение миграций..."
npx prisma db push --skip-generate 2>&1 | tail -3
log_ok "Схема БД синхронизирована"
# Create MinIO bucket (через docker exec если mc не установлен)
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
# Создание через curl (MinIO S3 API)
if curl -sf -o /dev/null -X PUT "http://localhost:9000/${S3_BUCKET}" \
-u "${MINIO_USER}:${MINIO_PASS}" 2>/dev/null; then
log_ok "Bucket '${S3_BUCKET}' создан через API"
else
log_warn "Не удалось создать bucket автоматически"
echo -e " Создай вручную: ${CYAN}http://localhost:9001${NC} → Buckets → Create"
echo -e " Логин: ${CYAN}${MINIO_USER}${NC} / пароль в .env"
fi
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
echo ""
echo -e " ${BOLD}Сгенерированные пароли (сохранены в .env):${NC}"
echo -e " ${CYAN}PostgreSQL:${NC} ${PG_PASSWORD}"
echo -e " ${CYAN}MinIO:${NC} ${MINIO_USER} / ${MINIO_PASS}"
if [[ "$MODE" == "1" && -n "${DEV_ACCESS_KEY:-}" ]]; then
echo -e " ${CYAN}DEV_ACCESS_KEY:${NC} ${DEV_ACCESS_KEY}"
fi
echo -e " ${YELLOW}Изменить пароли: отредактируй .env и перезапусти docker compose${NC}"
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 ""