Files
YouduWiki/deploy.sh
2026-05-21 20:34:55 +08:00

519 lines
16 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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 "$@"