diff --git a/CLAUDE.md b/CLAUDE.md index 0451ead..2c6517f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -106,7 +106,7 @@ Clients → Traefik (LB) → Next.js (x2 replicas) → PgBouncer (pool 25, max 5 ```bash # Dev npm run dev # Next.js dev server (localhost:3000) -docker compose up -d postgres minio redis # DB + Storage + Redis +docker compose up -d postgres minio redis pgbouncer # DB + Storage + Redis + PgBouncer npm run lint # TypeScript type-check (tsc --noEmit) # Database @@ -124,6 +124,18 @@ docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build # # AI Agent cd ai-agent && python main.py start + +# Setup script (universal installer/doctor) +./setup.sh # Interactive menu +./setup.sh dev # Update + launch dev server (auto LAN, kill old processes) +./setup.sh install # Full first-time setup (auto-generates passwords) +./setup.sh update # git pull + npm install + prisma + rebuild +./setup.sh doctor # Diagnose & auto-fix common issues +./setup.sh status # Health check all services +./setup.sh restart # Restart all containers +./setup.sh logs [service] # Tail service logs (default: app) +./setup.sh admin # Promote user to ADMIN role +./setup.sh reset # Full teardown (with confirmation) ``` ## Environment Variables @@ -137,6 +149,11 @@ LIVEKIT_API_KEY=... LIVEKIT_API_SECRET=... BETTER_AUTH_SECRET=... +# Auth — trusted origins for non-localhost access (comma-separated) +BETTER_AUTH_TRUSTED_ORIGINS=http://192.168.1.78:3000,http://localhost:3000 +# LAN IP for auto trustedOrigins in dev (auto-detected by ./setup.sh dev) +# LAN_HOST=192.168.1.78 + # Local dev protection (when DOMAIN is not set) DEV_ACCESS_KEY=mySecretKey123 # ALLOWED_IPS=192.168.1.10,192.168.1.11 @@ -160,6 +177,14 @@ DEV_ACCESS_KEY=mySecretKey123 - Schema has no `url` in datasource block — only `provider` - Use `-- --webpack` flag for `next build` on Windows (Turbopack WASM issue) +## Auth Notes + +- `better-auth` handles registration/login via `/api/auth/[...all]` catch-all route +- **Client:** `auth-client.ts` uses `createAuthClient()` without `baseURL` — auto-detects current origin (works from any IP/domain) +- **Server:** `auth.ts` uses `BETTER_AUTH_URL` for `baseURL` and `BETTER_AUTH_TRUSTED_ORIGINS` (comma-separated) for CSRF origin validation. Without `BETTER_AUTH_TRUSTED_ORIGINS`, auto-allows `localhost`/`127.0.0.1` on ports 3000–3010 + `LAN_HOST` if set +- First admin: register normally, then promote via `./setup.sh admin` or manually in DB +- **No `"type"` field in `package.json`** — removed to fix Turbopack ESM/CJS conflict in dev mode. Next.js handles ESM in `.ts/.tsx` automatically + ## Agent Orchestration | Task | Agent | @@ -187,6 +212,16 @@ DEV_ACCESS_KEY=mySecretKey123 Не пропускать этот чеклист. Если изменение не затрагивает файл — просто подтвердить что файл актуален. +## Known Issues & Fixes + +| Issue | Cause | Fix | +|-------|-------|-----| +| White screen in dev (ESM/CJS conflict) | `"type": "module"` or `"type": "commonjs"` in package.json | Remove the `"type"` field entirely — Next.js doesn't need it | +| Auth silently fails from LAN | `auth-client.ts` had hardcoded `baseURL` → requests went to localhost from browser | Removed `baseURL`, added `trustedOrigins` config | +| `setup.sh doctor` crashes on .env check | Comments in `.env.example` parsed as variable names by `set -euo pipefail` | Fixed parsing logic | +| `setup.sh update` runs git pull after stash declined | Missing `else` branch after stash prompt | Fixed control flow | +| Auth form resets on non-localhost (no error shown) | `trustedOrigins` fallback only had `localhost:3000`, CSRF rejected other origins silently | `auth.ts` auto-allows ports 3000–3010 + `LAN_HOST`; `setup.sh dev` auto-detects LAN IP | + ## Conventions - Communication language: Russian @@ -196,3 +231,4 @@ DEV_ACCESS_KEY=mySecretKey123 - Files store `fileKey` (S3 object key), not full URLs - `LectureArtifact` is 1:1 with Room - All cascade deletes: removing Room removes all related data +- MinIO stays as S3 storage — GitHub repo archived (Feb 2026) but Docker image still maintained, no viable lightweight alternative diff --git a/next.config.ts b/next.config.ts index eb3e7f5..92ecea6 100644 --- a/next.config.ts +++ b/next.config.ts @@ -3,6 +3,7 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { output: "standalone", serverExternalPackages: ["@prisma/client", "bcryptjs", "ioredis"], + allowedDevOrigins: ["192.168.1.78"], }; export default nextConfig; diff --git a/setup.sh b/setup.sh index 28ac965..096708f 100644 --- a/setup.sh +++ b/setup.sh @@ -6,6 +6,7 @@ set -uo pipefail # ============================================================ # Usage: # ./setup.sh — интерактивное меню +# ./setup.sh dev — обновить + запустить dev-сервер (auto LAN, kill old) # ./setup.sh install — первоначальная установка # ./setup.sh update — обновить проект (git pull + npm + prisma + rebuild) # ./setup.sh doctor — диагностика и автоисправление @@ -206,6 +207,7 @@ print_usage() { echo -e " ${BOLD}Команды:${NC}" echo -e " ${CYAN}install${NC} Первоначальная установка" echo -e " ${CYAN}update${NC} Обновить проект (git pull + npm + prisma + rebuild)" + echo -e " ${CYAN}dev${NC} Обновить + запустить dev-сервер (auto LAN, kill old)" echo -e " ${CYAN}doctor${NC} Диагностика и автоисправление проблем" echo -e " ${CYAN}status${NC} Статус всех сервисов" echo -e " ${CYAN}admin${NC} Создать администратора" @@ -1266,6 +1268,86 @@ cmd_reset() { echo "" } +# ============================================================ +# COMMAND: dev — обновить + запустить dev-сервер +# ============================================================ + +cmd_dev() { + log_step "Dev Server — обновление и запуск" + + # 1. Git pull (без интерактива — stash автоматически) + log_step "1/6 — Обновление кода" + if [[ -d .git ]]; then + if ! git diff --quiet 2>/dev/null || ! git diff --cached --quiet 2>/dev/null; then + git stash push -m "auto-stash before dev $(date +%Y%m%d_%H%M%S)" 2>&1 + log_ok "Изменения сохранены в stash" + fi + git pull --rebase 2>&1 | tail -5 + log_ok "git pull завершён" + fi + + # 2. npm install (только если package-lock изменился) + log_step "2/6 — Зависимости" + npm install 2>&1 | tail -3 + log_ok "npm install завершён" + + # 3. Prisma + log_step "3/6 — Prisma" + npx prisma generate 2>&1 | tail -2 + if docker compose exec -T postgres pg_isready -U postgres &>/dev/null; then + npx prisma db push --skip-generate --accept-data-loss 2>&1 | tail -3 + log_ok "Схема БД синхронизирована" + else + log_warn "PostgreSQL недоступен — миграция пропущена" + fi + + # 4. Инфраструктурные контейнеры + log_step "4/6 — Контейнеры" + docker compose up -d postgres minio redis pgbouncer 2>&1 | tail -5 + log_ok "Инфра-контейнеры запущены" + + # 5. Убить старые процессы Next.js + log_step "5/6 — Очистка старых процессов" + local killed=0 + for pid in $(pgrep -f "next dev" 2>/dev/null || true); do + kill "$pid" 2>/dev/null && ((killed++)) || true + done + # Убить процесс на порту 3000 + local port_pid + port_pid=$(lsof -ti :3000 2>/dev/null || true) + if [[ -n "$port_pid" ]]; then + kill "$port_pid" 2>/dev/null && ((killed++)) || true + fi + if [[ $killed -gt 0 ]]; then + log_ok "Убито процессов: $killed" + sleep 1 + else + log_ok "Старых процессов нет" + fi + + # 6. Очистка .next кеша + log_step "6/6 — Очистка кеша" + [[ -d ".next" ]] && rm -rf .next + log_ok "Кеш очищен" + + # Автоопределение LAN IP для auth + local lan_ip + lan_ip=$(hostname -I 2>/dev/null | awk '{print $1}' || true) + if [[ -n "$lan_ip" && -z "$(env_get LAN_HOST)" ]]; then + log_info "LAN IP: ${lan_ip}" + if ! grep -q "^LAN_HOST=" .env 2>/dev/null; then + echo "LAN_HOST=${lan_ip}" >> .env + log_ok "LAN_HOST=${lan_ip} добавлен в .env" + fi + fi + + # Запуск + echo "" + log_ok "${GREEN}${BOLD}Готово! Запускаю dev-сервер...${NC}" + echo "" + exec npm run dev +} + # ============================================================ # MAIN — Роутер команд # ============================================================ @@ -1282,6 +1364,9 @@ case "$CMD" in update) cmd_update ;; + dev) + cmd_dev + ;; doctor|fix) cmd_doctor ;; @@ -1316,24 +1401,26 @@ case "$CMD" in else echo -e " ${BOLD}Что делаем?${NC}" echo "" - echo -e " ${BOLD}1)${NC} ${CYAN}doctor${NC} — диагностика и исправление" - echo -e " ${BOLD}2)${NC} ${CYAN}update${NC} — обновить проект" - echo -e " ${BOLD}3)${NC} ${CYAN}status${NC} — статус сервисов" - echo -e " ${BOLD}4)${NC} ${CYAN}admin${NC} — создать администратора" - echo -e " ${BOLD}5)${NC} ${CYAN}restart${NC} — перезапуск контейнеров" - echo -e " ${BOLD}6)${NC} ${CYAN}install${NC} — полная переустановка" - echo -e " ${BOLD}7)${NC} ${CYAN}reset${NC} — сброс всего" + echo -e " ${BOLD}1)${NC} ${CYAN}dev${NC} — ${GREEN}обновить + запустить dev-сервер${NC}" + echo -e " ${BOLD}2)${NC} ${CYAN}doctor${NC} — диагностика и исправление" + echo -e " ${BOLD}3)${NC} ${CYAN}update${NC} — обновить проект" + echo -e " ${BOLD}4)${NC} ${CYAN}status${NC} — статус сервисов" + echo -e " ${BOLD}5)${NC} ${CYAN}admin${NC} — создать администратора" + echo -e " ${BOLD}6)${NC} ${CYAN}restart${NC} — перезапуск контейнеров" + echo -e " ${BOLD}7)${NC} ${CYAN}install${NC} — полная переустановка" + echo -e " ${BOLD}8)${NC} ${CYAN}reset${NC} — сброс всего" echo "" ask "Выбери действие" "1" CHOICE case "$CHOICE" in - 1|doctor) cmd_doctor ;; - 2|update) cmd_update ;; - 3|status) cmd_status ;; - 4|admin) cmd_admin ;; - 5|restart) cmd_restart ;; - 6|install) cmd_install ;; - 7|reset) cmd_reset ;; + 1|dev) cmd_dev ;; + 2|doctor) cmd_doctor ;; + 3|update) cmd_update ;; + 4|status) cmd_status ;; + 5|admin) cmd_admin ;; + 6|restart) cmd_restart ;; + 7|install) cmd_install ;; + 8|reset) cmd_reset ;; *) log_err "Неизвестный выбор: ${CHOICE}"; print_usage ;; esac fi diff --git a/src/lib/auth.ts b/src/lib/auth.ts index 061e96b..cbed08b 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -2,11 +2,32 @@ import { betterAuth } from "better-auth"; import { prismaAdapter } from "better-auth/adapters/prisma"; import { prisma } from "./prisma"; +function getTrustedOrigins(): string[] { + if (process.env.BETTER_AUTH_TRUSTED_ORIGINS) { + return process.env.BETTER_AUTH_TRUSTED_ORIGINS.split(",").map((o) => o.trim()); + } + + // Без явной конфигурации — разрешаем localhost на типичных портах Next.js + const origins: string[] = []; + for (let port = 3000; port <= 3010; port++) { + origins.push(`http://localhost:${port}`); + origins.push(`http://127.0.0.1:${port}`); + } + + // LAN-доступ: если задан LAN_HOST (IP сервера в локальной сети) + const lanHost = process.env.LAN_HOST; + if (lanHost) { + for (let port = 3000; port <= 3010; port++) { + origins.push(`http://${lanHost}:${port}`); + } + } + + return origins; +} + export const auth = betterAuth({ baseURL: process.env.BETTER_AUTH_URL, - trustedOrigins: process.env.BETTER_AUTH_TRUSTED_ORIGINS - ? process.env.BETTER_AUTH_TRUSTED_ORIGINS.split(",") - : ["http://localhost:3000"], + trustedOrigins: getTrustedOrigins(), database: prismaAdapter(prisma, { provider: "postgresql", }),