#!/bin/bash set -e RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' 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" } # 配置变量 INSTALL_DIR="${1:-$(pwd)}" # 使用第一个参数作为安装目录,如果没有参数则使用当前目录 TEMP_DIR="/tmp/metrics-install-$$" VERSION_FILE="version.json" check_root() { if [[ $EUID -ne 0 ]]; then log_error "此脚本需要 root 权限运行" log_info "请使用: sudo $0 [安装目录]" log_info "如果不指定安装目录,将使用当前目录: $(pwd)" exit 1 fi } # 检查系统要求 check_system() { log_info "检查系统要求..." # 检查操作系统 if [[ ! -f /etc/os-release ]]; then log_error "无法检测操作系统版本" exit 1 fi source /etc/os-release log_info "检测到操作系统: $NAME $VERSION" # 检查系统架构 arch=$(uname -m) log_info "系统架构: $arch" # 检查磁盘空间 available_space=$(df / | awk 'NR==2 {print $4}') if [[ $available_space -lt 10485760 ]]; then # 10GB in KB log_warning "可用磁盘空间不足 10GB,当前可用: $(($available_space / 1024 / 1024))GB" fi # 检查内存 total_mem=$(free -m | awk 'NR==2{print $2}') if [[ $total_mem -lt 4096 ]]; then # 4GB log_warning "系统内存不足 4GB,当前: ${total_mem}MB" fi } # 查找版本文件 find_version_file() { log_info "查找版本信息文件..." # 在当前目录查找 if [[ -f "$VERSION_FILE" ]]; then VERSION_FILE_PATH="$VERSION_FILE" log_success "找到版本文件: $VERSION_FILE" return 0 fi # 在 artifact 目录查找 for version_dir in artifact/*/; do if [[ -f "${version_dir}${VERSION_FILE}" ]]; then VERSION_FILE_PATH="${version_dir}${VERSION_FILE}" log_success "找到版本文件: $VERSION_FILE_PATH" return 0 fi done log_error "未找到版本信息文件 $VERSION_FILE" exit 1 } # 解析版本信息 parse_version_info() { log_info "解析版本信息..." if [[ ! -f "$VERSION_FILE_PATH" ]]; then log_error "版本文件不存在: $VERSION_FILE_PATH" exit 1 fi # 使用 jq 解析 JSON(如果可用) if command -v jq &> /dev/null; then # 验证JSON文件格式 if ! jq empty "$VERSION_FILE_PATH" 2>/dev/null; then log_error "JSON文件格式错误,请检查 $VERSION_FILE_PATH" exit 1 fi VERSION=$(jq -r '.version' "$VERSION_FILE_PATH") BUILD_TIME=$(jq -r '.build_time' "$VERSION_FILE_PATH") # 解析 artifact_list if jq -e '.artifact_list' "$VERSION_FILE_PATH" > /dev/null 2>&1; then jq -r '.artifact_list | to_entries[] | "\(.key):\(.value)"' "$VERSION_FILE_PATH" > "$TEMP_DIR/components.txt" else log_error "version.json 中缺少 artifact_list 字段" exit 1 fi # 解析 checksums if jq -e '.checksums' "$VERSION_FILE_PATH" > /dev/null 2>&1; then jq -r '.checksums | to_entries[] | "\(.key):\(.value)"' "$VERSION_FILE_PATH" > "$TEMP_DIR/checksums.txt" else log_error "version.json 中缺少 checksums 字段" exit 1 fi # 解析 install_order(现在包含完整的文件名) if jq -e '.install_order' "$VERSION_FILE_PATH" > /dev/null 2>&1; then jq -r '.install_order[]' "$VERSION_FILE_PATH" > "$TEMP_DIR/install_order.txt" else log_error "version.json 中缺少 install_order 字段" exit 1 fi else log_warning "jq 未安装,使用简单的 JSON 解析" # 简单的 JSON 解析 VERSION=$(grep '"version"' "$VERSION_FILE_PATH" | sed 's/.*"version": *"\([^"]*\)".*/\1/') BUILD_TIME=$(grep '"build_time"' "$VERSION_FILE_PATH" | sed 's/.*"build_time": *"\([^"]*\)".*/\1/') # 解析 artifact_list grep -A 100 '"artifact_list"' "$VERSION_FILE_PATH" | grep -E '^\s*"[^"]+":\s*"[^"]+"' | while read line; do component=$(echo "$line" | sed 's/.*"\([^"]*\)":\s*"[^"]*".*/\1/') version=$(echo "$line" | sed 's/.*"[^"]*":\s*"\([^"]*\)".*/\1/') echo "$component:$version" >> "$TEMP_DIR/components.txt" done # 解析 checksums grep -A 100 '"checksums"' "$VERSION_FILE_PATH" | grep -E '^\s*"[^"]+":\s*"[^"]+"' | while read line; do component=$(echo "$line" | sed 's/.*"\([^"]*\)":\s*"[^"]*".*/\1/') checksum=$(echo "$line" | sed 's/.*"[^"]*":\s*"\([^"]*\)".*/\1/') echo "$component:$checksum" >> "$TEMP_DIR/checksums.txt" done # 解析 install_order grep -A 100 '"install_order"' "$VERSION_FILE_PATH" | grep -E '^\s*"[^"]+"' | while read line; do component=$(echo "$line" | sed 's/.*"\([^"]*\)".*/\1/') echo "$component" >> "$TEMP_DIR/install_order.txt" done # 验证解析结果 if [[ ! -f "$TEMP_DIR/components.txt" || ! -s "$TEMP_DIR/components.txt" ]]; then log_error "无法解析 artifact_list,请检查 version.json 格式" exit 1 fi if [[ ! -f "$TEMP_DIR/checksums.txt" || ! -s "$TEMP_DIR/checksums.txt" ]]; then log_error "无法解析 checksums,请检查 version.json 格式" exit 1 fi if [[ ! -f "$TEMP_DIR/install_order.txt" || ! -s "$TEMP_DIR/install_order.txt" ]]; then log_error "无法解析 install_order,请检查 version.json 格式" exit 1 fi fi log_success "版本信息解析完成" log_info " 版本: $VERSION" log_info " 构建时间: $BUILD_TIME" component_count=0 if [[ -f "$TEMP_DIR/components.txt" ]]; then component_count=$(wc -l < "$TEMP_DIR/components.txt") log_info " 组件数量: $component_count" log_info " 组件列表:" while IFS= read -r line; do component=$(echo "$line" | cut -d':' -f1) version=$(echo "$line" | cut -d':' -f2) log_info " - $component v$version" done < "$TEMP_DIR/components.txt" else log_error "components.txt 文件不存在" exit 1 fi } # 验证文件完整性 verify_checksums() { log_info "验证文件完整性..." artifact_dir=$(dirname "$VERSION_FILE_PATH") failed_verification=0 if [[ -f "$TEMP_DIR/checksums.txt" ]]; then while IFS= read -r line; do component=$(echo "$line" | cut -d':' -f1) expected_checksum=$(echo "$line" | cut -d':' -f2-) # 查找匹配的 tar 文件 actual_file="" for file in "$artifact_dir/${component}-"*.tar.gz; do if [[ -f "$file" ]]; then actual_file="$file" break fi done if [[ -z "$actual_file" ]]; then log_error "找不到组件文件: $component" failed_verification=1 continue fi # 计算实际校验和 actual_checksum="sha256:$(sha256sum "$actual_file" | cut -d' ' -f1)" if [[ "$actual_checksum" == "$expected_checksum" ]]; then log_success " $component: 校验通过" else log_error " $component: 校验失败" log_error " 期望: $expected_checksum" log_error " 实际: $actual_checksum" failed_verification=1 fi done < "$TEMP_DIR/checksums.txt" fi if [[ $failed_verification -eq 1 ]]; then log_error "文件完整性验证失败" exit 1 fi log_success "所有文件校验通过" } # 创建安装目录 create_install_dirs() { log_info "创建安装目录..." mkdir -p "$INSTALL_DIR" mkdir -p "$TEMP_DIR" log_success "安装目录创建完成: $INSTALL_DIR" } # 安装系统依赖包 install_system_deps() { log_info "检查系统依赖包..." local script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" local deps_dir="$script_dir/deps" # 检查deps目录是否存在 if [[ ! -d "$deps_dir" ]]; then log_info "deps 目录不存在,跳过系统依赖包安装" return 0 fi # 检查是否有tar.gz文件 local deps_count=$(find "$deps_dir" -name "*.tar.gz" | wc -l) if [[ $deps_count -eq 0 ]]; then log_info "deps 目录中没有 tar.gz 文件,跳过系统依赖包安装" return 0 fi log_info "找到 $deps_count 个系统依赖包,开始安装..." # 创建临时目录用于解压依赖包 local deps_temp_dir="$TEMP_DIR/deps" mkdir -p "$deps_temp_dir" # 处理每个tar.gz文件 find "$deps_dir" -name "*.tar.gz" | while read tar_file; do local tar_basename=$(basename "$tar_file") local extract_name="${tar_basename%.tar.gz}" log_info "处理依赖包: $tar_basename" # 解压到临时目录 local extract_dir="$deps_temp_dir/$extract_name" mkdir -p "$extract_dir" if tar -xzf "$tar_file" -C "$extract_dir"; then log_success " $tar_basename 解压完成" else log_error " $tar_basename 解压失败" continue fi # 进入解压目录,查找deb包 cd "$extract_dir" local deb_count=$(find . -name "*.deb" | wc -l) if [[ $deb_count -gt 0 ]]; then log_info " 找到 $deb_count 个 deb 包,开始安装..." # 1. 先尝试安装所有deb包 log_info " 第1步:批量安装deb包..." if dpkg -i *.deb 2>/dev/null; then log_success " 所有deb包安装成功" else log_warning " 部分deb包安装失败,可能存在依赖问题" # 2. 使用apt-get修复依赖 log_info " 第2步:修复依赖关系..." if apt-get install -f -y; then log_success " 依赖关系修复完成" else log_error " 依赖关系修复失败" # 继续处理其他包,不退出 fi fi else log_info " $tar_basename 中没有找到deb包,跳过" fi # 返回到依赖临时目录 cd "$deps_temp_dir" done log_success "系统依赖包安装完成" } # 安装组件 install_components() { log_info "开始安装组件..." artifact_dir=$(dirname "$VERSION_FILE_PATH") install_count=0 total_count=0 if [[ -f "$TEMP_DIR/install_order.txt" ]]; then total_count=$(wc -l < "$TEMP_DIR/install_order.txt") fi if [[ -f "$TEMP_DIR/install_order.txt" ]]; then while IFS= read -r filename; do install_count=$((install_count + 1)) # 从文件名中提取组件名(去掉时间戳后缀) component=$(echo "$filename" | sed 's/-[0-9]\{8\}-[0-9]\{6\}\.tar\.gz$//') log_info "[$install_count/$total_count] 安装 $component..." log_info " 文件名: $filename" # 直接使用完整的文件名 tar_file="$artifact_dir/$filename" if [[ ! -f "$tar_file" ]]; then log_error "找不到组件文件: $filename" log_info " 期望路径: $tar_file" log_info " 当前目录: $(pwd)" log_info " 目录内容:" ls -la "$artifact_dir" | while read line; do log_info " $line" done exit 1 fi log_info " 找到文件: $tar_file" # 解压到临时目录 component_temp_dir="$TEMP_DIR/$component" mkdir -p "$component_temp_dir" if tar -xzf "$tar_file" -C "$component_temp_dir"; then log_success " $component 解压完成" else log_error " $component 解压失败" exit 1 fi # 查找解压后的目录 extracted_dir="" for dir in "$component_temp_dir"/*; do if [[ -d "$dir" ]]; then extracted_dir="$dir" break fi done if [[ -z "$extracted_dir" ]]; then log_error " $component 解压后未找到目录" exit 1 fi # 执行安装脚本 if [[ -f "$extracted_dir/install.sh" ]]; then log_info " 执行 $component 安装脚本..." if (cd "$extracted_dir" && ./install.sh); then log_success " $component 安装完成" else log_error " $component 安装失败" exit 1 fi else log_error " $component 缺少 install.sh 文件" exit 1 fi # 将解压后的目录移动到安装目录,保留组件目录 component_install_dir="$INSTALL_DIR/$component" if [[ -d "$component_install_dir" ]]; then log_info " 组件目录已存在,备份后更新: $component_install_dir" mv "$component_install_dir" "${component_install_dir}.backup.$(date +%Y%m%d_%H%M%S)" fi mv "$extracted_dir" "$component_install_dir" log_success " 组件目录已保存: $component_install_dir" # 清理临时文件 rm -rf "$component_temp_dir" done < "$TEMP_DIR/install_order.txt" fi log_success "所有组件安装完成" } # 创建安装记录 create_install_record() { log_info "创建安装记录..." # 等待一段时间确保所有进程都已启动 log_info "等待进程启动..." sleep 3 local install_time=$(date -u +"%Y-%m-%dT%H:%M:%SZ") local install_record_file=".install_record" # 创建 JSON 格式的安装记录 cat > "$install_record_file" << EOF { "version": "$VERSION", "build_time": "$BUILD_TIME", "install_time": "$install_time", "install_dir": "$INSTALL_DIR", "install_pid": $$, "components": { EOF # 添加组件信息 local first_component=true if [[ -f "$TEMP_DIR/components.txt" ]]; then while IFS= read -r line; do component=$(echo "$line" | cut -d':' -f1) version=$(echo "$line" | cut -d':' -f2) # 获取组件的进程信息 local component_pid="" # 根据组件名查找进程,使用多种方法确保能找到PID case "$component" in "node-exporter-installer") # 尝试多种方式查找node_exporter进程 component_pid=$(pgrep -f "node_exporter" | head -1) if [[ -z "$component_pid" ]]; then component_pid=$(pgrep -f "node-exporter" | head -1) fi if [[ -z "$component_pid" ]]; then component_pid=$(ps aux | grep -v grep | grep "node_exporter" | awk '{print $2}' | head -1) fi ;; "dcgm-exporter-installer") # 尝试多种方式查找dcgm-exporter进程 component_pid=$(pgrep -f "dcgm-exporter" | head -1) if [[ -z "$component_pid" ]]; then component_pid=$(pgrep -f "dcgm_exporter" | head -1) fi if [[ -z "$component_pid" ]]; then component_pid=$(ps aux | grep -v grep | grep "dcgm-exporter" | awk '{print $2}' | head -1) fi ;; esac # 记录找到的PID信息 if [[ -n "$component_pid" ]]; then log_info " 找到 $component 进程 PID: $component_pid" else log_warning " 未找到 $component 进程" fi # 添加逗号分隔符 if [[ "$first_component" == "true" ]]; then first_component=false else echo "," >> "$install_record_file" fi # 添加组件信息 cat >> "$install_record_file" << EOF "$component": { "version": "$version", "pid": "$component_pid", "install_dir": "$INSTALL_DIR/$component" } EOF done < "$TEMP_DIR/components.txt" fi # 结束 JSON cat >> "$install_record_file" << EOF } } EOF log_success "安装记录已创建: $install_record_file" } # 设置健康检查定时任务 setup_health_check_cron() { log_info "设置健康检查定时任务..." local script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" local check_health_script="$script_dir/check_health.sh" # 检查健康检查脚本是否存在 if [[ ! -f "$check_health_script" ]]; then log_error "健康检查脚本不存在: $check_health_script" return 1 fi # 确保脚本有执行权限 chmod +x "$check_health_script" # 创建临时crontab文件 local temp_cron="/tmp/crontab_$$" # 获取当前用户的crontab(如果存在) crontab -l 2>/dev/null > "$temp_cron" || touch "$temp_cron" # 检查是否已经存在健康检查任务 if grep -q "check_health.sh" "$temp_cron"; then log_warning "健康检查定时任务已存在,跳过设置" rm -f "$temp_cron" return 0 fi # 添加新的定时任务(每5分钟执行一次) echo "# Argus-Metrics 健康检查定时任务" >> "$temp_cron" echo "*/5 * * * * $check_health_script >> $script_dir/.health_cron.log 2>&1" >> "$temp_cron" # 安装新的crontab if crontab "$temp_cron"; then log_success "健康检查定时任务设置成功" log_info " 执行频率: 每5分钟" log_info " 日志文件: $script_dir/.health_cron.log" log_info " 查看定时任务: crontab -l" log_info " 删除定时任务: crontab -e" else log_error "健康检查定时任务设置失败" rm -f "$temp_cron" return 1 fi # 清理临时文件 rm -f "$temp_cron" # 立即执行一次健康检查 log_info "执行首次健康检查..." if "$check_health_script"; then log_success "首次健康检查完成" else log_warning "首次健康检查失败,但定时任务已设置" fi } # 显示安装信息 show_install_info() { log_success "Argus-Metrics All-in-One 安装完成!" echo echo "安装信息:" echo " 版本: $VERSION" echo " 构建时间: $BUILD_TIME" echo " 安装目录: $INSTALL_DIR" echo echo "已安装组件:" if [[ -f "$TEMP_DIR/components.txt" ]]; then while IFS= read -r line; do component=$(echo "$line" | cut -d':' -f1) version=$(echo "$line" | cut -d':' -f2) echo " - $component v$version" done < "$TEMP_DIR/components.txt" fi echo echo "访问地址:" echo " Node Exporter: http://localhost:9100" echo " DCGM Exporter: http://localhost:9400" echo echo "健康检查:" echo " 安装记录: .install_record" echo " 健康日志: .health_log" echo " 定时任务日志: .health_cron.log" echo " 查看定时任务: crontab -l" echo } cleanup() { if [[ -d "$TEMP_DIR" ]]; then rm -rf "$TEMP_DIR" fi } trap cleanup EXIT # 主函数 main() { echo "==========================================" echo " Argus-Metrics All-in-One 安装脚本 v1.0" echo "==========================================" echo log_info "安装目录: $INSTALL_DIR" echo check_root check_system find_version_file create_install_dirs parse_version_info verify_checksums install_system_deps install_components create_install_record setup_health_check_cron show_install_info } if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then main "$@" fi