#!/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' \ --exclude='config/' \ "$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 " Checking frontend..." FRONTEND_OK=false for i in $(seq 1 5); do if curl -sf http://localhost:8083/ >/dev/null 2>&1; then FRONTEND_OK=true break fi sleep 1 done if [ "$FRONTEND_OK" = true ]; then echo " Frontend check passed ✅" else echo " ⚠️ Frontend check timeout" fi # ── 后端健康检查 ── echo " Checking backend..." HEALTH_URL="http://localhost:8083/api/daemon/status" 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 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"