diff --git a/.dockerignore b/.dockerignore index 63a3791..40c18a7 100644 --- a/.dockerignore +++ b/.dockerignore @@ -24,7 +24,7 @@ Thumbs.db *.log npm-debug.log* -# Environment files (use Railway/Vercel environment variables) +# Environment files .env .env.* !.env.example diff --git a/.github/workflows/bot-deploy.yml b/.github/workflows/bot-deploy.yml new file mode 100644 index 0000000..88e2b02 --- /dev/null +++ b/.github/workflows/bot-deploy.yml @@ -0,0 +1,102 @@ +name: πŸ€– Bot Build & Deploy + +on: + push: + branches: [dev] + paths: + - 'packages/bot/**' + - 'packages/shared/**' + - 'packages/bot/Dockerfile' + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: bot-deploy-${{ github.ref }} + cancel-in-progress: true + +env: + ECR_REPOSITORY: study-admin-bot + AWS_REGION: ap-northeast-2 + +jobs: + ci: + name: CI Gate + runs-on: ubuntu-latest + steps: + - name: βœ… μ½”λ“œ 체크아웃 + uses: actions/checkout@v4 + + - name: πŸ“¦ pnpm μ„€μ • + uses: pnpm/action-setup@v4 + + - name: 🟒 Node.js μ„€μ • + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: 'pnpm' + + - name: πŸ“₯ μ˜μ‘΄μ„± μ„€μΉ˜ + run: pnpm install --frozen-lockfile + + - name: πŸ”¨ shared λΉŒλ“œ + run: pnpm --filter @blog-study/shared build + + - name: πŸ” 린트 + run: pnpm --filter @blog-study/bot lint + + - name: πŸ”Ž νƒ€μž… 체크 + run: pnpm --filter @blog-study/bot typecheck + + - name: πŸ§ͺ ν…ŒμŠ€νŠΈ + run: pnpm --filter @blog-study/bot test + + build-and-push: + name: Build and Push to ECR + needs: ci + runs-on: ubuntu-latest + steps: + - name: βœ… μ½”λ“œ 체크아웃 + uses: actions/checkout@v4 + + - name: πŸ—οΈ Docker Buildx μ„€μ • + uses: docker/setup-buildx-action@v3 + + - name: πŸ” AWS 자격 증λͺ… μ„€μ • + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ env.AWS_REGION }} + + - name: 🐳 ECR 둜그인 + id: login-ecr + uses: aws-actions/amazon-ecr-login@v2 + + - name: 🐳 Docker 이미지 λΉŒλ“œ 및 ν‘Έμ‹œ (ARM64) + uses: docker/build-push-action@v6 + with: + context: . + file: ./packages/bot/Dockerfile + push: true + platforms: linux/arm64 + tags: | + ${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPOSITORY }}:${{ github.sha }} + ${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPOSITORY }}:latest + cache-from: type=gha + cache-to: type=gha,mode=max + + deploy: + name: Deploy to EC2 + needs: build-and-push + runs-on: ubuntu-latest + steps: + - name: πŸš€ SSH둜 배포 슀크립트 μ‹€ν–‰ + uses: appleboy/ssh-action@v1.0.3 + with: + host: ${{ secrets.SSH_HOST }} + username: ${{ secrets.SSH_USER }} + key: ${{ secrets.SSH_PRIVATE_KEY }} + script: | + ~/study-admin-bot/deploy.sh ${{ github.sha }} diff --git a/CLAUDE.md b/CLAUDE.md index 589019b..355ac77 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,9 +6,11 @@ ``` packages/ -β”œβ”€β”€ bot/ # Discord 봇 (μŠ€μΌ€μ€„λŸ¬ + 이벀트 ν•Έλ“€λŸ¬λ§Œ, μŠ¬λž˜μ‹œ μ»€λ§¨λ“œ μ—†μŒ) β†’ AWS EC2 배포 +β”œβ”€β”€ bot/ # Discord 봇 (μŠ€μΌ€μ€„λŸ¬ + 이벀트 ν•Έλ“€λŸ¬λ§Œ, μŠ¬λž˜μ‹œ μ»€λ§¨λ“œ μ—†μŒ) β†’ AWS EC2 (Docker) β”œβ”€β”€ web/ # Next.js 16 λŒ€μ‹œλ³΄λ“œ β†’ Vercel 배포 └── shared/ # 곡유 μ½”λ“œ (DB μŠ€ν‚€λ§ˆ, νƒ€μž…, μœ ν‹Έ) +deploy/ +└── bot/ # EC2 배포 슀크립트 (deploy.sh) β€” 리포에 μ»€λ°‹ν•˜μ§€ μ•ŠμŒ, EC2에 직접 배치 ``` **λͺ¨λ…Έλ ˆν¬**: pnpm workspace (`pnpm-workspace.yaml`) @@ -22,7 +24,8 @@ packages/ | Web | Next.js 16 App Router, React 19, shadcn/ui, Tailwind CSS v4, Tiptap (리치 에디터), sonner (ν† μŠ€νŠΈ), Framer Motion (λžœλ”© μ• λ‹ˆλ©”μ΄μ…˜) | | DB | Supabase PostgreSQL + Drizzle ORM (Transaction Pooler, `prepare: false`) | | Auth | Supabase Auth (Discord OAuth) + `@supabase/ssr` | -| 배포 | AWS EC2 (bot), Vercel (web), Supabase (DB + Auth) | +| 배포 | AWS EC2 Docker (bot), Vercel (web), Supabase (DB + Auth) | +| CI/CD | GitHub Actions β†’ ECR β†’ SSH deploy (bot), Vercel Git Integration (web) | ## 개발 λͺ…λ Ήμ–΄ @@ -102,6 +105,9 @@ pnpm --filter @blog-study/bot rss-collect # μˆ˜λ™ RSS μˆ˜μ§‘ (봇 없이) | `packages/web/src/components/landing/landing-client.tsx` | λžœλ”© νŽ˜μ΄μ§€ ν΄λΌμ΄μ–ΈνŠΈ (7μ„Ήμ…˜: Hero, Stats, Bento, HowItWorks, Marquee, CTA, Footer) | | `packages/web/src/components/landing/motion.tsx` | λžœλ”© μ• λ‹ˆλ©”μ΄μ…˜ μ»΄ν¬λ„ŒνŠΈ (FadeUp, StaggerContainer, CountUp, DrawLine) | | `packages/web/public/logo.svg` | ν’€ 둜고 SVG (ν”½ν† κ·Έλž¨ + ν…μŠ€νŠΈ) | +| `packages/bot/Dockerfile` | 봇 Docker 이미지 (multi-stage, node:22-alpine) | +| `.github/workflows/bot-deploy.yml` | 봇 CI/CD (CI Gate β†’ ECR λΉŒλ“œ/ν‘Έμ‹œ β†’ SSH 배포) | +| `.github/workflows/ci.yml` | PR/push CI (lint, typecheck, test, build) | ## 인증 ꡬ쑰 @@ -219,6 +225,15 @@ npx drizzle-kit push --force | `docs/plans/26-03-08-landing-page-redesign-design.md` | λžœλ”© νŽ˜μ΄μ§€ λ¦¬λ””μžμΈ λ””μžμΈ λ¬Έμ„œ | | `docs/plans/26-03-08-landing-page-redesign.md` | λžœλ”© νŽ˜μ΄μ§€ κ΅¬ν˜„ ν”Œλžœ | +## 봇 배포 (CI/CD) + +- **νŒŒμ΄ν”„λΌμΈ**: `dev` push β†’ CI Gate (lint+typecheck+test) β†’ ECR λΉŒλ“œ(ARM64) β†’ SSH 배포 +- **트리거**: `packages/bot/**`, `packages/shared/**` λ³€κ²½ μ‹œ + `workflow_dispatch` +- **EC2**: illdan-mgmt (t4g ARM64), `~/study-admin-bot/deploy.sh` + `.env` +- **ECR**: `101548339709.dkr.ecr.ap-northeast-2.amazonaws.com/study-admin-bot` +- **deploy.sh**: ECR 둜그인 β†’ pull β†’ μ»¨ν…Œμ΄λ„ˆ ꡐ체 β†’ health check β†’ Discord μ›Ήν›… μ•Œλ¦Ό +- **주의**: `deploy/bot/deploy.sh`λŠ” μ»€λ°‹ν•˜μ§€ μ•ŠμŒ (EC2에 직접 배치) + ## docs 파일λͺ… μ»¨λ²€μ…˜ `yy-mm-dd-{μ„€λͺ…}.md` β€” 예: `26-03-03-system-architecture.md` diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index d747ac4..e3f31e1 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -31,7 +31,7 @@ graph TB end end - subgraph Bot["Discord Bot Β· AWS EC2"] + subgraph Bot["Discord Bot Β· AWS EC2 (Docker)"] SCH["Schedulers
pg-boss"] EVT["Event Handlers
discord.js v14"] SVC["Service Layer
RSS Β· Fine Β· Curation Β· Score"] @@ -126,7 +126,7 @@ graph LR | νŒ¨ν‚€μ§€ | μ„€λͺ… | 배포 | |--------|------|------| | `packages/shared` | Drizzle μŠ€ν‚€λ§ˆ, νƒ€μž…, μœ ν‹Έ | npm (workspace λ‚΄λΆ€) | -| `packages/bot` | Discord 봇 (μŠ€μΌ€μ€„λŸ¬ + 이벀트 ν•Έλ“€λŸ¬, μŠ¬λž˜μ‹œ μ»€λ§¨λ“œ μ—†μŒ) | AWS EC2 | +| `packages/bot` | Discord 봇 (μŠ€μΌ€μ€„λŸ¬ + 이벀트 ν•Έλ“€λŸ¬, μŠ¬λž˜μ‹œ μ»€λ§¨λ“œ μ—†μŒ) | AWS EC2 (Docker) | | `packages/web` | Next.js λŒ€μ‹œλ³΄λ“œ, API Routes | Vercel | ## 인증 μ•„ν‚€ν…μ²˜ @@ -435,12 +435,22 @@ erDiagram ```mermaid graph LR + subgraph GHA["GitHub Actions"] + CI["CI Gate
(lint+typecheck+test)"] + ECR_PUSH["Docker Build
β†’ ECR Push (ARM64)"] + SSH["SSH Deploy"] + end + subgraph Vercel["Vercel (ICN)"] WEB_DEPLOY["@blog-study/web
Next.js"] end - subgraph EC2["AWS EC2"] - BOT_DEPLOY["@blog-study/bot
discord.js + pm2"] + subgraph EC2["AWS EC2 (t4g)"] + BOT_DEPLOY["@blog-study/bot
Docker Container"] + end + + subgraph AWS["AWS"] + ECR["ECR
study-admin-bot"] end subgraph Supabase["Supabase (ap-northeast-2)"] @@ -448,6 +458,11 @@ graph LR SA["Supabase Auth"] end + CI --> ECR_PUSH + ECR_PUSH --> ECR + ECR_PUSH --> SSH + SSH --> BOT_DEPLOY + BOT_DEPLOY -->|pull| ECR WEB_DEPLOY --> PG WEB_DEPLOY --> SA BOT_DEPLOY --> PG diff --git a/packages/bot/.dockerignore b/packages/bot/.dockerignore deleted file mode 100644 index 392f954..0000000 --- a/packages/bot/.dockerignore +++ /dev/null @@ -1,36 +0,0 @@ -# Dependencies -node_modules - -# Build output (will be built in container) -dist - -# Development files -*.test.ts -*.spec.ts -vitest.config.ts -tsconfig.tsbuildinfo - -# IDE -.vscode -.idea - -# OS -.DS_Store -Thumbs.db - -# Logs -*.log -npm-debug.log* - -# Environment files (use Railway environment variables) -.env -.env.* -!.env.example - -# Git -.git -.gitignore - -# Documentation -*.md -!README.md diff --git a/packages/bot/Dockerfile b/packages/bot/Dockerfile index b591372..6a0268b 100644 --- a/packages/bot/Dockerfile +++ b/packages/bot/Dockerfile @@ -1,7 +1,6 @@ # Build stage -FROM node:20-alpine AS builder +FROM node:22-alpine AS builder -# Install pnpm RUN corepack enable && corepack prepare pnpm@9.0.0 --activate WORKDIR /app @@ -11,7 +10,6 @@ COPY pnpm-workspace.yaml package.json pnpm-lock.yaml ./ COPY packages/shared/package.json ./packages/shared/ COPY packages/bot/package.json ./packages/bot/ -# Install dependencies RUN pnpm install --frozen-lockfile # Copy source code @@ -19,32 +17,26 @@ COPY tsconfig.json ./ COPY packages/shared ./packages/shared COPY packages/bot ./packages/bot -# Build shared package first, then bot -RUN pnpm --filter @blog-study/shared build -RUN pnpm --filter @blog-study/bot build +# Build shared β†’ bot +RUN pnpm --filter @blog-study/shared build && \ + pnpm --filter @blog-study/bot build # Production stage -FROM node:20-alpine AS runner +FROM node:22-alpine AS runner -# Install pnpm RUN corepack enable && corepack prepare pnpm@9.0.0 --activate WORKDIR /app -# Copy workspace configuration COPY pnpm-workspace.yaml package.json pnpm-lock.yaml ./ COPY packages/shared/package.json ./packages/shared/ COPY packages/bot/package.json ./packages/bot/ -# Install production dependencies only RUN pnpm install --frozen-lockfile --prod -# Copy built files COPY --from=builder /app/packages/shared/dist ./packages/shared/dist COPY --from=builder /app/packages/bot/dist ./packages/bot/dist -# Set environment ENV NODE_ENV=production -# Run the bot CMD ["node", "packages/bot/dist/index.js"] diff --git a/packages/bot/railway.json b/packages/bot/railway.json deleted file mode 100644 index d8af1cd..0000000 --- a/packages/bot/railway.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "$schema": "https://railway.app/railway.schema.json", - "build": { - "builder": "DOCKERFILE", - "dockerfilePath": "packages/bot/Dockerfile", - "watchPatterns": [ - "packages/bot/**", - "packages/shared/**" - ] - }, - "deploy": { - "startCommand": "node packages/bot/dist/index.js", - "healthcheckPath": null, - "healthcheckTimeout": 300, - "restartPolicyType": "ON_FAILURE", - "restartPolicyMaxRetries": 10 - } -}