From 30635864da77a3c1fd0e507689fffed79a482830 Mon Sep 17 00:00:00 2001 From: david_bai Date: Sat, 25 Oct 2025 00:03:20 +0800 Subject: [PATCH] feat(deploy): add build-and-deploy script; switch frontend to Next.js standalone; docs: add incremental update guide --- build-and-deploy.sh | 227 +++++++++++++++++++++++++++++++++++++++ deploy.config.example | 19 ++++ docs/DEPLOYMENT.md | 117 ++++++++++++++------ docs/DEPLOYMENT.zh-CN.md | 117 ++++++++++++++------ ecosystem.config.js | 10 +- 5 files changed, 420 insertions(+), 70 deletions(-) create mode 100644 build-and-deploy.sh create mode 100644 deploy.config.example diff --git a/build-and-deploy.sh b/build-and-deploy.sh new file mode 100644 index 0000000..7ac5b0c --- /dev/null +++ b/build-and-deploy.sh @@ -0,0 +1,227 @@ +#!/bin/bash + +set -euo pipefail + +# Check if a build package already exists +if [ -f "out.zip" ]; then + echo "📦 Detected existing build package: out.zip" + echo "📦 Package size: $(du -sh out.zip | cut -f1)" + echo "📝 Build info:" + if [ -f "out/deploy-info.txt" ]; then + cat out/deploy-info.txt + fi + echo "" + echo "⚠️ Choose an option:" + echo " 1. Deploy existing package" + echo " 2. Rebuild and deploy" + echo " 3. Exit" + echo "" + read -p "Select (1/2/3): " -n 1 -r + echo "" + + case $REPLY in + 1) + echo "🚀 Deploying existing package..." + DEPLOY_EXISTING=true + ;; + 2) + echo "🔄 Rebuilding..." + rm -rf out out.zip + ;; + 3) + echo "👋 Exit" + exit 0 + ;; + *) + echo "❌ Invalid option, aborting" + exit 1 + ;; + esac +fi + +if [ "${DEPLOY_EXISTING:-}" != "true" ]; then + echo "🚀 Start local build..." + + # Clean previous build outputs + echo "🧹 Cleaning previous build outputs..." + rm -rf frontend/.next + rm -rf backend/dist + rm -rf out + +# Create output directory for packaging +mkdir -p out + +# Build frontend +echo "📦 Building frontend..." +cd frontend +pnpm install +pnpm build +cd .. + +# Build backend +echo "📦 Building backend..." +cd backend +pnpm install +pnpm build +cd .. + +# Prepare deploy bundle +echo "📋 Preparing deploy bundle..." +mkdir -p out/frontend +mkdir -p out/backend + +# Copy frontend artifacts +cp -r frontend/.next out/frontend/ +cp frontend/package.json out/frontend/ +cp -r frontend/public out/frontend/ 2>/dev/null || true +cp -r frontend/app out/frontend/ 2>/dev/null || true +cp -r frontend/components out/frontend/ 2>/dev/null || true +cp -r frontend/lib out/frontend/ 2>/dev/null || true +cp -r frontend/styles out/frontend/ 2>/dev/null || true +cp frontend/next.config.js out/frontend/ 2>/dev/null || true +cp frontend/tailwind.config.ts out/frontend/ 2>/dev/null || true +cp frontend/postcss.config.js out/frontend/ 2>/dev/null || true +cp -r frontend/content out/frontend/ 2>/dev/null || true + +# Copy backend artifacts +cp -r backend/dist out/backend/ +cp backend/package.json out/backend/ + + +# Write deployment info +echo "📝 Writing deployment info..." +cat > out/deploy-info.txt << EOF +Build time: $(date) +Git commit: $(git rev-parse --short HEAD) +Git branch: $(git branch --show-current) +Frontend BUILD_ID: $(cat frontend/.next/BUILD_ID 2>/dev/null || echo "N/A") +EOF + +# Archive deploy bundle +echo "📦 Archiving deploy bundle..." +cd out +zip -r ../out.zip . +cd .. + +echo "✅ Local build and packaging completed!" +echo "📦 Package: out.zip" +echo "📦 Size: $(du -sh out.zip | cut -f1)" +fi + +# Deploy logic +if [ -f "out.zip" ]; then + echo "" + echo "🚀 Detected out.zip, ready to deploy to server" + echo "⚠️ Deployment will:" + echo " 1. Upload out.zip to server" + echo " 2. Backup current version" + echo " 3. Unzip and replace files" + echo " 4. Restart PM2 apps" + echo "" + read -p "Proceed with deployment? (y/N): " -n 1 -r + echo "" + if [[ $REPLY =~ ^[Yy]$ ]]; then + echo "🚀 Starting deployment..." + + # Load deploy config file + if [ -f "deploy.config" ]; then + source deploy.config + fi + + # Validate required environment variables + if [ -z "$DEPLOY_SERVER" ] || [ -z "$DEPLOY_USER" ] || [ -z "$DEPLOY_PATH" ]; then + echo "❌ Missing server configuration. Please configure one of the following:" + echo " 1. Copy deploy.config.example to deploy.config and edit values" + echo " 2. Or set environment variables:" + echo " export DEPLOY_SERVER=your-server-ip" + echo " export DEPLOY_USER=root" + echo " export DEPLOY_PATH=/root/PrivyDrop" + exit 1 + fi + + # Build SSH options (port/key) + SSH_OPTS="" + SCP_OPTS="" + if [ -n "${SSH_PORT:-}" ]; then + SSH_OPTS+=" -p $SSH_PORT" + SCP_OPTS+=" -P $SSH_PORT" + fi + if [ -n "${SSH_KEY_PATH:-}" ]; then + SSH_OPTS+=" -i $SSH_KEY_PATH" + SCP_OPTS+=" -i $SSH_KEY_PATH" + fi + + # Upload build package to server + echo "📤 Uploading package to server..." + # shellcheck disable=SC2086 + scp $SCP_OPTS out.zip $DEPLOY_USER@$DEPLOY_SERVER:/tmp/ + + # Run remote deployment (fix: ensure heredoc script actually executes) + echo "🔧 Executing remote deployment..." + # Inject DEPLOY_PATH and execute heredoc via 'bash -s' on remote host + # shellcheck disable=SC2086 + ssh $SSH_OPTS $DEPLOY_USER@$DEPLOY_SERVER "DEPLOY_PATH='$DEPLOY_PATH' bash -s" << 'EOF' +set -euo pipefail +# Create structured backup directory +BACKUP_ROOT="/tmp/privydrop_backup" +BACKUP_DIR="$BACKUP_ROOT/$(date +%Y%m%d_%H%M%S)" +mkdir -p "$BACKUP_DIR/frontend" "$BACKUP_DIR/backend" + +# Backup current artifacts if present +if [ -d "$DEPLOY_PATH/frontend/.next" ]; then + echo "📋 Backing up current frontend build..." + mv "$DEPLOY_PATH/frontend/.next" "$BACKUP_DIR/frontend/.next" +fi +if [ -d "$DEPLOY_PATH/backend/dist" ]; then + echo "📋 Backing up current backend build..." + mv "$DEPLOY_PATH/backend/dist" "$BACKUP_DIR/backend/dist" +fi + +# Stop PM2 processes +echo "⏹️ Stopping PM2 apps..." +sudo pm2 stop all || true +sudo pm2 delete all || true + +# Extract new version +echo "📂 Extracting new version..." +cd "$DEPLOY_PATH" +unzip -o /tmp/out.zip +rm -f /tmp/out.zip + +# Fix ownership +sudo chown -R "$(id -un)":"$(id -gn)" "$DEPLOY_PATH/frontend/.next" 2>/dev/null || true +sudo chown -R "$(id -un)":"$(id -gn)" "$DEPLOY_PATH/backend/dist" 2>/dev/null || true + +# Start PM2 apps +echo "▶️ Starting PM2 apps..." +sudo pm2 start ecosystem.config.js + +# Wait for services to start +sleep 5 + +# Check PM2 status +echo "🔍 Checking PM2 status..." +sudo pm2 status + +# Print version identifiers for verification +if [ -f "$DEPLOY_PATH/frontend/.next/BUILD_ID" ]; then + echo "📦 Frontend BUILD_ID: $(cat "$DEPLOY_PATH/frontend/.next/BUILD_ID")" +fi +if [ -f "$DEPLOY_PATH/deploy-info.txt" ]; then + echo "📝 Deploy info:" + cat "$DEPLOY_PATH/deploy-info.txt" || true +fi + +echo "✅ Deployment completed!" +echo "📋 Backup saved at: $BACKUP_DIR" +EOF + + echo "🎉 Deployment finished. Check PM2 status on server:" + echo " ssh $DEPLOY_USER@$DEPLOY_SERVER 'sudo pm2 status'" + else + echo "❌ Deployment canceled" + fi +else + echo "❌ out.zip not found" + exit 1 +fi diff --git a/deploy.config.example b/deploy.config.example new file mode 100644 index 0000000..c177b18 --- /dev/null +++ b/deploy.config.example @@ -0,0 +1,19 @@ +# Deployment configuration +# Copy this file to 'deploy.config' and fill in your server details + +# Server IP or domain +DEPLOY_SERVER="your-server-ip" + +# Server username (default: root) +# Note: Using 'ssh root' is recommended here for simplicity. Ensure you understand the +# security implications and restrict access appropriately (keys, firewall, etc.). +DEPLOY_USER="root" + +# Deploy path on the server (project root) +DEPLOY_PATH="/root/PrivyDrop" + +# SSH port (optional, default 22) +# SSH_PORT="22" + +# SSH private key path (optional) +# SSH_KEY_PATH="~/.ssh/id_rsa" diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md index d77d1ec..a3be482 100644 --- a/docs/DEPLOYMENT.md +++ b/docs/DEPLOYMENT.md @@ -287,41 +287,92 @@ PM2 is a powerful process manager for Node.js. We will use it to run both backen - Restart services: `pm2 restart all` or specific service `pm2 restart signaling-server` - Stop services: `pm2 stop all` or specific service `pm2 stop privydrop-frontend` +### 4.7. Daily Incremental Update (Local Build + Remote Replace) + +This section describes how to build locally and deploy only the built artifacts to the server. It is optimized for day-to-day releases: fast, low resource usage on the server, and easy to verify. + +- Assumes you have completed the first-time deployment and can access the app in production. +- The frontend runs in Next.js Standalone mode (configured in `ecosystem.config.js`), so the server does not need Next CLI or frontend dependencies installed. + +1. Prepare deployment configuration + + - From the project root: + ```bash + cp deploy.config.example deploy.config + ``` + - Edit `deploy.config` with at least: + ```bash + DEPLOY_SERVER="" + DEPLOY_USER="root" # Recommended: use ssh root for simplicity + DEPLOY_PATH="/root/PrivyDrop" # Project root on the server + # Optional: SSH_PORT, SSH_KEY_PATH + ``` + - Security notes: Use SSH key authentication, restrict source IPs, and enforce firewall rules in production. + +2. Build locally and deploy + + - From the project root: + ```bash + bash build-and-deploy.sh + ``` + - When an existing package (out.zip) is detected, the script lets you choose: + - 1. Deploy existing package + - 2. Rebuild and deploy + - Script flow (summary): + - Build frontend and backend locally + - Package artifacts into `out.zip` + - Upload to server path `/tmp/out.zip` + - Server-side backup to `/tmp/privydrop_backup/YYYYmmdd_HHMMSS/` + - Unzip and replace: + - Frontend: `frontend/.next` (includes `.next/standalone` and `.next/static`) + - Frontend static assets: `frontend/public` + - Frontend content: `frontend/content` (for blog file reads) + - Backend: `backend/dist` + - Restart using `pm2 start ecosystem.config.js` + +3. Post-deployment verification + + - Check process status on the server: + ```bash + ssh root@ 'sudo pm2 status' + ``` + - Compare frontend BUILD_ID (optional): + ```bash + ssh root@ 'cat /root/PrivyDrop/frontend/.next/BUILD_ID' + ``` + - Force refresh the browser or use an incognito window to confirm the new version. + +4. Backups and manual rollback + + - Each deployment creates a structured backup on the server at `/tmp/privydrop_backup/YYYYmmdd_HHMMSS/`: + - Frontend: `frontend/.next` + - Backend: `backend/dist` + - To rollback manually (example): + + ```bash + # Stop PM2 + sudo pm2 stop all && sudo pm2 delete all + + # Choose a backup directory, e.g. /tmp/privydrop_backup/20241024_235959 + export DEPLOY_PATH=/root/PrivyDrop + export BACKUP=/tmp/privydrop_backup/20241024_235959 + + # Restore frontend and backend build artifacts + rm -rf "$DEPLOY_PATH/frontend/.next" "$DEPLOY_PATH/backend/dist" + cp -a "$BACKUP/frontend/.next" "$DEPLOY_PATH/frontend/.next" + cp -a "$BACKUP/backend/dist" "$DEPLOY_PATH/backend/dist" + + # Restart PM2 + sudo pm2 start ecosystem.config.js + ``` + +5. Common issues + - Page still shows the old version: clear browser cache/force refresh; compare BUILD_ID; check Nginx/CDN caching. + - Blog posts not loading: ensure `frontend/content/blog` exists on the server and the PM2 frontend process `cwd` is `./frontend`. + - `out.zip not found`: choose “Rebuild and deploy” to create a new package. + ## 5. Troubleshooting -### 5.1 Common error: Missing production build (.next not generated) - -- Symptoms: - - - PM2/Next.js logs show: `Error: Could not find a production build in the '.next' directory. Try building your app with 'next build'...` - - Access via Nginx may return `502 Bad Gateway` (frontend upstream not ready). - -- Cause & Solution: - - - After pulling the latest code, dependencies were not installed and the frontend was not built in the `frontend` directory; `frontend/.next` is missing or incomplete; or the build was run in the wrong directory (e.g., repo root). - - - Run in the frontend directory: - ```bash - cd frontend - pnpm install --frozen-lockfile - pnpm build - # Then restart the frontend process (name depends on your PM2 config) - pm2 restart - ``` - -- Verify: - - ```bash - test -f frontend/.next/BUILD_ID && echo "build ok" - curl -fsSI http://127.0.0.1:3002/ | head -n1 # if frontend listens on 3002 - ``` - -- Notes: - - After every `git pull` or frontend code change, rebuild and then restart the frontend process. - - Ensure PM2 `cwd` points to `frontend`. Prefer building and running with the same user to avoid `.next` permission issues. - -### 5.2 Other common issues - - **Connection Issues:** Check firewall settings, Nginx proxy configurations, `CORS_ORIGIN` settings, and ensure all PM2 processes are running. - **Nginx Errors:** Use `sudo nginx -t` to check syntax and review `/var/log/nginx/error.log`. - **PM2 Issues:** Use `pm2 logs ` to view application logs. diff --git a/docs/DEPLOYMENT.zh-CN.md b/docs/DEPLOYMENT.zh-CN.md index 09a44d1..09fe198 100644 --- a/docs/DEPLOYMENT.zh-CN.md +++ b/docs/DEPLOYMENT.zh-CN.md @@ -286,41 +286,92 @@ PM2 是一个强大的 Node.js 进程管理器,我们将用它来运行后端 - 重启服务: `pm2 restart all` 或指定服务 `pm2 restart signaling-server` - 停止服务: `pm2 stop all` 或指定服务 `pm2 stop privydrop-frontend` +### 4.7. 日常增量更新(本地构建 + 远程替换) + +本小节介绍如何在本地构建后,将前后端的生产产物一并打包上传到服务器,完成“增量更新”。该流程适合日常发布,速度快、资源占用低。 + +- 默认假设你已按“首次部署”完成环境配置(包括 PM2、Nginx/证书等),并能正常访问应用。 +- 默认使用前端 Next.js Standalone 运行方式(ecosystem.config.js 已配置),服务器无需安装前端依赖和 next CLI。 + +1. 准备部署配置 + + - 在项目根目录复制示例配置: + ```bash + cp deploy.config.example deploy.config + ``` + - 编辑 `deploy.config`,至少设置: + ```bash + DEPLOY_SERVER="<你的服务器IP或域名>" + DEPLOY_USER="root" # 推荐使用 ssh root 登录(简单直接) + DEPLOY_PATH="/root/PrivyDrop" # 你的服务器项目根目录 + # 可选:SSH_PORT、SSH_KEY_PATH + ``` + - 安全建议:生产环境请启用密钥登录、限制来源 IP、开启防火墙(仅放行必要端口)。 + +2. 本地构建并部署 + + - 在项目根目录执行: + ```bash + bash build-and-deploy.sh + ``` + - 当脚本检测到现有打包(out.zip)时,可选择: + - 1. 直接部署现有包 + - 2. 重新构建并部署 + - 脚本流程(简述): + - 本地构建前端与后端 + - 将产物打包为 `out.zip` + - 上传至服务器 `/tmp/out.zip` + - 服务器侧备份当前版本到 `/tmp/privydrop_backup/YYYYmmdd_HHMMSS/` + - 解压替换: + - 前端:`frontend/.next`(包含 `.next/standalone` 与 `.next/static`) + - 前端静态资源:`frontend/public` + - 前端内容:`frontend/content`(用于博客文件读取) + - 后端:`backend/dist` + - 使用 `pm2 start ecosystem.config.js` 重启应用 + +3. 发布校验 + + - 服务器上查看进程状态: + ```bash + ssh root@ 'sudo pm2 status' + ``` + - 核对前端 BUILD_ID(可选): + ```bash + ssh root@ 'cat /root/PrivyDrop/frontend/.next/BUILD_ID' + ``` + - 浏览器强制刷新或使用隐身模式,确认页面为新版本。 + +4. 备份和回退(手工) + + - 每次部署会在服务器保存结构化备份:`/tmp/privydrop_backup/YYYYmmdd_HHMMSS/` + - 前端:`frontend/.next` + - 后端:`backend/dist` + - 如需回退,可手工执行(示例): + + ```bash + # 停止 PM2 + sudo pm2 stop all && sudo pm2 delete all + + # 假设选定备份目录为 /tmp/privydrop_backup/20241024_235959 + export DEPLOY_PATH=/root/PrivyDrop + export BACKUP=/tmp/privydrop_backup/20241024_235959 + + # 恢复前端与后端构建产物 + rm -rf "$DEPLOY_PATH/frontend/.next" "$DEPLOY_PATH/backend/dist" + cp -a "$BACKUP/frontend/.next" "$DEPLOY_PATH/frontend/.next" + cp -a "$BACKUP/backend/dist" "$DEPLOY_PATH/backend/dist" + + # 重启 PM2 + sudo pm2 start ecosystem.config.js + ``` + +5. 常见问题 + - 页面仍显示旧版本:清除浏览器缓存/强制刷新;核对 BUILD_ID;检查 Nginx/CDN 缓存。 + - 前端博客文章为空:确认服务器目录存在 `frontend/content/blog`,并确保 PM2 前端进程的 `cwd` 为 `./frontend`。 + - 部署脚本报错 `out.zip not found`:先选择“重新构建并部署”。 + ## 5. 故障排除 -### 5.1 常见错误:前端生产构建缺失(.next 未生成) - -- 现象: - - - PM2/Next.js 日志出现:`Error: Could not find a production build in the '.next' directory. Try building your app with 'next build'...` - - 通过 Nginx 访问可能返回 `502 Bad Gateway`(前端上游未就绪)。 - -- 原因 & 解决: - - - 拉取最新代码后未在 `frontend` 目录安装依赖并构建,`frontend/.next` 不存在或不完整;或构建在了错误目录(例如仓库根)。 - - - 进入前端目录执行: - ```bash - cd frontend - pnpm install --frozen-lockfile - pnpm build - # 然后重启前端进程(名称以你的 PM2 配置为准) - pm2 restart - ``` - -- 验证: - - ```bash - test -f frontend/.next/BUILD_ID && echo "build ok" - curl -fsSI http://127.0.0.1:3002/ | head -n1 # 若前端监听 3002 - ``` - -- 备注: - - 每次 `git pull` 或前端代码更新后,均需重新构建再重启前端进程。 - - 确保 PM2 的 `cwd` 指向 `frontend`,构建与运行尽量使用同一用户,避免权限导致 `.next` 不可读。 - -### 5.2 其他常见问题 - - **连接问题:** 检查防火墙、Nginx 代理设置、CORS_ORIGIN 配置,确保所有 PM2 进程都在运行。 - **Nginx 错误:** `sudo nginx -t` 检查语法,查看 `/var/log/nginx/error.log`。 - **PM2 问题:** `pm2 logs ` 查看应用日志。 diff --git a/ecosystem.config.js b/ecosystem.config.js index 5d3feb7..e7d04d7 100644 --- a/ecosystem.config.js +++ b/ecosystem.config.js @@ -20,15 +20,17 @@ module.exports = { { name: "privydrop-frontend", cwd: "./frontend", - script: "npm", - args: "run start", + script: "node", + args: ".next/standalone/server.js", watch: false, env: { - NODE_ENV: "production" + NODE_ENV: "production", + PORT: 3002, + HOSTNAME: "0.0.0.0" }, log_date_format: "YYYY-MM-DD HH:mm:ss", error_file: "/var/log/privydrop-frontend-error.log", out_file: "/var/log/privydrop-frontend-out.log", } ] -}; \ No newline at end of file +};