#!/usr/bin/env bash # ============================================ # PandaWiki 一键部署脚本 # 用法: ./deploy.sh [选项] # # 选项: # --build 强制重新构建所有镜像 # --skip-build 跳过镜像构建 (使用已有镜像) # --pull 拉取最新的基础镜像 # --clean 停止并清理所有容器和数据 (危险!) # --stop 停止所有服务 # --restart 重启所有服务 # --logs 查看所有服务日志 # --status 查看服务运行状态 # --help 显示帮助信息 # ============================================ set -e # ============================================ # 配置 # ============================================ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$SCRIPT_DIR" ENV_FILE=".env" COMPOSE_FILE="docker-compose.yml" # 默认端口 ADMIN_PORT="${ADMIN_PORT:-2443}" APP_PORT="${APP_PORT:-3010}" API_PORT="${API_PORT:-8000}" # 颜色输出 RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' CYAN='\033[0;36m' BOLD='\033[1m' NC='\033[0m' # No Color # ============================================ # 工具函数 # ============================================ log_info() { echo -e "${GREEN}[INFO]${NC} $1"; } log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } log_error() { echo -e "${RED}[ERROR]${NC} $1"; } log_step() { echo -e "\n${CYAN}${BOLD}==>${NC} ${CYAN}$1${NC}"; } log_title() { echo -e "\n${BLUE}${BOLD}━━━ $1 ━━━${NC}"; } banner() { echo -e "${BLUE}" echo " ╔══════════════════════════════════════╗" echo " ║ PandaWiki 部署工具 ║" echo " ║ v3.85.0 (功能全解锁版) ║" echo " ╚══════════════════════════════════════╝" echo -e "${NC}" } # ============================================ # 前置检查 # ============================================ check_prerequisites() { log_step "环境检查" # 检查 Docker if ! command -v docker &> /dev/null; then log_error "未找到 Docker,请先安装 Docker Engine 20.x+" echo " Ubuntu: sudo apt-get install docker-ce" echo " 官方文档: https://docs.docker.com/engine/install/" exit 1 fi log_info "Docker: $(docker --version)" # 检查 Docker Compose if docker compose version &> /dev/null; then log_info "Docker Compose: $(docker compose version --short)" elif command -v docker-compose &> /dev/null; then log_info "Docker Compose (独立版): $(docker-compose --version)" else log_error "未找到 Docker Compose" echo " 安装: sudo apt-get install docker-compose-plugin" exit 1 fi # 检查 docker-compose.yml if [ ! -f "$COMPOSE_FILE" ]; then log_error "未找到 $COMPOSE_FILE,请确保在项目根目录运行此脚本" exit 1 fi # 检查磁盘空间 (至少 10GB) local available_gb available_gb=$(df -BG . | awk 'NR==2 {print $4}' | sed 's/G//') if [ "${available_gb:-0}" -lt 10 ]; then log_warn "磁盘可用空间不足 10GB (当前: ${available_gb}GB),建议预留足够空间" fi log_info "环境检查通过" } # ============================================ # 环境变量配置 # ============================================ setup_env() { log_step "配置环境变量" if [ -f "$ENV_FILE" ]; then log_info "已找到 ${ENV_FILE},跳过创建" # 加载环境变量 set -a source "$ENV_FILE" set +a return fi log_warn "未找到 ${ENV_FILE},创建默认配置..." # 生成随机密码 local pg_pass=$(openssl rand -base64 16 2>/dev/null || head -c 16 /dev/urandom | base64) local redis_pass=$(openssl rand -base64 16 2>/dev/null || head -c 16 /dev/urandom | base64) local nats_pass=$(openssl rand -base64 16 2>/dev/null || head -c 16 /dev/urandom | base64) local s3_pass=$(openssl rand -base64 16 2>/dev/null || head -c 16 /dev/urandom | base64) local jwt_secret=$(openssl rand -base64 32 2>/dev/null || head -c 32 /dev/urandom | base64) cat > "$ENV_FILE" << EOF # ============================================ # PandaWiki 环境变量配置 # 生成时间: $(date '+%Y-%m-%d %H:%M:%S') # ============================================ # 数据库 (PostgreSQL) POSTGRES_PASSWORD=${pg_pass} # 缓存 (Redis) REDIS_PASSWORD=${redis_pass} # 消息队列 (NATS) NATS_USER=panda-wiki NATS_PASSWORD=${nats_pass} # 对象存储 (MinIO / S3) S3_ACCESS_KEY=s3panda-wiki S3_SECRET_KEY=${s3_pass} # JWT 签名密钥 JWT_SECRET=${jwt_secret} # 管理员初始密码 (首次登录后请修改) ADMIN_PASSWORD=admin123 # RAG 向量检索服务地址 (留空使用默认) # RAG_BASE_URL=http://your-rag-server:5050 # 日志级别: -4=debug, 0=info, 4=warn, 8=error LOG_LEVEL=0 # 端口配置 (如需修改,请确保与防火墙规则一致) ADMIN_PORT=2443 APP_PORT=3010 API_PORT=8000 EOF chmod 600 "$ENV_FILE" log_info "已创建 ${ENV_FILE},密码已随机生成并保存" set -a source "$ENV_FILE" set +a } # ============================================ # 构建镜像 # ============================================ build_images() { log_step "构建 Docker 镜像" local build_start build_start=$(date +%s) # 构建后端 log_info "构建 panda-wiki-api ..." docker compose -f "$COMPOSE_FILE" build api log_info "构建 panda-wiki-consumer ..." docker compose -f "$COMPOSE_FILE" build consumer # 构建前端 (多阶段构建,耗时较长) log_info "构建 panda-wiki-admin (管理后台) ..." docker compose -f "$COMPOSE_FILE" build admin log_info "构建 panda-wiki-app (Wiki 用户端) ..." docker compose -f "$COMPOSE_FILE" build app local build_end build_end=$(date +%s) local build_duration=$((build_end - build_start)) log_info "全部镜像构建完成 (耗时 ${build_duration} 秒)" # 显示构建的镜像 echo "" docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}" \ | grep -E "panda-wiki|REPOSITORY" } # ============================================ # 拉取基础镜像 # ============================================ pull_base_images() { log_step "拉取基础镜像" docker pull postgres:16-alpine & docker pull redis:7-alpine & docker pull nats:2-alpine & docker pull minio/minio:latest & wait log_info "基础镜像拉取完成" } # ============================================ # 启动服务 # ============================================ start_services() { log_step "启动服务" # 先启动基础设施 log_info "启动基础设施 (PostgreSQL, Redis, NATS, MinIO) ..." docker compose -f "$COMPOSE_FILE" up -d postgres redis nats minio # 等待基础设施就绪 log_info "等待基础设施就绪 ..." local max_wait=60 local waited=0 while [ $waited -lt $max_wait ]; do if docker compose -f "$COMPOSE_FILE" ps postgres 2>/dev/null | grep -q "healthy"; then break fi sleep 2 waited=$((waited + 2)) echo -n "." done echo "" if [ $waited -ge $max_wait ]; then log_error "基础设施启动超时,请检查: docker compose logs postgres" exit 1 fi log_info "基础设施就绪" # 启动业务服务 log_info "启动业务服务 (API, Consumer, Admin, App) ..." docker compose -f "$COMPOSE_FILE" up -d api consumer admin app # 等待 API 就绪 log_info "等待 API 服务就绪 ..." waited=0 while [ $waited -lt 30 ]; do if curl -sf http://localhost:${API_PORT}/api/v1/health > /dev/null 2>&1; then break fi sleep 2 waited=$((waited + 2)) echo -n "." done echo "" log_info "全部服务启动完成" } # ============================================ # 显示部署信息 # ============================================ show_info() { log_title "部署完成" # 尝试获取服务器 IP local server_ip if command -v hostname &> /dev/null; then server_ip=$(hostname -I 2>/dev/null | awk '{print $1}') fi if [ -z "$server_ip" ]; then server_ip="服务器IP" fi echo "" echo -e " ${BOLD}PandaWiki 已成功部署${NC}" echo "" echo -e " ${CYAN}访问地址:${NC}" echo -e " ┌─────────────────────────────────────────────┐" echo -e " │ 管理后台: ${GREEN}http://${server_ip}:${ADMIN_PORT}${NC}" echo -e " │ Wiki 前端: ${GREEN}http://${server_ip}:${APP_PORT}${NC}" echo -e " │ API 服务: ${GREEN}http://${server_ip}:${API_PORT}${NC}" echo -e " │ MinIO 控制台: ${GREEN}http://${server_ip}:9001${NC}" echo -e " └─────────────────────────────────────────────┘" echo "" echo -e " ${CYAN}登录信息:${NC}" echo -e " ┌─────────────────────────────────────────────┐" echo -e " │ 用户名: ${BOLD}admin${NC}" echo -e " │ 密码: ${BOLD}${ADMIN_PASSWORD:-admin123}${NC} (首次登录后请修改)" echo -e " └─────────────────────────────────────────────┘" echo "" echo -e " ${CYAN}常用命令:${NC}" echo -e " ┌─────────────────────────────────────────────┐" echo -e " │ 查看日志: ${BOLD}docker compose logs -f${NC}" echo -e " │ 查看状态: ${BOLD}./deploy.sh --status${NC}" echo -e " │ 重启服务: ${BOLD}./deploy.sh --restart${NC}" echo -e " │ 停止服务: ${BOLD}./deploy.sh --stop${NC}" echo -e " └─────────────────────────────────────────────┘" echo "" echo -e " ${YELLOW}下一步: 登录管理后台 → 配置 AI 模型 → 创建知识库${NC}" echo "" } # ============================================ # 辅助命令 # ============================================ stop_services() { log_step "停止所有服务" docker compose -f "$COMPOSE_FILE" down log_info "所有服务已停止" } restart_services() { log_step "重启所有服务" docker compose -f "$COMPOSE_FILE" restart log_info "所有服务已重启" } show_logs() { log_step "查看服务日志 (按 Ctrl+C 退出)" docker compose -f "$COMPOSE_FILE" logs -f --tail=50 } show_status() { log_step "服务运行状态" echo "" docker compose -f "$COMPOSE_FILE" ps echo "" # 检查 API 健康状态 if curl -sf http://localhost:${API_PORT}/api/v1/health > /dev/null 2>&1; then echo -e " API 健康检查: ${GREEN}正常${NC}" else echo -e " API 健康检查: ${RED}异常${NC}" fi # 显示资源占用 echo "" echo " 容器资源占用:" docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}" \ $(docker compose -f "$COMPOSE_FILE" ps -q) 2>/dev/null || true } clean_all() { log_step "清理所有容器和数据" echo -e "${RED}${BOLD}警告: 此操作将删除所有容器、数据卷和镜像!${NC}" echo -e "${RED}数据库中的所有数据将永久丢失!${NC}" echo "" read -p "确认清理? 输入 DELETE 继续: " confirm if [ "$confirm" != "DELETE" ]; then log_info "已取消清理操作" exit 0 fi log_info "停止并删除容器 ..." docker compose -f "$COMPOSE_FILE" down -v --remove-orphans log_info "删除构建的镜像 ..." docker images --format "{{.Repository}}:{{.Tag}}" \ | grep "panda-wiki" \ | xargs -r docker rmi 2>/dev/null || true log_info "清理完成" } # ============================================ # 主流程 # ============================================ main() { local DO_BUILD=true local DO_PULL=false # 解析命令行参数 while [[ $# -gt 0 ]]; do case "$1" in --build) DO_BUILD=true shift ;; --skip-build) DO_BUILD=false shift ;; --pull) DO_PULL=true shift ;; --clean) banner clean_all exit 0 ;; --stop) banner stop_services exit 0 ;; --restart) banner restart_services show_info exit 0 ;; --logs) show_logs exit 0 ;; --status) banner show_status exit 0 ;; --help|-h) banner echo "用法: $0 [选项]" echo "" echo "选项:" echo " --build 强制重新构建所有镜像 (默认)" echo " --skip-build 跳过镜像构建步骤" echo " --pull 拉取最新的基础镜像" echo " --clean 停止并清理所有容器和数据 (危险!)" echo " --stop 停止所有服务" echo " --restart 重启所有服务" echo " --logs 查看所有服务日志" echo " --status 查看服务运行状态" echo " --help 显示此帮助信息" echo "" echo "示例:" echo " $0 # 完整部署 (构建 + 启动)" echo " $0 --skip-build # 跳过构建,直接启动" echo " $0 --restart # 重启已部署的服务" echo " $0 --status # 查看运行状态" exit 0 ;; *) log_error "未知选项: $1" echo "使用 --help 查看帮助" exit 1 ;; esac done banner # 检查是否已有正在运行的服务 if docker compose -f "$COMPOSE_FILE" ps --services --filter "status=running" 2>/dev/null | grep -q .; then log_warn "检测到已有服务在运行" read -p "是否重新部署? [y/N]: " redeploy if [ "$redeploy" != "y" ] && [ "$redeploy" != "Y" ]; then log_info "已取消。使用 --restart 重启,或 --stop 停止" exit 0 fi docker compose -f "$COMPOSE_FILE" down fi # 部署流程 check_prerequisites setup_env if [ "$DO_PULL" = true ]; then pull_base_images fi if [ "$DO_BUILD" = true ]; then build_images else log_step "跳过镜像构建" # 检查镜像是否存在 local missing_images=() for img in panda-wiki-api:latest panda-wiki-consumer:latest panda-wiki-admin:latest panda-wiki-app:latest; do if ! docker image inspect "$img" &> /dev/null; then missing_images+=("$img") fi done if [ ${#missing_images[@]} -gt 0 ]; then log_warn "以下镜像不存在: ${missing_images[*]}" read -p "是否现在构建? [Y/n]: " do_build_now if [ "$do_build_now" != "n" ] && [ "$do_build_now" != "N" ]; then build_images else log_error "缺少必需镜像,无法继续" exit 1 fi fi fi start_services show_info } main "$@"