diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..b40289e --- /dev/null +++ b/deploy.sh @@ -0,0 +1,175 @@ +#!/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 <&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 "============================================================"