#!/bin/bash # OpenEuler服务器部署脚本 # 用于在OpenEuler服务器上部署Laravel知识库系统 set -e # 脚本配置 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_NAME="knowledge-base" DEPLOY_DIR="/opt/${PROJECT_NAME}" BACKUP_DIR="/opt/${PROJECT_NAME}-backup" LOG_FILE="/var/log/${PROJECT_NAME}-deploy.log" # 颜色输出 RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # 日志函数 log() { echo -e "${BLUE}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1" | tee -a "$LOG_FILE" } log_success() { echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')] ✓${NC} $1" | tee -a "$LOG_FILE" } log_warning() { echo -e "${YELLOW}[$(date +'%Y-%m-%d %H:%M:%S')] ⚠${NC} $1" | tee -a "$LOG_FILE" } log_error() { echo -e "${RED}[$(date +'%Y-%m-%d %H:%M:%S')] ✗${NC} $1" | tee -a "$LOG_FILE" } # 显示帮助信息 show_help() { cat << EOF OpenEuler服务器部署脚本 用法: $0 [选项] [镜像目录] 选项: -h, --help 显示此帮助信息 -d, --deploy-dir DIR 部署目录 (默认: /opt/knowledge-base) -b, --backup 部署前备份现有安装 -u, --update 更新现有部署 -r, --rollback 回滚到上一个版本 --skip-docker-install 跳过Docker安装 --skip-images 跳过镜像导入 --skip-env-setup 跳过环境配置 --dry-run 仅显示将要执行的操作 参数: 镜像目录 包含Docker镜像文件的目录 示例: $0 /path/to/docker-images # 全新部署 $0 -u /path/to/docker-images # 更新现有部署 $0 -b -d /custom/path # 备份并部署到自定义目录 $0 --rollback # 回滚到上一个版本 EOF } # 默认配置 BACKUP=false UPDATE=false ROLLBACK=false SKIP_DOCKER_INSTALL=false SKIP_IMAGES=false SKIP_ENV_SETUP=false DRY_RUN=false IMAGES_DIR="" # 解析命令行参数 while [[ $# -gt 0 ]]; do case $1 in -h|--help) show_help exit 0 ;; -d|--deploy-dir) DEPLOY_DIR="$2" BACKUP_DIR="${DEPLOY_DIR}-backup" shift 2 ;; -b|--backup) BACKUP=true shift ;; -u|--update) UPDATE=true shift ;; -r|--rollback) ROLLBACK=true shift ;; --skip-docker-install) SKIP_DOCKER_INSTALL=true shift ;; --skip-images) SKIP_IMAGES=true shift ;; --skip-env-setup) SKIP_ENV_SETUP=true shift ;; --dry-run) DRY_RUN=true shift ;; -*) log_error "未知参数: $1" show_help exit 1 ;; *) IMAGES_DIR="$1" shift ;; esac done # 检查是否以root权限运行 if [[ $EUID -ne 0 ]]; then log_error "此脚本需要root权限运行" exit 1 fi # 创建日志目录 mkdir -p "$(dirname "$LOG_FILE")" log "开始OpenEuler服务器部署..." log "部署目录: $DEPLOY_DIR" log "备份目录: $BACKUP_DIR" log "镜像目录: $IMAGES_DIR" # 检查系统信息 check_system() { log "检查系统信息..." # 检查操作系统 if [[ -f /etc/os-release ]]; then source /etc/os-release log "操作系统: $NAME $VERSION" if [[ "$ID" != "openEuler" ]] && [[ "$ID_LIKE" != *"rhel"* ]]; then log_warning "此脚本专为OpenEuler设计,当前系统: $NAME" read -p "是否继续? (y/N): " -n 1 -r echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then exit 1 fi fi else log_warning "无法检测操作系统版本" fi # 检查架构 local arch=$(uname -m) log "系统架构: $arch" if [[ "$arch" != "x86_64" ]]; then log_warning "推荐使用x86_64架构,当前: $arch" fi # 检查内存 local memory=$(free -h | awk '/^Mem:/ {print $2}') log "系统内存: $memory" # 检查磁盘空间 local disk_space=$(df -h / | awk 'NR==2 {print $4}') log "可用磁盘空间: $disk_space" } # 安装Docker install_docker() { if [[ "$SKIP_DOCKER_INSTALL" == true ]]; then log "跳过Docker安装" return 0 fi log "检查Docker安装状态..." if command -v docker >/dev/null 2>&1; then local docker_version=$(docker --version) log "Docker已安装: $docker_version" # 检查Docker是否运行 if systemctl is-active --quiet docker; then log_success "Docker服务正在运行" else log "启动Docker服务..." systemctl start docker systemctl enable docker log_success "Docker服务已启动" fi return 0 fi log "安装Docker..." if [[ "$DRY_RUN" == true ]]; then log "[DRY RUN] 将安装Docker" return 0 fi # 更新系统包 dnf update -y # 安装必要的包 dnf install -y dnf-plugins-core # 添加Docker仓库 dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo # 安装Docker dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin # 启动Docker服务 systemctl start docker systemctl enable docker # 验证安装 if docker --version >/dev/null 2>&1; then log_success "Docker安装成功" else log_error "Docker安装失败" exit 1 fi # 添加当前用户到docker组(如果不是root) if [[ -n "$SUDO_USER" ]]; then usermod -aG docker "$SUDO_USER" log "已将用户 $SUDO_USER 添加到docker组" fi } # 安装Docker Compose install_docker_compose() { log "检查Docker Compose..." if docker compose version >/dev/null 2>&1; then local compose_version=$(docker compose version) log_success "Docker Compose已安装: $compose_version" return 0 fi log "安装Docker Compose..." if [[ "$DRY_RUN" == true ]]; then log "[DRY RUN] 将安装Docker Compose" return 0 fi # Docker Compose通常随Docker一起安装 # 如果没有,可以手动安装 if ! docker compose version >/dev/null 2>&1; then log_error "Docker Compose未找到,请手动安装" exit 1 fi } # 备份现有部署 backup_existing() { if [[ "$BACKUP" == false ]]; then return 0 fi if [[ ! -d "$DEPLOY_DIR" ]]; then log "没有现有部署需要备份" return 0 fi log "备份现有部署..." if [[ "$DRY_RUN" == true ]]; then log "[DRY RUN] 将备份 $DEPLOY_DIR 到 $BACKUP_DIR" return 0 fi # 停止现有服务 if [[ -f "$DEPLOY_DIR/docker-compose.yml" ]]; then log "停止现有服务..." cd "$DEPLOY_DIR" docker compose down || true fi # 创建备份 local backup_name="${BACKUP_DIR}-$(date +%Y%m%d-%H%M%S)" if cp -r "$DEPLOY_DIR" "$backup_name"; then log_success "备份创建成功: $backup_name" # 创建符号链接到最新备份 rm -f "$BACKUP_DIR" ln -s "$backup_name" "$BACKUP_DIR" else log_error "备份创建失败" exit 1 fi } # 回滚部署 rollback_deployment() { if [[ "$ROLLBACK" == false ]]; then return 0 fi log "回滚部署..." if [[ ! -L "$BACKUP_DIR" ]] || [[ ! -d "$BACKUP_DIR" ]]; then log_error "没有找到备份,无法回滚" exit 1 fi if [[ "$DRY_RUN" == true ]]; then log "[DRY RUN] 将从 $BACKUP_DIR 回滚" return 0 fi # 停止当前服务 if [[ -f "$DEPLOY_DIR/docker-compose.yml" ]]; then log "停止当前服务..." cd "$DEPLOY_DIR" docker compose down || true fi # 恢复备份 rm -rf "$DEPLOY_DIR" cp -r "$BACKUP_DIR" "$DEPLOY_DIR" # 启动服务 cd "$DEPLOY_DIR" docker compose up -d log_success "回滚完成" exit 0 } # 导入Docker镜像 import_images() { if [[ "$SKIP_IMAGES" == true ]]; then log "跳过镜像导入" return 0 fi if [[ -z "$IMAGES_DIR" ]] || [[ ! -d "$IMAGES_DIR" ]]; then log_error "镜像目录不存在或未指定: $IMAGES_DIR" exit 1 fi log "导入Docker镜像..." # 查找镜像文件 local image_files=($(find "$IMAGES_DIR" -name "*.tar*" -type f)) if [[ ${#image_files[@]} -eq 0 ]]; then log_error "在 $IMAGES_DIR 中没有找到镜像文件" exit 1 fi log "找到 ${#image_files[@]} 个镜像文件" if [[ "$DRY_RUN" == true ]]; then for file in "${image_files[@]}"; do log "[DRY RUN] 将导入: $(basename "$file")" done return 0 fi # 导入镜像 for file in "${image_files[@]}"; do local filename=$(basename "$file") log "导入镜像: $filename" if [[ "$file" == *.gz ]]; then # 解压并导入 if gunzip -c "$file" | docker load; then log_success "镜像导入成功: $filename" else log_error "镜像导入失败: $filename" exit 1 fi else # 直接导入 if docker load -i "$file"; then log_success "镜像导入成功: $filename" else log_error "镜像导入失败: $filename" exit 1 fi fi done # 显示导入的镜像 log "已导入的镜像:" docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}" } # 设置部署环境 setup_deployment() { log "设置部署环境..." if [[ "$DRY_RUN" == true ]]; then log "[DRY RUN] 将创建部署目录: $DEPLOY_DIR" return 0 fi # 创建部署目录 mkdir -p "$DEPLOY_DIR" cd "$DEPLOY_DIR" # 复制配置文件(如果镜像目录包含) if [[ -n "$IMAGES_DIR" ]]; then # 查找配置文件 local config_files=( "docker-compose.yml" ".env.production" "docker" ) for config in "${config_files[@]}"; do if [[ -e "$IMAGES_DIR/../$config" ]]; then log "复制配置: $config" cp -r "$IMAGES_DIR/../$config" . fi done fi # 创建必要的目录 mkdir -p storage/{mysql,redis,meilisearch,app,logs} mkdir -p storage/logs/{app,queue} # 设置权限 chown -R 1000:1000 storage/ chmod -R 755 storage/ log_success "部署环境设置完成" } # 配置环境变量 setup_environment() { if [[ "$SKIP_ENV_SETUP" == true ]]; then log "跳过环境配置" return 0 fi log "配置环境变量..." local env_file="$DEPLOY_DIR/.env" if [[ "$DRY_RUN" == true ]]; then log "[DRY RUN] 将创建环境配置文件: $env_file" return 0 fi # 生成随机密钥 local app_key="base64:$(openssl rand -base64 32)" local db_password=$(openssl rand -base64 16) local meilisearch_key=$(openssl rand -base64 32) # 创建环境配置文件 cat > "$env_file" << EOF # Laravel知识库系统 - 生产环境配置 # 生成时间: $(date) # 应用配置 APP_NAME="知识库系统" APP_ENV=production APP_KEY=$app_key APP_DEBUG=false APP_URL=http://localhost # 数据库配置 DB_CONNECTION=mysql DB_HOST=mysql DB_PORT=3306 DB_DATABASE=knowledge_base DB_USERNAME=knowledge_user DB_PASSWORD=$db_password # Redis配置 REDIS_HOST=redis REDIS_PORT=6379 REDIS_PASSWORD= # 缓存配置 CACHE_STORE=redis SESSION_DRIVER=redis QUEUE_CONNECTION=redis # 搜索配置 SCOUT_DRIVER=meilisearch MEILISEARCH_HOST=http://meilisearch:7700 MEILISEARCH_KEY=$meilisearch_key # 日志配置 LOG_CHANNEL=stack LOG_DEPRECATIONS_CHANNEL=null LOG_LEVEL=error # 文件系统 FILESYSTEM_DISK=local # 邮件配置 MAIL_MAILER=log MAIL_HOST=localhost MAIL_PORT=2525 MAIL_USERNAME=null MAIL_PASSWORD=null MAIL_ENCRYPTION=null MAIL_FROM_ADDRESS="hello@example.com" MAIL_FROM_NAME="\${APP_NAME}" EOF log_success "环境配置文件创建成功" log_warning "请根据实际情况修改 $env_file 中的配置" } # 启动服务 start_services() { log "启动服务..." if [[ "$DRY_RUN" == true ]]; then log "[DRY RUN] 将启动Docker Compose服务" return 0 fi cd "$DEPLOY_DIR" # 检查配置文件 if [[ ! -f "docker-compose.yml" ]]; then log_error "docker-compose.yml 文件不存在" exit 1 fi if [[ ! -f ".env" ]]; then log_error ".env 文件不存在" exit 1 fi # 启动服务 if docker compose up -d; then log_success "服务启动成功" else log_error "服务启动失败" exit 1 fi # 等待服务就绪 log "等待服务就绪..." sleep 30 # 检查服务状态 docker compose ps # 运行Laravel初始化命令 log "运行Laravel初始化..." docker compose exec -T app php artisan migrate --force || log_warning "数据库迁移失败" docker compose exec -T app php artisan storage:link || log_warning "存储链接创建失败" log_success "部署完成!" } # 显示部署信息 show_deployment_info() { log "部署信息:" log "部署目录: $DEPLOY_DIR" log "访问地址: http://$(hostname -I | awk '{print $1}')" log "管理命令:" log " 查看日志: cd $DEPLOY_DIR && docker compose logs -f" log " 重启服务: cd $DEPLOY_DIR && docker compose restart" log " 停止服务: cd $DEPLOY_DIR && docker compose down" log " 更新应用: cd $DEPLOY_DIR && docker compose pull && docker compose up -d" } # 主执行流程 main() { check_system # 处理回滚 rollback_deployment # 备份现有部署 backup_existing # 安装Docker install_docker install_docker_compose # 导入镜像 import_images # 设置部署环境 setup_deployment # 配置环境 setup_environment # 启动服务 start_services # 显示部署信息 show_deployment_info } # 执行主流程 main