diff --git a/deploy.sh b/deploy.sh index 4c65a9b..bf709c5 100644 --- a/deploy.sh +++ b/deploy.sh @@ -53,6 +53,9 @@ cd "$PROJECT_ROOT" COMPOSE_FILE="docker-compose.yml" ENV_FILE=".env" +# Docker Compose 命令 (默认值,会在 check_environment 中更新) +DOCKER_COMPOSE="docker-compose" + # 域名配置 DOMAIN="${DOMAIN:-iconsulting.szaiai.com}" ADMIN_EMAIL="${ADMIN_EMAIL:-admin@szaiai.com}" @@ -131,23 +134,61 @@ check_command() { fi } -# 检查环境 +# 检查环境 (只需要 Docker) check_environment() { log_step "检查运行环境..." - check_command "node" - check_command "pnpm" check_command "docker" - check_command "docker-compose" - # 检查 Node 版本 - NODE_VERSION=$(node -v | cut -d'v' -f2 | cut -d'.' -f1) - if [ "$NODE_VERSION" -lt 18 ]; then - log_error "Node.js 版本需要 >= 18,当前版本: $(node -v)" + # 检查 docker-compose 或 docker compose + if command -v docker-compose &> /dev/null; then + DOCKER_COMPOSE="docker-compose" + elif docker compose version &> /dev/null 2>&1; then + DOCKER_COMPOSE="docker compose" + else + log_error "docker-compose 或 docker compose 未安装" exit 1 fi - log_success "环境检查通过" + log_success "环境检查通过 (使用 $DOCKER_COMPOSE)" +} + +# Builder 镜像名称 +BUILDER_IMAGE="iconsulting-builder:latest" + +# 构建 Builder 镜像 +build_builder_image() { + log_step "检查/构建 Builder 镜像..." + + # 检查镜像是否存在 + if docker images | grep -q "iconsulting-builder"; then + log_info "Builder 镜像已存在" + return 0 + fi + + log_info "创建 Builder 镜像..." + + # 创建临时 Dockerfile + cat > "$PROJECT_ROOT/.builder.Dockerfile" << 'DOCKERFILE' +FROM node:20-alpine + +# 安装 pnpm +RUN corepack enable && corepack prepare pnpm@latest --activate + +# 设置工作目录 +WORKDIR /app + +# 设置 pnpm store 目录 +ENV PNPM_HOME=/root/.local/share/pnpm +ENV PATH=$PNPM_HOME:$PATH + +CMD ["sh"] +DOCKERFILE + + docker build -t "$BUILDER_IMAGE" -f "$PROJECT_ROOT/.builder.Dockerfile" "$PROJECT_ROOT" + rm -f "$PROJECT_ROOT/.builder.Dockerfile" + + log_success "Builder 镜像创建完成" } # 加载环境变量 @@ -183,26 +224,35 @@ wait_for_service() { } #=============================================================================== -# 构建函数 +# 构建函数 (使用 Docker 容器构建,无需在主机安装 Node.js) #=============================================================================== -# 安装依赖 +# 在容器中执行命令 +run_in_builder() { + docker run --rm \ + -v "$PROJECT_ROOT:/app" \ + -v "iconsulting-pnpm-store:/root/.local/share/pnpm/store" \ + -w /app \ + "$BUILDER_IMAGE" \ + sh -c "$1" +} + +# 安装依赖 (在容器中) install_deps() { - log_step "安装项目依赖..." - pnpm install + log_step "安装项目依赖 (在 Docker 容器中)..." + build_builder_image + run_in_builder "pnpm install --frozen-lockfile || pnpm install" log_success "依赖安装完成" } -# 构建共享包 +# 构建共享包 (在容器中) build_shared() { log_step "构建 shared 包..." - cd "$PROJECT_ROOT/${SERVICE_DIRS[shared]}" - pnpm run build - cd "$PROJECT_ROOT" + run_in_builder "cd packages/shared && pnpm run build" log_success "shared 构建完成" } -# 构建单个后端服务 +# 构建单个后端服务 (在容器中) build_backend_service() { local service=$1 local dir="${SERVICE_DIRS[$service]}" @@ -213,19 +263,11 @@ build_backend_service() { fi log_step "构建 $service..." - cd "$PROJECT_ROOT/$dir" - - # 清理旧构建 - rm -rf dist - - # TypeScript 编译 - pnpm run build - - cd "$PROJECT_ROOT" + run_in_builder "cd $dir && rm -rf dist && pnpm run build" log_success "$service 构建完成" } -# 构建单个前端 +# 构建单个前端 (在容器中) build_frontend() { local service=$1 local dir="${SERVICE_DIRS[$service]}" @@ -236,15 +278,7 @@ build_frontend() { fi log_step "构建 $service..." - cd "$PROJECT_ROOT/$dir" - - # 清理旧构建 - rm -rf dist - - # Vite 构建 - pnpm run build - - cd "$PROJECT_ROOT" + run_in_builder "cd $dir && rm -rf dist && pnpm run build" log_success "$service 构建完成" } @@ -266,7 +300,8 @@ build_all_frontend() { # 构建所有 build_all() { - log_info "开始构建所有服务..." + log_info "开始构建所有服务 (使用 Docker 容器)..." + build_builder_image install_deps build_all_backend build_all_frontend @@ -277,6 +312,14 @@ build_all() { do_build() { local target=${1:-all} + # 确保 builder 镜像存在 + build_builder_image + + # 如果不是构建全部,先安装依赖 + if [ "$target" != "all" ]; then + install_deps + fi + case $target in all) build_all @@ -285,6 +328,7 @@ do_build() { build_shared ;; backend) + build_shared build_all_backend ;; frontend) @@ -294,6 +338,7 @@ do_build() { build_frontend "$target" ;; conversation|user|payment|knowledge|evolution) + build_shared build_backend_service "$target" ;; *) @@ -316,13 +361,13 @@ build_docker_images() { if [ -n "$service" ] && [ "$service" != "all" ]; then local docker_service="${DOCKER_SERVICES[$service]}" if [ -n "$docker_service" ]; then - docker-compose build "$docker_service" + $DOCKER_COMPOSE build "$docker_service" else log_error "未知服务: $service" return 1 fi else - docker-compose build + $DOCKER_COMPOSE build fi log_success "Docker 镜像构建完成" @@ -332,7 +377,7 @@ build_docker_images() { start_infrastructure() { log_step "启动基础设施服务..." - docker-compose up -d postgres redis neo4j + $DOCKER_COMPOSE up -d postgres redis neo4j # 等待数据库就绪 wait_for_service localhost 5432 "PostgreSQL" @@ -346,52 +391,23 @@ start_infrastructure() { start_kong() { log_step "启动 Kong API 网关..." - docker-compose up -d kong-database + $DOCKER_COMPOSE up -d kong-database sleep 5 # Kong 数据库迁移 - docker-compose run --rm kong kong migrations bootstrap || true + $DOCKER_COMPOSE run --rm kong kong migrations bootstrap || true - docker-compose up -d kong + $DOCKER_COMPOSE up -d kong wait_for_service localhost 8000 "Kong" log_success "Kong 启动完成" } -# 启动后端服务 (非 Docker 模式) +# 启动后端服务 (非 Docker 模式 - 已弃用,统一使用 Docker) +# 注意: 本函数已不再使用,保留仅作参考 start_backend_service_local() { - local service=$1 - local dir="${SERVICE_DIRS[$service]}" - local port="${SERVICE_PORTS[$service]}" - - if [ -z "$dir" ]; then - log_error "未知服务: $service" - return 1 - fi - - log_step "启动 $service (端口: $port)..." - - cd "$PROJECT_ROOT/$dir" - - # 检查是否已构建 - if [ ! -d "dist" ]; then - log_warning "$service 未构建,先进行构建..." - pnpm run build - fi - - # 使用 PM2 或直接启动 - if command -v pm2 &> /dev/null; then - pm2 start dist/main.js --name "iconsulting-$service" --cwd "$PROJECT_ROOT/$dir" - else - # 后台启动 - nohup node dist/main.js > "$PROJECT_ROOT/logs/$service.log" 2>&1 & - echo $! > "$PROJECT_ROOT/pids/$service.pid" - fi - - cd "$PROJECT_ROOT" - - sleep 2 - wait_for_service localhost "$port" "$service" 15 + log_warning "本地模式已弃用,将使用 Docker 模式启动服务" + start_backend_service_docker "$1" } # 启动后端服务 (Docker 模式) @@ -400,7 +416,7 @@ start_backend_service_docker() { local docker_service="${DOCKER_SERVICES[$service]}" log_step "启动 $service (Docker)..." - docker-compose up -d "$docker_service" + $DOCKER_COMPOSE up -d "$docker_service" local port="${SERVICE_PORTS[$service]}" wait_for_service localhost "$port" "$service" @@ -422,7 +438,7 @@ start_all_backend() { # 启动 Nginx (静态文件服务) start_nginx() { log_step "启动 Nginx..." - docker-compose up -d nginx + $DOCKER_COMPOSE up -d nginx wait_for_service localhost 80 "Nginx" log_success "Nginx 启动完成" } @@ -467,7 +483,7 @@ do_start() { start_nginx ;; postgres|redis|neo4j) - docker-compose up -d "$target" + $DOCKER_COMPOSE up -d "$target" ;; conversation|user|payment|knowledge|evolution) if [ "$mode" = "docker" ]; then @@ -517,7 +533,7 @@ stop_service_docker() { if [ -n "$docker_service" ]; then log_step "停止 $service..." - docker-compose stop "$docker_service" + $DOCKER_COMPOSE stop "$docker_service" log_success "$service 已停止" fi } @@ -529,12 +545,12 @@ stop_all() { log_info "停止所有服务..." if [ "$mode" = "docker" ]; then - docker-compose down + $DOCKER_COMPOSE down else for service in conversation user payment knowledge evolution; do stop_service_local "$service" done - docker-compose down + $DOCKER_COMPOSE down fi log_success "所有服务已停止" @@ -550,7 +566,7 @@ do_stop() { stop_all "$mode" ;; infra|infrastructure) - docker-compose stop postgres redis neo4j + $DOCKER_COMPOSE stop postgres redis neo4j ;; conversation|user|payment|knowledge|evolution) if [ "$mode" = "docker" ]; then @@ -560,7 +576,7 @@ do_stop() { fi ;; kong|postgres|redis|neo4j|nginx) - docker-compose stop "$target" + $DOCKER_COMPOSE stop "$target" ;; *) log_error "未知停止目标: $target" @@ -596,7 +612,7 @@ do_status() { # Docker 服务状态 echo -e "${PURPLE}Docker 容器状态:${NC}" - docker-compose ps + $DOCKER_COMPOSE ps echo "" # 端口检查 @@ -632,11 +648,11 @@ do_logs() { local lines=${2:-100} if [ "$service" = "all" ]; then - docker-compose logs -f --tail="$lines" + $DOCKER_COMPOSE logs -f --tail="$lines" else local docker_service="${DOCKER_SERVICES[$service]}" if [ -n "$docker_service" ]; then - docker-compose logs -f --tail="$lines" "$docker_service" + $DOCKER_COMPOSE logs -f --tail="$lines" "$docker_service" else # 本地日志 local log_file="$PROJECT_ROOT/logs/$service.log" @@ -674,7 +690,7 @@ do_clean() { ;; docker) log_step "清理 Docker 资源..." - docker-compose down -v --rmi local + $DOCKER_COMPOSE down -v --rmi local docker system prune -f log_success "Docker 资源已清理" ;; @@ -799,7 +815,7 @@ obtain_ssl_cert() { mkdir -p "$PROJECT_ROOT/nginx/ssl" # 停止 Nginx (释放 80 端口) - docker-compose stop nginx 2>/dev/null || true + $DOCKER_COMPOSE stop nginx 2>/dev/null || true # 使用 standalone 模式申请证书 certbot certonly \ @@ -825,7 +841,7 @@ obtain_ssl_cert() { fi # 重启 Nginx - docker-compose up -d nginx + $DOCKER_COMPOSE up -d nginx log_success "SSL 证书配置完成" } @@ -842,7 +858,7 @@ renew_ssl_cert() { cp "/etc/letsencrypt/live/$DOMAIN/privkey.pem" "$PROJECT_ROOT/nginx/ssl/" # 重载 Nginx - docker-compose exec nginx nginx -s reload + $DOCKER_COMPOSE exec nginx nginx -s reload log_success "SSL 证书续期完成" fi } @@ -1069,13 +1085,12 @@ do_db() { case $action in migrate) - log_step "执行数据库迁移..." - # 可以添加 TypeORM 迁移命令 + log_step "执行数据库迁移 (在 Docker 容器中)..." + build_builder_image + # 在容器中执行迁移命令 for service in user payment knowledge conversation evolution; do local dir="${SERVICE_DIRS[$service]}" - cd "$PROJECT_ROOT/$dir" - pnpm run migration:run 2>/dev/null || log_warning "$service 无迁移或迁移失败" - cd "$PROJECT_ROOT" + run_in_builder "cd $dir && pnpm run migration:run 2>/dev/null" || log_warning "$service 无迁移或迁移失败" done log_success "数据库迁移完成" ;; @@ -1089,7 +1104,7 @@ do_db() { mkdir -p "$backup_dir" log_step "备份数据库..." - docker-compose exec -T postgres pg_dump -U postgres iconsulting > "$backup_dir/postgres.sql" + $DOCKER_COMPOSE exec -T postgres pg_dump -U postgres iconsulting > "$backup_dir/postgres.sql" log_success "数据库备份到: $backup_dir" ;; restore) @@ -1100,15 +1115,15 @@ do_db() { fi log_step "恢复数据库..." - docker-compose exec -T postgres psql -U postgres iconsulting < "$backup_file" + $DOCKER_COMPOSE exec -T postgres psql -U postgres iconsulting < "$backup_file" log_success "数据库恢复完成" ;; reset) log_warning "这将删除所有数据!" read -p "确认继续? (y/N) " confirm if [ "$confirm" = "y" ] || [ "$confirm" = "Y" ]; then - docker-compose down -v - docker-compose up -d postgres redis neo4j + $DOCKER_COMPOSE down -v + $DOCKER_COMPOSE up -d postgres redis neo4j wait_for_service localhost 5432 "PostgreSQL" do_db migrate log_success "数据库已重置" @@ -1116,8 +1131,8 @@ do_db() { ;; status) echo -e "${PURPLE}数据库状态:${NC}" - docker-compose exec postgres psql -U postgres -c "SELECT version();" 2>/dev/null || echo "PostgreSQL 未运行" - docker-compose exec redis redis-cli ping 2>/dev/null || echo "Redis 未运行" + $DOCKER_COMPOSE exec postgres psql -U postgres -c "SELECT version();" 2>/dev/null || echo "PostgreSQL 未运行" + $DOCKER_COMPOSE exec redis redis-cli ping 2>/dev/null || echo "Redis 未运行" curl -s http://localhost:7474 > /dev/null && echo "Neo4j 运行中" || echo "Neo4j 未运行" ;; *) @@ -1209,6 +1224,11 @@ show_help() { DOMAIN 域名 (默认: iconsulting.szaiai.com) ADMIN_EMAIL 管理员邮箱 (用于 SSL 证书) +系统要求: + - Docker (必须) + - Docker Compose 或 docker compose (必须) + - 无需安装 Node.js / pnpm (构建在 Docker 容器中进行) + EOF } @@ -1220,6 +1240,11 @@ main() { local command=${1:-help} shift || true + # 对于 help 命令不需要检查环境 + if [ "$command" != "help" ] && [ "$command" != "--help" ] && [ "$command" != "-h" ]; then + check_environment + fi + case $command in build) do_build "$@"