234 lines
6.6 KiB
Bash
Executable File
234 lines
6.6 KiB
Bash
Executable File
#!/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="${MOZIPLUS_V2_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 " --version 显示当前部署版本"
|
|
echo " --rollback 回滚到上一个部署版本"
|
|
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 ;;
|
|
--version) ACTION=version ;;
|
|
--rollback) ACTION=rollback ;;
|
|
--health-check) ;; # 保留兼容,无额外操作
|
|
-h|--help) usage ;;
|
|
esac
|
|
done
|
|
|
|
ACTION="${ACTION:-deploy}"
|
|
|
|
# ── 部署历史文件 ──
|
|
HISTORY_FILE="$TARGET_DIR/data/deploy-history.jsonl"
|
|
|
|
# ── version 分支 ──
|
|
if [ "$ACTION" = "version" ]; then
|
|
if [ -f "$HISTORY_FILE" ]; then
|
|
LAST_COMMIT=$(tail -1 "$HISTORY_FILE" | python3 -c 'import sys,json; print(json.load(sys.stdin).get("commit","unknown"))' 2>/dev/null || echo "unknown")
|
|
echo "$LAST_COMMIT"
|
|
else
|
|
echo "No deployment history found."
|
|
fi
|
|
exit 0
|
|
fi
|
|
|
|
# ── rollback 分支 ──
|
|
if [ "$ACTION" = "rollback" ]; then
|
|
if [ ! -f "$HISTORY_FILE" ]; then
|
|
echo "❌ No deployment history, cannot rollback."
|
|
exit 1
|
|
fi
|
|
LINE_COUNT=$(wc -l < "$HISTORY_FILE")
|
|
if [ "$LINE_COUNT" -lt 2 ]; then
|
|
echo "❌ Not enough history for rollback (need at least 2 entries)."
|
|
exit 1
|
|
fi
|
|
ROLLBACK_COMMIT=$(tail -2 "$HISTORY_FILE" | head -1 | python3 -c 'import sys,json; print(json.load(sys.stdin).get("commit",""))' 2>/dev/null || echo "")
|
|
if [ -z "$ROLLBACK_COMMIT" ]; then
|
|
echo "❌ Could not parse previous commit from history."
|
|
exit 1
|
|
fi
|
|
echo "🔄 Rolling back to commit: $ROLLBACK_COMMIT"
|
|
# 保存当前分支/commit 以便恢复
|
|
CURRENT_REF=$(git -C "$SOURCE_DIR" rev-parse HEAD 2>/dev/null || echo "")
|
|
# checkout 到目标 commit
|
|
git -C "$SOURCE_DIR" checkout "$ROLLBACK_COMMIT" 2>/dev/null
|
|
# 使用 DEPLOY_OVERRIDE_COMMIT 显式传 commit hash
|
|
DEPLOY_OVERRIDE_COMMIT="$ROLLBACK_COMMIT" bash "$0" --source="$SOURCE_DIR" --target="$TARGET_DIR" --skip-build
|
|
# 恢复到原来的 commit
|
|
if [ -n "$CURRENT_REF" ]; then
|
|
git -C "$SOURCE_DIR" checkout "$CURRENT_REF" 2>/dev/null || true
|
|
fi
|
|
exit 0
|
|
fi
|
|
|
|
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 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|