diff --git a/scripts/deploy.sh b/scripts/deploy.sh new file mode 100644 index 0000000..20d9de8 --- /dev/null +++ b/scripts/deploy.sh @@ -0,0 +1,166 @@ +#!/usr/bin/env bash +# deploy.sh — 部署/升级 moziplus v2 +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PROJECT_DIR="$(dirname "$SCRIPT_DIR")" + +# 默认值 +SOURCE_DIR="$PROJECT_DIR" +TARGET_DIR="$HOME/.sanguo_projects/sanguo_moziplus_v2" +PM2_NAME="sanguo-moziplus-v2" +SKIP_BUILD=false + +usage() { + echo "Usage: $0 [options]" + echo "" + echo "Options:" + echo " --source=DIR 源码目录 (default: 项目开发目录)" + echo " --target=DIR 安装目标目录 (default: ~/.sanguo_projects/sanguo_moziplus_v2)" + echo " --skip-build 跳过前端构建" + echo " -h, --help 显示帮助" + exit 0 +} + +for arg in "$@"; do + case "$arg" in + --source=*) SOURCE_DIR="${arg#*=}" ;; + --target=*) TARGET_DIR="${arg#*=}" ;; + --skip-build) SKIP_BUILD=true ;; + -h|--help) usage ;; + esac +done + +echo "🚀 Deploying moziplus v2" +echo " Source: $SOURCE_DIR" +echo " Target: $TARGET_DIR" + +# ── 前置检查 ── +echo "" +echo "📋 Pre-flight checks..." + +if ! command -v python3 &>/dev/null; then + echo "❌ python3 not found" && exit 1 +fi +PYTHON_VERSION=$(python3 -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")') +echo " Python: $PYTHON_VERSION ✅" + +if ! command -v pm2 &>/dev/null; then + echo "❌ pm2 not found" && exit 1 +fi +echo " PM2: $(pm2 --version) ✅" + +if [ ! -d "$SOURCE_DIR/src" ]; then + echo "❌ Source directory invalid: $SOURCE_DIR/src not found" && exit 1 +fi + +# ── 前端构建 ── +if [ "$SKIP_BUILD" = false ]; then + echo "" + echo "🔨 Building frontend..." + bash "$SCRIPT_DIR/build-frontend.sh" +else + echo "" + echo "⏭️ Skipping frontend build (--skip-build)" + # 确保前端 dist 存在 + if [ ! -d "$SOURCE_DIR/src/frontend/dist" ]; then + echo "❌ Frontend dist not found. Run without --skip-build first." && exit 1 + fi +fi + +# ── 创建目标目录 ── +FIRST_INSTALL=false +if [ ! -d "$TARGET_DIR" ]; then + FIRST_INSTALL=true + echo "" + echo "📁 Creating target directory: $TARGET_DIR" + mkdir -p "$TARGET_DIR" +fi + +# ── 同步代码 ── +echo "" +echo "🔄 Syncing code..." +rsync -a --delete \ + --exclude='.git/' \ + --exclude='docs/' \ + --exclude='tests/' \ + --exclude='scripts/' \ + --exclude='data/' \ + --exclude='logs/' \ + --exclude='inbox/' \ + --exclude='node_modules/' \ + --exclude='tsconfig.tsbuildinfo' \ + --exclude='__pycache__/' \ + --exclude='.venv/' \ + --exclude='.DS_Store' \ + "$SOURCE_DIR/" "$TARGET_DIR/" + +# 但不覆盖运行时配置和数据 +# rsync --delete 不影响 exclude 的目录 + +echo " Code synced ✅" + +# ── 确保目录结构 ── +mkdir -p "$TARGET_DIR/data" +mkdir -p "$TARGET_DIR/logs" +mkdir -p "$TARGET_DIR/inbox" + +# 确保默认配置存在(不覆盖已有配置) +if [ ! -f "$TARGET_DIR/config/default.yaml" ]; then + if [ -f "$SOURCE_DIR/config/default.yaml" ]; then + cp "$SOURCE_DIR/config/default.yaml" "$TARGET_DIR/config/default.yaml" + echo " Created default config ✅" + fi +fi + +if [ ! -f "$TARGET_DIR/config/guardrails.yaml" ]; then + if [ -f "$SOURCE_DIR/config/guardrails.yaml" ]; then + cp "$SOURCE_DIR/config/guardrails.yaml" "$TARGET_DIR/config/guardrails.yaml" + echo " Created guardrails config ✅" + fi +fi + +# ── PM2 管理 ── +echo "" +if [ "$FIRST_INSTALL" = true ]; then + echo "🆕 First install — starting with PM2..." + cd "$TARGET_DIR" + pm2 start ecosystem.config.cjs +else + echo "♻️ Upgrading — restarting PM2..." + pm2 restart "$PM2_NAME" --update-env +fi +pm2 save + +# ── 健康检查 ── +echo "" +echo "🏥 Health check..." +HEALTH_URL="http://localhost:8083/api/health" +OK=false +for i in $(seq 1 10); do + if curl -sf "$HEALTH_URL" >/dev/null 2>&1; then + OK=true + break + fi + sleep 1 +done + +if [ "$OK" = true ]; then + echo " Health check passed ✅" +else + echo " ⚠️ Health check timeout (service may still be starting)" + echo " Check: pm2 logs $PM2_NAME" +fi + +# ── 完成 ── +echo "" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +if [ "$FIRST_INSTALL" = true ]; then + echo "✅ moziplus v2 installed successfully!" +else + echo "✅ moziplus v2 upgraded successfully!" +fi +echo " URL: http://localhost:8083/" +echo " PM2: pm2 status $PM2_NAME" +echo " Logs: pm2 logs $PM2_NAME" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"