🚀 자동 배포 시스템 설정
- Prisma Client darwin-arm64 바이너리 타겟 추가 - 웹훅 기반 자동 배포 스크립트 추가 - PM2 설정 파일들 추가 - 배포 가이드 문서 추가
This commit is contained in:
171
DEPLOYMENT.md
Normal file
171
DEPLOYMENT.md
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
# 🚀 자동 배포 설정 가이드
|
||||||
|
|
||||||
|
## 서버 설정 단계
|
||||||
|
|
||||||
|
### 1. 서버에 프로젝트 클론
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 서버에서 실행
|
||||||
|
cd /var/www/music/
|
||||||
|
git clone https://git.poptong.net/poptong/music-admin.git
|
||||||
|
cd music-admin
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 환경 변수 설정
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# .env 파일 생성
|
||||||
|
nano .env
|
||||||
|
```
|
||||||
|
|
||||||
|
다음 내용 추가:
|
||||||
|
|
||||||
|
```env
|
||||||
|
DATABASE_URL="postgresql://username:password@localhost:5432/music_admin?schema=public"
|
||||||
|
JWT_SECRET="your-secure-jwt-secret-key"
|
||||||
|
NODE_ENV="production"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 의존성 설치 및 빌드
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
npx prisma generate
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. PM2 설치 및 설정
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# PM2 전역 설치
|
||||||
|
npm install -g pm2
|
||||||
|
|
||||||
|
# 웹훅 서버 시작
|
||||||
|
pm2 start webhook-ecosystem.config.js
|
||||||
|
|
||||||
|
# 메인 앱 시작
|
||||||
|
pm2 start ecosystem.config.js
|
||||||
|
|
||||||
|
# PM2 자동 시작 설정
|
||||||
|
pm2 startup
|
||||||
|
pm2 save
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 웹훅 서버 포트 열기
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# UFW 사용 시
|
||||||
|
sudo ufw allow 9000
|
||||||
|
|
||||||
|
# 또는 iptables 사용 시
|
||||||
|
sudo iptables -A INPUT -p tcp --dport 9000 -j ACCEPT
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Nginx 설정 (선택사항)
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
# /etc/nginx/sites-available/music-admin
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name youtooplay.com;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://localhost:3000;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection 'upgrade';
|
||||||
|
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_cache_bypass $http_upgrade;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Gitea 웹훅 설정
|
||||||
|
|
||||||
|
### 1. Gitea 저장소 설정
|
||||||
|
|
||||||
|
1. https://git.poptong.net/ 접속
|
||||||
|
2. music-admin 저장소로 이동
|
||||||
|
3. Settings → Webhooks 클릭
|
||||||
|
4. "Add Webhook" → "Gitea" 선택
|
||||||
|
|
||||||
|
### 2. 웹훅 설정
|
||||||
|
|
||||||
|
- **Payload URL**: `http://youtooplay.com:9000/webhook`
|
||||||
|
- **Content Type**: `application/json`
|
||||||
|
- **Secret**: `your-webhook-secret-key` (webhook-server.js의 SECRET과 동일)
|
||||||
|
- **Events**: `Push` 체크
|
||||||
|
- **Branch**: `main` 선택
|
||||||
|
|
||||||
|
### 3. 웹훅 테스트
|
||||||
|
|
||||||
|
- "Test Delivery" 버튼으로 테스트
|
||||||
|
- 서버 로그 확인: `pm2 logs music-admin-webhook`
|
||||||
|
|
||||||
|
## 배포 테스트
|
||||||
|
|
||||||
|
### 1. 코드 변경 후 push
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add .
|
||||||
|
git commit -m "테스트 배포"
|
||||||
|
git push origin main
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 배포 로그 확인
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 웹훅 서버 로그
|
||||||
|
pm2 logs music-admin-webhook
|
||||||
|
|
||||||
|
# 메인 앱 로그
|
||||||
|
pm2 logs music-admin
|
||||||
|
|
||||||
|
# 배포 스크립트 로그
|
||||||
|
tail -f /var/log/music-admin-deploy.log
|
||||||
|
```
|
||||||
|
|
||||||
|
## 문제 해결
|
||||||
|
|
||||||
|
### 1. 권한 문제
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 배포 스크립트 실행 권한
|
||||||
|
chmod +x deploy.sh
|
||||||
|
|
||||||
|
# 프로젝트 디렉토리 권한
|
||||||
|
sudo chown -R $USER:$USER /var/www/music/music-admin
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. PM2 프로세스 관리
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 프로세스 상태 확인
|
||||||
|
pm2 status
|
||||||
|
|
||||||
|
# 프로세스 재시작
|
||||||
|
pm2 restart music-admin
|
||||||
|
pm2 restart music-admin-webhook
|
||||||
|
|
||||||
|
# 프로세스 중지
|
||||||
|
pm2 stop music-admin
|
||||||
|
pm2 stop music-admin-webhook
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 포트 확인
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 포트 사용 확인
|
||||||
|
netstat -tlnp | grep :3000
|
||||||
|
netstat -tlnp | grep :9000
|
||||||
|
```
|
||||||
|
|
||||||
|
## 보안 고려사항
|
||||||
|
|
||||||
|
1. **웹훅 시크릿**: 강력한 시크릿 키 사용
|
||||||
|
2. **방화벽**: 필요한 포트만 열기
|
||||||
|
3. **SSL**: HTTPS 사용 권장
|
||||||
|
4. **환경 변수**: 민감한 정보는 환경 변수로 관리
|
||||||
66
deploy.sh
Executable file
66
deploy.sh
Executable file
@@ -0,0 +1,66 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# 배포 스크립트 - 서버에서 실행
|
||||||
|
# 사용법: ./deploy.sh
|
||||||
|
|
||||||
|
set -e # 오류 발생 시 스크립트 중단
|
||||||
|
|
||||||
|
# 프로젝트 경로
|
||||||
|
PROJECT_DIR="/var/www/music/music-admin"
|
||||||
|
LOG_FILE="/var/log/music-admin-deploy.log"
|
||||||
|
|
||||||
|
# 로그 함수
|
||||||
|
log() {
|
||||||
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
log "=== 배포 시작 ==="
|
||||||
|
|
||||||
|
# 프로젝트 디렉토리로 이동
|
||||||
|
cd "$PROJECT_DIR" || {
|
||||||
|
log "ERROR: 프로젝트 디렉토리로 이동 실패: $PROJECT_DIR"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Git pull
|
||||||
|
log "Git pull 실행 중..."
|
||||||
|
git pull origin main || {
|
||||||
|
log "ERROR: Git pull 실패"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# 의존성 설치
|
||||||
|
log "의존성 설치 중..."
|
||||||
|
npm ci --production=false || {
|
||||||
|
log "ERROR: npm install 실패"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Prisma 클라이언트 생성
|
||||||
|
log "Prisma 클라이언트 생성 중..."
|
||||||
|
npx prisma generate || {
|
||||||
|
log "ERROR: Prisma generate 실패"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# 데이터베이스 마이그레이션 (필요시)
|
||||||
|
log "데이터베이스 마이그레이션 실행 중..."
|
||||||
|
npx prisma db push || {
|
||||||
|
log "WARNING: 데이터베이스 마이그레이션 실패 (계속 진행)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 프로덕션 빌드
|
||||||
|
log "프로덕션 빌드 중..."
|
||||||
|
npm run build || {
|
||||||
|
log "ERROR: 빌드 실패"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# PM2로 앱 재시작
|
||||||
|
log "PM2로 앱 재시작 중..."
|
||||||
|
pm2 restart music-admin || pm2 start ecosystem.config.js || {
|
||||||
|
log "ERROR: PM2 재시작 실패"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
log "=== 배포 완료 ==="
|
||||||
24
ecosystem.config.js
Normal file
24
ecosystem.config.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
module.exports = {
|
||||||
|
apps: [
|
||||||
|
{
|
||||||
|
name: "music-admin",
|
||||||
|
script: ".output/server/index.mjs",
|
||||||
|
cwd: "/var/www/music/music-admin",
|
||||||
|
instances: 1,
|
||||||
|
autorestart: true,
|
||||||
|
watch: false,
|
||||||
|
max_memory_restart: "1G",
|
||||||
|
env: {
|
||||||
|
NODE_ENV: "production",
|
||||||
|
PORT: 3000,
|
||||||
|
DATABASE_URL:
|
||||||
|
"postgresql://username:password@localhost:5432/music_admin?schema=public",
|
||||||
|
JWT_SECRET: "your-jwt-secret-key-here",
|
||||||
|
},
|
||||||
|
error_file: "/var/log/music-admin-error.log",
|
||||||
|
out_file: "/var/log/music-admin-out.log",
|
||||||
|
log_file: "/var/log/music-admin.log",
|
||||||
|
time: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
25
prisma/schema.prisma
Normal file
25
prisma/schema.prisma
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
// This is your Prisma schema file,
|
||||||
|
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||||||
|
|
||||||
|
generator client {
|
||||||
|
provider = "prisma-client-js"
|
||||||
|
binaryTargets = ["native", "darwin-arm64"]
|
||||||
|
}
|
||||||
|
|
||||||
|
datasource db {
|
||||||
|
provider = "postgresql"
|
||||||
|
url = env("DATABASE_URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
model members {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
user_id String @unique
|
||||||
|
password String
|
||||||
|
name String
|
||||||
|
role_level Int
|
||||||
|
created_at DateTime @default(now())
|
||||||
|
updated_at DateTime @updatedAt
|
||||||
|
|
||||||
|
@@map("members")
|
||||||
|
}
|
||||||
|
|
||||||
20
webhook-ecosystem.config.js
Normal file
20
webhook-ecosystem.config.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
module.exports = {
|
||||||
|
apps: [
|
||||||
|
{
|
||||||
|
name: "music-admin-webhook",
|
||||||
|
script: "webhook-server.js",
|
||||||
|
cwd: "/var/www/music/music-admin",
|
||||||
|
instances: 1,
|
||||||
|
autorestart: true,
|
||||||
|
watch: false,
|
||||||
|
max_memory_restart: "100M",
|
||||||
|
env: {
|
||||||
|
NODE_ENV: "production",
|
||||||
|
},
|
||||||
|
error_file: "/var/log/music-admin-webhook-error.log",
|
||||||
|
out_file: "/var/log/music-admin-webhook-out.log",
|
||||||
|
log_file: "/var/log/music-admin-webhook.log",
|
||||||
|
time: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
72
webhook-server.js
Normal file
72
webhook-server.js
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
const express = require("express");
|
||||||
|
const { exec } = require("child_process");
|
||||||
|
const crypto = require("crypto");
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
const PORT = 9000;
|
||||||
|
const SECRET = "your-webhook-secret-key"; // 보안을 위해 변경하세요
|
||||||
|
const PROJECT_DIR = "/var/www/music/music-admin";
|
||||||
|
|
||||||
|
// JSON 파싱 미들웨어
|
||||||
|
app.use(express.json());
|
||||||
|
|
||||||
|
// 웹훅 시크릿 검증 함수
|
||||||
|
function verifySignature(payload, signature, secret) {
|
||||||
|
const expectedSignature =
|
||||||
|
"sha256=" +
|
||||||
|
crypto.createHmac("sha256", secret).update(payload).digest("hex");
|
||||||
|
|
||||||
|
return crypto.timingSafeEqual(
|
||||||
|
Buffer.from(signature),
|
||||||
|
Buffer.from(expectedSignature)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 웹훅 엔드포인트
|
||||||
|
app.post("/webhook", (req, res) => {
|
||||||
|
const signature = req.headers["x-gitea-signature"];
|
||||||
|
const payload = JSON.stringify(req.body);
|
||||||
|
|
||||||
|
console.log(`[${new Date().toISOString()}] 웹훅 수신됨`);
|
||||||
|
|
||||||
|
// 시크릿 검증 (선택사항)
|
||||||
|
if (signature && !verifySignature(payload, signature, SECRET)) {
|
||||||
|
console.log("잘못된 시크릿");
|
||||||
|
return res.status(401).send("Unauthorized");
|
||||||
|
}
|
||||||
|
|
||||||
|
// main 브랜치 push 이벤트만 처리
|
||||||
|
if (req.body.ref === "refs/heads/main") {
|
||||||
|
console.log("main 브랜치 push 감지, 배포 시작...");
|
||||||
|
|
||||||
|
// 배포 스크립트 실행
|
||||||
|
exec(
|
||||||
|
`cd ${PROJECT_DIR} && chmod +x deploy.sh && ./deploy.sh`,
|
||||||
|
(error, stdout, stderr) => {
|
||||||
|
if (error) {
|
||||||
|
console.error(`배포 오류: ${error}`);
|
||||||
|
return res.status(500).send("Deployment failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("배포 성공");
|
||||||
|
console.log(stdout);
|
||||||
|
if (stderr) console.error(stderr);
|
||||||
|
|
||||||
|
res.status(200).send("Deployment successful");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.log("main 브랜치가 아닌 push, 무시됨");
|
||||||
|
res.status(200).send("Ignored");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 헬스체크 엔드포인트
|
||||||
|
app.get("/health", (req, res) => {
|
||||||
|
res.status(200).send("Webhook server is running");
|
||||||
|
});
|
||||||
|
|
||||||
|
app.listen(PORT, () => {
|
||||||
|
console.log(`웹훅 서버가 포트 ${PORT}에서 실행 중입니다.`);
|
||||||
|
console.log(`웹훅 URL: http://youtooplay.com:${PORT}/webhook`);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user