519 lines
16 KiB
Bash
519 lines
16 KiB
Bash
#!/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 "$@"
|