#!/bin/bash # patch_verifier.sh - 企业级补丁包验证脚本 set -euo pipefail shopt -s nullglob extglob # 脚本目录 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" CONFIG_FILE="${SCRIPT_DIR}/patch_config.sh" # 颜色定义 RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' PURPLE='\033[0;35m' CYAN='\033[0;36m' NC='\033[0m' # 日志函数 log() { local level="$1" local message="$2" local timestamp=$(date '+%Y-%m-%d %H:%M:%S') case "$level" in "INFO") color="$GREEN" ;; "WARN") color="$YELLOW" ;; "ERROR") color="$RED" ;; "DEBUG") color="$BLUE" ;; *) color="$NC" ;; esac echo -e "${color}[$timestamp] [$level]${NC} $message" | tee -a "$LOG_FILE" } info() { log "INFO" "$1"; } warn() { log "WARN" "$1"; } error() { log "ERROR" "$1"; } debug() { log "DEBUG" "$1"; } # 配置和初始化 load_config() { if [[ ! -f "$CONFIG_FILE" ]]; then error "配置文件不存在: $CONFIG_FILE" exit 1 fi source "$CONFIG_FILE" info "配置文件加载完成" : ${LOG_LEVEL:="INFO"} : ${LOG_FILE:="/tmp/patch_verify_$(date +%Y%m%d_%H%M%S).log"} : ${TEMP_DIR:="/tmp/patch_verify_$$"} } setup_environment() { mkdir -p "$(dirname "$LOG_FILE")" mkdir -p "$TEMP_DIR" info "日志文件: $LOG_FILE" info "临时目录: $TEMP_DIR" } cleanup() { if [[ -d "$TEMP_DIR" ]]; then rm -rf "$TEMP_DIR" info "临时目录已清理: $TEMP_DIR" fi } trap cleanup EXIT # 验证函数 verify_package_integrity() { local package_path="$1" local verify_type="$2" # pre-apply, post-apply, standalone info "开始验证补丁包: $package_path (类型: $verify_type)" local overall_result=true # 1. 基础验证 if ! verify_basic_integrity "$package_path"; then overall_result=false fi # 2. 安全验证 if ! verify_security "$package_path"; then overall_result=false fi # 3. 内容验证 if ! verify_content "$package_path" "$verify_type"; then overall_result=false fi # 4. 系统状态验证 if [[ "$verify_type" == "post-apply" ]]; then if ! verify_system_state "$package_path"; then overall_result=false fi fi # 生成验证报告 generate_verification_report "$package_path" "$overall_result" "$verify_type" if $overall_result; then info "✅ 补丁包验证通过" return 0 else error "❌ 补丁包验证失败" return 1 fi } verify_basic_integrity() { local package_path="$1" info "执行基础完整性验证..." local result=true # 文件存在性检查 if [[ ! -f "$package_path" ]]; then error "❌ 补丁包不存在: $package_path" result=false fi # 文件大小检查 local size=$(stat -c%s "$package_path" 2>/dev/null || echo "0") if [[ $size -eq 0 ]]; then error "❌ 补丁包为空" result=false else info "✅ 文件大小: $(numfmt --to=iec-i --suffix=B $size)" fi # 压缩包完整性检查 if ! tar -tzf "$package_path" >/dev/null 2>&1; then error "❌ 压缩包损坏或格式错误" result=false else info "✅ 压缩包完整性验证通过" fi $result } verify_security() { local package_path="$1" info "执行安全验证..." local result=true # 校验和验证 if [[ -f "${package_path}.sha256" ]]; then if sha256sum -c "${package_path}.sha256" >/dev/null 2>&1; then info "✅ 校验和验证通过" else error "❌ 校验和验证失败" result=false fi else warn "⚠️ 未找到校验和文件" fi # 签名验证 if [[ -f "${package_path}.sig" ]]; then if command -v gpg >/dev/null 2>&1; then if gpg --verify "${package_path}.sig" "$package_path" >/dev/null 2>&1; then info "✅ 签名验证通过" else error "❌ 签名验证失败" result=false fi else warn "⚠️ GPG未安装,跳过签名验证" fi else warn "⚠️ 未找到签名文件" fi $result } verify_content() { local package_path="$1" local verify_type="$2" info "执行内容验证..." local result=true # 解压补丁包 local extract_dir="$TEMP_DIR/extract" if ! tar -xzf "$package_path" -C "$extract_dir"; then error "❌ 补丁包解压失败" return false fi # 检查清单文件 local manifest_file="$extract_dir/MANIFEST.MF" if [[ ! -f "$manifest_file" ]]; then error "❌ 清单文件不存在" result=false else info "✅ 清单文件存在" # 验证清单格式 if ! validate_manifest_format "$manifest_file"; then result=false fi # 验证文件完整性 if ! validate_file_integrity "$extract_dir" "$manifest_file" "$verify_type"; then result=false fi fi $result } validate_manifest_format() { local manifest_file="$1" local result=true # 检查必需字段 local required_fields=("名称" "版本" "生成时间") for field in "${required_fields[@]}"; do if ! grep -q "^$field:" "$manifest_file"; then error "❌ 清单缺少必需字段: $field" result=false fi done # 检查变更记录格式 local change_count=$(grep -c -E "^(ADDED|MODIFIED|DELETED)" "$manifest_file" || true) if [[ $change_count -eq 0 ]]; then warn "⚠️ 清单中没有变更记录" else info "✅ 变更记录数量: $change_count" fi $result } validate_file_integrity() { local extract_dir="$1" local manifest_file="$2" local verify_type="$3" info "验证文件完整性..." local result=true local verified_count=0 local error_count=0 # 处理清单中的每个变更记录 while IFS='|' read -r change_type path extra_info; do case "$change_type" in "ADDED"|"MODIFIED") if ! verify_patch_file "$extract_dir" "$path" "$change_type" "$verify_type"; then error_count=$((error_count + 1)) result=false else verified_count=$((verified_count + 1)) fi ;; "DELETED") if ! verify_deleted_file "$path" "$verify_type"; then error_count=$((error_count + 1)) result=false else verified_count=$((verified_count + 1)) fi ;; esac done < <(grep -E "^(ADDED|MODIFIED|DELETED)" "$manifest_file") info "文件验证完成: $verified_count 个文件成功, $error_count 个文件失败" $result } verify_patch_file() { local extract_dir="$1" local file_path="$2" local change_type="$3" local verify_type="$4" local patch_file="$extract_dir/files/$file_path" local target_file="/$file_path" # 检查补丁包中的文件是否存在 if [[ ! -f "$patch_file" ]]; then error "❌ 补丁包中文件不存在: $file_path" return 1 fi # 对于应用后验证,检查目标文件 if [[ "$verify_type" == "post-apply" ]]; then if [[ ! -f "$target_file" ]]; then error "❌ 目标文件不存在: $target_file" return 1 fi # 比较文件内容 local patch_hash=$(sha256sum "$patch_file" | cut -d' ' -f1) local target_hash=$(sha256sum "$target_file" | cut -d' ' -f1) if [[ "$patch_hash" != "$target_hash" ]]; then error "❌ 文件内容不匹配: $file_path" return 1 fi info "✅ 文件验证通过: $file_path" else info "✅ 补丁文件存在: $file_path" fi return 0 } verify_deleted_file() { local file_path="$1" local verify_type="$2" local target_file="/$file_path" # 对于应用后验证,检查文件是否已删除 if [[ "$verify_type" == "post-apply" ]]; then if [[ -f "$target_file" ]]; then error "❌ 文件未成功删除: $target_file" return 1 fi info "✅ 文件删除验证通过: $file_path" else info "✅ 删除操作记录存在: $file_path" fi return 0 } verify_system_state() { local package_path="$1" info "验证系统状态..." local result=true # 检查关键服务状态 if ! verify_services; then result=false fi # 检查磁盘空间 if ! verify_disk_space; then result=false fi # 检查系统负载 if ! verify_system_load; then result=false fi $result } verify_services() { local result=true # 检查Web服务 if systemctl is-active --quiet nginx || systemctl is-active --quiet apache2; then info "✅ Web服务运行正常" else warn "⚠️ Web服务未运行" fi # 检查数据库服务 if systemctl is-active --quiet mysql || systemctl is-active --quiet postgresql; then info "✅ 数据库服务运行正常" else warn "⚠️ 数据库服务未运行" fi $result } verify_disk_space() { local result=true local threshold=90 # 90% 使用率阈值 # 检查根分区使用率 local usage=$(df / | awk 'NR==2 {print $5}' | sed 's/%//') if [[ $usage -gt $threshold ]]; then error "❌ 磁盘空间不足: / 分区使用率 $usage%" result=false else info "✅ 磁盘空间正常: / 分区使用率 $usage%" fi $result } verify_system_load() { local result=true local load_threshold=10.0 # 负载阈值 # 获取系统负载 local load=$(awk '{print $1}' /proc/loadavg) local cores=$(nproc) if (( $(echo "$load > $cores" | bc -l) )); then warn "⚠️ 系统负载较高: $load (CPU核心数: $cores)" else info "✅ 系统负载正常: $load (CPU核心数: $cores)" fi $result } # 报告生成函数 generate_verification_report() { local package_path="$1" local overall_result="$2" local verify_type="$3" local report_file="$TEMP_DIR/verification_report_$(date +%Y%m%d_%H%M%S).txt" cat > "$report_file" << EOF 补丁包验证报告 ======================================== 补丁包: $(basename "$package_path") 验证类型: $verify_type 验证时间: $(date) 验证主机: $(hostname) 总体结果: $(if $overall_result; then echo "通过"; else echo "失败"; fi) 详细验证结果: EOF # 添加详细验证信息 { echo "1. 基础完整性验证: $(if verify_basic_integrity "$package_path"; then echo "通过"; else echo "失败"; fi)" echo "2. 安全验证: $(if verify_security "$package_path"; then echo "通过"; else echo "失败"; fi)" echo "3. 内容验证: $(if verify_content "$package_path" "$verify_type"; then echo "通过"; else echo "失败"; fi)" if [[ "$verify_type" == "post-apply" ]]; then echo "4. 系统状态验证: $(if verify_system_state "$package_path"; then echo "通过"; else echo "失败"; fi)" fi } >> "$report_file" info "验证报告生成完成: $report_file" # 显示报告摘要 echo echo "验证报告摘要:" cat "$report_file" echo } # 批量验证函数 batch_verify() { local patch_dir="${1:-$OUTPUT_DIRECTORY}" local verify_type="${2:-standalone}" info "开始批量验证补丁包目录: $patch_dir" local total_count=0 local success_count=0 local fail_count=0 # 查找所有补丁包文件 for patch_file in "$patch_dir"/*.tar.gz; do if [[ -f "$patch_file" ]]; then ((total_count++)) info "验证补丁包: $(basename "$patch_file")" if verify_package_integrity "$patch_file" "$verify_type"; then ((success_count++)) else ((fail_count++)) fi echo "----------------------------------------" fi done info "批量验证完成: $success_count/$total_count 成功, $fail_count/$total_count 失败" if [[ $fail_count -eq 0 ]]; then return 0 else return 1 fi } # 主验证函数 main_verify() { local patch_path="${1:-}" local verify_type="${2:-standalone}" local batch_mode="${3:-false}" # 加载配置 load_config # 设置环境 setup_environment # 批量验证模式 if [[ "$batch_mode" == "true" ]] || [[ -d "$patch_path" ]]; then batch_verify "$patch_path" "$verify_type" return $? fi # 单个文件验证 if [[ -z "$patch_path" ]]; then echo "用法: $0 <补丁包路径|目录> [verify-type] [batch]" echo "验证类型:" echo " standalone - 独立验证(默认)" echo " pre-apply - 应用前验证" echo " post-apply - 应用后验证" echo "示例:" echo " $0 /opt/patches/patch.tar.gz" echo " $0 /opt/patches/patch.tar.gz pre-apply" echo " $0 /opt/patches/ batch" exit 1 fi # 执行验证 if verify_package_integrity "$patch_path" "$verify_type"; then info "🎉 补丁包验证成功" exit 0 else error "💥 补丁包验证失败" exit 1 fi } # 异常处理 trap 'error "脚本执行中断"; cleanup; exit 1' INT TERM main_verify "$@"