refactor: 改用 Docker 容器构建,无需在主机安装 Node.js
- 移除对主机 node/pnpm 的依赖,只需要 Docker - 新增 builder 镜像自动创建功能 - 所有构建操作在容器中执行 - 支持 docker-compose 和 docker compose (V2) - 使用 pnpm store volume 加速依赖安装 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
3ac83e9305
commit
ae9b33a491
231
deploy.sh
231
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 "$@"
|
||||
|
|
|
|||
Loading…
Reference in New Issue