Files
shop-platform/scripts/patch_tools/patch_verifier.sh

541 lines
14 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.
#!/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 [[ "$SIGNING_ENABLED" == "true" ]] ;then
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
fi
$result
}
verify_content() {
local package_path="$1"
local verify_type="$2"
info "执行内容验证..."
local result=true
# 解压补丁包
local extract_dir="$TEMP_DIR/extract"
mkdir -p "$extract_dir"
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 "$@"