chore: CI/CD 파이프라인 + 배포 설정 + 루트 정리
- .gitea/workflows/deploy.yml: main merge 시 frontend/backend 자동 빌드·배포 - deploy/kcg-backend.service: systemd 서비스 (JDK 17, 2~4GB 힙) - deploy/nginx-kcg.conf: SSL + SPA 서빙 + API 프록시 + 외부 API CORS 프록시 - .githooks/pre-commit: 모노레포 대응 (frontend tsc+eslint, backend mvn compile) - .gitignore: frontend/backend/prediction 각각 빌드 산출물 추가 - CLAUDE.md: 모노레포 구조 반영 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
부모
512020d6bb
커밋
fea77361d8
75
.gitea/workflows/deploy.yml
Normal file
75
.gitea/workflows/deploy.yml
Normal file
@ -0,0 +1,75 @@
|
||||
name: Deploy KCG
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# ═══ Frontend ═══
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '24'
|
||||
|
||||
- name: Configure npm registry
|
||||
run: |
|
||||
echo "registry=https://nexus.gc-si.dev/repository/npm-public/" > frontend/.npmrc
|
||||
echo "//nexus.gc-si.dev/repository/npm-public/:_auth=${{ secrets.NEXUS_NPM_AUTH }}" >> frontend/.npmrc
|
||||
|
||||
- name: Build frontend
|
||||
working-directory: frontend
|
||||
env:
|
||||
VITE_GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }}
|
||||
run: |
|
||||
npm ci
|
||||
npx vite build
|
||||
|
||||
- name: Deploy frontend
|
||||
run: |
|
||||
mkdir -p /deploy/kcg
|
||||
rm -rf /deploy/kcg/*
|
||||
cp -r frontend/dist/* /deploy/kcg/
|
||||
|
||||
# ═══ Backend ═══
|
||||
- name: Install JDK 17 + Maven
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y -qq openjdk-17-jdk-headless maven
|
||||
|
||||
- name: Build backend
|
||||
working-directory: backend
|
||||
run: mvn -B clean package -DskipTests
|
||||
|
||||
- name: Deploy backend
|
||||
run: |
|
||||
DEPLOY_DIR=/deploy/kcg-backend
|
||||
mkdir -p $DEPLOY_DIR/backup
|
||||
|
||||
# JAR 백업 (최근 5개 유지)
|
||||
if [ -f $DEPLOY_DIR/kcg.jar ]; then
|
||||
cp $DEPLOY_DIR/kcg.jar $DEPLOY_DIR/backup/kcg-$(date +%Y%m%d%H%M%S).jar
|
||||
ls -t $DEPLOY_DIR/backup/*.jar | tail -n +6 | xargs -r rm
|
||||
fi
|
||||
|
||||
# 서비스 중지 → JAR 교체 → 서비스 시작
|
||||
sudo systemctl stop kcg-backend || true
|
||||
cp backend/target/kcg.jar $DEPLOY_DIR/kcg.jar
|
||||
sudo systemctl start kcg-backend
|
||||
|
||||
- name: Health check
|
||||
run: |
|
||||
echo "Waiting for backend to start..."
|
||||
for i in $(seq 1 30); do
|
||||
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/api/auth/me 2>/dev/null || echo "000")
|
||||
if [ "$HTTP_CODE" != "000" ]; then
|
||||
echo "Backend is up (HTTP $HTTP_CODE, attempt $i)"
|
||||
exit 0
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
echo "Backend health check failed after 60s"
|
||||
exit 1
|
||||
@ -1,54 +1,88 @@
|
||||
#!/bin/bash
|
||||
#==============================================================================
|
||||
# pre-commit hook (React TypeScript)
|
||||
# TypeScript 컴파일 + 린트 검증 — 실패 시 커밋 차단
|
||||
# pre-commit hook (모노레포: Frontend + Backend)
|
||||
# Frontend: TypeScript 컴파일 + 린트 검증
|
||||
# Backend: Maven 컴파일 검증
|
||||
# 실패 시 커밋 차단
|
||||
#==============================================================================
|
||||
|
||||
echo "pre-commit: TypeScript 타입 체크 중..."
|
||||
FAILED=0
|
||||
|
||||
# npm 확인
|
||||
if ! command -v npx &>/dev/null; then
|
||||
echo "경고: npx가 설치되지 않았습니다. 검증을 건너뜁니다."
|
||||
exit 0
|
||||
fi
|
||||
#------------------------------------------------------------------------------
|
||||
# Frontend 검증
|
||||
#------------------------------------------------------------------------------
|
||||
if [ -d "frontend" ]; then
|
||||
echo "pre-commit: [Frontend] TypeScript 타입 체크 중..."
|
||||
|
||||
# node_modules 확인
|
||||
if [ ! -d "node_modules" ]; then
|
||||
echo "경고: node_modules가 없습니다. 'npm install' 실행 후 다시 시도하세요."
|
||||
exit 1
|
||||
fi
|
||||
if ! command -v npx &>/dev/null; then
|
||||
echo "경고: npx가 설치되지 않았습니다. Frontend 검증을 건너뜁니다."
|
||||
elif [ ! -d "frontend/node_modules" ]; then
|
||||
echo "경고: frontend/node_modules가 없습니다. 'cd frontend && npm install' 실행 후 다시 시도하세요."
|
||||
FAILED=1
|
||||
else
|
||||
(cd frontend && npx tsc --noEmit --pretty 2>&1)
|
||||
TSC_RESULT=$?
|
||||
|
||||
# TypeScript 타입 체크
|
||||
npx tsc --noEmit --pretty 2>&1
|
||||
TSC_RESULT=$?
|
||||
|
||||
if [ $TSC_RESULT -ne 0 ]; then
|
||||
if [ $TSC_RESULT -ne 0 ]; then
|
||||
echo ""
|
||||
echo "╔══════════════════════════════════════════════════════════╗"
|
||||
echo "║ TypeScript 타입 에러! 커밋이 차단되었습니다. ║"
|
||||
echo "║ 타입 에러를 수정한 후 다시 커밋해주세요. ║"
|
||||
echo "║ [Frontend] TypeScript 타입 에러! 커밋이 차단되었습니다.║"
|
||||
echo "╚══════════════════════════════════════════════════════════╝"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
FAILED=1
|
||||
else
|
||||
echo "pre-commit: [Frontend] 타입 체크 성공"
|
||||
fi
|
||||
|
||||
echo "pre-commit: 타입 체크 성공"
|
||||
|
||||
# ESLint 검증 (설정 파일이 있는 경우만)
|
||||
if [ -f ".eslintrc.js" ] || [ -f ".eslintrc.json" ] || [ -f ".eslintrc.cjs" ] || [ -f "eslint.config.js" ] || [ -f "eslint.config.mjs" ]; then
|
||||
echo "pre-commit: ESLint 검증 중..."
|
||||
npx eslint src/ --ext .ts,.tsx --quiet 2>&1
|
||||
# ESLint 검증
|
||||
if [ -f "frontend/eslint.config.js" ] || [ -f "frontend/eslint.config.mjs" ] || [ -f "frontend/.eslintrc.js" ] || [ -f "frontend/.eslintrc.json" ]; then
|
||||
echo "pre-commit: [Frontend] ESLint 검증 중..."
|
||||
(cd frontend && npx eslint src/ --ext .ts,.tsx --quiet 2>&1)
|
||||
LINT_RESULT=$?
|
||||
|
||||
if [ $LINT_RESULT -ne 0 ]; then
|
||||
echo ""
|
||||
echo "╔══════════════════════════════════════════════════════════╗"
|
||||
echo "║ ESLint 에러! 커밋이 차단되었습니다. ║"
|
||||
echo "║ 'npm run lint -- --fix'로 자동 수정을 시도해보세요. ║"
|
||||
echo "║ [Frontend] ESLint 에러! 커밋이 차단되었습니다. ║"
|
||||
echo "╚══════════════════════════════════════════════════════════╝"
|
||||
echo ""
|
||||
exit 1
|
||||
FAILED=1
|
||||
else
|
||||
echo "pre-commit: [Frontend] ESLint 통과"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "pre-commit: ESLint 통과"
|
||||
fi
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Backend 검증
|
||||
#------------------------------------------------------------------------------
|
||||
if [ -d "backend" ] && [ -f "backend/pom.xml" ]; then
|
||||
echo "pre-commit: [Backend] Maven 컴파일 검증 중..."
|
||||
|
||||
if ! command -v mvn &>/dev/null; then
|
||||
echo "경고: mvn이 설치되지 않았습니다. Backend 검증을 건너뜁니다."
|
||||
else
|
||||
(cd backend && mvn compile -q 2>&1)
|
||||
MVN_RESULT=$?
|
||||
|
||||
if [ $MVN_RESULT -ne 0 ]; then
|
||||
echo ""
|
||||
echo "╔══════════════════════════════════════════════════════════╗"
|
||||
echo "║ [Backend] Maven 컴파일 에러! 커밋이 차단되었습니다. ║"
|
||||
echo "╚══════════════════════════════════════════════════════════╝"
|
||||
FAILED=1
|
||||
else
|
||||
echo "pre-commit: [Backend] 컴파일 성공"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# 결과
|
||||
#------------------------------------------------------------------------------
|
||||
if [ $FAILED -ne 0 ]; then
|
||||
echo ""
|
||||
echo "pre-commit: 검증 실패! 에러를 수정한 후 다시 커밋해주세요."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "pre-commit: 모든 검증 통과"
|
||||
|
||||
15
.gitignore
vendored
15
.gitignore
vendored
@ -68,3 +68,18 @@ dist-ssr
|
||||
*.sln
|
||||
*.sw?
|
||||
*.pdf
|
||||
|
||||
# === Frontend ===
|
||||
frontend/dist/
|
||||
frontend/node_modules/
|
||||
|
||||
# === Backend ===
|
||||
backend/target/
|
||||
backend/.env
|
||||
backend/src/main/resources/application-local.yml
|
||||
backend/src/main/resources/application-prod.yml
|
||||
|
||||
# === Prediction ===
|
||||
prediction/__pycache__/
|
||||
prediction/venv/
|
||||
prediction/.env
|
||||
|
||||
102
CLAUDE.md
102
CLAUDE.md
@ -1,48 +1,90 @@
|
||||
# 프로젝트 개요
|
||||
|
||||
- **타입**: React + TypeScript + Vite
|
||||
- **타입**: 모노레포 (Frontend + Backend + Prediction)
|
||||
- **Frontend**: React + TypeScript + Vite
|
||||
- **Backend**: Spring Boot 3.2.5 + Java 17 + PostgreSQL
|
||||
- **Prediction**: FastAPI (Python)
|
||||
- **Node.js**: `.node-version` 참조
|
||||
- **패키지 매니저**: npm
|
||||
- **빌드 도구**: Vite
|
||||
- **Java**: `backend/.sdkmanrc` 참조
|
||||
- **패키지 매니저**: npm (frontend), Maven (backend), pip (prediction)
|
||||
|
||||
## 빌드 및 실행
|
||||
|
||||
### Frontend
|
||||
```bash
|
||||
# 의존성 설치
|
||||
cd frontend
|
||||
npm install
|
||||
|
||||
# 개발 서버
|
||||
npm run dev
|
||||
```
|
||||
|
||||
# 빌드
|
||||
npm run build
|
||||
### Backend
|
||||
```bash
|
||||
cd backend
|
||||
# application-local.yml 설정 필요 (application-local.yml.example 참조)
|
||||
cp src/main/resources/application-local.yml.example src/main/resources/application-local.yml
|
||||
mvn spring-boot:run -Dspring-boot.run.profiles=local
|
||||
```
|
||||
|
||||
# 테스트
|
||||
npm run test
|
||||
### Database
|
||||
```bash
|
||||
psql -U postgres -f database/init.sql
|
||||
psql -U postgres -d kcgdb -f database/migration/001_initial_schema.sql
|
||||
```
|
||||
|
||||
# 린트
|
||||
npm run lint
|
||||
### Prediction
|
||||
```bash
|
||||
cd prediction
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
uvicorn main:app --reload --port 8000
|
||||
```
|
||||
|
||||
# 포맷팅
|
||||
npm run format
|
||||
### 린트/검증
|
||||
```bash
|
||||
# Frontend
|
||||
cd frontend && npm run lint
|
||||
|
||||
# Backend
|
||||
cd backend && mvn compile
|
||||
```
|
||||
|
||||
## 프로젝트 구조
|
||||
|
||||
```
|
||||
src/
|
||||
├── assets/ # 정적 리소스 (이미지, 폰트 등)
|
||||
├── components/ # 공통 UI 컴포넌트
|
||||
│ ├── common/ # 범용 컴포넌트 (Button, Input 등)
|
||||
│ └── layout/ # 레이아웃 컴포넌트 (Header, Sidebar 등)
|
||||
├── hooks/ # 커스텀 훅
|
||||
├── pages/ # 페이지 컴포넌트 (라우팅 단위)
|
||||
├── services/ # API 호출 로직
|
||||
├── store/ # 상태 관리 (Context, Zustand 등)
|
||||
├── types/ # TypeScript 타입 정의
|
||||
├── utils/ # 유틸리티 함수
|
||||
├── App.tsx
|
||||
└── main.tsx
|
||||
frontend/
|
||||
├── src/
|
||||
│ ├── assets/
|
||||
│ ├── components/
|
||||
│ ├── hooks/
|
||||
│ ├── pages/
|
||||
│ ├── services/
|
||||
│ ├── store/
|
||||
│ ├── types/
|
||||
│ ├── utils/
|
||||
│ ├── App.tsx
|
||||
│ └── main.tsx
|
||||
├── package.json
|
||||
└── vite.config.ts
|
||||
|
||||
backend/
|
||||
├── src/main/java/gc/mda/kcg/
|
||||
│ ├── config/ # 설정 (CORS, Security, Properties)
|
||||
│ ├── auth/ # 인증 (Google OAuth + JWT)
|
||||
│ ├── domain/ # 도메인 (event, news, osint, aircraft)
|
||||
│ └── collector/ # 데이터 수집기 (GDELT, GoogleNews, CENTCOM)
|
||||
├── src/main/resources/
|
||||
│ ├── application.yml
|
||||
│ └── application-*.yml.example
|
||||
└── pom.xml
|
||||
|
||||
database/
|
||||
├── init.sql
|
||||
└── migration/
|
||||
|
||||
prediction/
|
||||
├── main.py
|
||||
└── requirements.txt
|
||||
```
|
||||
|
||||
## 팀 규칙
|
||||
@ -55,6 +97,6 @@ src/
|
||||
|
||||
## 의존성 관리
|
||||
|
||||
- Nexus 프록시 레포지토리를 통해 npm 패키지 관리 (`.npmrc`)
|
||||
- 새 의존성 추가: `npm install 패키지명`
|
||||
- devDependency: `npm install -D 패키지명`
|
||||
- Frontend: Nexus 프록시 레포지토리를 통해 npm 패키지 관리 (`.npmrc`)
|
||||
- Backend: Maven Central (pom.xml)
|
||||
- Prediction: pip (requirements.txt)
|
||||
|
||||
25
deploy/kcg-backend.service
Normal file
25
deploy/kcg-backend.service
Normal file
@ -0,0 +1,25 @@
|
||||
[Unit]
|
||||
Description=KCG Monitoring Backend
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
Group=root
|
||||
WorkingDirectory=/deploy/kcg-backend
|
||||
ExecStart=/usr/lib/jvm/java-17-openjdk-17.0.18.0.8-1.el9.x86_64/bin/java \
|
||||
-Xms2g -Xmx4g \
|
||||
-Dspring.profiles.active=prod \
|
||||
-Dspring.config.additional-location=file:/deploy/kcg-backend/ \
|
||||
-jar /deploy/kcg-backend/kcg.jar
|
||||
|
||||
Restart=on-failure
|
||||
RestartSec=10
|
||||
TimeoutStopSec=30
|
||||
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
SyslogIdentifier=kcg-backend
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
107
deploy/nginx-kcg.conf
Normal file
107
deploy/nginx-kcg.conf
Normal file
@ -0,0 +1,107 @@
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name kcg.gc-si.dev;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/gitea.gc-si.dev/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/gitea.gc-si.dev/privkey.pem;
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf;
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
||||
|
||||
# ── Frontend SPA ──
|
||||
root /deploy/kcg;
|
||||
# Static cache
|
||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
# ── Backend API (direct) ──
|
||||
location /api/ {
|
||||
proxy_pass http://127.0.0.1:8080/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;
|
||||
proxy_read_timeout 30s;
|
||||
}
|
||||
|
||||
# ── Backend API (dev prefix 호환) ──
|
||||
location /api/kcg/ {
|
||||
rewrite ^/api/kcg/(.*) /api/$1 break;
|
||||
proxy_pass http://127.0.0.1:8080;
|
||||
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;
|
||||
proxy_read_timeout 30s;
|
||||
}
|
||||
|
||||
# ── signal-batch 프록시 (선박 위치 API) ──
|
||||
location /signal-batch/ {
|
||||
proxy_pass https://wing.gc-si.dev/signal-batch/;
|
||||
proxy_set_header Host wing.gc-si.dev;
|
||||
proxy_ssl_server_name on;
|
||||
proxy_read_timeout 30s;
|
||||
}
|
||||
|
||||
# ── 선박 이미지 프록시 ──
|
||||
location /shipimg/ {
|
||||
proxy_pass https://wing.gc-si.dev/shipimg/;
|
||||
proxy_set_header Host wing.gc-si.dev;
|
||||
proxy_ssl_server_name on;
|
||||
}
|
||||
|
||||
# ── 외부 API 프록시 (프론트엔드 CORS 우회) ──
|
||||
location /api/airplaneslive/ {
|
||||
proxy_pass https://api.airplanes.live/;
|
||||
proxy_set_header Host api.airplanes.live;
|
||||
proxy_ssl_server_name on;
|
||||
}
|
||||
|
||||
location /api/opensky/ {
|
||||
proxy_pass https://opensky-network.org/;
|
||||
proxy_set_header Host opensky-network.org;
|
||||
proxy_ssl_server_name on;
|
||||
}
|
||||
|
||||
location /api/celestrak/ {
|
||||
proxy_pass https://celestrak.org/;
|
||||
proxy_set_header Host celestrak.org;
|
||||
proxy_ssl_server_name on;
|
||||
proxy_set_header User-Agent "Mozilla/5.0 (compatible; KCG-Monitor/1.0)";
|
||||
}
|
||||
|
||||
location /api/gdelt/ {
|
||||
proxy_pass https://api.gdeltproject.org/;
|
||||
proxy_set_header Host api.gdeltproject.org;
|
||||
proxy_ssl_server_name on;
|
||||
}
|
||||
|
||||
location /api/rss/ {
|
||||
proxy_pass https://news.google.com/;
|
||||
proxy_set_header Host news.google.com;
|
||||
proxy_ssl_server_name on;
|
||||
}
|
||||
|
||||
location /api/ais/ {
|
||||
proxy_pass https://aisapi.maritime.spglobal.com/;
|
||||
proxy_set_header Host aisapi.maritime.spglobal.com;
|
||||
proxy_ssl_server_name on;
|
||||
}
|
||||
|
||||
# gzip
|
||||
gzip on;
|
||||
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
|
||||
gzip_min_length 1024;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name kcg.gc-si.dev;
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
불러오는 중...
Reference in New Issue
Block a user