diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..2fc74be --- /dev/null +++ b/.dockerignore @@ -0,0 +1,13 @@ +node_modules +dist +.git +.gitignore +*.md +!DEPLOY.md +.env +.env.local +*.log +.DS_Store +coverage +.vscode +.idea diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..edc8898 --- /dev/null +++ b/.env.production @@ -0,0 +1,14 @@ +# 生产环境配置 +NODE_ENV=production + +# 服务端口 +PORT=3000 + +# Redis 连接 +REDIS_URL=redis://redis:6379 + +# CORS 允许的域名(多个用逗号分隔) +CORS_ORIGINS=https://your-domain.com,https://www.your-domain.com + +# 你的域名(用于生成二维码等) +DOMAIN=your-domain.com diff --git a/DEPLOY.md b/DEPLOY.md new file mode 100644 index 0000000..34087a4 --- /dev/null +++ b/DEPLOY.md @@ -0,0 +1,75 @@ +# 部署指南 + +## 前置要求 + +- Docker 20.10+ +- Docker Compose 2.0+ +- 已备案的域名 +- SSL 证书 + +## 快速部署 + +### 1. 上传项目到服务器 + +```bash +scp -r company-celebration2 user@your-server:/opt/ +``` + +### 2. 配置 SSL 证书 + +将证书文件放入 `deploy/ssl/` 目录: +``` +deploy/ssl/ +├── fullchain.pem # 证书链 +└── privkey.pem # 私钥 +``` + +### 3. 启用 SSL 配置 + +```bash +cp deploy/nginx.ssl.conf deploy/nginx.conf +``` + +### 4. 配置环境变量 + +```bash +cp .env.production .env +# 编辑 .env 文件,填入你的域名 +``` + +### 5. 构建并启动 + +```bash +docker-compose up -d --build +``` + +### 6. 查看日志 + +```bash +docker-compose logs -f +``` + +## 访问地址 + +| 端点 | 地址 | +|------|------| +| 手机端 | https://your-domain.com/ | +| 大屏端 | https://your-domain.com/screen | +| 导演控制台 | https://your-domain.com/screen/admin/director-console | + +## 常用命令 + +```bash +# 停止服务 +docker-compose down + +# 重启服务 +docker-compose restart + +# 查看状态 +docker-compose ps + +# 清理重建 +docker-compose down -v +docker-compose up -d --build +``` diff --git a/deploy/Dockerfile.frontend b/deploy/Dockerfile.frontend new file mode 100644 index 0000000..776769f --- /dev/null +++ b/deploy/Dockerfile.frontend @@ -0,0 +1,41 @@ +FROM node:20-alpine AS builder + +WORKDIR /app + +# Install pnpm +RUN npm install -g pnpm + +# Copy workspace files +COPY pnpm-workspace.yaml package.json pnpm-lock.yaml ./ +COPY packages/shared ./packages/shared +COPY packages/client-screen ./packages/client-screen +COPY packages/client-mobile ./packages/client-mobile + +# Install dependencies +RUN pnpm install --frozen-lockfile + +# Build shared package +WORKDIR /app/packages/shared +RUN pnpm build + +# Build client-screen +WORKDIR /app/packages/client-screen +RUN pnpm build + +# Build client-mobile +WORKDIR /app/packages/client-mobile +RUN pnpm build + +# Production stage - Nginx +FROM nginx:alpine + +# Copy built files +COPY --from=builder /app/packages/client-screen/dist /usr/share/nginx/html/screen +COPY --from=builder /app/packages/client-mobile/dist /usr/share/nginx/html/mobile + +# Copy nginx config +COPY deploy/nginx.conf /etc/nginx/nginx.conf + +EXPOSE 80 443 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/deploy/nginx.conf b/deploy/nginx.conf new file mode 100644 index 0000000..823d009 --- /dev/null +++ b/deploy/nginx.conf @@ -0,0 +1,73 @@ +user nginx; +worker_processes auto; +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + keepalive_timeout 65; + gzip on; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml; + + # Upstream for API server + upstream api_server { + server server:3000; + } + + server { + listen 80; + server_name _; + + # Redirect HTTP to HTTPS (uncomment when SSL is configured) + # return 301 https://$host$request_uri; + + # Mobile client (default) + location / { + root /usr/share/nginx/html/mobile; + index index.html; + try_files $uri $uri/ /index.html; + } + + # Screen client + location /screen { + alias /usr/share/nginx/html/screen; + index index.html; + try_files $uri $uri/ /screen/index.html; + } + + # API proxy + location /api { + proxy_pass http://api_server; + proxy_http_version 1.1; + 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; + } + + # WebSocket proxy + location /socket.io { + proxy_pass http://api_server; + 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_read_timeout 86400; + } + } +} diff --git a/deploy/nginx.ssl.conf b/deploy/nginx.ssl.conf new file mode 100644 index 0000000..aa4f632 --- /dev/null +++ b/deploy/nginx.ssl.conf @@ -0,0 +1,80 @@ +user nginx; +worker_processes auto; +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent"'; + + access_log /var/log/nginx/access.log main; + sendfile on; + keepalive_timeout 65; + gzip on; + gzip_types text/plain text/css application/json application/javascript text/xml; + + upstream api_server { + server server:3000; + } + + # HTTP -> HTTPS redirect + server { + listen 80; + server_name _; + return 301 https://$host$request_uri; + } + + # HTTPS server + server { + listen 443 ssl http2; + server_name _; + + ssl_certificate /etc/nginx/ssl/fullchain.pem; + ssl_certificate_key /etc/nginx/ssl/privkey.pem; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + + # Mobile client (default) + location / { + root /usr/share/nginx/html/mobile; + index index.html; + try_files $uri $uri/ /index.html; + } + + # Screen client + location /screen { + alias /usr/share/nginx/html/screen; + index index.html; + try_files $uri $uri/ /screen/index.html; + } + + # API proxy + location /api { + proxy_pass http://api_server; + proxy_http_version 1.1; + 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; + } + + # WebSocket proxy + location /socket.io { + proxy_pass http://api_server; + 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_read_timeout 86400; + } + } +} diff --git a/deploy/ssl/.gitkeep b/deploy/ssl/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..492f9a2 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,50 @@ +version: '3.8' + +services: + redis: + image: redis:7-alpine + container_name: gala-redis + restart: unless-stopped + volumes: + - redis_data:/data + networks: + - gala-network + + server: + build: + context: . + dockerfile: packages/server/Dockerfile + container_name: gala-server + restart: unless-stopped + environment: + - NODE_ENV=production + - PORT=3000 + - REDIS_URL=redis://redis:6379 + - CORS_ORIGINS=${CORS_ORIGINS:-*} + depends_on: + - redis + networks: + - gala-network + + nginx: + build: + context: . + dockerfile: deploy/Dockerfile.frontend + container_name: gala-nginx + restart: unless-stopped + ports: + - "80:80" + - "443:443" + volumes: + - ./deploy/ssl:/etc/nginx/ssl:ro + depends_on: + - server + networks: + - gala-network + +volumes: + redis_data: + +networks: + gala-network: + driver: bridge diff --git a/packages/client-mobile/.env.production b/packages/client-mobile/.env.production new file mode 100644 index 0000000..458cb65 --- /dev/null +++ b/packages/client-mobile/.env.production @@ -0,0 +1,2 @@ +VITE_SOCKET_URL= +VITE_API_URL= diff --git a/packages/client-screen/.env.production b/packages/client-screen/.env.production new file mode 100644 index 0000000..458cb65 --- /dev/null +++ b/packages/client-screen/.env.production @@ -0,0 +1,2 @@ +VITE_SOCKET_URL= +VITE_API_URL= diff --git a/packages/server/Dockerfile b/packages/server/Dockerfile new file mode 100644 index 0000000..fe44ce6 --- /dev/null +++ b/packages/server/Dockerfile @@ -0,0 +1,43 @@ +FROM node:20-alpine AS builder + +WORKDIR /app + +# Install pnpm +RUN npm install -g pnpm + +# Copy workspace files +COPY pnpm-workspace.yaml package.json pnpm-lock.yaml ./ +COPY packages/shared ./packages/shared +COPY packages/server ./packages/server + +# Install dependencies +RUN pnpm install --frozen-lockfile + +# Build shared package first +WORKDIR /app/packages/shared +RUN pnpm build + +# Build server +WORKDIR /app/packages/server +RUN pnpm build + +# Production stage +FROM node:20-alpine AS production + +WORKDIR /app + +RUN npm install -g pnpm + +# Copy built files +COPY --from=builder /app/packages/server/dist ./dist +COPY --from=builder /app/packages/server/package.json ./ +COPY --from=builder /app/packages/shared /app/packages/shared +COPY --from=builder /app/node_modules ./node_modules +COPY --from=builder /app/packages/server/node_modules ./packages/server/node_modules + +ENV NODE_ENV=production +ENV PORT=3000 + +EXPOSE 3000 + +CMD ["node", "dist/index.js"]