#!/bin/bash set -e # 颜色定义 RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # 日志函数 log_info() { echo -e "${BLUE}[INFO]${NC} $1" } log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1" } log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1" } log_error() { echo -e "${RED}[ERROR]${NC} $1" } # 显示帮助信息 show_help() { echo "AIOps All-in-One 打包脚本" echo echo "用法: $0 [选项]" echo echo "选项:" echo " --force 强制重新打包,即使版本已存在" echo " --help 显示此帮助信息" echo echo "示例:" echo " $0 # 正常打包,跳过已存在的版本" echo " $0 --force # 强制重新打包" echo } # 解析命令行参数 FORCE_PACKAGE=false if [[ "$1" == "--force" ]]; then FORCE_PACKAGE=true log_info "强制重新打包模式" elif [[ "$1" == "--help" || "$1" == "-h" ]]; then show_help exit 0 fi # 获取当前目录和版本 CURRENT_DIR=$(pwd) VERSION=$(cat config/VERSION 2>/dev/null || echo "1.0.0") ARTIFACT_DIR="artifact/$VERSION" log_info "开始打包 AIOps All-in-One 安装包 v$VERSION" # 若强制打包且目录已存在,先清理旧产物以避免同一版本下残留多个 tar.gz 导致校验混乱 if [[ "$FORCE_PACKAGE" == "true" && -d "$ARTIFACT_DIR" ]]; then log_info "--force: 清理旧的 $ARTIFACT_DIR 下的 tar 与元数据" rm -rf "$ARTIFACT_DIR" fi # 检查必要文件 log_info "检查必要文件..." if [[ ! -f "config/VERSION" ]]; then log_error "VERSION 文件不存在" exit 1 fi if [[ ! -f "config/checklist" ]]; then log_error "checklist 文件不存在" exit 1 fi # 检查是否已存在该版本 if [[ -d "$ARTIFACT_DIR" && "$FORCE_PACKAGE" == "false" ]]; then log_info "检查版本 $VERSION 是否已存在..." # 检查 version.json 是否存在 if [[ -f "$ARTIFACT_DIR/version.json" ]]; then log_info "找到已存在的版本信息文件" # 检查是否所有组件文件都存在 missing_files=0 existing_components=0 # 解析已存在的 version.json 来检查文件 if command -v jq &> /dev/null; then # 使用 jq 解析 while IFS= read -r component; do existing_components=$((existing_components + 1)) # 查找对应的 tar 文件 found_file=false for file in "$ARTIFACT_DIR/${component}-"*.tar.gz; do if [[ -f "$file" ]]; then found_file=true break fi done if [[ "$found_file" == "false" ]]; then missing_files=$((missing_files + 1)) log_warning " 缺少文件: $component" fi done < <(jq -r '.artifact_list | keys[]' "$ARTIFACT_DIR/version.json" 2>/dev/null) else # 简单的文件检查 for file in "$ARTIFACT_DIR"/*.tar.gz; do if [[ -f "$file" ]]; then existing_components=$((existing_components + 1)) fi done fi # 如果所有文件都存在,则跳过打包 if [[ $missing_files -eq 0 && $existing_components -gt 0 ]]; then log_success "版本 $VERSION 已完整打包,跳过重复打包" echo echo "现有文件:" ls -la "$ARTIFACT_DIR" echo echo "如需强制重新打包,请删除目录: rm -rf $ARTIFACT_DIR" echo "或使用: ./package.sh --force" exit 0 else log_warning "版本 $VERSION 存在但不完整,将重新打包" log_info " 现有组件: $existing_components" log_info " 缺少文件: $missing_files" fi else log_warning "版本目录存在但缺少 version.json,将重新打包" fi fi # 创建 artifact 目录(清理后重建) mkdir -p "$ARTIFACT_DIR" log_info "创建输出目录: $ARTIFACT_DIR" # 创建临时文件存储数据 TEMP_DIR=$(mktemp -d) COMPONENTS_FILE="$TEMP_DIR/components.txt" VERSIONS_FILE="$TEMP_DIR/versions.txt" DEPENDENCIES_FILE="$TEMP_DIR/dependencies.txt" INSTALL_ORDER_FILE="$TEMP_DIR/install_order.txt" CHECKSUMS_FILE="$TEMP_DIR/checksums.txt" ARTIFACT_LIST_FILE="$TEMP_DIR/artifact_list.txt" # 解析 checklist 文件 log_info "解析组件清单..." line_num=0 component_count=0 while IFS= read -r line; do [[ -z "$line" || "$line" =~ ^[[:space:]]*# ]] && continue line_num=$((line_num + 1)) # 解析行: 组件名 目录路径 版本 [依赖组件] [安装顺序] read -r component component_path version dep_component order <<< "$line" if [[ -z "$component" || -z "$component_path" || -z "$version" ]]; then log_warning "跳过无效行 $line_num: $line" continue fi # 存储组件信息 echo "$component" >> "$COMPONENTS_FILE" echo "$component:$version" >> "$VERSIONS_FILE" echo "$component:$component_path" >> "$TEMP_DIR/component_paths.txt" if [[ -n "$dep_component" && "$dep_component" != "$component" ]]; then echo "$component:$dep_component" >> "$DEPENDENCIES_FILE" fi if [[ -n "$order" && "$order" =~ ^[0-9]+$ ]]; then echo "$order:$component" >> "$INSTALL_ORDER_FILE" else # 如果没有指定顺序,按解析顺序分配 echo "$line_num:$component" >> "$INSTALL_ORDER_FILE" fi component_count=$((component_count + 1)) log_info " - $component v$version" done < config/checklist if [[ $component_count -eq 0 ]]; then log_error "没有找到有效的组件" rm -rf "$TEMP_DIR" exit 1 fi log_success "找到 $component_count 个组件" # 检查组件目录是否存在 log_info "检查组件目录..." missing_components=() while IFS= read -r component; do # 获取组件路径 component_path=$(grep "^$component:" "$TEMP_DIR/component_paths.txt" | cut -d':' -f2-) if [[ -z "$component_path" ]]; then log_error "未找到组件 $component 的路径配置" log_info "请检查 component_paths.txt 文件或添加路径配置" exit 1 fi if [[ ! -d "$component_path" ]]; then missing_components+=("$component:$component_path") fi done < "$COMPONENTS_FILE" if [[ ${#missing_components[@]} -gt 0 ]]; then log_error "以下组件目录不存在:" for component_path in "${missing_components[@]}"; do echo " - $component_path" done rm -rf "$TEMP_DIR" exit 1 fi # 额外校验:阻止将 Git LFS 指针文件打进安装包 # 仅检查各组件目录下的 bin/ 内文件(常见为二进制或 .deb/.tar.gz 制品) is_lfs_pointer() { local f="$1" # 读取首行判断是否为 LFS pointer(无需依赖 file 命令) head -n1 "$f" 2>/dev/null | grep -q '^version https://git-lfs.github.com/spec/v1$' } log_info "检查组件二进制是否已从 LFS 拉取..." while IFS= read -r component; do component_path=$(grep "^$component:" "$TEMP_DIR/component_paths.txt" | cut -d':' -f2-) bin_dir="$component_path/bin" [[ -d "$bin_dir" ]] || continue while IFS= read -r f; do # 只检查常见可执行/包后缀;无后缀的也检查 case "$f" in *.sh) continue;; *) :;; esac if is_lfs_pointer "$f"; then log_error "检测到 Git LFS 指针文件: $f" log_error "请在仓库根目录执行: git lfs fetch --all && git lfs checkout" log_error "或确保 CI 在打包前已还原 LFS 大文件。" rm -rf "$TEMP_DIR" exit 1 fi done < <(find "$bin_dir" -maxdepth 1 -type f 2>/dev/null | sort) done < "$COMPONENTS_FILE" log_success "LFS 校验通过:未发现指针文件" # 打包各个组件 log_info "开始打包组件..." while IFS= read -r component; do # 获取组件版本和路径 version=$(grep "^$component:" "$VERSIONS_FILE" | cut -d':' -f2) component_path=$(grep "^$component:" "$TEMP_DIR/component_paths.txt" | cut -d':' -f2-) if [[ -z "$component_path" ]]; then log_error "未找到组件 $component 的路径配置" log_info "请检查 component_paths.txt 文件或添加路径配置" exit 1 fi log_info "打包 $component v$version..." log_info " 组件路径: $component_path" # 进入组件目录 cd "$component_path" # 组件内二次防御:若包脚本缺失 LFS 校验,这里再次阻断 if [[ -d bin ]]; then for f in bin/*; do [[ -f "$f" ]] || continue if head -n1 "$f" 2>/dev/null | grep -q '^version https://git-lfs.github.com/spec/v1$'; then log_error "组件 $component 含 LFS 指针文件: $f" log_error "请执行: git lfs fetch --all && git lfs checkout" cd "$CURRENT_DIR"; rm -rf "$TEMP_DIR"; exit 1 fi done fi # 检查组件是否有 package.sh if [[ ! -f "package.sh" ]]; then log_error "$component 缺少 package.sh 文件" cd "$CURRENT_DIR" rm -rf "$TEMP_DIR" exit 1 fi # 清理组件目录内历史 tar 包,避免 find 误选旧文件 rm -f ./*.tar.gz 2>/dev/null || true # 执行组件的打包脚本 if ./package.sh; then # 查找生成的 tar 包 tar_file=$(ls -1t ./*.tar.gz 2>/dev/null | head -1) if [[ -n "$tar_file" ]]; then # 移动到 artifact 目录 mv "$tar_file" "$CURRENT_DIR/$ARTIFACT_DIR/" tar_filename=$(basename "$tar_file") # 计算校验和 checksum=$(sha256sum "$CURRENT_DIR/$ARTIFACT_DIR/$tar_filename" | cut -d' ' -f1) echo "$component:sha256:$checksum" >> "$CHECKSUMS_FILE" echo "$component:$version" >> "$ARTIFACT_LIST_FILE" # 将完整的文件名存储到安装顺序文件中 echo "$tar_filename" >> "$TEMP_DIR/install_order_files.txt" log_success " $component 打包完成: $tar_filename" else log_error "$component 打包失败,未找到生成的 tar 包" cd "$CURRENT_DIR" rm -rf "$TEMP_DIR" exit 1 fi else log_error "$component 打包失败" cd "$CURRENT_DIR" rm -rf "$TEMP_DIR" exit 1 fi # 返回主目录 cd "$CURRENT_DIR" done < "$COMPONENTS_FILE" # 生成 version.json log_info "生成版本信息文件..." version_json="$ARTIFACT_DIR/version.json" # 构建依赖关系 JSON deps_json="" if [[ -f "$DEPENDENCIES_FILE" ]]; then first=true while IFS= read -r line; do component=$(echo "$line" | cut -d':' -f1) dep=$(echo "$line" | cut -d':' -f2) if [[ "$first" == "true" ]]; then deps_json="\"$component\":[\"$dep\"]" first=false else deps_json="$deps_json,\"$component\":[\"$dep\"]" fi done < "$DEPENDENCIES_FILE" fi # 构建安装顺序数组 order_array="" if [[ -f "$TEMP_DIR/install_order_files.txt" ]]; then first=true while IFS= read -r filename; do if [[ "$first" == "true" ]]; then order_array="\"$filename\"" first=false else order_array="$order_array,\"$filename\"" fi done < "$TEMP_DIR/install_order_files.txt" fi # 构建 artifact_list JSON artifact_json="" if [[ -f "$ARTIFACT_LIST_FILE" ]]; then first=true while IFS= read -r line; do component=$(echo "$line" | cut -d':' -f1) version=$(echo "$line" | cut -d':' -f2) if [[ "$first" == "true" ]]; then artifact_json="\"$component\":\"$version\"" first=false else artifact_json="$artifact_json,\"$component\":\"$version\"" fi done < "$ARTIFACT_LIST_FILE" fi # 构建 checksums JSON checksums_json="" if [[ -f "$CHECKSUMS_FILE" ]]; then first=true while IFS= read -r line; do component=$(echo "$line" | cut -d':' -f1) checksum=$(echo "$line" | cut -d':' -f2-) if [[ "$first" == "true" ]]; then checksums_json="\"$component\":\"$checksum\"" first=false else checksums_json="$checksums_json,\"$component\":\"$checksum\"" fi done < "$CHECKSUMS_FILE" fi # 生成完整的 version.json cat > "$version_json" << EOF { "version": "$VERSION", "build_time": "$(date -u +%Y-%m-%dT%H:%M:%SZ)", "artifact_list": { $artifact_json }, "checksums": { $checksums_json }, "dependencies": { $deps_json }, "install_order": [ $order_array ] } EOF log_success "版本信息文件生成完成: $version_json" # 复制`安装`脚本到 artifact 目录 log_info "复制安装脚本..." if [[ -f "scripts/install_artifact.sh" ]]; then cp "scripts/install_artifact.sh" "$ARTIFACT_DIR/install.sh" chmod +x "$ARTIFACT_DIR/install.sh" log_success "安装脚本复制完成: $ARTIFACT_DIR/install.sh" else log_warning "scripts/install_artifact.sh 文件不存在" fi # 复制`卸载`脚本到 artifact 目录 log_info "复制卸载脚本..." if [[ -f "scripts/uninstall_artifact.sh" ]]; then cp "scripts/uninstall_artifact.sh" "$ARTIFACT_DIR/uninstall.sh" chmod +x "$ARTIFACT_DIR/uninstall.sh" log_success "卸载脚本复制完成: $ARTIFACT_DIR/uninstall.sh" else log_warning "scripts/uninstall_artifact.sh 文件不存在" fi # 复制`健康检查`脚本到 artifact 目录 log_info "复制健康检查脚本..." if [[ -f "scripts/check_health.sh" ]]; then cp "scripts/check_health.sh" "$ARTIFACT_DIR/check_health.sh" chmod +x "$ARTIFACT_DIR/check_health.sh" log_success "健康检查脚本复制完成: $ARTIFACT_DIR/check_health.sh" else log_warning "scripts/check_health.sh 文件不存在" fi # 复制`DNS 同步`脚本到 artifact 目录 log_info "复制 DNS 同步脚本..." if [[ -f "scripts/sync_dns.sh" ]]; then cp "scripts/sync_dns.sh" "$ARTIFACT_DIR/sync_dns.sh" chmod +x "$ARTIFACT_DIR/sync_dns.sh" log_success "DNS 同步脚本复制完成: $ARTIFACT_DIR/sync_dns.sh" else log_warning "scripts/sync_dns.sh 文件不存在" fi # 复制`版本校验`脚本到 artifact 目录 log_info "复制版本校验脚本..." if [[ -f "scripts/check_version.sh" ]]; then cp "scripts/check_version.sh" "$ARTIFACT_DIR/check_version.sh" chmod +x "$ARTIFACT_DIR/check_version.sh" log_success "版本校验脚本复制完成: $ARTIFACT_DIR/check_version.sh" else log_warning "scripts/check_version.sh 文件不存在" fi # 复制`自动重启`脚本到 artifact 目录 log_info "复制自动重启脚本..." if [[ -f "scripts/restart_unhealthy.sh" ]]; then cp "scripts/restart_unhealthy.sh" "$ARTIFACT_DIR/restart_unhealthy.sh" chmod +x "$ARTIFACT_DIR/restart_unhealthy.sh" log_success "自动重启脚本复制完成: $ARTIFACT_DIR/restart_unhealthy.sh" else log_warning "scripts/restart_unhealthy.sh 文件不存在" fi # 复制配置文件到 artifact 目录 log_info "复制配置文件..." if [[ -f "config/config.env" ]]; then cp "config/config.env" "$ARTIFACT_DIR/" log_success "配置文件复制完成: $ARTIFACT_DIR/config.env" else log_warning "config 目录不存在,跳过配置文件复制" fi # DNS 配置文件不需要复制到版本目录,直接从 FTP 服务器根目录获取 # 复制 deps 目录到 artifact 目录 log_info "复制系统依赖包..." if [[ -d "deps" ]]; then cp -r "deps" "$ARTIFACT_DIR/" log_success "系统依赖包复制完成: $ARTIFACT_DIR/deps" # 显示deps目录内容 log_info " 依赖包列表:" find "$ARTIFACT_DIR/deps" -name "*.tar.gz" -exec basename {} \; | while read dep_file; do log_info " - $dep_file" done else log_warning "deps 目录不存在,跳过依赖包复制" fi # 显示打包结果 log_success "打包完成!" echo echo "版本: $VERSION" echo "输出目录: $ARTIFACT_DIR" echo "包含组件:" if [[ -f "$ARTIFACT_LIST_FILE" ]]; then while IFS= read -r line; do component=$(echo "$line" | cut -d':' -f1) version=$(echo "$line" | cut -d':' -f2) echo " - $component v$version" done < "$ARTIFACT_LIST_FILE" fi echo echo "文件列表:" ls -la "$ARTIFACT_DIR" echo # 清理临时文件 rm -rf "$TEMP_DIR"