chore(deploy): postinstall, standalone, PM2 ecosystem, README — QA-6.C2 C4 M1 M2
backend/package.json - Add 'postinstall: prisma generate' — fresh pnpm install now produces a usable @prisma/client without a manual step. - Add engines.node >= 20 and packageManager pnpm@9.12.0. frontend/package.json - Same engines + packageManager pin. frontend/next.config.js - output: 'standalone' enabled for PM2's .next/standalone/server.js pattern (reduces production bundle size, removes runtime node_modules dependency for the web process). frontend/.env.example (new) - Documents NEXT_PUBLIC_API_URL as a BUILD-TIME variable. ecosystem.config.js (new) - PM2 config for reloop-api (cwd=backend, script=dist/main.js, port 3001) and reloop-web (cwd=frontend, script=.next/standalone/server.js, port 3000, HOSTNAME=0.0.0.0). max_memory_restart=512M each with separate error/out log files. README.md (new) - Root deploy guide: stack, env vars table (backend + frontend), local dev quickstart, prod deploy steps (rsync → pnpm install --frozen-lockfile → prisma migrate deploy → build → copy .next/static and public into standalone → pm2 start), reverse proxy mapping, pm2 ops. Also covers Cluster F items: 'Dev VM 에서 prisma migrate dev --name init' for migrations and 'pnpm install' for lockfile generation. .gitignore - Ignore .claude/state/session* + .claude/state/*.lock + .claude/sessions/ (ephemeral harness runtime metadata).
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -12,3 +12,8 @@ backend/node_modules
|
||||
frontend/node_modules
|
||||
frontend/.next
|
||||
backend/dist
|
||||
|
||||
# harness runtime metadata (state is ephemeral; contracts/plans are committed)
|
||||
.claude/state/session*
|
||||
.claude/state/*.lock
|
||||
.claude/sessions/
|
||||
|
||||
101
README.md
Normal file
101
README.md
Normal file
@@ -0,0 +1,101 @@
|
||||
# ReLoop v2
|
||||
|
||||
적응형 복습 스케줄링 기반 수능 학습 최적화 플랫폼.
|
||||
페르소나 망각 곡선(persona-based forgetting curve)으로 개인화된 복습 간격을 산출한다.
|
||||
|
||||
## Stack
|
||||
|
||||
- **Backend**: NestJS 10 + Prisma 5 + MariaDB, JWT 인증
|
||||
- **Frontend**: Next.js 14 (App Router) + React 18 + styled-components 6
|
||||
- **Deploy**: PM2 on Debian VM, nginx/Caddy reverse proxy
|
||||
|
||||
## Environment variables
|
||||
|
||||
### Backend (`backend/.env`)
|
||||
|
||||
| Var | Required | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `DATABASE_URL` | ✓ | — | `mysql://user:pass@host:3306/reloop` |
|
||||
| `JWT_SECRET` | ✓ | — | long random string; boot fails without it |
|
||||
| `JWT_EXPIRES_IN` | — | `30d` | access token TTL |
|
||||
| `CORS_ORIGINS` | — | `https://reloop.nabomhalang.co.kr,http://localhost:3000` | comma-separated |
|
||||
| `PORT` | — | `3001` | NestJS HTTP port |
|
||||
|
||||
### Frontend (`frontend/.env` at build time)
|
||||
|
||||
| Var | Required | Default |
|
||||
|---|---|---|
|
||||
| `NEXT_PUBLIC_API_URL` | — | `https://reloop-api.nabomhalang.co.kr/api` |
|
||||
|
||||
`NEXT_PUBLIC_*` 변수는 **빌드 시점**에 클라이언트 번들에 박힘. 배포 환경별로 다른 값을 쓰려면 환경마다 다시 빌드해야 함.
|
||||
|
||||
## Local development
|
||||
|
||||
```bash
|
||||
# 1. Install
|
||||
cd backend && pnpm install && cd ..
|
||||
cd frontend && pnpm install && cd ..
|
||||
|
||||
# 2. Backend DB setup (first time)
|
||||
cd backend
|
||||
cp .env.example .env # edit DATABASE_URL, JWT_SECRET
|
||||
pnpm prisma migrate dev --name init
|
||||
pnpm prisma:seed
|
||||
pnpm dev
|
||||
|
||||
# 3. Frontend
|
||||
cd frontend
|
||||
cp .env.example .env.local
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
## Production deploy (Dev VM)
|
||||
|
||||
```bash
|
||||
# Sync sources
|
||||
rsync -avz --delete \
|
||||
--exclude node_modules --exclude .next --exclude dist \
|
||||
/path/to/reloop-v2/ dev:/home/deploy/reloop-v2/
|
||||
|
||||
# On Dev VM
|
||||
cd /home/deploy/reloop-v2
|
||||
|
||||
# Backend
|
||||
cd backend
|
||||
pnpm install --frozen-lockfile # postinstall runs prisma generate automatically
|
||||
pnpm prisma migrate deploy # apply migrations to prod DB
|
||||
pnpm build # produces dist/main.js
|
||||
cd ..
|
||||
|
||||
# Frontend
|
||||
cd frontend
|
||||
pnpm install --frozen-lockfile
|
||||
NEXT_PUBLIC_API_URL=https://reloop-api.nabomhalang.co.kr/api pnpm build
|
||||
# Copy static assets next to the standalone server
|
||||
cp -r .next/static .next/standalone/.next/
|
||||
cp -r public .next/standalone/ 2>/dev/null || true
|
||||
cd ..
|
||||
|
||||
# PM2 boot
|
||||
pm2 start ecosystem.config.js
|
||||
pm2 save
|
||||
```
|
||||
|
||||
## Reverse proxy (nginx/Caddy)
|
||||
|
||||
- `reloop.nabomhalang.co.kr` → `127.0.0.1:3000` (frontend)
|
||||
- `reloop-api.nabomhalang.co.kr` → `127.0.0.1:3001` (backend)
|
||||
|
||||
Backend allows the frontend domain via `CORS_ORIGINS`.
|
||||
|
||||
## Operations
|
||||
|
||||
```bash
|
||||
pm2 status # show reloop-api / reloop-web
|
||||
pm2 logs reloop-api # tail backend
|
||||
pm2 reload ecosystem.config.js # zero-downtime restart after redeploy
|
||||
```
|
||||
|
||||
## QA
|
||||
|
||||
`.qa/` 아래에 Breezing QA 리포트 6개가 있어. `Plans.md` 와 `.claude/state/pending-fix-proposals.jsonl` 에 후속 fix 이력이 있음.
|
||||
@@ -11,6 +11,7 @@
|
||||
"dev": "nest start --watch",
|
||||
"start:debug": "nest start --debug --watch",
|
||||
"start:prod": "node dist/main",
|
||||
"postinstall": "prisma generate",
|
||||
"lint": "eslint \"{src,test}/**/*.ts\" --fix",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
@@ -59,5 +60,9 @@
|
||||
"tsconfig-paths": "^4.2.0",
|
||||
"typescript": "^5.1.3",
|
||||
"vitest": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"packageManager": "pnpm@9.12.0"
|
||||
}
|
||||
|
||||
47
ecosystem.config.js
Normal file
47
ecosystem.config.js
Normal file
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* PM2 ecosystem for ReLoop v2
|
||||
*
|
||||
* Usage (on Dev VM after build):
|
||||
* pm2 start ecosystem.config.js
|
||||
* pm2 save
|
||||
* pm2 reload ecosystem.config.js # zero-downtime restart
|
||||
*/
|
||||
module.exports = {
|
||||
apps: [
|
||||
{
|
||||
name: 'reloop-api',
|
||||
cwd: './backend',
|
||||
script: 'dist/main.js',
|
||||
instances: 1,
|
||||
exec_mode: 'fork',
|
||||
env: {
|
||||
NODE_ENV: 'production',
|
||||
PORT: '3001',
|
||||
},
|
||||
max_memory_restart: '512M',
|
||||
error_file: './logs/reloop-api.error.log',
|
||||
out_file: './logs/reloop-api.out.log',
|
||||
merge_logs: true,
|
||||
time: true,
|
||||
},
|
||||
{
|
||||
name: 'reloop-web',
|
||||
cwd: './frontend',
|
||||
// Next.js standalone server (requires `output: 'standalone'` in next.config.js
|
||||
// and manual copy of `.next/static` and `public` into `.next/standalone` after build).
|
||||
script: '.next/standalone/server.js',
|
||||
instances: 1,
|
||||
exec_mode: 'fork',
|
||||
env: {
|
||||
NODE_ENV: 'production',
|
||||
PORT: '3000',
|
||||
HOSTNAME: '0.0.0.0',
|
||||
},
|
||||
max_memory_restart: '512M',
|
||||
error_file: './logs/reloop-web.error.log',
|
||||
out_file: './logs/reloop-web.out.log',
|
||||
merge_logs: true,
|
||||
time: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
3
frontend/.env.example
Normal file
3
frontend/.env.example
Normal file
@@ -0,0 +1,3 @@
|
||||
# Build-time env var — baked into the client bundle by Next.js.
|
||||
# Must be set BEFORE running `pnpm build`, not at runtime.
|
||||
NEXT_PUBLIC_API_URL=https://reloop-api.nabomhalang.co.kr/api
|
||||
@@ -1,9 +1,10 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
output: 'standalone',
|
||||
reactStrictMode: true,
|
||||
compiler: {
|
||||
styledComponents: true,
|
||||
},
|
||||
reactStrictMode: true,
|
||||
};
|
||||
|
||||
module.exports = nextConfig;
|
||||
|
||||
@@ -28,5 +28,9 @@
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"prettier": "^3.0.0",
|
||||
"typescript": "^5"
|
||||
}
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"packageManager": "pnpm@9.12.0"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user