From 3c10907a973a579dc2136503ff0653b18157984e Mon Sep 17 00:00:00 2001 From: Mayne0213 Date: Sat, 22 Nov 2025 23:44:51 +0900 Subject: [PATCH] INIT(app): initial commit - Initialize project structure - Add base configuration --- .dockerignore | 70 + .github/workflows/build.yml | 69 + .github/workflows/ci.yml | 45 + .github/workflows/deploy.yml | 111 + .gitignore | 52 + deploy/docker/Dockerfile.dev | 26 + deploy/docker/Dockerfile.prod | 59 + deploy/docker/docker-compose.dev.yml | 27 + deploy/docker/docker-compose.yml | 22 + deploy/k8s/app-deployment.yaml | 50 + deploy/k8s/app-service.yaml | 16 + scripts/common.sh | 180 + scripts/docker-build.sh | 96 + scripts/docker-cleanup.sh | 21 + scripts/k8s-cleanup.sh | 112 + scripts/k8s-deploy.sh | 163 + services/nextjs/.eslintrc.json | 3 + services/nextjs/README.md | 36 + .../nextjs/app/(movies)/movies/[id]/error.tsx | 5 + .../app/(movies)/movies/[id]/loading.tsx | 3 + .../nextjs/app/(movies)/movies/[id]/page.tsx | 26 + services/nextjs/app/about-us/page.tsx | 3 + services/nextjs/app/favicon.ico | Bin 0 -> 12465 bytes services/nextjs/app/layout.tsx | 18 + services/nextjs/app/page.tsx | 30 + services/nextjs/components/movie-info.tsx | 29 + services/nextjs/components/movie-videos.tsx | 25 + services/nextjs/components/movie.tsx | 24 + services/nextjs/components/navigation.tsx | 23 + services/nextjs/next.config.mjs | 6 + services/nextjs/package-lock.json | 5478 +++++++++++++++++ services/nextjs/package.json | 26 + services/nextjs/postcss.config.mjs | 8 + services/nextjs/styles/global.css | 154 + services/nextjs/styles/home.module.css | 8 + services/nextjs/styles/movie-info.module.css | 27 + .../nextjs/styles/movie-videos.module.css | 19 + services/nextjs/styles/movie.module.css | 26 + services/nextjs/styles/navigation.module.css | 28 + services/nextjs/tailwind.config.ts | 20 + services/nextjs/tsconfig.json | 26 + 41 files changed, 7170 insertions(+) create mode 100644 .dockerignore create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/deploy.yml create mode 100644 .gitignore create mode 100644 deploy/docker/Dockerfile.dev create mode 100644 deploy/docker/Dockerfile.prod create mode 100644 deploy/docker/docker-compose.dev.yml create mode 100644 deploy/docker/docker-compose.yml create mode 100644 deploy/k8s/app-deployment.yaml create mode 100644 deploy/k8s/app-service.yaml create mode 100755 scripts/common.sh create mode 100755 scripts/docker-build.sh create mode 100755 scripts/docker-cleanup.sh create mode 100755 scripts/k8s-cleanup.sh create mode 100755 scripts/k8s-deploy.sh create mode 100644 services/nextjs/.eslintrc.json create mode 100644 services/nextjs/README.md create mode 100644 services/nextjs/app/(movies)/movies/[id]/error.tsx create mode 100644 services/nextjs/app/(movies)/movies/[id]/loading.tsx create mode 100644 services/nextjs/app/(movies)/movies/[id]/page.tsx create mode 100644 services/nextjs/app/about-us/page.tsx create mode 100644 services/nextjs/app/favicon.ico create mode 100644 services/nextjs/app/layout.tsx create mode 100644 services/nextjs/app/page.tsx create mode 100644 services/nextjs/components/movie-info.tsx create mode 100644 services/nextjs/components/movie-videos.tsx create mode 100644 services/nextjs/components/movie.tsx create mode 100644 services/nextjs/components/navigation.tsx create mode 100644 services/nextjs/next.config.mjs create mode 100644 services/nextjs/package-lock.json create mode 100644 services/nextjs/package.json create mode 100644 services/nextjs/postcss.config.mjs create mode 100644 services/nextjs/styles/global.css create mode 100644 services/nextjs/styles/home.module.css create mode 100644 services/nextjs/styles/movie-info.module.css create mode 100644 services/nextjs/styles/movie-videos.module.css create mode 100644 services/nextjs/styles/movie.module.css create mode 100644 services/nextjs/styles/navigation.module.css create mode 100644 services/nextjs/tailwind.config.ts create mode 100644 services/nextjs/tsconfig.json diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..35589ea --- /dev/null +++ b/.dockerignore @@ -0,0 +1,70 @@ +# Dependencies +node_modules +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Next.js +.next/ +out/ +build/ + +# Production +dist/ + +# Environment variables +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# Logs +logs +*.log + +# Misc +.DS_Store +*.pem + +# Vercel +.vercel + +# TypeScript +*.tsbuildinfo +next-env.d.ts + +# Git +.git +.gitignore + +# Docker +Dockerfile* +docker-compose* +.dockerignore + +# Documentation +README.md +*.md + +# Scripts +scripts/ +deploy/ + +# Coverage +coverage/ +.nyc_output/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db + +# Trunk +.trunk diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..1132309 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,69 @@ +name: Build Docker Image + +on: + push: + branches: [main] + tags: + - 'v*' + workflow_dispatch: + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + build-and-push: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + outputs: + image-tag: ${{ steps.meta.outputs.tags }} + image-digest: ${{ steps.build.outputs.digest }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=sha,prefix={{branch}}- + type=raw,value=latest,enable={{is_default_branch}} + + - name: Build and push Docker image + id: build + uses: docker/build-push-action@v5 + with: + context: ./services/nextjs + file: ./deploy/docker/Dockerfile.prod + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Display image information + run: | + echo "βœ… Image built and pushed successfully!" + echo "πŸ“¦ Image tags:" + echo "${{ steps.meta.outputs.tags }}" + echo "πŸ”– Digest: ${{ steps.build.outputs.digest }}" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..e2879a5 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,45 @@ +name: CI + +on: + push: + branches: [main, develop] + pull_request: + branches: [main, develop] + +jobs: + lint-and-build: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: services/nextjs/package-lock.json + + - name: Install dependencies + working-directory: services/nextjs + run: npm ci + + - name: Run ESLint + working-directory: services/nextjs + run: npm run lint + + - name: Build Next.js application + working-directory: services/nextjs + run: npm run build + env: + NEXT_TELEMETRY_DISABLED: 1 + + - name: Check build output + working-directory: services/nextjs + run: | + if [ ! -d ".next" ]; then + echo "Build failed: .next directory not found" + exit 1 + fi + echo "βœ… Build completed successfully" diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..e966597 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,111 @@ +name: Deploy to Kubernetes + +on: + workflow_run: + workflows: ["Build Docker Image"] + types: + - completed + branches: [main] + workflow_dispatch: + inputs: + image_tag: + description: 'Docker image tag to deploy (e.g., main-abc1234)' + required: false + default: 'latest' + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + K8S_NAMESPACE: jovies + +jobs: + deploy: + runs-on: ubuntu-latest + if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup kubectl + uses: azure/setup-kubectl@v3 + with: + version: 'latest' + + - name: Configure kubectl with Lightsail + run: | + mkdir -p ~/.kube + echo "${{ secrets.KUBE_CONFIG }}" | base64 -d > ~/.kube/config + chmod 600 ~/.kube/config + + # Verify connection + kubectl cluster-info + kubectl get nodes + + - name: Determine image tag + id: image + run: | + if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then + TAG="${{ github.event.inputs.image_tag }}" + else + # Use the commit SHA from the workflow_run event + TAG="main-$(echo ${{ github.sha }} | cut -c1-7)" + fi + + FULL_IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${TAG}" + echo "tag=${TAG}" >> $GITHUB_OUTPUT + echo "full_image=${FULL_IMAGE}" >> $GITHUB_OUTPUT + echo "🐳 Deploying image: ${FULL_IMAGE}" + + - name: Make scripts executable + run: | + chmod +x ./scripts/common.sh + chmod +x ./scripts/k8s-deploy.sh + + - name: Deploy to Kubernetes using script + run: | + ./scripts/k8s-deploy.sh \ + --namespace ${{ env.K8S_NAMESPACE }} \ + --no-build \ + --app-image ${{ steps.image.outputs.full_image }} + env: + TERM: dumb + + - name: Wait for rollout to complete + run: | + echo "⏳ Waiting for deployment rollout..." + kubectl rollout status deployment/jovies-app \ + -n ${{ env.K8S_NAMESPACE }} \ + --timeout=5m + + - name: Verify deployment + run: | + echo "πŸ“Š Deployment status:" + kubectl get deployments -n ${{ env.K8S_NAMESPACE }} + + echo "" + echo "πŸ” Pod status:" + kubectl get pods -n ${{ env.K8S_NAMESPACE }} + + echo "" + echo "🌐 Service status:" + kubectl get services -n ${{ env.K8S_NAMESPACE }} + + - name: Get deployment info + run: | + echo "βœ… Deployment completed!" + echo "" + echo "πŸ“¦ Deployed image: ${{ steps.image.outputs.full_image }}" + echo "🏷️ Namespace: ${{ env.K8S_NAMESPACE }}" + echo "" + echo "πŸ”— Useful commands:" + echo " - View logs: kubectl logs -n ${{ env.K8S_NAMESPACE }} -l app=jovies-app -f" + echo " - Port forward: kubectl port-forward -n ${{ env.K8S_NAMESPACE }} deploy/jovies-app 3000:3000" + echo " - Rollback: kubectl rollout undo deployment/jovies-app -n ${{ env.K8S_NAMESPACE }}" + + - name: Deployment failure notification + if: failure() + run: | + echo "❌ Deployment failed!" + echo "Check logs with: kubectl logs -n ${{ env.K8S_NAMESPACE }} -l app=jovies-app" + exit 1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1ddb3f6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,52 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +node_modules +services/nextjs/node_modules +services/nextjs/.pnp +services/nextjs/.pnp.js +services/nextjs/.yarn/install-state.gz + +# testing +services/nextjs/coverage + +# next.js +services/nextjs/.next/ +services/nextjs/out/ + +# production +services/nextjs/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files +.env* +!.env.example + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts + +# IDE +.vscode +.idea +*.swp +*.swo +*~ + +# OS +Thumbs.db + +# trunk +.trunk diff --git a/deploy/docker/Dockerfile.dev b/deploy/docker/Dockerfile.dev new file mode 100644 index 0000000..b1dd32d --- /dev/null +++ b/deploy/docker/Dockerfile.dev @@ -0,0 +1,26 @@ +# Development Dockerfile for Movie Next.js application +FROM node:20-alpine AS base + +# Install dependencies for development +RUN apk add --no-cache libc6-compat curl + +WORKDIR /app + +# Copy package files +COPY package.json package-lock.json* ./ + +# Install all dependencies (including dev dependencies) +RUN npm ci + +# Copy source code +COPY . . + +# Expose port +EXPOSE 3000 + +# Health check +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD curl -f http://localhost:3000 || exit 1 + +# Default command for development +CMD ["npm", "run", "dev"] diff --git a/deploy/docker/Dockerfile.prod b/deploy/docker/Dockerfile.prod new file mode 100644 index 0000000..69db9e4 --- /dev/null +++ b/deploy/docker/Dockerfile.prod @@ -0,0 +1,59 @@ +# Multi-stage build for Movie Next.js application +FROM node:20-alpine AS base + +# Install dependencies only when needed +FROM base AS deps +# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. +RUN apk add --no-cache libc6-compat curl +WORKDIR /app + +# Install dependencies based on the preferred package manager +COPY package.json package-lock.json* ./ +RUN npm ci + +# Rebuild the source code only when needed +FROM base AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . + +# Build the application +ENV NEXT_TELEMETRY_DISABLED=1 +RUN npm run build + +# Production image, copy all the files and run next +FROM base AS runner +WORKDIR /app + +RUN apk add --no-cache curl + +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs + +# Copy built application +COPY --from=builder /app/public ./public + +# Set the correct permission for prerender cache +RUN mkdir .next +RUN chown nextjs:nodejs .next + +# Automatically leverage output traces to reduce image size +# https://nextjs.org/docs/advanced-features/output-file-tracing +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +USER nextjs + +EXPOSE 3000 + +ENV PORT=3000 +ENV HOSTNAME=0.0.0.0 + +# Health check +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD curl -f http://localhost:3000 || exit 1 + +CMD ["node", "server.js"] diff --git a/deploy/docker/docker-compose.dev.yml b/deploy/docker/docker-compose.dev.yml new file mode 100644 index 0000000..fdebf42 --- /dev/null +++ b/deploy/docker/docker-compose.dev.yml @@ -0,0 +1,27 @@ +services: + # Development Jovies Next.js Application + app: + build: + context: ../../services + dockerfile: ../deploy/docker/Dockerfile.dev + container_name: jovies-app-dev + restart: unless-stopped + labels: + kompose.namespace: jovies + ports: + - 3003:3000 + environment: + - NODE_ENV=development + - WATCHPACK_POLLING=true + networks: + - jovies-network + volumes: + - ../../services:/app + - /app/node_modules + - /app/.next + command: npm run dev + +networks: + jovies-network: + driver: bridge + name: jovies-network-dev diff --git a/deploy/docker/docker-compose.yml b/deploy/docker/docker-compose.yml new file mode 100644 index 0000000..4ee643c --- /dev/null +++ b/deploy/docker/docker-compose.yml @@ -0,0 +1,22 @@ +services: + # Production Jovies Next.js Application + app: + image: jovies-app + build: + context: ../../services/nextjs + dockerfile: ../../deploy/docker/Dockerfile.prod + container_name: jovies-app-prod + restart: unless-stopped + labels: + kompose.namespace: jovies + ports: + - 3003:3000 + environment: + - NODE_ENV=production + networks: + - jovies-network + +networks: + jovies-network: + driver: bridge + name: jovies-network-prod diff --git a/deploy/k8s/app-deployment.yaml b/deploy/k8s/app-deployment.yaml new file mode 100644 index 0000000..06a3864 --- /dev/null +++ b/deploy/k8s/app-deployment.yaml @@ -0,0 +1,50 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: jovies-app + name: jovies-app +spec: + replicas: 1 + selector: + matchLabels: + app: jovies-app + strategy: + type: RollingUpdate + rollingUpdate: + maxUnavailable: 1 + maxSurge: 1 + template: + metadata: + labels: + app: jovies-app + spec: + containers: + - name: jovies-app + image: jovies-app-image:latest + ports: + - containerPort: 3000 + protocol: TCP + env: + - name: NODE_ENV + value: production + resources: + requests: + memory: "512Mi" + cpu: "500m" + limits: + memory: "1Gi" + cpu: "1000m" + livenessProbe: + httpGet: + path: / + port: 3000 + initialDelaySeconds: 30 + periodSeconds: 10 + readinessProbe: + httpGet: + path: / + port: 3000 + initialDelaySeconds: 5 + periodSeconds: 5 + restartPolicy: Always diff --git a/deploy/k8s/app-service.yaml b/deploy/k8s/app-service.yaml new file mode 100644 index 0000000..a9ebafe --- /dev/null +++ b/deploy/k8s/app-service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + app: jovies-app + version: prod + name: jovies-service +spec: + type: ClusterIP + ports: + - name: "http" + port: 80 + targetPort: 3000 + protocol: TCP + selector: + app: jovies-app diff --git a/scripts/common.sh b/scripts/common.sh new file mode 100755 index 0000000..7bcbdc7 --- /dev/null +++ b/scripts/common.sh @@ -0,0 +1,180 @@ +#!/bin/bash + +# Jovies 슀크립트 곡톡 μœ ν‹Έλ¦¬ν‹° ν•¨μˆ˜λ“€ +# λͺ¨λ“  Jovies μŠ€ν¬λ¦½νŠΈμ—μ„œ μ‚¬μš©ν•  수 μžˆλŠ” 곡톡 κΈ°λŠ₯듀을 μ •μ˜ + +set -e + +# 곡톡 슀크립트의 μ ˆλŒ€ 경둜 기반 디렉토리 μƒμˆ˜ +# ν•¨μˆ˜ 호좜 μ»¨ν…μŠ€νŠΈμ— 따라 BASH_SOURCE 해석이 λ‹¬λΌμ§ˆ 수 μžˆμœΌλ―€λ‘œ +# λ‘œλ“œ μ‹œμ μ— κ³ μ •ν•΄ μ‹ λ’° κ°€λŠ₯ν•œ 루트λ₯Ό κ³„μ‚°ν•œλ‹€ +JOVIES_SCRIPTS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +JOVIES_ROOT="$(dirname "$JOVIES_SCRIPTS_DIR")" + +# 색상 μ •μ˜ +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# λ‘œκΉ… ν•¨μˆ˜λ“€ +log_info() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +log_warn() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +log_debug() { + echo -e "${BLUE}[DEBUG]${NC} $1" +} + +# 경둜 계산 ν•¨μˆ˜ +get_jovies_root() { + # λ‘œλ“œ μ‹œμ μ— κ³ μ •λœ 루트 경둜 λ°˜ν™˜ + echo "$JOVIES_ROOT" +} + +get_mayne_root() { + # jovies 루트λ₯Ό κΈ°μ€€μœΌλ‘œ mayne 루트 경둜 계산 + local jovies_root="$(get_jovies_root)" + echo "$(dirname "$(dirname "$jovies_root")")" +} + +# 확인 ν•¨μˆ˜ +confirm_action() { + local message="$1" + local default="${2:-N}" + + if [[ "$default" == "Y" ]]; then + read -p "$message (Y/n): " -n 1 -r + echo + if [[ $REPLY =~ ^[Nn]$ ]]; then + return 1 + fi + return 0 + else + read -p "$message (y/N): " -n 1 -r + echo + if [[ $REPLY =~ ^[Yy]$ ]]; then + return 0 + fi + return 1 + fi +} + +# ν•„μˆ˜ 디렉토리 확인 +check_required_dirs() { + local jovies_root="$(get_jovies_root)" + local dirs=("$@") + + for dir in "${dirs[@]}"; do + if [ ! -d "$jovies_root/$dir" ]; then + log_error "ν•„μˆ˜ 디렉토리가 μ—†μŠ΅λ‹ˆλ‹€: $dir" + exit 1 + fi + done +} + +# ν•„μˆ˜ 파일 확인 +check_required_files() { + local jovies_root="$(get_jovies_root)" + local files=("$@") + + for file in "${files[@]}"; do + if [ ! -f "$jovies_root/$file" ]; then + log_error "ν•„μˆ˜ 파일이 μ—†μŠ΅λ‹ˆλ‹€: $file" + exit 1 + fi + done +} + +# Docker κ΄€λ ¨ μœ ν‹Έλ¦¬ν‹° +docker_cleanup_jovies() { + log_info "Jovies κ΄€λ ¨ Docker λ¦¬μ†ŒμŠ€ 정리 쀑..." + + # μ»¨ν…Œμ΄λ„ˆ 쀑지 및 μ‚­μ œ + docker-compose -p jovies -f deploy/docker/docker-compose.yml down --remove-orphans 2>/dev/null || true + docker-compose -p jovies -f deploy/docker/docker-compose.dev.yml down --remove-orphans 2>/dev/null || true + local containers=$(docker ps -aq --filter "name=jovies" 2>/dev/null) + if [[ -n "$containers" ]]; then + echo "$containers" | xargs docker rm -f 2>/dev/null || true + fi + + # 이미지 μ‚­μ œ + local images=$(docker images --filter "reference=jovies*" -q 2>/dev/null) + if [[ -n "$images" ]]; then + echo "$images" | xargs docker rmi -f 2>/dev/null || true + fi + images=$(docker images --filter "reference=*jovies*" -q 2>/dev/null) + if [[ -n "$images" ]]; then + echo "$images" | xargs docker rmi -f 2>/dev/null || true + fi + + # λ³Όλ₯¨ μ‚­μ œ + local volumes=$(docker volume ls -q --filter "name=jovies" 2>/dev/null) + if [[ -n "$volumes" ]]; then + echo "$volumes" | xargs docker volume rm 2>/dev/null || true + fi + + # λ„€νŠΈμ›Œν¬ μ‚­μ œ + docker network rm jovies-network-dev jovies-network-prod 2>/dev/null || true + + # μ‹œμŠ€ν…œ 정리 + docker system prune -f + + log_info "Docker λ¦¬μ†ŒμŠ€ 정리 μ™„λ£Œ" +} + +# ν™˜κ²½ λ³€μˆ˜ λ‘œλ“œ +load_env_file() { + local jovies_root="$(get_jovies_root)" + local env_file="$jovies_root/.env" + + if [ -f "$env_file" ]; then + log_info "ν™˜κ²½ λ³€μˆ˜ 파일 λ‘œλ“œ: $env_file" + source "$env_file" + return 0 + else + log_warn "ν™˜κ²½ λ³€μˆ˜ 파일이 μ—†μŠ΅λ‹ˆλ‹€: $env_file" + return 1 + fi +} + +# μ—λŸ¬ 처리 +handle_error() { + local exit_code=$? + log_error "슀크립트 μ‹€ν–‰ 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€ (μ’…λ£Œ μ½”λ“œ: $exit_code)" + exit $exit_code +} + +# 슀크립트 μ‹œμž‘ μ‹œ 곡톡 μ„€μ • +setup_script() { + # μ—λŸ¬ λ°œμƒ μ‹œ μžλ™μœΌλ‘œ handle_error ν•¨μˆ˜ 호좜 + trap handle_error ERR + + # 슀크립트 λ””λ ‰ν† λ¦¬λ‘œ 이동 + cd "$(get_jovies_root)" + + log_info "슀크립트 μ‹œμž‘: $(basename "${BASH_SOURCE[1]}")" +} + +# 슀크립트 μ’…λ£Œ μ‹œ 정리 +cleanup_script() { + local exit_code=$? + if [ $exit_code -eq 0 ]; then + log_info "슀크립트 μ™„λ£Œ: $(basename "${BASH_SOURCE[1]}")" + else + log_error "슀크립트 μ‹€νŒ¨: $(basename "${BASH_SOURCE[1]}") (μ’…λ£Œ μ½”λ“œ: $exit_code)" + fi + exit $exit_code +} + +# 슀크립트 μ’…λ£Œ μ‹œ 정리 ν•¨μˆ˜ 등둝 +trap cleanup_script EXIT diff --git a/scripts/docker-build.sh b/scripts/docker-build.sh new file mode 100755 index 0000000..26281a5 --- /dev/null +++ b/scripts/docker-build.sh @@ -0,0 +1,96 @@ +#!/bin/bash + +# Jovies Docker λΉŒλ“œ 및 μ‹€ν–‰ 슀크립트 +# 곡톡 μœ ν‹Έλ¦¬ν‹° ν•¨μˆ˜ λ‘œλ“œ +source "$(dirname "${BASH_SOURCE[0]}")/common.sh" + +# 슀크립트 μ„€μ • +setup_script + +log_info "πŸš€ Jovies Docker λΉŒλ“œ 및 μ‹€ν–‰ μ‹œμž‘..." + +# ν•„μˆ˜ 디렉토리 및 파일 확인 +log_info "πŸ“ 폴더 ꡬ쑰 확인 쀑..." +check_required_dirs "services" "deploy/docker" + +log_info "πŸ“„ ν•„μˆ˜ 파일 확인 쀑..." +check_required_files "deploy/docker/docker-compose.yml" "deploy/docker/docker-compose.dev.yml" "deploy/docker/Dockerfile.prod" "deploy/docker/Dockerfile.dev" + +log_info "βœ… 폴더 ꡬ쑰 및 ν•„μˆ˜ 파일 확인 μ™„λ£Œ!" + +# ν™˜κ²½ 선택 +echo "" +log_info "🎯 μ‹€ν–‰ν•  ν™˜κ²½μ„ μ„ νƒν•˜μ„Έμš”:" +echo "1) 개발 ν™˜κ²½ (Development)" +echo "2) ν”„λ‘œλ•μ…˜ ν™˜κ²½ (Production)" +echo "3) λΉŒλ“œλ§Œ (Build Only)" +read -p "선택 (1-3): " -n 1 -r +echo + +case ${REPLY} in + 1) + log_info "πŸ”§ 개발 ν™˜κ²½ λΉŒλ“œ 및 μ‹€ν–‰ 쀑..." + cd deploy/docker + docker-compose -p jovies -f docker-compose.dev.yml build --no-cache + docker-compose -p jovies -f docker-compose.dev.yml up -d + JOVIES_ROOT=$(get_jovies_root) + cd "${JOVIES_ROOT}" + ENV_TYPE="development" + COMPOSE_FILE_PATH="deploy/docker/docker-compose.dev.yml" + ;; + 2) + log_info "🏭 ν”„λ‘œλ•μ…˜ ν™˜κ²½ λΉŒλ“œ 및 μ‹€ν–‰ 쀑..." + cd deploy/docker + docker-compose -p jovies -f docker-compose.yml build --no-cache + docker-compose -p jovies -f docker-compose.yml up -d + JOVIES_ROOT=$(get_jovies_root) + cd "${JOVIES_ROOT}" + ENV_TYPE="production" + COMPOSE_FILE_PATH="deploy/docker/docker-compose.yml" + ;; + 3) + log_info "πŸ”¨ 이미지 λΉŒλ“œλ§Œ μ‹€ν–‰ 쀑..." + cd deploy/docker + log_info " - 개발 이미지 λΉŒλ“œ 쀑..." + docker-compose -p jovies -f docker-compose.dev.yml build --no-cache + log_info " - ν”„λ‘œλ•μ…˜ 이미지 λΉŒλ“œ 쀑..." + docker-compose -p jovies -f docker-compose.yml build --no-cache + JOVIES_ROOT=$(get_jovies_root) + cd "${JOVIES_ROOT}" + log_info "βœ… λΉŒλ“œ μ™„λ£Œ! μ‹€ν–‰ν•˜λ €λ©΄ λ‹€μ‹œ 이 슀크립트λ₯Ό μ‹€ν–‰ν•˜κ³  ν™˜κ²½μ„ μ„ νƒν•˜μ„Έμš”." + exit 0 + ;; + *) + log_error "잘λͺ»λœ μ„ νƒμž…λ‹ˆλ‹€." + exit 1 + ;; +esac + +# μ„œλΉ„μŠ€ μƒνƒœ 확인 +echo "" +log_info "⏳ μ„œλΉ„μŠ€ μ‹œμž‘ λŒ€κΈ° 쀑..." +sleep 10 + +echo "" +log_info "πŸ“Š μ„œλΉ„μŠ€ μƒνƒœ 확인:" +docker-compose -p jovies -f "$COMPOSE_FILE_PATH" ps + +echo "" +log_info "πŸ” μ»¨ν…Œμ΄λ„ˆ 둜그 확인:" +echo " - μ• ν”Œλ¦¬μΌ€μ΄μ…˜ 둜그: docker-compose -p jovies -f $COMPOSE_FILE_PATH logs -f app" + +echo "" +log_info "🌐 접속 URL:" +if [ "$ENV_TYPE" = "development" ]; then + echo " - μ• ν”Œλ¦¬μΌ€μ΄μ…˜: http://localhost:3002" +else + echo " - μ• ν”Œλ¦¬μΌ€μ΄μ…˜: http://localhost:3002" +fi + +echo "" +log_info "βœ… Docker λΉŒλ“œ 및 μ‹€ν–‰ μ™„λ£Œ!" +echo "" +log_info "πŸ“‹ μœ μš©ν•œ λͺ…λ Ήμ–΄:" +echo " - μ„œλΉ„μŠ€ 쀑지: docker-compose -p jovies -f $COMPOSE_FILE_PATH down" +echo " - 둜그 확인: docker-compose -p jovies -f $COMPOSE_FILE_PATH logs -f" +echo " - μ„œλΉ„μŠ€ μž¬μ‹œμž‘: docker-compose -p jovies -f $COMPOSE_FILE_PATH restart" diff --git a/scripts/docker-cleanup.sh b/scripts/docker-cleanup.sh new file mode 100755 index 0000000..20c1480 --- /dev/null +++ b/scripts/docker-cleanup.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +# Jovies Docker Cleanup Script +# 곡톡 μœ ν‹Έλ¦¬ν‹° ν•¨μˆ˜ λ‘œλ“œ +source "$(dirname "${BASH_SOURCE[0]}")/common.sh" + +# 슀크립트 μ„€μ • +setup_script + +log_info "🧹 Jovies Docker λ¦¬μ†ŒμŠ€ 정리 μ‹œμž‘..." + +# 확인 λ©”μ‹œμ§€ +if ! confirm_action "λͺ¨λ“  Jovies κ΄€λ ¨ Docker λ¦¬μ†ŒμŠ€λ₯Ό μ •λ¦¬ν•˜μ‹œκ² μŠ΅λ‹ˆκΉŒ?"; then + log_info "정리λ₯Ό μ·¨μ†Œν–ˆμŠ΅λ‹ˆλ‹€." + exit 0 +fi + +# Docker λ¦¬μ†ŒμŠ€ 정리 μ‹€ν–‰ +docker_cleanup_jovies + +log_info "βœ… Jovies Docker λ¦¬μ†ŒμŠ€ 정리 μ™„λ£Œ!" diff --git a/scripts/k8s-cleanup.sh b/scripts/k8s-cleanup.sh new file mode 100755 index 0000000..7e8ffa3 --- /dev/null +++ b/scripts/k8s-cleanup.sh @@ -0,0 +1,112 @@ +#!/bin/bash + +# Jovies Kubernetes 정리 슀크립트 +# 곡톡 μœ ν‹Έλ¦¬ν‹° ν•¨μˆ˜ λ‘œλ“œ +source "$(dirname "${BASH_SOURCE[0]}")/common.sh" + +# 슀크립트 μ„€μ • +setup_script + +print_usage() { + echo "" + echo "μ‚¬μš©λ²•: $(basename "$0") [-n NAMESPACE] [--context CONTEXT] [--delete-namespace] [--delete-images] [--force]" + echo " -n, --namespace 정리할 λ„€μž„μŠ€νŽ˜μ΄μŠ€ (κΈ°λ³Έ: jovies)" + echo " --context kubectl μ»¨ν…μŠ€νŠΈ μ§€μ •" + echo " --delete-namespace λ„€μž„μŠ€νŽ˜μ΄μŠ€ 자체λ₯Ό μ‚­μ œ (주의)" + echo " --delete-images 둜컬 Docker의 jovies κ΄€λ ¨ 이미지 μ‚­μ œ" + echo " --force 확인 ν”„λ‘¬ν”„νŠΈ 없이 μ§„ν–‰" + echo "" +} + +# κΈ°λ³Έκ°’ +K8S_NAMESPACE="jovies" +KUBE_CONTEXT="" +DELETE_NAMESPACE="true" +DELETE_IMAGES="true" +FORCE="false" + +# 인자 νŒŒμ‹± +while [[ $# -gt 0 ]]; do + case "$1" in + -n|--namespace) + K8S_NAMESPACE="$2"; shift; shift ;; + --context) + KUBE_CONTEXT="$2"; shift; shift ;; + --delete-namespace) + DELETE_NAMESPACE="true"; shift ;; + --delete-images) + DELETE_IMAGES="true"; shift ;; + --force) + FORCE="true"; shift ;; + -h|--help) + print_usage; exit 0 ;; + *) + log_warn "μ•Œ 수 μ—†λŠ” 인자: $1"; print_usage; exit 1 ;; + esac +done + +# μ»¨ν…μŠ€νŠΈ μ„€μ • +if [[ -n "$KUBE_CONTEXT" ]]; then + log_info "kubectl μ»¨ν…μŠ€νŠΈ μ„€μ •: $KUBE_CONTEXT" + kubectl config use-context "$KUBE_CONTEXT" +else + CURRENT_CTX=$(kubectl config current-context 2>/dev/null || echo "") + if [[ -n "$CURRENT_CTX" ]]; then + log_info "ν˜„μž¬ kubectl μ»¨ν…μŠ€νŠΈ: $CURRENT_CTX" + else + log_warn "kubectl μ»¨ν…μŠ€νŠΈκ°€ μ„€μ •λ˜μ–΄ μžˆμ§€ μ•ŠμŠ΅λ‹ˆλ‹€. kubeconfig λ₯Ό ν™•μΈν•˜μ„Έμš”." + fi +fi + +# λ„€μž„μŠ€νŽ˜μ΄μŠ€ 쑴재 확인 +if ! kubectl get namespace "$K8S_NAMESPACE" >/dev/null 2>&1; then + log_warn "λ„€μž„μŠ€νŽ˜μ΄μŠ€κ°€ μ‘΄μž¬ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€: $K8S_NAMESPACE" + if [[ "$DELETE_IMAGES" == "false" ]]; then + exit 0 + fi +fi + +echo "" +log_info "🧹 정리 λŒ€μƒ μš”μ•½:" +echo " - λ„€μž„μŠ€νŽ˜μ΄μŠ€: $K8S_NAMESPACE" +if [[ "$DELETE_NAMESPACE" == "true" ]]; then echo " - λ™μž‘: λ„€μž„μŠ€νŽ˜μ΄μŠ€ μ‚­μ œ"; else echo " - λ™μž‘: λ„€μž„μŠ€νŽ˜μ΄μŠ€ λ‚΄λΆ€ λ¦¬μ†ŒμŠ€ μ‚­μ œ"; fi +if [[ "$DELETE_IMAGES" == "true" ]]; then echo " - 둜컬 이미지 μ‚­μ œ: ν™œμ„±ν™”"; fi + +# 확인 ν”„λ‘¬ν”„νŠΈ +if [[ "$FORCE" != "true" ]]; then + if ! confirm_action "정리λ₯Ό μ§„ν–‰ν•˜μ‹œκ² μŠ΅λ‹ˆκΉŒ?" "N"; then + log_warn "μ‚¬μš©μžμ— μ˜ν•΄ μ·¨μ†Œλ˜μ—ˆμŠ΅λ‹ˆλ‹€." + exit 0 + fi +fi + +# λ¦¬μ†ŒμŠ€ μ‚­μ œ +if [[ "$DELETE_NAMESPACE" == "true" ]]; then + log_info "πŸ”» λ„€μž„μŠ€νŽ˜μ΄μŠ€ μ‚­μ œ: $K8S_NAMESPACE" + kubectl delete namespace "$K8S_NAMESPACE" --wait=false || true +else + log_info "πŸ—‘οΈ λ„€μž„μŠ€νŽ˜μ΄μŠ€ λ‚΄λΆ€ λ¦¬μ†ŒμŠ€ μ‚­μ œ: $K8S_NAMESPACE" + # κΈ°λ³Έ λ¦¬μ†ŒμŠ€ (배포/μ„œλΉ„μŠ€/ConfigMap/Secret/Job/CronJob λ“±) 일괄 μ‚­μ œ + kubectl -n "$K8S_NAMESPACE" delete all --all || true + # PVC 및 PV μ‚­μ œ (주의: 데이터 손싀) + kubectl -n "$K8S_NAMESPACE" delete pvc --all || true + # λ°”μš΄λ“œ PV 쀑 λ„€μž„μŠ€νŽ˜μ΄μŠ€ μ—°κ΄€ PVCκ°€ μ‚­μ œλœ 경우 κ³ μ•„ PV 제거 μ‹œλ„ + for pv in $(kubectl get pv -o jsonpath='{.items[*].metadata.name}' 2>/dev/null); do + claim_ns=$(kubectl get pv "$pv" -o jsonpath='{.spec.claimRef.namespace}' 2>/dev/null || echo "") + if [[ "$claim_ns" == "$K8S_NAMESPACE" ]]; then + log_info "πŸ“¦ PV μ‚­μ œ: $pv" + kubectl delete pv "$pv" || true + fi + done + # λ‚¨μ•„μžˆλŠ” λ„€μž„μŠ€νŽ˜μ΄μŠ€ μŠ€μ½”ν”„ λ¦¬μ†ŒμŠ€ 정리 + kubectl -n "$K8S_NAMESPACE" delete configmap --all || true + kubectl -n "$K8S_NAMESPACE" delete secret --all || true +fi + +# 둜컬 Docker 이미지 μ‚­μ œ +if [[ "$DELETE_IMAGES" == "true" ]]; then + log_info "🧽 둜컬 Docker 이미지 정리: jovies-app κ΄€λ ¨ νƒœκ·Έ" + docker rmi -f $(docker images --format '{{.Repository}}:{{.Tag}}' | grep -E '^jovies-app:local|^jovies-app:' || true) 2>/dev/null || true +fi + +log_info "βœ… 정리 μž‘μ—… μ™„λ£Œ" diff --git a/scripts/k8s-deploy.sh b/scripts/k8s-deploy.sh new file mode 100755 index 0000000..32362c2 --- /dev/null +++ b/scripts/k8s-deploy.sh @@ -0,0 +1,163 @@ +#!/bin/bash + +# Jovies Kubernetes 배포 슀크립트 +# 곡톡 μœ ν‹Έλ¦¬ν‹° ν•¨μˆ˜ λ‘œλ“œ +source "$(dirname "${BASH_SOURCE[0]}")/common.sh" + +# 슀크립트 μ„€μ • +setup_script + +print_usage() { + echo "" + echo "μ‚¬μš©λ²•: $(basename "$0") [-n NAMESPACE] [-c CONTEXT] [--app-image IMAGE[:TAG]] [--build|--no-build] [--tag TAG] [--dry-run]" + echo " -n, --namespace Kubernetes λ„€μž„μŠ€νŽ˜μ΄μŠ€ (κΈ°λ³Έ: jovies)" + echo " -c, --context kubectl μ»¨ν…μŠ€νŠΈ μ§€μ • (kubectl config current-context κΈ°λ³Έ)" + echo " --app-image app λ””ν”Œλ‘œμ΄λ¨ΌνŠΈμ— μ‚¬μš©ν•  이미지 레퍼런슀(예: your/repo:jovies)" + echo " --build 배포 전에 Dockerfile둜 둜컬 이미지λ₯Ό λΉŒλ“œ (κΈ°λ³Έ)" + echo " --no-build λΉŒλ“œ μƒλž΅" + echo " --tag --build μ‹œ μ‚¬μš©ν•  이미지 νƒœκ·Έ (κΈ°λ³Έ: jovies-app:local-)" + echo " --dry-run μ‹€μ œ 적용 λŒ€μ‹  미리보기 μˆ˜ν–‰" + echo "" +} + +# κΈ°λ³Έκ°’ +K8S_NAMESPACE="jovies" +KUBE_CONTEXT="" +DRY_RUN="false" +APP_IMAGE="" +DO_BUILD="true" +IMAGE_TAG="" + +# 인자 νŒŒμ‹± +while [[ $# -gt 0 ]]; do + case "$1" in + -n|--namespace) + K8S_NAMESPACE="$2"; shift; shift ;; + -c|--context) + KUBE_CONTEXT="$2"; shift; shift ;; + --dry-run) + DRY_RUN="true"; shift ;; + --app-image) + APP_IMAGE="$2"; shift; shift ;; + --build) + DO_BUILD="true"; shift ;; + --no-build) + DO_BUILD="false"; shift ;; + --tag) + IMAGE_TAG="$2"; shift; shift ;; + -h|--help) + print_usage; exit 0 ;; + *) + log_warn "μ•Œ 수 μ—†λŠ” 인자: $1"; print_usage; exit 1 ;; + esac +done + +# 사전 점검 +check_required_dirs "deploy/k8s" + +if ! command -v kubectl >/dev/null 2>&1; then + log_error "kubectl 이 μ„€μΉ˜λ˜μ–΄ μžˆμ§€ μ•ŠμŠ΅λ‹ˆλ‹€. μ„€μΉ˜ ν›„ λ‹€μ‹œ μ‹œλ„ν•˜μ„Έμš”." + exit 1 +fi + +if ! command -v kompose >/dev/null 2>&1; then + log_warn "kompose κ°€ μ„€μΉ˜λ˜μ–΄ μžˆμ§€ μ•ŠμŠ΅λ‹ˆλ‹€. κΈ°μ‘΄ μƒμ„±λœ λ§€λ‹ˆνŽ˜μŠ€νŠΈλ§Œ μ μš©ν•©λ‹ˆλ‹€." +fi + +# K8S 디렉토리 μ„€μ • (prod μ„€μ •λ§Œ μ‚¬μš©) +K8S_DIR="deploy/k8s" + +# μ»¨ν…μŠ€νŠΈ μ„€μ • +if [[ -n "$KUBE_CONTEXT" ]]; then + log_info "kubectl μ»¨ν…μŠ€νŠΈ μ„€μ •: $KUBE_CONTEXT" + kubectl config use-context "$KUBE_CONTEXT" +else + CURRENT_CTX=$(kubectl config current-context 2>/dev/null || echo "") + if [[ -n "$CURRENT_CTX" ]]; then + log_info "ν˜„μž¬ kubectl μ»¨ν…μŠ€νŠΈ: $CURRENT_CTX" + else + log_warn "kubectl μ»¨ν…μŠ€νŠΈκ°€ μ„€μ •λ˜μ–΄ μžˆμ§€ μ•ŠμŠ΅λ‹ˆλ‹€. kubeconfig λ₯Ό ν™•μΈν•˜μ„Έμš”." + fi +fi + +# λ„€μž„μŠ€νŽ˜μ΄μŠ€ 보μž₯ +if [[ "$K8S_NAMESPACE" != "default" ]]; then + if ! kubectl get namespace "$K8S_NAMESPACE" >/dev/null 2>&1; then + log_info "λ„€μž„μŠ€νŽ˜μ΄μŠ€ 생성: $K8S_NAMESPACE" + kubectl create namespace "$K8S_NAMESPACE" + fi +fi + +# λΉŒλ“œκ°€ ν•„μš”ν•œ 경우 둜컬 이미지 λΉŒλ“œ +if [[ "$DO_BUILD" == "true" && "$DRY_RUN" != "true" ]]; then + BUILD_CONTEXT="." + DOCKERFILE_PATH="deploy/docker/Dockerfile.prod" + if [[ -z "$IMAGE_TAG" ]]; then + IMAGE_TAG="jovies-app:local-$(date +%Y%m%d%H%M%S)" + fi + log_info "πŸ”¨ Docker 이미지 λΉŒλ“œ: $IMAGE_TAG" + docker build -t "$IMAGE_TAG" -f "$DOCKERFILE_PATH" "$BUILD_CONTEXT" + # λΉŒλ“œ κ²°κ³Όλ₯Ό 배포 μ΄λ―Έμ§€λ‘œ κΈ°λ³Έ μ„€μ • (λͺ…μ‹œμ μœΌλ‘œ --app-image μ£Όλ©΄ κ·Έ 값이 μš°μ„ ) + if [[ -z "$APP_IMAGE" ]]; then + APP_IMAGE="$IMAGE_TAG" + fi +fi + +echo "" +log_info "πŸ“¦ 적용 λŒ€μƒ 디렉토리: $K8S_DIR" +log_info "🧭 λ„€μž„μŠ€νŽ˜μ΄μŠ€: $K8S_NAMESPACE" +if [[ -n "$APP_IMAGE" ]]; then + log_info "πŸ–ΌοΈ μ μš©ν•  μ•± 이미지: $APP_IMAGE" +fi + +# Dry-run ν”Œλž˜κ·Έ ꡬ성 +APPLY_FLAGS=(apply -f "$K8S_DIR" -n "$K8S_NAMESPACE") +if [[ "$DRY_RUN" == "true" ]]; then + APPLY_FLAGS+=(--dry-run=client) +fi + +# 적용 μ „ μš”μ•½ 및 사전 검증 +log_info "πŸ“ 적용 μš”μ•½:" +echo " - 디렉토리: $K8S_DIR" +echo " - λ„€μž„μŠ€νŽ˜μ΄μŠ€: $K8S_NAMESPACE" +if [[ -n "$KUBE_CONTEXT" ]]; then echo " - μ»¨ν…μŠ€νŠΈ: $KUBE_CONTEXT"; fi +if [[ "$DRY_RUN" == "true" ]]; then echo " - λͺ¨λ“œ: Dry Run"; fi +if grep -qE "^\s*image:\s*app(\s|$)" "$K8S_DIR/app-deployment.yaml" 2>/dev/null; then + if [[ -z "$APP_IMAGE" ]]; then + log_warn "app-deployment.yaml 에 image: app 이 μ„€μ •λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€. μ‹€μ œ λ ˆμ§€μŠ€νŠΈλ¦¬ 이미지λ₯Ό --app-image 둜 μ§€μ •ν•˜λŠ” 것을 ꢌμž₯ν•©λ‹ˆλ‹€." + fi +fi + +# 확인 ν›„ μ§„ν–‰ +if ! confirm_action "계속 μ§„ν–‰ν•˜μ‹œκ² μŠ΅λ‹ˆκΉŒ?" "Y"; then + log_warn "μ‚¬μš©μžμ— μ˜ν•΄ μ·¨μ†Œλ˜μ—ˆμŠ΅λ‹ˆλ‹€." + exit 0 +fi + +echo "" +log_info "πŸš€ Kubernetes λ¦¬μ†ŒμŠ€ 적용 쀑..." +kubectl "${APPLY_FLAGS[@]}" + +# ν•„μš” μ‹œ 이미지 μ˜€λ²„λΌμ΄λ“œ +if [[ -n "$APP_IMAGE" ]]; then + log_info "πŸ”„ μ•± λ””ν”Œλ‘œμ΄λ¨ΌνŠΈ 이미지 μ—…λ°μ΄νŠΈ: $APP_IMAGE" + kubectl -n "$K8S_NAMESPACE" set image deployment/jovies-app jovies-app="$APP_IMAGE" + # latest νƒœκ·Έλ‚˜ κΈ°λ³Έ μ •μ±…μœΌλ‘œ μΈν•œ κ°•μ œ Pull λ°©μ§€ + log_info "πŸ› οΈ 이미지 Pull μ •μ±… 패치: IfNotPresent" + kubectl -n "$K8S_NAMESPACE" patch deployment jovies-app \ + --type='json' \ + -p='[{"op":"replace","path":"/spec/template/spec/containers/0/imagePullPolicy","value":"IfNotPresent"}]' || true +fi + +echo "" +log_info "πŸ“Š λ¦¬μ†ŒμŠ€ μƒνƒœ:" +kubectl get all -n "$K8S_NAMESPACE" + +echo "" +log_info "πŸ“„ μœ μš©ν•œ λͺ…λ Ήμ–΄:" +echo " - μ‚­μ œ: kubectl delete -f $K8S_DIR -n $K8S_NAMESPACE" +echo " - 둜그: kubectl logs deploy/jovies-app -n $K8S_NAMESPACE -f" +echo " - 상세: kubectl describe deploy/jovies-app -n $K8S_NAMESPACE" +echo " - 포트 ν¬μ›Œλ“œ: kubectl port-forward -n $K8S_NAMESPACE deploy/jovies-app 3002:3000" + +log_info "βœ… Kubernetes 배포 μž‘μ—… μ™„λ£Œ" diff --git a/services/nextjs/.eslintrc.json b/services/nextjs/.eslintrc.json new file mode 100644 index 0000000..bffb357 --- /dev/null +++ b/services/nextjs/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "next/core-web-vitals" +} diff --git a/services/nextjs/README.md b/services/nextjs/README.md new file mode 100644 index 0000000..c403366 --- /dev/null +++ b/services/nextjs/README.md @@ -0,0 +1,36 @@ +This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +# or +bun dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. + +This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. + +## Learn More + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. + +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! + +## Deploy on Vercel + +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. + +Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. diff --git a/services/nextjs/app/(movies)/movies/[id]/error.tsx b/services/nextjs/app/(movies)/movies/[id]/error.tsx new file mode 100644 index 0000000..6ccbc4e --- /dev/null +++ b/services/nextjs/app/(movies)/movies/[id]/error.tsx @@ -0,0 +1,5 @@ +"use client" + +export default function Error(){ + return

Something broke...

+} \ No newline at end of file diff --git a/services/nextjs/app/(movies)/movies/[id]/loading.tsx b/services/nextjs/app/(movies)/movies/[id]/loading.tsx new file mode 100644 index 0000000..0a1e768 --- /dev/null +++ b/services/nextjs/app/(movies)/movies/[id]/loading.tsx @@ -0,0 +1,3 @@ +export default function Loading() { + return

Loading a movie :id

; +} diff --git a/services/nextjs/app/(movies)/movies/[id]/page.tsx b/services/nextjs/app/(movies)/movies/[id]/page.tsx new file mode 100644 index 0000000..6a85e52 --- /dev/null +++ b/services/nextjs/app/(movies)/movies/[id]/page.tsx @@ -0,0 +1,26 @@ +import { Suspense } from "react"; +import MovieInfo from "@/components/movie-info"; +import VideosInfo from "@/components/movie-videos"; +import { getMovie } from "@/components/movie-info"; + +interface Iparams { + params: { id: string }; +} + +export async function generateMetadata({ params: { id } }: Iparams) { + const movie = await getMovie(id); + return { title: movie.title }; +} + +export default async function MovieDetail({ params: { id } }: Iparams) { + return ( +
+ Loading movie info}> + + + Loading videos info}> + + +
+ ); +} diff --git a/services/nextjs/app/about-us/page.tsx b/services/nextjs/app/about-us/page.tsx new file mode 100644 index 0000000..3199f70 --- /dev/null +++ b/services/nextjs/app/about-us/page.tsx @@ -0,0 +1,3 @@ +export default function AboutUs(){ + return
gfdgd
+} \ No newline at end of file diff --git a/services/nextjs/app/favicon.ico b/services/nextjs/app/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..df2deacc7131a0ce93417acfcc939e4963426869 GIT binary patch literal 12465 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 I(X@;BKO+va@c;k- literal 0 HcmV?d00001 diff --git a/services/nextjs/app/layout.tsx b/services/nextjs/app/layout.tsx new file mode 100644 index 0000000..49025c7 --- /dev/null +++ b/services/nextjs/app/layout.tsx @@ -0,0 +1,18 @@ +import "../styles/global.css"; +import React from "react"; +import Navigation from "@/components/navigation"; + +export const metadata = { + title: "movieClone", +}; + +export default function Layout({ children }: { children: React.ReactNode }) { + return ( + + + + {children} + + + ); +} diff --git a/services/nextjs/app/page.tsx b/services/nextjs/app/page.tsx new file mode 100644 index 0000000..100e604 --- /dev/null +++ b/services/nextjs/app/page.tsx @@ -0,0 +1,30 @@ +import styles from "../styles/home.module.css"; +import Movie from "@/components/movie"; + +const URL = "https://nomad-movies.nomadcoders.workers.dev/movies"; + +export const metadata = { + title: "Jovies", +}; + +async function getMovies() { + const response = await fetch(URL); + const json = await response.json(); + return json; +} + +export default async function HomePage() { + const movies = await getMovies(); + return ( +
+ {movies.map((movie: any) => ( + + ))} +
+ ); +} diff --git a/services/nextjs/components/movie-info.tsx b/services/nextjs/components/movie-info.tsx new file mode 100644 index 0000000..f7b3d7c --- /dev/null +++ b/services/nextjs/components/movie-info.tsx @@ -0,0 +1,29 @@ +import styles from "../styles/movie-info.module.css"; + +const URL = "https://nomad-movies.nomadcoders.workers.dev/movies"; + +export async function getMovie(id: string) { + const response = await fetch(`${URL}/${id}`); + return response.json(); +} + +export default async function MovieInfo({ id }: { id: string }) { + const movie = await getMovie(id); + return ( +
+ ); +} diff --git a/services/nextjs/components/movie-videos.tsx b/services/nextjs/components/movie-videos.tsx new file mode 100644 index 0000000..3589551 --- /dev/null +++ b/services/nextjs/components/movie-videos.tsx @@ -0,0 +1,25 @@ +import styles from "../styles/movie-videos.module.css"; + +const URL = "https://nomad-movies.nomadcoders.workers.dev/movies"; + +async function getVideos(id: string) { + const response = await fetch(`${URL}/${id}/videos`); + return response.json(); +} + +export default async function VideosInfo({ id }: { id: string }) { + const videos = await getVideos(id); + return ( +
+ {videos.map((video: any) => ( +