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`); +});