feat: auto LAN auth + setup.sh dev command
- auth.ts: auto-allow localhost:3000-3010 + LAN_HOST for trustedOrigins - setup.sh: new `dev` command (update + kill old processes + auto LAN IP + launch) - next.config.ts: allowedDevOrigins for LAN HMR
This commit is contained in:
@@ -106,7 +106,7 @@ Clients → Traefik (LB) → Next.js (x2 replicas) → PgBouncer (pool 25, max 5
|
|||||||
```bash
|
```bash
|
||||||
# Dev
|
# Dev
|
||||||
npm run dev # Next.js dev server (localhost:3000)
|
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)
|
npm run lint # TypeScript type-check (tsc --noEmit)
|
||||||
|
|
||||||
# Database
|
# Database
|
||||||
@@ -124,6 +124,18 @@ docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build #
|
|||||||
|
|
||||||
# AI Agent
|
# AI Agent
|
||||||
cd ai-agent && python main.py start
|
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
|
## Environment Variables
|
||||||
@@ -137,6 +149,11 @@ LIVEKIT_API_KEY=...
|
|||||||
LIVEKIT_API_SECRET=...
|
LIVEKIT_API_SECRET=...
|
||||||
BETTER_AUTH_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)
|
# Local dev protection (when DOMAIN is not set)
|
||||||
DEV_ACCESS_KEY=mySecretKey123
|
DEV_ACCESS_KEY=mySecretKey123
|
||||||
# ALLOWED_IPS=192.168.1.10,192.168.1.11
|
# 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`
|
- Schema has no `url` in datasource block — only `provider`
|
||||||
- Use `-- --webpack` flag for `next build` on Windows (Turbopack WASM issue)
|
- 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
|
## Agent Orchestration
|
||||||
|
|
||||||
| Task | Agent |
|
| 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
|
## Conventions
|
||||||
|
|
||||||
- Communication language: Russian
|
- Communication language: Russian
|
||||||
@@ -196,3 +231,4 @@ DEV_ACCESS_KEY=mySecretKey123
|
|||||||
- Files store `fileKey` (S3 object key), not full URLs
|
- Files store `fileKey` (S3 object key), not full URLs
|
||||||
- `LectureArtifact` is 1:1 with Room
|
- `LectureArtifact` is 1:1 with Room
|
||||||
- All cascade deletes: removing Room removes all related data
|
- 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
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import type { NextConfig } from "next";
|
|||||||
const nextConfig: NextConfig = {
|
const nextConfig: NextConfig = {
|
||||||
output: "standalone",
|
output: "standalone",
|
||||||
serverExternalPackages: ["@prisma/client", "bcryptjs", "ioredis"],
|
serverExternalPackages: ["@prisma/client", "bcryptjs", "ioredis"],
|
||||||
|
allowedDevOrigins: ["192.168.1.78"],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default nextConfig;
|
export default nextConfig;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ set -uo pipefail
|
|||||||
# ============================================================
|
# ============================================================
|
||||||
# Usage:
|
# Usage:
|
||||||
# ./setup.sh — интерактивное меню
|
# ./setup.sh — интерактивное меню
|
||||||
|
# ./setup.sh dev — обновить + запустить dev-сервер (auto LAN, kill old)
|
||||||
# ./setup.sh install — первоначальная установка
|
# ./setup.sh install — первоначальная установка
|
||||||
# ./setup.sh update — обновить проект (git pull + npm + prisma + rebuild)
|
# ./setup.sh update — обновить проект (git pull + npm + prisma + rebuild)
|
||||||
# ./setup.sh doctor — диагностика и автоисправление
|
# ./setup.sh doctor — диагностика и автоисправление
|
||||||
@@ -206,6 +207,7 @@ print_usage() {
|
|||||||
echo -e " ${BOLD}Команды:${NC}"
|
echo -e " ${BOLD}Команды:${NC}"
|
||||||
echo -e " ${CYAN}install${NC} Первоначальная установка"
|
echo -e " ${CYAN}install${NC} Первоначальная установка"
|
||||||
echo -e " ${CYAN}update${NC} Обновить проект (git pull + npm + prisma + rebuild)"
|
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}doctor${NC} Диагностика и автоисправление проблем"
|
||||||
echo -e " ${CYAN}status${NC} Статус всех сервисов"
|
echo -e " ${CYAN}status${NC} Статус всех сервисов"
|
||||||
echo -e " ${CYAN}admin${NC} Создать администратора"
|
echo -e " ${CYAN}admin${NC} Создать администратора"
|
||||||
@@ -1266,6 +1268,86 @@ cmd_reset() {
|
|||||||
echo ""
|
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 — Роутер команд
|
# MAIN — Роутер команд
|
||||||
# ============================================================
|
# ============================================================
|
||||||
@@ -1282,6 +1364,9 @@ case "$CMD" in
|
|||||||
update)
|
update)
|
||||||
cmd_update
|
cmd_update
|
||||||
;;
|
;;
|
||||||
|
dev)
|
||||||
|
cmd_dev
|
||||||
|
;;
|
||||||
doctor|fix)
|
doctor|fix)
|
||||||
cmd_doctor
|
cmd_doctor
|
||||||
;;
|
;;
|
||||||
@@ -1316,24 +1401,26 @@ case "$CMD" in
|
|||||||
else
|
else
|
||||||
echo -e " ${BOLD}Что делаем?${NC}"
|
echo -e " ${BOLD}Что делаем?${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
echo -e " ${BOLD}1)${NC} ${CYAN}doctor${NC} — диагностика и исправление"
|
echo -e " ${BOLD}1)${NC} ${CYAN}dev${NC} — ${GREEN}обновить + запустить dev-сервер${NC}"
|
||||||
echo -e " ${BOLD}2)${NC} ${CYAN}update${NC} — обновить проект"
|
echo -e " ${BOLD}2)${NC} ${CYAN}doctor${NC} — диагностика и исправление"
|
||||||
echo -e " ${BOLD}3)${NC} ${CYAN}status${NC} — статус сервисов"
|
echo -e " ${BOLD}3)${NC} ${CYAN}update${NC} — обновить проект"
|
||||||
echo -e " ${BOLD}4)${NC} ${CYAN}admin${NC} — создать администратора"
|
echo -e " ${BOLD}4)${NC} ${CYAN}status${NC} — статус сервисов"
|
||||||
echo -e " ${BOLD}5)${NC} ${CYAN}restart${NC} — перезапуск контейнеров"
|
echo -e " ${BOLD}5)${NC} ${CYAN}admin${NC} — создать администратора"
|
||||||
echo -e " ${BOLD}6)${NC} ${CYAN}install${NC} — полная переустановка"
|
echo -e " ${BOLD}6)${NC} ${CYAN}restart${NC} — перезапуск контейнеров"
|
||||||
echo -e " ${BOLD}7)${NC} ${CYAN}reset${NC} — сброс всего"
|
echo -e " ${BOLD}7)${NC} ${CYAN}install${NC} — полная переустановка"
|
||||||
|
echo -e " ${BOLD}8)${NC} ${CYAN}reset${NC} — сброс всего"
|
||||||
echo ""
|
echo ""
|
||||||
ask "Выбери действие" "1" CHOICE
|
ask "Выбери действие" "1" CHOICE
|
||||||
|
|
||||||
case "$CHOICE" in
|
case "$CHOICE" in
|
||||||
1|doctor) cmd_doctor ;;
|
1|dev) cmd_dev ;;
|
||||||
2|update) cmd_update ;;
|
2|doctor) cmd_doctor ;;
|
||||||
3|status) cmd_status ;;
|
3|update) cmd_update ;;
|
||||||
4|admin) cmd_admin ;;
|
4|status) cmd_status ;;
|
||||||
5|restart) cmd_restart ;;
|
5|admin) cmd_admin ;;
|
||||||
6|install) cmd_install ;;
|
6|restart) cmd_restart ;;
|
||||||
7|reset) cmd_reset ;;
|
7|install) cmd_install ;;
|
||||||
|
8|reset) cmd_reset ;;
|
||||||
*) log_err "Неизвестный выбор: ${CHOICE}"; print_usage ;;
|
*) log_err "Неизвестный выбор: ${CHOICE}"; print_usage ;;
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|||||||
+24
-3
@@ -2,11 +2,32 @@ import { betterAuth } from "better-auth";
|
|||||||
import { prismaAdapter } from "better-auth/adapters/prisma";
|
import { prismaAdapter } from "better-auth/adapters/prisma";
|
||||||
import { prisma } from "./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({
|
export const auth = betterAuth({
|
||||||
baseURL: process.env.BETTER_AUTH_URL,
|
baseURL: process.env.BETTER_AUTH_URL,
|
||||||
trustedOrigins: process.env.BETTER_AUTH_TRUSTED_ORIGINS
|
trustedOrigins: getTrustedOrigins(),
|
||||||
? process.env.BETTER_AUTH_TRUSTED_ORIGINS.split(",")
|
|
||||||
: ["http://localhost:3000"],
|
|
||||||
database: prismaAdapter(prisma, {
|
database: prismaAdapter(prisma, {
|
||||||
provider: "postgresql",
|
provider: "postgresql",
|
||||||
}),
|
}),
|
||||||
|
|||||||
Reference in New Issue
Block a user