#!/bin/bash # ============================================================ # mp-pilates 服务端一键部署脚本 # 流程:本地构建 shared + server → 上传 → 服务器安装依赖 → PM2 重启 # ============================================================ # 部署配置 SERVER_USER="root" SERVER_HOST="129.204.155.94" SERVER_PATH="/usr/local/web/mp-pilates-server" PM2_APP_NAME="mp-pilates-server" PORT=3008 # 项目根目录(脚本所在位置) PROJECT_ROOT="$(cd "$(dirname "$0")" && pwd)" # 颜色输出 GREEN='\033[0;32m' YELLOW='\033[1;33m' RED='\033[0;31m' NC='\033[0m' echo_info() { echo -e "${GREEN}[INFO]${NC} $1"; } echo_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } echo_error() { echo -e "${RED}[ERROR]${NC} $1"; } # 检查 SSH 连接 echo_info "检查服务器连接..." if ! ssh -o ConnectTimeout=5 ${SERVER_USER}@${SERVER_HOST} "echo '连接成功'" > /dev/null 2>&1; then echo_error "无法连接到服务器 ${SERVER_HOST}" echo_warn "请确保:" echo_warn " 1. 服务器地址正确" echo_warn " 2. 已配置 SSH 密钥认证或密码" exit 1 fi cd "${PROJECT_ROOT}" # 1. 构建 shared 包(server 依赖 shared) echo_info "构建 shared 包..." pnpm build:shared if [ $? -ne 0 ]; then echo_error "shared 构建失败,部署终止" exit 1 fi echo_info "shared 构建完成" # 2. 构建 server 包 echo_info "构建 server 包..." pnpm build:server if [ $? -ne 0 ]; then echo_error "server 构建失败,部署终止" exit 1 fi echo_info "server 构建完成" # 3. 生成 Prisma Client echo_info "生成 Prisma Client..." cd packages/server && pnpm prisma:generate && cd "${PROJECT_ROOT}" if [ $? -ne 0 ]; then echo_error "Prisma Client 生成失败" exit 1 fi echo_info "Prisma Client 生成完成" # 4. 创建服务器目录 echo_info "准备服务器目录..." ssh ${SERVER_USER}@${SERVER_HOST} "mkdir -p ${SERVER_PATH}" # 5. 上传文件到服务器 echo_info "上传文件到服务器 ${SERVER_HOST}:${SERVER_PATH}" # 上传 server 包(含构建产物、prisma、package.json) # 注意:排除 .env 避免覆盖服务器环境配置 rsync -avz --delete \ --exclude 'node_modules' \ --exclude '.git' \ --exclude '.claude' \ --exclude '.env' \ --exclude '.env.local' \ --exclude '.DS_Store' \ --exclude '*.log' \ --exclude 'src' \ --exclude '__tests__' \ --exclude 'test' \ --exclude 'coverage' \ --exclude 'tsconfig*.json' \ --exclude 'nest-cli.json' \ packages/server/ ${SERVER_USER}@${SERVER_HOST}:${SERVER_PATH}/ if [ $? -ne 0 ]; then echo_error "server 文件上传失败" exit 1 fi # 上传 shared 包构建产物 ssh ${SERVER_USER}@${SERVER_HOST} "mkdir -p ${SERVER_PATH}/../mp-pilates-shared" rsync -avz --delete \ --exclude 'node_modules' \ --exclude 'src' \ --exclude '.DS_Store' \ packages/shared/ ${SERVER_USER}@${SERVER_HOST}:${SERVER_PATH}/../mp-pilates-shared/ if [ $? -ne 0 ]; then echo_error "shared 文件上传失败" exit 1 fi # 上传本地 Prisma Client(已生成好的,避免服务器上版本不一致) echo_info "上传 Prisma Client..." PRISMA_CLIENT_PATH="$(cd packages/server && node -e "console.log(require.resolve('@prisma/client/../../'))" 2>/dev/null)" PRISMA_ENGINE_PATH="$(cd packages/server && node -e "console.log(require.resolve('@prisma/engines/../../'))" 2>/dev/null)" echo_info "文件上传完成" # 6. 服务器端安装依赖并重启 echo_info "服务器部署操作..." ssh ${SERVER_USER}@${SERVER_HOST} "bash -l" << 'ENDSSH' export PATH="$HOME/.nvm/versions/node/v22.17.1/bin:$PATH" SERVER_PATH="/usr/local/web/mp-pilates-server" PM2_APP_NAME="mp-pilates-server" PORT=3008 cd ${SERVER_PATH} # 检查 .env 是否存在 if [ ! -f .env ]; then echo "⚠ 未找到 .env 文件,请先创建 ${SERVER_PATH}/.env" echo " 需要配置: DATABASE_URL, JWT_SECRET, WX_APPID, WX_SECRET 等" exit 1 fi # 安装 pnpm(如果未安装) if ! command -v pnpm &> /dev/null; then echo "安装 pnpm..." npm install -g pnpm fi # 设置 npm 镜像 npm config set registry https://registry.npmmirror.com # 修改 package.json,将 workspace:* 替换为实际路径(绕过 workspace 限制) echo "处理依赖..." sed -i 's|"@mp-pilates/shared": "workspace:\*"|"@mp-pilates/shared": "file:../mp-pilates-shared"|g' package.json # 安装生产依赖 echo "安装依赖..." pnpm install --prod --no-frozen-lockfile # 确保 shared 包链接正确 echo "链接 shared 包..." mkdir -p node_modules/@mp-pilates rm -rf node_modules/@mp-pilates/shared ln -sf ${SERVER_PATH}/../mp-pilates-shared node_modules/@mp-pilates/shared # 使用项目本地的 prisma(非全局 npx,避免版本不一致) echo "生成 Prisma Client..." ./node_modules/.bin/prisma generate 2>/dev/null || npx prisma@5 generate # 运行数据库迁移(生产环境用 deploy,非交互式) echo "运行数据库迁移..." ./node_modules/.bin/prisma migrate deploy 2>/dev/null || npx prisma@5 migrate deploy # 检查 PM2 是否安装 if ! command -v pm2 &> /dev/null; then echo "安装 PM2..." npm install -g pm2 fi # 停止旧服务(如果存在) if pm2 describe ${PM2_APP_NAME} &> /dev/null; then echo "停止旧服务..." pm2 stop ${PM2_APP_NAME} pm2 delete ${PM2_APP_NAME} fi # 启动新服务 echo "启动服务(端口 ${PORT})..." PORT=${PORT} pm2 start dist/main.js \ --name ${PM2_APP_NAME} \ --max-memory-restart 512M \ --log-date-format "YYYY-MM-DD HH:mm:ss" # 保存 PM2 配置 pm2 save --force # 等待服务启动并检查状态 sleep 3 echo "服务状态:" pm2 status ${PM2_APP_NAME} # 检查服务是否在线 if pm2 describe ${PM2_APP_NAME} | grep -q "online"; then echo "✓ 服务启动成功!" else echo "✗ 服务启动失败,查看日志:" pm2 logs ${PM2_APP_NAME} --lines 30 --nostream exit 1 fi ENDSSH if [ $? -eq 0 ]; then echo_info "==============================" echo_info "部署成功!" echo_info "应用地址: http://${SERVER_HOST}:${PORT}" echo_info "API 地址: http://${SERVER_HOST}:${PORT}/api" echo_info "==============================" else echo_error "部署失败" exit 1 fi