wing-ops/docs/INSTALL_GUIDE.md

734 lines
22 KiB
Markdown
Executable File

# WING-OPS 설치 매뉴얼
## 목차
1. [시스템 요구사항](#1-시스템-요구사항)
2. [프로젝트 구조](#2-프로젝트-구조)
3. [온라인 설치](#3-온라인-설치)
4. [오프라인 설치](#4-오프라인-설치)
5. [DB 초기화 및 마이그레이션](#5-db-초기화-및-마이그레이션)
6. [개발 서버 실행](#6-개발-서버-실행)
7. [운영 서버 배포](#7-운영-서버-배포)
8. [CI/CD 자동 배포](#8-cicd-자동-배포)
9. [접속 정보 요약](#9-접속-정보-요약)
10. [트러블슈팅](#10-트러블슈팅)
---
## 1. 시스템 요구사항
### 필수 소프트웨어
| 소프트웨어 | 최소 버전 | 권장 버전 | 용도 |
|-----------|----------|----------|------|
| Node.js | v20 | v20 LTS | Frontend/Backend 실행 |
| npm | v10 | v10+ | 패키지 관리 (Node.js 포함) |
| PostgreSQL | v15 | v16 | 운영 DB + 인증 DB |
| PostGIS | v3.3 | v3.4 | 공간 데이터 처리 |
| Git | v2.30 | v2.40+ | 소스 코드 관리 |
### 하드웨어 권장 사양
| 항목 | 개발 환경 | 운영 환경 |
|------|----------|----------|
| CPU | 2 Core | 4 Core |
| RAM | 4 GB | 8 GB |
| Disk | 10 GB | 50 GB |
| OS | macOS / Linux / Windows | Rocky Linux 9 / CentOS 9 |
### Node.js 설치 (fnm 권장)
fnm(Fast Node Manager)으로 Node.js 버전을 관리한다. 프로젝트 루트의 `.node-version` 파일에 `20`이 지정되어 있다.
**macOS / Linux:**
```bash
# fnm 설치
curl -fsSL https://fnm.vercel.app/install | bash
# 셸 재시작 후
fnm install 20
fnm use 20
# 확인
node -v # v20.x.x
npm -v # v10.x.x
```
**수동 설치 (fnm 없이):**
https://nodejs.org 에서 Node.js 20 LTS를 다운로드하여 설치한다.
---
## 2. 프로젝트 구조
```
wing/
├── frontend/ React 19 + Vite 7 + TypeScript 5.9 + Tailwind CSS 3
│ ├── src/
│ │ ├── App.tsx 메인 (탭 라우팅, 감사 로그 자동 기록)
│ │ ├── common/ 공통 모듈 (@common/ alias)
│ │ │ ├── components/ auth/, layer/, layout/, map/, ui/
│ │ │ ├── hooks/ useLayers, useSubMenu, useAuth
│ │ │ ├── services/ api.ts (Axios), authApi.ts, layerService.ts
│ │ │ ├── store/ authStore, menuStore (Zustand)
│ │ │ ├── types/ backtrack, boomLine, hns, navigation
│ │ │ └── utils/ coordinates, geo, sanitize
│ │ └── components/ 탭 단위 패키지 (11개, MPA 구조)
│ │ ├── prediction/ 확산 예측
│ │ ├── hns/ HNS 분석
│ │ ├── rescue/ 구조 시나리오
│ │ ├── aerial/ 항공 방제
│ │ ├── weather/ 해양 기상
│ │ ├── incidents/ 사건/사고 관리
│ │ ├── board/ 게시판
│ │ ├── reports/ 보고서
│ │ ├── assets/ 자산 관리
│ │ ├── scat/ Pre-SCAT 조사
│ │ └── admin/ 관리자
│ ├── package.json
│ └── vite.config.ts
├── backend/ Express 4 + TypeScript + PostgreSQL
│ ├── src/
│ │ ├── server.ts 진입점 (보안 미들웨어 + 라우터 등록)
│ │ ├── auth/ 인증 (JWT, Google OAuth)
│ │ ├── users/ 사용자 관리
│ │ ├── roles/ 역할/권한 (RBAC 2차원 권한)
│ │ ├── db/ DB Pool (wingDb, authDb), seed
│ │ ├── middleware/ 보안 (입력 살균, rate-limit)
│ │ └── {도메인}/ 도메인별 모듈 (Router + Service)
│ ├── package.json
│ └── tsconfig.json
├── database/ SQL 스크립트
│ ├── init.sql wing DB 초기 스키마 (PostGIS)
│ ├── auth_init.sql wing_auth DB 초기 스키마
│ └── migration/ 마이그레이션 (001 ~ 016)
├── docs/ 개발 문서
├── .githooks/ Git Hooks (pre-commit, commit-msg)
├── .gitea/workflows/ CI/CD (Gitea Actions)
├── .node-version Node.js 20
└── .npmrc npm 레지스트리 (Nexus 프록시)
```
---
## 3. 온라인 설치
인터넷이 가능한 환경에서의 설치 절차.
### 3-1. 소스 코드 클론
```bash
git clone https://gitea.gc-si.dev/gc/wing-ops.git wing
cd wing
```
### 3-2. Git Hooks 설정
```bash
# pre-commit (TypeScript + ESLint 검증), commit-msg (Conventional Commits 검증)
git config core.hooksPath .githooks
chmod +x .githooks/pre-commit .githooks/commit-msg
```
### 3-3. 의존성 설치
npm 레지스트리는 프로젝트 루트의 `.npmrc`에 Nexus 프록시로 설정되어 있다.
```bash
# Frontend
cd frontend
npm install
# Backend
cd ../backend
npm install
```
### 3-4. 환경변수 설정
#### Backend (`backend/.env`)
```bash
# ===========================================
# 서버 설정
# ===========================================
PORT=3001
NODE_ENV=development
# ===========================================
# wing DB (운영 데이터 - PostgreSQL + PostGIS)
# ===========================================
WING_DB_HOST=211.208.115.83
WING_DB_PORT=5432
WING_DB_USER=wing
WING_DB_PASS=<비밀번호>
WING_DB_NAME=wing
# ===========================================
# wing_auth DB (인증/권한 - PostgreSQL)
# ===========================================
AUTH_DB_HOST=211.208.115.83
AUTH_DB_PORT=5432
AUTH_DB_USER=wing_auth
AUTH_DB_PASS=<비밀번호>
AUTH_DB_NAME=wing_auth
# ===========================================
# JWT 인증
# ===========================================
JWT_SECRET=<랜덤 시크릿 문자열>
JWT_EXPIRES_IN=24h
# ===========================================
# CORS (프론트엔드 출처)
# ===========================================
FRONTEND_URL=http://localhost:5173
# ===========================================
# Google OAuth (선택)
# ===========================================
GOOGLE_CLIENT_ID=<Google OAuth 클라이언트 ID>
```
#### Frontend (`frontend/.env`)
```bash
# 백엔드 API URL
VITE_API_URL=http://localhost:3001/api
# Google OAuth (선택)
VITE_GOOGLE_CLIENT_ID=<Google OAuth 클라이언트 ID>
# 공공데이터 API 키 (해양기상, 선택)
VITE_DATA_GO_KR_API_KEY=<data.go.kr API 키>
VITE_WEATHER_API_KEY=<기상 API 키>
```
> `.env` 파일은 `.gitignore`에 포함된다. 실제 값은 팀 내부에서 공유한다.
### 3-5. 실행
```bash
# 터미널 1: 백엔드
cd backend
npm run dev
# 출력: 서버가 포트 3001에서 실행 중입니다.
# 터미널 2: 프론트엔드
cd frontend
npm run dev
# 출력: VITE vX.X.X ready in XXXms
# -> Local: http://localhost:5173/
```
브라우저에서 http://localhost:5173 접속하여 확인한다.
---
## 4. 오프라인 설치
인터넷이 불가능한 폐쇄망 환경에서의 설치 절차.
### 4-1. 온라인 환경에서 패키지 준비
인터넷이 되는 PC에서 다음을 실행한다:
```bash
# 1. 프로젝트 클론 및 의존성 설치
git clone https://gitea.gc-si.dev/gc/wing-ops.git wing
cd wing/frontend && npm install
cd ../backend && npm install
# 2. 전체 프로젝트 압축 (node_modules 포함)
cd ../..
tar -czf wing_full.tar.gz wing/
```
### 4-2. Node.js 오프라인 설치 파일 준비
대상 OS에 맞는 설치 파일을 다운로드한다:
| OS | 파일 | 다운로드 |
|----|------|---------|
| Linux (x64) | `node-v20.x.x-linux-x64.tar.xz` | https://nodejs.org/dist/v20.x.x/ |
| macOS (arm64) | `node-v20.x.x-darwin-arm64.tar.gz` | https://nodejs.org/dist/v20.x.x/ |
| macOS (x64) | `node-v20.x.x-darwin-x64.tar.gz` | https://nodejs.org/dist/v20.x.x/ |
| Windows | `node-v20.x.x-x64.msi` | https://nodejs.org/dist/v20.x.x/ |
### 4-3. 대상 서버에 설치
```bash
# 1. Node.js 설치 (Linux 예시)
tar -xJf node-v20.x.x-linux-x64.tar.xz
export PATH=$PWD/node-v20.x.x-linux-x64/bin:$PATH
# .bashrc 또는 .profile에 PATH 영구 등록
# 2. 프로젝트 압축 해제
tar -xzf wing_full.tar.gz
cd wing
# 3. 환경변수 설정 (3-4 참조)
vi backend/.env
vi frontend/.env
# 4. 실행 (node_modules가 이미 포함되어 있으므로 npm install 불필요)
cd backend && npm run dev # 터미널 1
cd frontend && npm run dev # 터미널 2
```
### 4-4. 오프라인 환경 주의사항
- `npm install` 불가하므로 패키지 추가 시 온라인 환경에서 설치 후 재배포해야 한다.
- DB는 대상 환경에서 접근 가능한 PostgreSQL을 사용해야 한다.
- 공공데이터 API (기상, 해양) 키가 필요한 기능은 외부 네트워크 접근이 필요하다.
---
## 5. DB 초기화 및 마이그레이션
### 5-1. DB 구성
프로젝트는 동일 PostgreSQL 서버에 2개의 데이터베이스를 사용한다:
| DB | 용도 | 확장 |
|----|------|------|
| `wing` | 운영 데이터 (사고, 예측, 자산 등) | PostGIS |
| `wing_auth` | 인증/권한 (사용자, 역할, 메뉴) | uuid-ossp, pgcrypto |
### 5-2. 신규 DB 초기화
PostgreSQL에 최초 설치 시 아래 순서로 실행한다.
```bash
# 1. wing DB 초기화 (PostgreSQL superuser로 실행)
psql -U postgres -f database/init.sql
# 2. wing_auth DB 초기화
psql -U postgres -f database/auth_init.sql
```
`init.sql`에 포함된 내용:
- 사용자(wing) 및 데이터베이스(wing) 생성
- PostGIS 확장 설치
- 공통코드, 시뮬레이션, 사고, 예측 등 핵심 테이블 생성
`auth_init.sql`에 포함된 내용:
- 사용자(wing_auth) 및 데이터베이스(wing_auth) 생성
- uuid-ossp, pgcrypto 확장 설치
- 조직, 역할, 사용자, 권한, 메뉴, 설정, 감사로그 테이블 생성
### 5-3. 마이그레이션 적용
초기 스키마 이후 추가된 변경 사항은 `database/migration/` 디렉토리에 순번대로 관리된다.
```
database/migration/
├── 001_layer_table.sql 레이어 테이블
├── 002_hns_substance.sql HNS 물질 데이터
├── 003_perm_tree.sql 권한 트리
├── 004_oper_cd.sql 오퍼레이션 코드
├── 005_db_consolidation.sql DB 통합
├── 006_board.sql 게시판
├── 007_reports.sql 보고서
├── 008_assets.sql 자산
├── 008_assets_seed.sql 자산 시드 데이터
├── 009_incidents.sql 사건/사고
├── 010_postgis_geom.sql PostGIS 지오메트리
├── 011_scat.sql SCAT 조사
├── 012_board_ext.sql 게시판 확장
├── 013_hns_analysis.sql HNS 분석
├── 014_prediction.sql 확산 예측
├── 015_aerial.sql 항공 방제
└── 016_rescue.sql 구조 시나리오
```
**마이그레이션 실행 (수동):**
```bash
# wing DB에 순번대로 적용
psql -h <호스트> -p 5432 -U wing -d wing -f database/migration/001_layer_table.sql
psql -h <호스트> -p 5432 -U wing -d wing -f database/migration/002_hns_substance.sql
# ... 순번대로 계속
```
> 이미 적용된 마이그레이션을 다시 실행해도 대부분 `IF NOT EXISTS` 조건이 포함되어 있어 안전하다.
### 5-4. 시드 데이터
초기 데이터(관리자 계정, 공통코드 등)를 입력한다:
```bash
cd backend
npm run db:seed
```
---
## 6. 개발 서버 실행
### 6-1. 명령어 요약
**Frontend:**
| 명령어 | 설명 |
|--------|------|
| `npm run dev` | Vite 개발 서버 (localhost:5173, HMR 지원) |
| `npm run build` | 프로덕션 빌드 (`tsc -b && vite build` -> `dist/`) |
| `npm run lint` | ESLint 검증 |
| `npm run preview` | 빌드 결과 미리보기 |
**Backend:**
| 명령어 | 설명 |
|--------|------|
| `npm run dev` | tsx watch 개발 서버 (localhost:3001, 파일 변경 시 자동 재시작) |
| `npm run build` | TypeScript 컴파일 (`tsc` -> `dist/`) |
| `npm start` | 프로덕션 실행 (`node dist/server.js`) |
| `npm run db:seed` | DB 시드 데이터 입력 |
### 6-2. 개발 서버 시작
```bash
# 터미널 1: 백엔드 (먼저 실행)
cd backend
npm run dev
# 서버가 포트 3001에서 실행 중입니다.
# wing DB 연결 성공 (211.208.115.83:5432/wing)
# 터미널 2: 프론트엔드
cd frontend
npm run dev
# VITE v7.x.x ready in XXXms
# -> Local: http://localhost:5173/
```
### 6-3. 검증 명령어
```bash
# TypeScript 타입 체크
cd frontend && npx tsc --noEmit
cd backend && npx tsc --noEmit
# ESLint
cd frontend && npx eslint src/
# Prettier
cd frontend && npx prettier --check src/
cd frontend && npx prettier --write src/ # 자동 수정
```
---
## 7. 운영 서버 배포
### 7-1. 서버 환경 준비
운영 서버(Rocky Linux 9 기준)에 다음을 설치한다:
```bash
# Node.js 20 설치 (fnm 또는 직접 설치)
curl -fsSL https://fnm.vercel.app/install | bash
source ~/.bashrc
fnm install 20
fnm use 20
# PostgreSQL 16 + PostGIS 설치 (dnf 또는 직접 설치)
# 이미 설치된 경우 생략
```
### 7-2. 수동 배포 절차
#### Frontend
```bash
cd frontend
# 의존성 설치
npm ci
# 환경변수 설정 (빌드 시 Vite가 주입)
export VITE_API_URL=/api
export VITE_GOOGLE_CLIENT_ID=<클라이언트ID>
export VITE_DATA_GO_KR_API_KEY=<키>
export VITE_WEATHER_API_KEY=<키>
# 프로덕션 빌드
npx vite build
# 빌드 결과를 웹 서버 디렉토리로 복사
cp -r dist/* /deploy/wing-demo/
```
#### Backend
```bash
cd backend
# 의존성 설치
npm ci
# TypeScript 컴파일
npx tsc
# 프로덕션 의존성만 유지 (devDependencies 제거)
npm prune --omit=dev
# 배포 디렉토리로 복사
mkdir -p /deploy/wing-demo-backend/dist
cp -r dist/* /deploy/wing-demo-backend/dist/
cp -r node_modules /deploy/wing-demo-backend/
cp package.json /deploy/wing-demo-backend/
```
### 7-3. systemd 서비스 등록
Backend를 systemd 서비스로 등록하여 자동 시작/재시작을 설정한다.
**서비스 파일 생성** (`/etc/systemd/system/wing-demo-api.service`):
```ini
[Unit]
Description=WING-OPS Backend API
After=network.target postgresql.service
[Service]
Type=simple
User=deploy
WorkingDirectory=/deploy/wing-demo-backend
ExecStart=/home/deploy/.local/share/fnm/aliases/default/bin/node dist/server.js
Restart=on-failure
RestartSec=5
# 환경변수
Environment=NODE_ENV=production
Environment=PORT=3001
Environment=WING_DB_HOST=211.208.115.83
Environment=WING_DB_PORT=5432
Environment=WING_DB_USER=wing
Environment=WING_DB_PASS=<비밀번호>
Environment=WING_DB_NAME=wing
Environment=AUTH_DB_HOST=211.208.115.83
Environment=AUTH_DB_PORT=5432
Environment=AUTH_DB_USER=wing_auth
Environment=AUTH_DB_PASS=<비밀번호>
Environment=AUTH_DB_NAME=wing_auth
Environment=JWT_SECRET=<시크릿>
Environment=JWT_EXPIRES_IN=24h
Environment=FRONTEND_URL=https://wing-demo.gc-si.dev
Environment=GOOGLE_CLIENT_ID=<클라이언트ID>
[Install]
WantedBy=multi-user.target
```
**서비스 등록 및 시작:**
```bash
sudo systemctl daemon-reload
sudo systemctl enable wing-demo-api
sudo systemctl start wing-demo-api
# 상태 확인
sudo systemctl status wing-demo-api
# 로그 확인
sudo journalctl -u wing-demo-api -f
```
### 7-4. 리버스 프록시 (Nginx)
Frontend 정적 파일과 Backend API를 하나의 도메인으로 서비스한다.
```nginx
server {
listen 443 ssl;
server_name wing-demo.gc-si.dev;
ssl_certificate /etc/ssl/certs/wing-demo.crt;
ssl_certificate_key /etc/ssl/private/wing-demo.key;
# Frontend 정적 파일
root /deploy/wing-demo;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
# Backend API 프록시
location /api/ {
proxy_pass http://127.0.0.1:3001/api/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# 헬스 체크
location /health {
proxy_pass http://127.0.0.1:3001/health;
}
}
# HTTP -> HTTPS 리다이렉트
server {
listen 80;
server_name wing-demo.gc-si.dev;
return 301 https://$host$request_uri;
}
```
---
## 8. CI/CD 자동 배포
### 8-1. Gitea Actions 파이프라인
main 브랜치에 push(머지)되면 `.gitea/workflows/deploy.yml`이 자동 실행된다.
```
main 브랜치 push
|
[Checkout] 소스 코드 체크아웃
|
[Setup Node.js] Node.js 24 설정
|
[Configure npm] Nexus 프록시 레지스트리 설정
|
[Frontend Build] npm ci -> vite build -> /deploy/wing-demo/
|
[Backend Build] npm ci -> tsc -> npm prune --omit=dev
| -> /deploy/wing-demo-backend/
|
[Deploy Trigger] .deploy-trigger 파일 생성
-> cron/watchdog이 감지 -> 서비스 재시작
```
### 8-2. Gitea Secrets 설정
CI/CD에서 사용하는 시크릿을 Gitea에 등록해야 한다:
**등록 경로:** Settings -> Actions -> Secrets -> Add Secret
| Secret 이름 | 용도 |
|-------------|------|
| `NEXUS_NPM_AUTH` | npm Nexus 프록시 인증 토큰 |
| `GOOGLE_CLIENT_ID` | Google OAuth 클라이언트 ID |
| `DATA_GO_KR_API_KEY` | 공공데이터 API 키 |
| `WEATHER_API_KEY` | 기상 API 키 |
### 8-3. 배포 확인
```bash
# 프론트엔드 응답 확인
curl -s -o /dev/null -w '%{http_code}' https://wing-demo.gc-si.dev/
# 기대: 200
# 백엔드 헬스 체크
curl -s https://wing-demo.gc-si.dev/api/health
# 기대: {"status":"ok"}
# 백엔드 API 정보
curl -s https://wing-demo.gc-si.dev/api/
# 기대: {"name":"WING Backend API","version":"1.0.0","status":"running"}
```
---
## 9. 접속 정보 요약
### 개발 환경
| 서비스 | URL | 포트 |
|--------|-----|------|
| Frontend (Vite dev) | http://localhost:5173 | 5173 |
| Backend API | http://localhost:3001 | 3001 |
| Backend 헬스 체크 | http://localhost:3001/health | 3001 |
| PostgreSQL (wing) | 211.208.115.83:5432/wing | 5432 |
| PostgreSQL (wing_auth) | 211.208.115.83:5432/wing_auth | 5432 |
### 운영 환경
| 서비스 | URL |
|--------|-----|
| Frontend | https://wing-demo.gc-si.dev |
| Backend API | https://wing-demo.gc-si.dev/api/ |
| 헬스 체크 | https://wing-demo.gc-si.dev/api/health |
| Gitea | https://gitea.gc-si.dev/gc/wing-ops |
### API 엔드포인트
| 경로 | 용도 |
|------|------|
| `GET /` | API 정보 |
| `GET /health` | 헬스 체크 |
| `POST /api/auth/login` | 로그인 |
| `POST /api/auth/logout` | 로그아웃 |
| `GET /api/auth/me` | 현재 사용자 |
| `GET /api/users` | 사용자 목록 |
| `GET /api/roles` | 역할 목록 |
| `GET /api/menus` | 메뉴 목록 |
| `GET /api/settings` | 시스템 설정 |
| `GET /api/audit` | 감사 로그 |
| `GET /api/board` | 게시판 |
| `GET /api/layers` | 레이어 |
| `GET /api/simulation` | 시뮬레이션 |
| `GET /api/hns` | HNS 물질 |
| `GET /api/reports` | 보고서 |
| `GET /api/assets` | 자산 |
| `GET /api/incidents` | 사건/사고 |
| `GET /api/scat` | SCAT 조사 |
| `GET /api/prediction` | 확산 예측 |
| `GET /api/aerial` | 항공 방제 |
| `GET /api/rescue` | 구조 시나리오 |
---
## 10. 트러블슈팅
### 의존성 설치
| 증상 | 원인 | 해결 |
|------|------|------|
| `npm install` 실패 (ENETUNREACH) | Nexus 프록시 접근 불가 | `.npmrc`의 registry URL 확인, VPN/네트워크 상태 확인 |
| `npm install` 실패 (EAUTH) | Nexus 인증 토큰 만료 | `.npmrc``_auth` 값 갱신 |
| `npm install` 실패 (peer dependency) | 패키지 버전 충돌 | `npm install --legacy-peer-deps` 시도 |
| `MODULE_NOT_FOUND` | node_modules 누락/손상 | `rm -rf node_modules package-lock.json && npm install` |
### 서버 실행
| 증상 | 원인 | 해결 |
|------|------|------|
| 포트 5173/3001 충돌 | 이미 다른 프로세스가 사용 중 | `lsof -i :5173` 또는 `lsof -i :3001`로 PID 확인 후 `kill -9 <PID>` |
| 백엔드 시작 시 DB 연결 실패 | DB 접속 정보 오류 또는 네트워크 | `backend/.env`의 DB 호스트/포트/사용자/비밀번호 확인, `nc -zv <호스트> 5432`로 네트워크 확인 |
| 프론트엔드에서 API 호출 CORS 에러 | 백엔드 CORS 설정 불일치 | `backend/.env``FRONTEND_URL`이 프론트엔드 URL과 일치하는지 확인 |
| `env: tsx: No such file or directory` | tsx 미설치 | `cd backend && npm install` 재실행 |
### DB
| 증상 | 원인 | 해결 |
|------|------|------|
| `relation "XXX" does not exist` | 테이블 미생성 | `database/init.sql` 또는 해당 마이그레이션 SQL 실행 |
| PostGIS 함수 에러 | PostGIS 확장 미설치 | `psql -U postgres -d wing -c "CREATE EXTENSION IF NOT EXISTS postgis"` |
| 인코딩 문제 (한글 깨짐) | DB 인코딩 설정 | DB 생성 시 `ENCODING='UTF8' LC_COLLATE='ko_KR.UTF-8'` 지정 |
| seed 실패 | 이미 데이터 존재 또는 FK 제약 | 에러 메시지 확인 후 해당 테이블 데이터 정리 |
### Git Hooks
| 증상 | 원인 | 해결 |
|------|------|------|
| pre-commit이 실행되지 않음 | hooksPath 미설정 | `git config core.hooksPath .githooks` |
| pre-commit permission denied | 실행 권한 없음 | `chmod +x .githooks/pre-commit .githooks/commit-msg` |
| TypeScript 에러로 커밋 차단 | 타입 에러 존재 | `npx tsc --noEmit`으로 에러 확인 후 수정 |
| commit-msg 형식 오류 | Conventional Commits 미준수 | `type(scope): subject` 형식 준수 (type: feat, fix, docs 등) |
### 빌드
| 증상 | 원인 | 해결 |
|------|------|------|
| `vite build` 메모리 부족 | Node.js 힙 메모리 부족 | `export NODE_OPTIONS=--max-old-space-size=4096` 후 재시도 |
| `tsc` 타입 에러 | TypeScript strict 모드 위반 | `npx tsc --noEmit`으로 에러 목록 확인 후 수정 |
| 빌드 후 라우팅 404 | SPA 서버 설정 누락 | Nginx `try_files $uri $uri/ /index.html` 설정 확인 |