#!/bin/bash # # math_cms 统一部署脚本 # # 用法: # ./deploy.sh # 部署第一台服务器(主站) # ./deploy.sh api # 部署第二台服务器(API) # ./deploy.sh pdf # 部署第三/四台服务器(PDF Worker) # ./deploy.sh all # 部署当前服务器所有服务 # # 流程:git pull → 前端构建 → 清 Laravel 缓存 → 重建并重启容器 # set -e # ============ 配置 ============ COMPOSE_FILES_MAIN="-f docker-compose.yml -f docker-compose.mount.yml" COMPOSE_FILES_API="-f docker-compose.api.yml -f docker-compose.api.mount.yml" COMPOSE_FILES_PDF="-f docker-compose.pdf.yml -f docker-compose.pdf.mount.yml" # ============ 颜色 ============ RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' info() { echo -e "${BLUE}[INFO]${NC} $1"; } ok() { echo -e "${GREEN}[OK]${NC} $1"; } warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } error() { echo -e "${RED}[ERROR]${NC} $1"; exit 1; } # ============ 步骤 1:拉取代码 ============ git_pull() { info "拉取最新代码..." git fetch origin main LOCAL=$(git rev-parse HEAD) REMOTE=$(git rev-parse origin/main) if [ "$LOCAL" = "$REMOTE" ]; then ok "代码已是最新 ($LOCAL)" else git pull origin main ok "代码已更新" fi } # ============ 步骤 2:前端构建 ============ build_frontend() { info "构建前端资源..." # 检测宿主机是否有 bun/npm if command -v bun &>/dev/null; then info "使用 bun 构建..." bun install bun run build elif command -v npm &>/dev/null; then info "使用 npm 构建..." npm install --prefer-offline npm run build else # 宿主机没有 Node.js,用 Docker 容器构建 info "宿主机无 Node.js,使用 Docker 容器构建..." docker run --rm \ -v "$(pwd):/app" \ -w /app \ node:20-alpine \ sh -c "npm config set registry https://registry.npmmirror.com && npm install && npm run build" fi # 验证构建产物 if [ ! -f "public/build/manifest.json" ]; then error "前端构建失败:public/build/manifest.json 不存在" fi ok "前端构建完成" info "构建产物:" ls -la public/build/assets/ } # ============ 步骤 3:清 Laravel 缓存 ========== clear_cache() { info "清除 Laravel 缓存..." # 如果容器正在运行,通过容器执行 if docker ps --format '{{.Names}}' 2>/dev/null | grep -q 'math_cms_app'; then local container=$(docker ps --format '{{.Names}}' | grep 'math_cms_app' | head -1) docker exec "$container" php artisan view:clear 2>/dev/null || true docker exec "$container" php artisan cache:clear 2>/dev/null || true ok "通过容器清除缓存" else # 容器未运行时本地执行(需要 PHP) php artisan view:clear 2>/dev/null || true php artisan cache:clear 2>/dev/null || true ok "本地清除缓存" fi } # ============ 步骤 4:构建镜像并重启 ============ deploy_services() { local compose_files="$1" shift local services="$@" info "构建 Docker 镜像..." docker compose $compose_files build --no-cache app if [ -z "$services" ]; then services="app" fi info "重启服务: $services" docker compose $compose_files up -d --no-deps --force-recreate $services ok "服务已重启: $services" } # ============ 步骤 5:验证 ============ verify() { local compose_files="$1" info "等待服务启动..." sleep 5 # 检查容器状态 local status=$(docker compose $compose_files ps --format '{{.Status}}' 2>/dev/null | head -1) if echo "$status" | grep -q 'Up'; then ok "容器运行正常 ($status)" else warn "容器状态: $status" warn "请检查日志: docker compose $compose_files logs --tail=50" fi # 检查前端资源是否可访问 local port=$(docker compose $compose_files port app 8000 2>/dev/null | cut -d: -f2) if [ -n "$port" ]; then local manifest_status=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:$port/build/manifest.json" 2>/dev/null || echo "000") if [ "$manifest_status" = "200" ]; then ok "前端资源验证通过 (manifest.json HTTP $manifest_status)" else warn "前端资源验证失败 (manifest.json HTTP $manifest_status)" fi fi } # ============ 主流程 ============ main() { local mode="${1:-main}" echo "" echo -e "${BLUE}========================================${NC}" echo -e "${BLUE} math_cms 部署脚本${NC}" echo -e "${BLUE}========================================${NC}" echo "" git_pull build_frontend clear_cache case "$mode" in main|1) info "部署模式:主站(第一台服务器)" deploy_services "$COMPOSE_FILES_MAIN" app queue pdf-worker gotenberg verify "$COMPOSE_FILES_MAIN" ;; api|2) info "部署模式:API(第二台服务器)" deploy_services "$COMPOSE_FILES_API" app verify "$COMPOSE_FILES_API" ;; pdf|3) info "部署模式:PDF Worker(第三/四台服务器)" deploy_services "$COMPOSE_FILES_PDF" app pdf-worker-1 pdf-worker-2 logic-worker-1 logic-worker-2 gotenberg verify "$COMPOSE_FILES_PDF" ;; all) info "部署模式:所有服务" # 自动检测当前服务器使用的 compose 文件 if [ -f "docker-compose.pdf.yml" ] && docker ps --format '{{.Names}}' 2>/dev/null | grep -q 'pdf_worker'; then deploy_services "$COMPOSE_FILES_PDF" app pdf-worker-1 pdf-worker-2 logic-worker-1 logic-worker-2 gotenberg verify "$COMPOSE_FILES_PDF" elif docker ps --format '{{.Names}}' 2>/dev/null | grep -q 'math_cms_queue'; then deploy_services "$COMPOSE_FILES_MAIN" app queue pdf-worker gotenberg verify "$COMPOSE_FILES_MAIN" else deploy_services "$COMPOSE_FILES_API" app verify "$COMPOSE_FILES_API" fi ;; *) echo "用法: $0 [main|api|pdf|all]" echo " main - 第一台服务器(主站,默认)" echo " api - 第二台服务器(API)" echo " pdf - 第三/四台服务器(PDF Worker)" echo " all - 自动检测并部署当前服务器所有服务" exit 1 ;; esac echo "" ok "部署完成!" echo "" } main "$@"