diff --git a/deploy.sh b/deploy.sh new file mode 100644 index 0000000..10a427d --- /dev/null +++ b/deploy.sh @@ -0,0 +1,518 @@ +#!/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 "$@"