1273 lines
36 KiB
Bash
1273 lines
36 KiB
Bash
#!/bin/bash
|
||
|
||
#===============================================================================
|
||
# iConsulting 部署管理脚本
|
||
#
|
||
# 用法: ./deploy.sh <command> [service] [options]
|
||
#
|
||
# 命令:
|
||
# build - 编译构建
|
||
# start - 启动服务
|
||
# stop - 停止服务
|
||
# restart - 重启服务
|
||
# status - 查看状态
|
||
# logs - 查看日志
|
||
# clean - 清理构建产物
|
||
# deploy - 完整部署(构建+启动)
|
||
# db - 数据库操作
|
||
# help - 显示帮助
|
||
#
|
||
# 服务:
|
||
# all - 所有服务
|
||
# web-client - 用户前端
|
||
# admin-client - 管理后台前端
|
||
# conversation - 对话服务
|
||
# user - 用户服务
|
||
# payment - 支付服务
|
||
# knowledge - 知识库服务
|
||
# evolution - 进化服务
|
||
# kong - API网关
|
||
# postgres - PostgreSQL数据库
|
||
# redis - Redis缓存
|
||
# neo4j - Neo4j图数据库
|
||
# nginx - Nginx静态服务
|
||
#
|
||
#===============================================================================
|
||
|
||
set -e
|
||
|
||
# 颜色定义
|
||
RED='\033[0;31m'
|
||
GREEN='\033[0;32m'
|
||
YELLOW='\033[1;33m'
|
||
BLUE='\033[0;34m'
|
||
PURPLE='\033[0;35m'
|
||
CYAN='\033[0;36m'
|
||
NC='\033[0m' # No Color
|
||
|
||
# 项目根目录
|
||
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
cd "$PROJECT_ROOT"
|
||
|
||
# 配置
|
||
COMPOSE_FILE="docker-compose.yml"
|
||
ENV_FILE=".env"
|
||
|
||
# 域名配置
|
||
DOMAIN="${DOMAIN:-iconsulting.szaiai.com}"
|
||
ADMIN_EMAIL="${ADMIN_EMAIL:-admin@szaiai.com}"
|
||
|
||
# 服务端口配置
|
||
declare -A SERVICE_PORTS=(
|
||
["conversation"]=3004
|
||
["user"]=3001
|
||
["payment"]=3002
|
||
["knowledge"]=3003
|
||
["evolution"]=3005
|
||
["kong"]=8000
|
||
["postgres"]=5432
|
||
["redis"]=6379
|
||
["neo4j"]=7474
|
||
["nginx"]=80
|
||
)
|
||
|
||
# 服务目录映射
|
||
declare -A SERVICE_DIRS=(
|
||
["conversation"]="packages/services/conversation-service"
|
||
["user"]="packages/services/user-service"
|
||
["payment"]="packages/services/payment-service"
|
||
["knowledge"]="packages/services/knowledge-service"
|
||
["evolution"]="packages/services/evolution-service"
|
||
["web-client"]="packages/web-client"
|
||
["admin-client"]="packages/admin-client"
|
||
["shared"]="packages/shared"
|
||
)
|
||
|
||
# Docker服务名映射
|
||
declare -A DOCKER_SERVICES=(
|
||
["conversation"]="conversation-service"
|
||
["user"]="user-service"
|
||
["payment"]="payment-service"
|
||
["knowledge"]="knowledge-service"
|
||
["evolution"]="evolution-service"
|
||
["web-client"]="web-client"
|
||
["admin-client"]="admin-client"
|
||
["kong"]="kong"
|
||
["postgres"]="postgres"
|
||
["redis"]="redis"
|
||
["neo4j"]="neo4j"
|
||
["nginx"]="nginx"
|
||
)
|
||
|
||
#===============================================================================
|
||
# 工具函数
|
||
#===============================================================================
|
||
|
||
log_info() {
|
||
echo -e "${BLUE}[INFO]${NC} $1"
|
||
}
|
||
|
||
log_success() {
|
||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||
}
|
||
|
||
log_warning() {
|
||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||
}
|
||
|
||
log_error() {
|
||
echo -e "${RED}[ERROR]${NC} $1"
|
||
}
|
||
|
||
log_step() {
|
||
echo -e "${PURPLE}[STEP]${NC} $1"
|
||
}
|
||
|
||
# 检查命令是否存在
|
||
check_command() {
|
||
if ! command -v "$1" &> /dev/null; then
|
||
log_error "$1 未安装,请先安装"
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
# 检查环境
|
||
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)"
|
||
exit 1
|
||
fi
|
||
|
||
log_success "环境检查通过"
|
||
}
|
||
|
||
# 加载环境变量
|
||
load_env() {
|
||
if [ -f "$ENV_FILE" ]; then
|
||
export $(grep -v '^#' "$ENV_FILE" | xargs)
|
||
fi
|
||
}
|
||
|
||
# 等待服务就绪
|
||
wait_for_service() {
|
||
local host=$1
|
||
local port=$2
|
||
local service=$3
|
||
local max_attempts=${4:-30}
|
||
local attempt=1
|
||
|
||
log_info "等待 $service ($host:$port) 就绪..."
|
||
|
||
while [ $attempt -le $max_attempts ]; do
|
||
if nc -z "$host" "$port" 2>/dev/null; then
|
||
log_success "$service 已就绪"
|
||
return 0
|
||
fi
|
||
echo -n "."
|
||
sleep 2
|
||
attempt=$((attempt + 1))
|
||
done
|
||
|
||
echo ""
|
||
log_error "$service 启动超时"
|
||
return 1
|
||
}
|
||
|
||
#===============================================================================
|
||
# 构建函数
|
||
#===============================================================================
|
||
|
||
# 安装依赖
|
||
install_deps() {
|
||
log_step "安装项目依赖..."
|
||
pnpm install
|
||
log_success "依赖安装完成"
|
||
}
|
||
|
||
# 构建共享包
|
||
build_shared() {
|
||
log_step "构建 shared 包..."
|
||
cd "$PROJECT_ROOT/${SERVICE_DIRS[shared]}"
|
||
pnpm run build
|
||
cd "$PROJECT_ROOT"
|
||
log_success "shared 构建完成"
|
||
}
|
||
|
||
# 构建单个后端服务
|
||
build_backend_service() {
|
||
local service=$1
|
||
local dir="${SERVICE_DIRS[$service]}"
|
||
|
||
if [ -z "$dir" ]; then
|
||
log_error "未知服务: $service"
|
||
return 1
|
||
fi
|
||
|
||
log_step "构建 $service..."
|
||
cd "$PROJECT_ROOT/$dir"
|
||
|
||
# 清理旧构建
|
||
rm -rf dist
|
||
|
||
# TypeScript 编译
|
||
pnpm run build
|
||
|
||
cd "$PROJECT_ROOT"
|
||
log_success "$service 构建完成"
|
||
}
|
||
|
||
# 构建单个前端
|
||
build_frontend() {
|
||
local service=$1
|
||
local dir="${SERVICE_DIRS[$service]}"
|
||
|
||
if [ -z "$dir" ]; then
|
||
log_error "未知服务: $service"
|
||
return 1
|
||
fi
|
||
|
||
log_step "构建 $service..."
|
||
cd "$PROJECT_ROOT/$dir"
|
||
|
||
# 清理旧构建
|
||
rm -rf dist
|
||
|
||
# Vite 构建
|
||
pnpm run build
|
||
|
||
cd "$PROJECT_ROOT"
|
||
log_success "$service 构建完成"
|
||
}
|
||
|
||
# 构建所有后端服务
|
||
build_all_backend() {
|
||
build_shared
|
||
|
||
for service in conversation user payment knowledge evolution; do
|
||
build_backend_service "$service"
|
||
done
|
||
}
|
||
|
||
# 构建所有前端
|
||
build_all_frontend() {
|
||
for service in web-client admin-client; do
|
||
build_frontend "$service"
|
||
done
|
||
}
|
||
|
||
# 构建所有
|
||
build_all() {
|
||
log_info "开始构建所有服务..."
|
||
install_deps
|
||
build_all_backend
|
||
build_all_frontend
|
||
log_success "所有服务构建完成"
|
||
}
|
||
|
||
# 构建入口
|
||
do_build() {
|
||
local target=${1:-all}
|
||
|
||
case $target in
|
||
all)
|
||
build_all
|
||
;;
|
||
shared)
|
||
build_shared
|
||
;;
|
||
backend)
|
||
build_all_backend
|
||
;;
|
||
frontend)
|
||
build_all_frontend
|
||
;;
|
||
web-client|admin-client)
|
||
build_frontend "$target"
|
||
;;
|
||
conversation|user|payment|knowledge|evolution)
|
||
build_backend_service "$target"
|
||
;;
|
||
*)
|
||
log_error "未知构建目标: $target"
|
||
exit 1
|
||
;;
|
||
esac
|
||
}
|
||
|
||
#===============================================================================
|
||
# Docker 操作函数
|
||
#===============================================================================
|
||
|
||
# 构建 Docker 镜像
|
||
build_docker_images() {
|
||
local service=${1:-}
|
||
|
||
log_step "构建 Docker 镜像..."
|
||
|
||
if [ -n "$service" ] && [ "$service" != "all" ]; then
|
||
local docker_service="${DOCKER_SERVICES[$service]}"
|
||
if [ -n "$docker_service" ]; then
|
||
docker-compose build "$docker_service"
|
||
else
|
||
log_error "未知服务: $service"
|
||
return 1
|
||
fi
|
||
else
|
||
docker-compose build
|
||
fi
|
||
|
||
log_success "Docker 镜像构建完成"
|
||
}
|
||
|
||
# 启动基础设施
|
||
start_infrastructure() {
|
||
log_step "启动基础设施服务..."
|
||
|
||
docker-compose up -d postgres redis neo4j
|
||
|
||
# 等待数据库就绪
|
||
wait_for_service localhost 5432 "PostgreSQL"
|
||
wait_for_service localhost 6379 "Redis"
|
||
wait_for_service localhost 7474 "Neo4j"
|
||
|
||
log_success "基础设施启动完成"
|
||
}
|
||
|
||
# 启动 Kong 网关
|
||
start_kong() {
|
||
log_step "启动 Kong API 网关..."
|
||
|
||
docker-compose up -d kong-database
|
||
sleep 5
|
||
|
||
# Kong 数据库迁移
|
||
docker-compose run --rm kong kong migrations bootstrap || true
|
||
|
||
docker-compose up -d kong
|
||
wait_for_service localhost 8000 "Kong"
|
||
|
||
log_success "Kong 启动完成"
|
||
}
|
||
|
||
# 启动后端服务 (非 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
|
||
}
|
||
|
||
# 启动后端服务 (Docker 模式)
|
||
start_backend_service_docker() {
|
||
local service=$1
|
||
local docker_service="${DOCKER_SERVICES[$service]}"
|
||
|
||
log_step "启动 $service (Docker)..."
|
||
docker-compose up -d "$docker_service"
|
||
|
||
local port="${SERVICE_PORTS[$service]}"
|
||
wait_for_service localhost "$port" "$service"
|
||
}
|
||
|
||
# 启动所有后端服务
|
||
start_all_backend() {
|
||
local mode=${1:-docker}
|
||
|
||
for service in user payment knowledge conversation evolution; do
|
||
if [ "$mode" = "docker" ]; then
|
||
start_backend_service_docker "$service"
|
||
else
|
||
start_backend_service_local "$service"
|
||
fi
|
||
done
|
||
}
|
||
|
||
# 启动 Nginx (静态文件服务)
|
||
start_nginx() {
|
||
log_step "启动 Nginx..."
|
||
docker-compose up -d nginx
|
||
wait_for_service localhost 80 "Nginx"
|
||
log_success "Nginx 启动完成"
|
||
}
|
||
|
||
# 启动所有服务
|
||
start_all() {
|
||
local mode=${1:-docker}
|
||
|
||
log_info "开始启动所有服务 (模式: $mode)..."
|
||
|
||
# 创建必要目录
|
||
mkdir -p "$PROJECT_ROOT/logs"
|
||
mkdir -p "$PROJECT_ROOT/pids"
|
||
|
||
start_infrastructure
|
||
start_kong
|
||
start_all_backend "$mode"
|
||
start_nginx
|
||
|
||
log_success "所有服务启动完成"
|
||
do_status
|
||
}
|
||
|
||
# 启动入口
|
||
do_start() {
|
||
local target=${1:-all}
|
||
local mode=${2:-docker}
|
||
|
||
load_env
|
||
|
||
case $target in
|
||
all)
|
||
start_all "$mode"
|
||
;;
|
||
infra|infrastructure)
|
||
start_infrastructure
|
||
;;
|
||
kong)
|
||
start_kong
|
||
;;
|
||
nginx)
|
||
start_nginx
|
||
;;
|
||
postgres|redis|neo4j)
|
||
docker-compose up -d "$target"
|
||
;;
|
||
conversation|user|payment|knowledge|evolution)
|
||
if [ "$mode" = "docker" ]; then
|
||
start_backend_service_docker "$target"
|
||
else
|
||
start_backend_service_local "$target"
|
||
fi
|
||
;;
|
||
backend)
|
||
start_all_backend "$mode"
|
||
;;
|
||
*)
|
||
log_error "未知启动目标: $target"
|
||
exit 1
|
||
;;
|
||
esac
|
||
}
|
||
|
||
#===============================================================================
|
||
# 停止函数
|
||
#===============================================================================
|
||
|
||
# 停止单个服务 (本地模式)
|
||
stop_service_local() {
|
||
local service=$1
|
||
|
||
log_step "停止 $service..."
|
||
|
||
if command -v pm2 &> /dev/null; then
|
||
pm2 stop "iconsulting-$service" 2>/dev/null || true
|
||
pm2 delete "iconsulting-$service" 2>/dev/null || true
|
||
else
|
||
local pid_file="$PROJECT_ROOT/pids/$service.pid"
|
||
if [ -f "$pid_file" ]; then
|
||
kill $(cat "$pid_file") 2>/dev/null || true
|
||
rm -f "$pid_file"
|
||
fi
|
||
fi
|
||
|
||
log_success "$service 已停止"
|
||
}
|
||
|
||
# 停止单个服务 (Docker 模式)
|
||
stop_service_docker() {
|
||
local service=$1
|
||
local docker_service="${DOCKER_SERVICES[$service]}"
|
||
|
||
if [ -n "$docker_service" ]; then
|
||
log_step "停止 $service..."
|
||
docker-compose stop "$docker_service"
|
||
log_success "$service 已停止"
|
||
fi
|
||
}
|
||
|
||
# 停止所有服务
|
||
stop_all() {
|
||
local mode=${1:-docker}
|
||
|
||
log_info "停止所有服务..."
|
||
|
||
if [ "$mode" = "docker" ]; then
|
||
docker-compose down
|
||
else
|
||
for service in conversation user payment knowledge evolution; do
|
||
stop_service_local "$service"
|
||
done
|
||
docker-compose down
|
||
fi
|
||
|
||
log_success "所有服务已停止"
|
||
}
|
||
|
||
# 停止入口
|
||
do_stop() {
|
||
local target=${1:-all}
|
||
local mode=${2:-docker}
|
||
|
||
case $target in
|
||
all)
|
||
stop_all "$mode"
|
||
;;
|
||
infra|infrastructure)
|
||
docker-compose stop postgres redis neo4j
|
||
;;
|
||
conversation|user|payment|knowledge|evolution)
|
||
if [ "$mode" = "docker" ]; then
|
||
stop_service_docker "$target"
|
||
else
|
||
stop_service_local "$target"
|
||
fi
|
||
;;
|
||
kong|postgres|redis|neo4j|nginx)
|
||
docker-compose stop "$target"
|
||
;;
|
||
*)
|
||
log_error "未知停止目标: $target"
|
||
exit 1
|
||
;;
|
||
esac
|
||
}
|
||
|
||
#===============================================================================
|
||
# 重启函数
|
||
#===============================================================================
|
||
|
||
do_restart() {
|
||
local target=${1:-all}
|
||
local mode=${2:-docker}
|
||
|
||
log_info "重启 $target..."
|
||
do_stop "$target" "$mode"
|
||
sleep 2
|
||
do_start "$target" "$mode"
|
||
}
|
||
|
||
#===============================================================================
|
||
# 状态查看
|
||
#===============================================================================
|
||
|
||
do_status() {
|
||
echo ""
|
||
echo -e "${CYAN}═══════════════════════════════════════════════════════════════${NC}"
|
||
echo -e "${CYAN} iConsulting 服务状态 ${NC}"
|
||
echo -e "${CYAN}═══════════════════════════════════════════════════════════════${NC}"
|
||
echo ""
|
||
|
||
# Docker 服务状态
|
||
echo -e "${PURPLE}Docker 容器状态:${NC}"
|
||
docker-compose ps
|
||
echo ""
|
||
|
||
# 端口检查
|
||
echo -e "${PURPLE}服务端口检查:${NC}"
|
||
printf "%-20s %-10s %-10s\n" "服务" "端口" "状态"
|
||
echo "----------------------------------------"
|
||
|
||
for service in "${!SERVICE_PORTS[@]}"; do
|
||
local port="${SERVICE_PORTS[$service]}"
|
||
if nc -z localhost "$port" 2>/dev/null; then
|
||
printf "%-20s %-10s ${GREEN}%-10s${NC}\n" "$service" "$port" "运行中"
|
||
else
|
||
printf "%-20s %-10s ${RED}%-10s${NC}\n" "$service" "$port" "未运行"
|
||
fi
|
||
done
|
||
|
||
echo ""
|
||
|
||
# PM2 状态 (如果使用)
|
||
if command -v pm2 &> /dev/null; then
|
||
echo -e "${PURPLE}PM2 进程状态:${NC}"
|
||
pm2 list 2>/dev/null | grep iconsulting || echo "无 PM2 管理的服务"
|
||
echo ""
|
||
fi
|
||
}
|
||
|
||
#===============================================================================
|
||
# 日志查看
|
||
#===============================================================================
|
||
|
||
do_logs() {
|
||
local service=${1:-all}
|
||
local lines=${2:-100}
|
||
|
||
if [ "$service" = "all" ]; then
|
||
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"
|
||
else
|
||
# 本地日志
|
||
local log_file="$PROJECT_ROOT/logs/$service.log"
|
||
if [ -f "$log_file" ]; then
|
||
tail -f -n "$lines" "$log_file"
|
||
else
|
||
log_error "日志文件不存在: $log_file"
|
||
fi
|
||
fi
|
||
fi
|
||
}
|
||
|
||
#===============================================================================
|
||
# 清理函数
|
||
#===============================================================================
|
||
|
||
do_clean() {
|
||
local target=${1:-build}
|
||
|
||
case $target in
|
||
build)
|
||
log_step "清理构建产物..."
|
||
for dir in "${SERVICE_DIRS[@]}"; do
|
||
rm -rf "$PROJECT_ROOT/$dir/dist"
|
||
done
|
||
log_success "构建产物已清理"
|
||
;;
|
||
deps)
|
||
log_step "清理依赖..."
|
||
rm -rf node_modules
|
||
for dir in "${SERVICE_DIRS[@]}"; do
|
||
rm -rf "$PROJECT_ROOT/$dir/node_modules"
|
||
done
|
||
log_success "依赖已清理"
|
||
;;
|
||
docker)
|
||
log_step "清理 Docker 资源..."
|
||
docker-compose down -v --rmi local
|
||
docker system prune -f
|
||
log_success "Docker 资源已清理"
|
||
;;
|
||
logs)
|
||
log_step "清理日志..."
|
||
rm -rf "$PROJECT_ROOT/logs/*"
|
||
log_success "日志已清理"
|
||
;;
|
||
all)
|
||
do_clean build
|
||
do_clean deps
|
||
do_clean docker
|
||
do_clean logs
|
||
;;
|
||
*)
|
||
log_error "未知清理目标: $target (可选: build, deps, docker, logs, all)"
|
||
exit 1
|
||
;;
|
||
esac
|
||
}
|
||
|
||
#===============================================================================
|
||
# 完整部署
|
||
#===============================================================================
|
||
|
||
do_deploy() {
|
||
local mode=${1:-docker}
|
||
|
||
log_info "开始完整部署 (模式: $mode)..."
|
||
|
||
check_environment
|
||
|
||
# 构建
|
||
do_build all
|
||
|
||
# 如果是 Docker 模式,构建镜像
|
||
if [ "$mode" = "docker" ]; then
|
||
build_docker_images
|
||
fi
|
||
|
||
# 启动
|
||
do_start all "$mode"
|
||
|
||
# 配置 Kong 路由
|
||
setup_kong_routes
|
||
|
||
log_success "部署完成!"
|
||
echo ""
|
||
echo -e "${CYAN}访问地址:${NC}"
|
||
echo " 用户前端: https://$DOMAIN"
|
||
echo " 管理后台: https://$DOMAIN/admin"
|
||
echo " API 网关: https://$DOMAIN/api"
|
||
echo " Kong 管理: http://localhost:8001 (仅本地)"
|
||
echo ""
|
||
echo -e "${YELLOW}提示: 如需配置 SSL 证书,请执行:${NC}"
|
||
echo " ./deploy.sh ssl obtain"
|
||
echo ""
|
||
}
|
||
|
||
# 完整部署 (含 SSL)
|
||
do_deploy_full() {
|
||
log_info "开始完整部署 (含 SSL 证书)..."
|
||
|
||
# 基础部署
|
||
do_deploy docker
|
||
|
||
# 安装并获取 SSL 证书
|
||
do_ssl obtain
|
||
|
||
# 配置自动续期
|
||
do_ssl auto-renew
|
||
|
||
log_success "完整部署完成 (含 SSL)!"
|
||
echo ""
|
||
echo -e "${CYAN}访问地址:${NC}"
|
||
echo " 用户前端: https://$DOMAIN"
|
||
echo " 管理后台: https://$DOMAIN/admin"
|
||
echo ""
|
||
}
|
||
|
||
#===============================================================================
|
||
# SSL 证书管理 (Let's Encrypt)
|
||
#===============================================================================
|
||
|
||
# 安装 Certbot
|
||
install_certbot() {
|
||
log_step "检查/安装 Certbot..."
|
||
|
||
if command -v certbot &> /dev/null; then
|
||
log_success "Certbot 已安装"
|
||
return 0
|
||
fi
|
||
|
||
# 检测操作系统
|
||
if [ -f /etc/debian_version ]; then
|
||
# Debian/Ubuntu
|
||
apt-get update
|
||
apt-get install -y certbot python3-certbot-nginx
|
||
elif [ -f /etc/redhat-release ]; then
|
||
# CentOS/RHEL
|
||
yum install -y epel-release
|
||
yum install -y certbot python3-certbot-nginx
|
||
elif [ -f /etc/alpine-release ]; then
|
||
# Alpine
|
||
apk add certbot certbot-nginx
|
||
else
|
||
log_error "无法识别的操作系统,请手动安装 certbot"
|
||
exit 1
|
||
fi
|
||
|
||
log_success "Certbot 安装完成"
|
||
}
|
||
|
||
# 申请 SSL 证书
|
||
obtain_ssl_cert() {
|
||
local domain=${1:-$DOMAIN}
|
||
local email=${2:-$ADMIN_EMAIL}
|
||
|
||
log_step "申请 SSL 证书: $domain..."
|
||
|
||
# 创建证书目录
|
||
mkdir -p "$PROJECT_ROOT/nginx/ssl"
|
||
|
||
# 停止 Nginx (释放 80 端口)
|
||
docker-compose stop nginx 2>/dev/null || true
|
||
|
||
# 使用 standalone 模式申请证书
|
||
certbot certonly \
|
||
--standalone \
|
||
--non-interactive \
|
||
--agree-tos \
|
||
--email "$email" \
|
||
-d "$domain" \
|
||
--cert-path "$PROJECT_ROOT/nginx/ssl/cert.pem" \
|
||
--key-path "$PROJECT_ROOT/nginx/ssl/privkey.pem" \
|
||
--fullchain-path "$PROJECT_ROOT/nginx/ssl/fullchain.pem"
|
||
|
||
# 复制证书到项目目录
|
||
if [ -d "/etc/letsencrypt/live/$domain" ]; then
|
||
cp "/etc/letsencrypt/live/$domain/fullchain.pem" "$PROJECT_ROOT/nginx/ssl/"
|
||
cp "/etc/letsencrypt/live/$domain/privkey.pem" "$PROJECT_ROOT/nginx/ssl/"
|
||
chmod 644 "$PROJECT_ROOT/nginx/ssl/fullchain.pem"
|
||
chmod 600 "$PROJECT_ROOT/nginx/ssl/privkey.pem"
|
||
log_success "SSL 证书已获取并复制到 nginx/ssl/"
|
||
else
|
||
log_error "证书申请失败"
|
||
return 1
|
||
fi
|
||
|
||
# 重启 Nginx
|
||
docker-compose up -d nginx
|
||
|
||
log_success "SSL 证书配置完成"
|
||
}
|
||
|
||
# 续期 SSL 证书
|
||
renew_ssl_cert() {
|
||
log_step "续期 SSL 证书..."
|
||
|
||
certbot renew --quiet
|
||
|
||
# 复制新证书
|
||
if [ -d "/etc/letsencrypt/live/$DOMAIN" ]; then
|
||
cp "/etc/letsencrypt/live/$DOMAIN/fullchain.pem" "$PROJECT_ROOT/nginx/ssl/"
|
||
cp "/etc/letsencrypt/live/$DOMAIN/privkey.pem" "$PROJECT_ROOT/nginx/ssl/"
|
||
|
||
# 重载 Nginx
|
||
docker-compose exec nginx nginx -s reload
|
||
log_success "SSL 证书续期完成"
|
||
fi
|
||
}
|
||
|
||
# 配置证书自动续期
|
||
setup_ssl_auto_renew() {
|
||
log_step "配置证书自动续期..."
|
||
|
||
# 创建续期脚本
|
||
cat > /etc/cron.d/certbot-renew << EOF
|
||
# 每天凌晨 3 点检查证书续期
|
||
0 3 * * * root certbot renew --quiet && cp /etc/letsencrypt/live/$DOMAIN/*.pem $PROJECT_ROOT/nginx/ssl/ && docker-compose -f $PROJECT_ROOT/docker-compose.yml exec -T nginx nginx -s reload
|
||
EOF
|
||
|
||
log_success "自动续期已配置 (每天 3:00 检查)"
|
||
}
|
||
|
||
# SSL 操作入口
|
||
do_ssl() {
|
||
local action=${1:-status}
|
||
|
||
case $action in
|
||
install)
|
||
install_certbot
|
||
;;
|
||
obtain|get)
|
||
install_certbot
|
||
obtain_ssl_cert "$2" "$3"
|
||
;;
|
||
renew)
|
||
renew_ssl_cert
|
||
;;
|
||
auto-renew)
|
||
setup_ssl_auto_renew
|
||
;;
|
||
status)
|
||
echo -e "${PURPLE}SSL 证书状态:${NC}"
|
||
if [ -f "$PROJECT_ROOT/nginx/ssl/fullchain.pem" ]; then
|
||
echo "证书文件: 存在"
|
||
openssl x509 -in "$PROJECT_ROOT/nginx/ssl/fullchain.pem" -noout -dates 2>/dev/null || echo "无法读取证书信息"
|
||
else
|
||
echo "证书文件: 不存在"
|
||
fi
|
||
|
||
if command -v certbot &> /dev/null; then
|
||
echo ""
|
||
echo "Certbot 证书列表:"
|
||
certbot certificates 2>/dev/null || echo "无证书"
|
||
fi
|
||
;;
|
||
*)
|
||
log_error "未知 SSL 操作: $action (可选: install, obtain, renew, auto-renew, status)"
|
||
exit 1
|
||
;;
|
||
esac
|
||
}
|
||
|
||
#===============================================================================
|
||
# Kong API Gateway 配置
|
||
#===============================================================================
|
||
|
||
# 配置 Kong 路由
|
||
setup_kong_routes() {
|
||
local kong_admin="${KONG_ADMIN_URL:-http://localhost:8001}"
|
||
|
||
log_step "配置 Kong API Gateway 路由..."
|
||
|
||
# 等待 Kong 就绪
|
||
local max_attempts=30
|
||
local attempt=1
|
||
while [ $attempt -le $max_attempts ]; do
|
||
if curl -s "$kong_admin" > /dev/null 2>&1; then
|
||
break
|
||
fi
|
||
echo -n "."
|
||
sleep 2
|
||
attempt=$((attempt + 1))
|
||
done
|
||
|
||
if [ $attempt -gt $max_attempts ]; then
|
||
log_error "Kong Admin API 不可用"
|
||
return 1
|
||
fi
|
||
|
||
echo ""
|
||
log_info "Kong Admin API 就绪"
|
||
|
||
# 创建服务和路由
|
||
log_info "创建微服务..."
|
||
|
||
# User Service
|
||
curl -s -X POST "$kong_admin/services" \
|
||
-d "name=user-service" \
|
||
-d "url=http://user-service:3001" > /dev/null 2>&1 || true
|
||
|
||
curl -s -X POST "$kong_admin/services/user-service/routes" \
|
||
-d "name=user-route" \
|
||
-d "paths[]=/v1/users" \
|
||
-d "paths[]=/v1/auth" \
|
||
-d "strip_path=false" > /dev/null 2>&1 || true
|
||
|
||
# Payment Service
|
||
curl -s -X POST "$kong_admin/services" \
|
||
-d "name=payment-service" \
|
||
-d "url=http://payment-service:3002" > /dev/null 2>&1 || true
|
||
|
||
curl -s -X POST "$kong_admin/services/payment-service/routes" \
|
||
-d "name=payment-route" \
|
||
-d "paths[]=/v1/payments" \
|
||
-d "paths[]=/v1/balance" \
|
||
-d "strip_path=false" > /dev/null 2>&1 || true
|
||
|
||
# Knowledge Service
|
||
curl -s -X POST "$kong_admin/services" \
|
||
-d "name=knowledge-service" \
|
||
-d "url=http://knowledge-service:3003" > /dev/null 2>&1 || true
|
||
|
||
curl -s -X POST "$kong_admin/services/knowledge-service/routes" \
|
||
-d "name=knowledge-route" \
|
||
-d "paths[]=/v1/knowledge" \
|
||
-d "strip_path=false" > /dev/null 2>&1 || true
|
||
|
||
# Conversation Service
|
||
curl -s -X POST "$kong_admin/services" \
|
||
-d "name=conversation-service" \
|
||
-d "url=http://conversation-service:3004" > /dev/null 2>&1 || true
|
||
|
||
curl -s -X POST "$kong_admin/services/conversation-service/routes" \
|
||
-d "name=conversation-route" \
|
||
-d "paths[]=/v1/conversations" \
|
||
-d "strip_path=false" > /dev/null 2>&1 || true
|
||
|
||
# Evolution Service (Admin)
|
||
curl -s -X POST "$kong_admin/services" \
|
||
-d "name=evolution-service" \
|
||
-d "url=http://evolution-service:3005" > /dev/null 2>&1 || true
|
||
|
||
curl -s -X POST "$kong_admin/services/evolution-service/routes" \
|
||
-d "name=evolution-route" \
|
||
-d "paths[]=/v1/evolution" \
|
||
-d "paths[]=/v1/memory" \
|
||
-d "paths[]=/v1/admin" \
|
||
-d "strip_path=false" > /dev/null 2>&1 || true
|
||
|
||
log_info "配置全局插件..."
|
||
|
||
# Rate Limiting
|
||
curl -s -X POST "$kong_admin/plugins" \
|
||
-d "name=rate-limiting" \
|
||
-d "config.minute=100" \
|
||
-d "config.policy=local" > /dev/null 2>&1 || true
|
||
|
||
# CORS
|
||
curl -s -X POST "$kong_admin/plugins" \
|
||
-d "name=cors" \
|
||
-d "config.origins=https://$DOMAIN,http://localhost" \
|
||
-d "config.methods=GET,POST,PUT,DELETE,OPTIONS,PATCH" \
|
||
-d "config.headers=Accept,Authorization,Content-Type,X-User-Id,X-Request-Id" \
|
||
-d "config.credentials=true" \
|
||
-d "config.max_age=3600" > /dev/null 2>&1 || true
|
||
|
||
# Request ID
|
||
curl -s -X POST "$kong_admin/plugins" \
|
||
-d "name=correlation-id" \
|
||
-d "config.header_name=X-Request-Id" \
|
||
-d "config.generator=uuid" > /dev/null 2>&1 || true
|
||
|
||
log_success "Kong 路由配置完成"
|
||
|
||
# 显示配置结果
|
||
echo ""
|
||
echo -e "${PURPLE}已配置的服务:${NC}"
|
||
curl -s "$kong_admin/services" | grep -o '"name":"[^"]*"' | sed 's/"name":"//g;s/"//g' | while read name; do
|
||
echo " - $name"
|
||
done
|
||
|
||
echo ""
|
||
echo -e "${PURPLE}已配置的路由:${NC}"
|
||
curl -s "$kong_admin/routes" | grep -o '"name":"[^"]*"' | sed 's/"name":"//g;s/"//g' | while read name; do
|
||
echo " - $name"
|
||
done
|
||
}
|
||
|
||
# Kong 操作入口
|
||
do_kong() {
|
||
local action=${1:-status}
|
||
|
||
case $action in
|
||
setup|init)
|
||
setup_kong_routes
|
||
;;
|
||
status)
|
||
local kong_admin="${KONG_ADMIN_URL:-http://localhost:8001}"
|
||
echo -e "${PURPLE}Kong 状态:${NC}"
|
||
curl -s "$kong_admin" > /dev/null 2>&1 && echo "Kong Admin API: 运行中" || echo "Kong Admin API: 未运行"
|
||
curl -s "http://localhost:8000" > /dev/null 2>&1 && echo "Kong Proxy: 运行中" || echo "Kong Proxy: 未运行"
|
||
|
||
echo ""
|
||
echo "服务数量: $(curl -s "$kong_admin/services" 2>/dev/null | grep -o '"total":[0-9]*' | cut -d: -f2 || echo 0)"
|
||
echo "路由数量: $(curl -s "$kong_admin/routes" 2>/dev/null | grep -o '"total":[0-9]*' | cut -d: -f2 || echo 0)"
|
||
;;
|
||
services)
|
||
curl -s "http://localhost:8001/services" | python3 -m json.tool 2>/dev/null || curl -s "http://localhost:8001/services"
|
||
;;
|
||
routes)
|
||
curl -s "http://localhost:8001/routes" | python3 -m json.tool 2>/dev/null || curl -s "http://localhost:8001/routes"
|
||
;;
|
||
plugins)
|
||
curl -s "http://localhost:8001/plugins" | python3 -m json.tool 2>/dev/null || curl -s "http://localhost:8001/plugins"
|
||
;;
|
||
*)
|
||
log_error "未知 Kong 操作: $action (可选: setup, status, services, routes, plugins)"
|
||
exit 1
|
||
;;
|
||
esac
|
||
}
|
||
|
||
#===============================================================================
|
||
# 数据库操作
|
||
#===============================================================================
|
||
|
||
do_db() {
|
||
local action=${1:-status}
|
||
|
||
case $action in
|
||
migrate)
|
||
log_step "执行数据库迁移..."
|
||
# 可以添加 TypeORM 迁移命令
|
||
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"
|
||
done
|
||
log_success "数据库迁移完成"
|
||
;;
|
||
seed)
|
||
log_step "初始化种子数据..."
|
||
# 添加种子数据脚本
|
||
log_success "种子数据初始化完成"
|
||
;;
|
||
backup)
|
||
local backup_dir="$PROJECT_ROOT/backups/$(date +%Y%m%d_%H%M%S)"
|
||
mkdir -p "$backup_dir"
|
||
|
||
log_step "备份数据库..."
|
||
docker-compose exec -T postgres pg_dump -U postgres iconsulting > "$backup_dir/postgres.sql"
|
||
log_success "数据库备份到: $backup_dir"
|
||
;;
|
||
restore)
|
||
local backup_file=$2
|
||
if [ -z "$backup_file" ]; then
|
||
log_error "请指定备份文件: ./deploy.sh db restore <backup_file>"
|
||
exit 1
|
||
fi
|
||
|
||
log_step "恢复数据库..."
|
||
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
|
||
wait_for_service localhost 5432 "PostgreSQL"
|
||
do_db migrate
|
||
log_success "数据库已重置"
|
||
fi
|
||
;;
|
||
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 未运行"
|
||
curl -s http://localhost:7474 > /dev/null && echo "Neo4j 运行中" || echo "Neo4j 未运行"
|
||
;;
|
||
*)
|
||
log_error "未知数据库操作: $action (可选: migrate, seed, backup, restore, reset, status)"
|
||
exit 1
|
||
;;
|
||
esac
|
||
}
|
||
|
||
#===============================================================================
|
||
# 帮助信息
|
||
#===============================================================================
|
||
|
||
show_help() {
|
||
cat << EOF
|
||
|
||
╔═══════════════════════════════════════════════════════════════════════════════╗
|
||
║ iConsulting 部署管理脚本 ║
|
||
║ 域名: $DOMAIN ║
|
||
╚═══════════════════════════════════════════════════════════════════════════════╝
|
||
|
||
用法: ./deploy.sh <command> [target] [options]
|
||
|
||
命令:
|
||
build [target] 编译构建
|
||
target: all, shared, backend, frontend,
|
||
conversation, user, payment, knowledge, evolution,
|
||
web-client, admin-client
|
||
|
||
start [target] [mode] 启动服务
|
||
target: all, infra, kong, nginx, backend,
|
||
conversation, user, payment, knowledge, evolution,
|
||
postgres, redis, neo4j
|
||
mode: docker (默认), local
|
||
|
||
stop [target] [mode] 停止服务
|
||
(target 同上)
|
||
|
||
restart [target] [mode] 重启服务
|
||
(target 同上)
|
||
|
||
status 查看所有服务状态
|
||
|
||
logs [service] [lines] 查看日志
|
||
service: 服务名或 all
|
||
lines: 显示行数 (默认 100)
|
||
|
||
clean [target] 清理
|
||
target: build, deps, docker, logs, all
|
||
|
||
deploy [mode] 完整部署 (构建 + 启动)
|
||
mode: docker (默认), local
|
||
|
||
deploy-full 完整部署 (含 SSL 证书自动申请)
|
||
|
||
ssl <action> SSL 证书管理 (Let's Encrypt)
|
||
action: install - 安装 certbot
|
||
obtain - 申请证书
|
||
renew - 续期证书
|
||
auto-renew - 配置自动续期
|
||
status - 查看证书状态
|
||
|
||
kong <action> Kong API Gateway 管理
|
||
action: setup - 配置路由和插件
|
||
status - 查看状态
|
||
services - 查看服务列表
|
||
routes - 查看路由列表
|
||
plugins - 查看插件列表
|
||
|
||
db <action> 数据库操作
|
||
action: migrate, seed, backup, restore, reset, status
|
||
|
||
help 显示此帮助信息
|
||
|
||
示例:
|
||
./deploy.sh deploy # 完整部署
|
||
./deploy.sh deploy-full # 完整部署 (含 SSL)
|
||
./deploy.sh ssl obtain # 申请 SSL 证书
|
||
./deploy.sh kong setup # 配置 Kong 路由
|
||
./deploy.sh build conversation # 只构建对话服务
|
||
./deploy.sh start backend local # 本地模式启动所有后端
|
||
./deploy.sh restart user docker # 重启用户服务 (Docker)
|
||
./deploy.sh logs conversation 200 # 查看对话服务最近200行日志
|
||
./deploy.sh clean all # 清理所有构建产物和依赖
|
||
./deploy.sh db backup # 备份数据库
|
||
./deploy.sh db migrate # 执行数据库迁移
|
||
|
||
环境变量:
|
||
DOMAIN 域名 (默认: iconsulting.szaiai.com)
|
||
ADMIN_EMAIL 管理员邮箱 (用于 SSL 证书)
|
||
|
||
EOF
|
||
}
|
||
|
||
#===============================================================================
|
||
# 主入口
|
||
#===============================================================================
|
||
|
||
main() {
|
||
local command=${1:-help}
|
||
shift || true
|
||
|
||
case $command in
|
||
build)
|
||
do_build "$@"
|
||
;;
|
||
start)
|
||
do_start "$@"
|
||
;;
|
||
stop)
|
||
do_stop "$@"
|
||
;;
|
||
restart)
|
||
do_restart "$@"
|
||
;;
|
||
status)
|
||
do_status
|
||
;;
|
||
logs)
|
||
do_logs "$@"
|
||
;;
|
||
clean)
|
||
do_clean "$@"
|
||
;;
|
||
deploy)
|
||
do_deploy "$@"
|
||
;;
|
||
deploy-full)
|
||
do_deploy_full
|
||
;;
|
||
ssl)
|
||
do_ssl "$@"
|
||
;;
|
||
kong)
|
||
do_kong "$@"
|
||
;;
|
||
db)
|
||
do_db "$@"
|
||
;;
|
||
help|--help|-h)
|
||
show_help
|
||
;;
|
||
*)
|
||
log_error "未知命令: $command"
|
||
show_help
|
||
exit 1
|
||
;;
|
||
esac
|
||
}
|
||
|
||
# 执行主函数
|
||
main "$@"
|