From f331b52e647d67520737a440af06bd776fc81a69 Mon Sep 17 00:00:00 2001 From: poptong Date: Wed, 1 Oct 2025 01:40:46 +0900 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=80=20=EC=9E=90=EB=8F=99=20=EB=B0=B0?= =?UTF-8?q?=ED=8F=AC=20=EC=8B=9C=EC=8A=A4=ED=85=9C=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Prisma Client darwin-arm64 바이너리 타겟 추가 - 웹훅 기반 자동 배포 스크립트 추가 - PM2 설정 파일들 추가 - 배포 가이드 문서 추가 --- DEPLOYMENT.md | 171 ++++++++++++++++++++++++++++++++++++ deploy.sh | 66 ++++++++++++++ ecosystem.config.js | 24 +++++ prisma/schema.prisma | 25 ++++++ webhook-ecosystem.config.js | 20 +++++ webhook-server.js | 72 +++++++++++++++ 6 files changed, 378 insertions(+) create mode 100644 DEPLOYMENT.md create mode 100755 deploy.sh create mode 100644 ecosystem.config.js create mode 100644 prisma/schema.prisma create mode 100644 webhook-ecosystem.config.js create mode 100644 webhook-server.js diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md new file mode 100644 index 0000000..dfd021c --- /dev/null +++ b/DEPLOYMENT.md @@ -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. **환경 변수**: 민감한 정보는 환경 변수로 관리 diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..ab3512e --- /dev/null +++ b/deploy.sh @@ -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 "=== 배포 완료 ===" diff --git a/ecosystem.config.js b/ecosystem.config.js new file mode 100644 index 0000000..e1ffa2e --- /dev/null +++ b/ecosystem.config.js @@ -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, + }, + ], +}; diff --git a/prisma/schema.prisma b/prisma/schema.prisma new file mode 100644 index 0000000..d397fcc --- /dev/null +++ b/prisma/schema.prisma @@ -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") +} + diff --git a/webhook-ecosystem.config.js b/webhook-ecosystem.config.js new file mode 100644 index 0000000..a4b4ac6 --- /dev/null +++ b/webhook-ecosystem.config.js @@ -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, + }, + ], +}; diff --git a/webhook-server.js b/webhook-server.js new file mode 100644 index 0000000..3e6a4a9 --- /dev/null +++ b/webhook-server.js @@ -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`); +});