Files
selfrelease 2b42da4421 chore(deploy): 新增一键部署脚本 deploy.sh
- rsync 源码并排除 .env/node_modules/构建产物,避免覆盖生产配置
- 构建前 source .env,确保 NEXT_PUBLIC_*/VITE_API_URL/ADMIN_BASE 正确注入
- 支持 --migrate/--seed/--api/--web/--admin/--restart/--no-sync
- pm2 startOrReload + 健康检查;支持 SSH 密钥或 DEPLOY_PASS
2026-06-15 10:05:10 +08:00

176 lines
6.3 KiB
Bash
Executable File

#!/usr/bin/env bash
# ============================================================
# 中华文明全图鉴 —— 一键部署脚本
#
# 用法:
# ./deploy.sh # 同步源码 + 安装 + 构建 + 重启(默认全量)
# ./deploy.sh --migrate # 额外执行数据库 migration
# ./deploy.sh --seed # 额外执行 migration + seed(首次部署用)
# ./deploy.sh --api # 只构建并重启后端 api
# ./deploy.sh --web # 只构建并重启前端 web
# ./deploy.sh --admin # 只构建管理后台(静态)
# ./deploy.sh --restart # 不构建,仅重启 pm2 进程
# ./deploy.sh --no-sync # 跳过 rsync(用服务器现有代码构建)
#
# 鉴权:
# 优先使用 SSH 密钥(推荐,配好后免密)。
# 如需密码登录,设置环境变量 DEPLOY_PASS 后运行(需本机装 sshpass):
# DEPLOY_PASS='xxxx' ./deploy.sh
#
# 可用环境变量覆盖目标(默认指向生产机):
# REMOTE_USER REMOTE_HOST REMOTE_DIR SSH_PORT
# ============================================================
set -euo pipefail
# ---- 目标服务器(可用环境变量覆盖)----
REMOTE_USER="${REMOTE_USER:-ubuntu}"
REMOTE_HOST="${REMOTE_HOST:-152.136.182.184}"
REMOTE_DIR="${REMOTE_DIR:-/home/ubuntu/wenwumap}"
SSH_PORT="${SSH_PORT:-22}"
# ---- 本地项目根(脚本所在目录)----
LOCAL_DIR="$(cd "$(dirname "$0")" && pwd)"
# ---- 解析参数 ----
DO_SYNC=1; DO_INSTALL=1
BUILD_API=0; BUILD_WEB=0; BUILD_ADMIN=0
DO_MIGRATE=0; DO_SEED=0; RESTART_ONLY=0
SELECTIVE=0
for arg in "$@"; do
case "$arg" in
--no-sync) DO_SYNC=0 ;;
--migrate) DO_MIGRATE=1 ;;
--seed) DO_SEED=1; DO_MIGRATE=1 ;;
--api) BUILD_API=1; SELECTIVE=1 ;;
--web) BUILD_WEB=1; SELECTIVE=1 ;;
--admin) BUILD_ADMIN=1; SELECTIVE=1 ;;
--restart) RESTART_ONLY=1 ;;
-h|--help) grep '^#' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
*) echo "未知参数:$arg(用 --help 查看用法)"; exit 1 ;;
esac
done
# 默认(未指定 --api/--web/--admin)则三者全构建
if [ "$SELECTIVE" -eq 0 ]; then BUILD_API=1; BUILD_WEB=1; BUILD_ADMIN=1; fi
# ---- 组装 ssh / rsync 鉴权 ----
SSH_BASE=(ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new -p "$SSH_PORT")
if [ -n "${DEPLOY_PASS:-}" ]; then
if ! command -v sshpass >/dev/null 2>&1; then
echo "✗ 设置了 DEPLOY_PASS 但未安装 sshpass。请 brew install hudochenkov/sshpass/sshpass 或改用 SSH 密钥。"
exit 1
fi
SSH=(sshpass -p "$DEPLOY_PASS" "${SSH_BASE[@]}")
else
SSH=("${SSH_BASE[@]}")
fi
REMOTE=("${SSH[@]}" "${REMOTE_USER}@${REMOTE_HOST}")
run_remote() { "${REMOTE[@]}" "bash -s"; }
echo "============================================================"
echo " 部署目标 : ${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_DIR}"
echo " 同步源码 : $([ $DO_SYNC -eq 1 ] && echo|| echo)"
echo " 构建 : api=$BUILD_API web=$BUILD_WEB admin=$BUILD_ADMIN"
echo " 迁移/灌种: migrate=$DO_MIGRATE seed=$DO_SEED"
echo "============================================================"
# ============================================================
# 1) 同步源码(关键:绝不覆盖服务器 .env / node_modules / 构建产物)
# ============================================================
if [ "$RESTART_ONLY" -eq 0 ] && [ "$DO_SYNC" -eq 1 ]; then
echo "▶ [1/5] rsync 源码到服务器(排除 .env 等)..."
RSYNC_RSH="${SSH[*]}"
rsync -az --delete \
--exclude='.git/' \
--exclude='.env' \
--exclude='**/.env' \
--exclude='node_modules/' \
--exclude='**/node_modules/' \
--exclude='**/dist/' \
--exclude='**/.next/' \
--exclude='**/.cache/' \
--exclude='apps/api/.cache/' \
--exclude='e2e/videos/' \
--exclude='*.zip' \
--exclude='.DS_Store' \
-e "${RSYNC_RSH}" \
"${LOCAL_DIR}/" "${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_DIR}/"
echo " ✓ 源码已同步"
fi
# ============================================================
# 2) 远程:检查 .env、安装依赖、迁移、构建、重启
# ============================================================
run_remote <<REMOTE_SCRIPT
set -euo pipefail
cd "${REMOTE_DIR}"
# --- 防呆:生产 .env 必须存在(脚本不会同步它)---
if [ ! -f .env ]; then
echo "✗ 服务器缺少 ${REMOTE_DIR}/.env,请先在服务器上创建生产配置后再部署。"
exit 1
fi
RESTART_ONLY=${RESTART_ONLY}
DO_INSTALL=${DO_INSTALL}
DO_MIGRATE=${DO_MIGRATE}
DO_SEED=${DO_SEED}
BUILD_API=${BUILD_API}
BUILD_WEB=${BUILD_WEB}
BUILD_ADMIN=${BUILD_ADMIN}
if [ "\$RESTART_ONLY" -eq 0 ]; then
if [ "\$DO_INSTALL" -eq 1 ]; then
echo "▶ [2/5] pnpm install ..."
pnpm install --no-frozen-lockfile 2>&1 | tail -4
fi
if [ "\$DO_MIGRATE" -eq 1 ]; then
echo "▶ 数据库 migration ..."
pnpm --filter @wenwumap/db migrate 2>&1 | tail -6
fi
if [ "\$DO_SEED" -eq 1 ]; then
echo "▶ 数据库 seed ..."
pnpm --filter @wenwumap/db seed 2>&1 | tail -4
fi
# 关键:构建前加载 .env,使 NEXT_PUBLIC_* / VITE_API_URL / ADMIN_BASE 正确注入产物
set -a; . ./.env; set +a
export NODE_OPTIONS="--max-old-space-size=1536"
if [ "\$BUILD_API" -eq 1 ]; then
echo "▶ [3/5] 构建 api ..."
pnpm --filter @wenwumap/api build 2>&1 | tail -3
fi
if [ "\$BUILD_ADMIN" -eq 1 ]; then
echo "▶ 构建 admin (base=\${ADMIN_BASE:-/}) ..."
pnpm --filter @wenwumap/admin build 2>&1 | tail -3
fi
if [ "\$BUILD_WEB" -eq 1 ]; then
echo "▶ 构建 web (API=\${NEXT_PUBLIC_API_URL:-?}) ..."
pnpm --filter @wenwumap/web build 2>&1 | tail -4
fi
fi
echo "▶ [4/5] 重启 pm2 进程 ..."
if [ -f ecosystem.config.js ]; then
pm2 startOrReload ecosystem.config.js --update-env >/dev/null 2>&1 || pm2 restart wenwumap-api wenwumap-web --update-env
else
pm2 restart wenwumap-api wenwumap-web --update-env
fi
pm2 save >/dev/null 2>&1 || true
echo "▶ [5/5] 健康检查 ..."
sleep 3
PORT=\$(grep -E '^PORT=' .env | cut -d= -f2 | tr -d '[:space:]'); PORT=\${PORT:-3101}
echo -n " api : "; curl -s -m 8 -o /dev/null -w "%{http_code}\n" "http://127.0.0.1:\${PORT}/api/v1/map/stats" || echo "FAIL"
pm2 list | grep -E "wenwumap" || true
echo "✓ 远程部署完成"
REMOTE_SCRIPT
echo "============================================================"
echo " ✅ 部署完成:https://ww.all8ai.top"
echo "============================================================"