From 1426da2f8a4604042bc67a85b54952b1fad1bc91 Mon Sep 17 00:00:00 2001 From: ZF sun <34314687@qq.com> Date: Mon, 17 Nov 2025 16:10:07 +0800 Subject: [PATCH] =?UTF-8?q?chore:=20=E4=BF=AE=E5=A4=8D=E5=BA=94=E7=94=A8?= =?UTF-8?q?=E8=A1=A5=E4=B8=81=E5=A4=87=E4=BB=BD=E4=B8=8D=E6=AD=A3=E7=A1=AE?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/patch_tools/patch_applier.sh | 851 +++++++++++++++++++-------- 1 file changed, 610 insertions(+), 241 deletions(-) diff --git a/scripts/patch_tools/patch_applier.sh b/scripts/patch_tools/patch_applier.sh index ae704e937..a647ad349 100644 --- a/scripts/patch_tools/patch_applier.sh +++ b/scripts/patch_tools/patch_applier.sh @@ -7,6 +7,7 @@ shopt -s nullglob extglob # 脚本目录 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" CONFIG_FILE="${SCRIPT_DIR}/patch_config.sh" +LOCK_FILE="/tmp/patch_apply.lock" # 颜色定义 RED='\033[0;31m' @@ -17,6 +18,7 @@ PURPLE='\033[0;35m' CYAN='\033[0;36m' NC='\033[0m' + # 日志函数 log() { local level="$1" @@ -54,6 +56,11 @@ load_config() { : ${LOG_FILE:="/tmp/patch_apply_$(date +%Y%m%d_%H%M%S).log"} : ${BACKUP_DIR:="/var/backups/patch"} : ${TEMP_DIR:="/tmp/patch_apply_$$"} + : ${ROLLBACK_ENABLED:="true"} + : ${VALIDATION_ENABLED:="true"} + : ${NOTIFICATIONS_ENABLED:="true"} + : ${ATOMIC_OPERATIONS:="true"} + : ${MAX_BACKUP_COUNT:=10} } setup_environment() { @@ -66,38 +73,107 @@ setup_environment() { info "临时目录: $TEMP_DIR" } +# 锁管理 +acquire_lock() { + local max_retries=5 + local retry_count=0 + + while [[ $retry_count -lt $max_retries ]]; do + if (set -C; echo $$ > "$LOCK_FILE") 2>/dev/null; then + info "🔒 获取应用锁" + return 0 + fi + + local lock_pid=$(cat "$LOCK_FILE" 2>/dev/null) + if kill -0 "$lock_pid" 2>/dev/null; then + warn "补丁应用正在进行中 (PID: $lock_pid),等待..." + sleep 5 + ((retry_count++)) + else + warn "清理过期的锁文件" + rm -f "$LOCK_FILE" + fi + done + + error "无法获取锁,可能已有补丁应用在进行中" + exit 1 +} + +release_lock() { + rm -f "$LOCK_FILE" + info "🔓 释放应用锁" +} + cleanup() { + local exit_code=$? + if [[ -d "$TEMP_DIR" ]]; then rm -rf "$TEMP_DIR" info "临时目录已清理: $TEMP_DIR" fi + + release_lock + + if [[ $exit_code -eq 0 ]]; then + info "✅ 补丁应用流程完成" + else + error "💥 补丁应用流程失败 (退出码: $exit_code)" + fi + + exit $exit_code } -trap cleanup EXIT +trap cleanup EXIT INT TERM -# 验证函数 -verify_package() { - local package_path="$1" +# 依赖检查 +check_dependencies() { + local deps=("tar" "gzip" "sha256sum" "cp" "mkdir" "find") + local missing=() - info "开始验证补丁包: $package_path" + for dep in "${deps[@]}"; do + if ! command -v "$dep" >/dev/null 2>&1; then + missing+=("$dep") + fi + done + + if [[ ${#missing[@]} -gt 0 ]]; then + error "缺少依赖工具: ${missing[*]}" + return 1 + fi + + info "依赖检查通过" + return 0 +} + +# 补丁包验证 +verify_patch_package() { + local patch_file="$1" + + info "🔍 验证补丁包: $patch_file" # 检查文件存在性 - if [[ ! -f "$package_path" ]]; then - error "补丁包不存在: $package_path" + if [[ ! -f "$patch_file" ]]; then + error "补丁包不存在: $patch_file" return 1 fi # 检查文件大小 - local size=$(stat -c%s "$package_path" 2>/dev/null || echo "0") + local size=$(stat -c%s "$patch_file" 2>/dev/null || echo "0") if [[ $size -eq 0 ]]; then - error "补丁包为空: $package_path" + error "补丁包为空: $patch_file" return 1 fi - # 验证校验和 - if [[ -f "${package_path}.sha256" ]]; then - info "验证校验和..." - if sha256sum -c "${package_path}.sha256" >/dev/null 2>&1; then + # 验证压缩包完整性 + if ! tar -tzf "$patch_file" >/dev/null 2>&1; then + error "补丁包损坏或格式错误: $patch_file" + return 1 + fi + + # 验证校验和(如果存在) + local checksum_file="${patch_file}.sha256" + if [[ -f "$checksum_file" ]]; then + if sha256sum -c "$checksum_file" >/dev/null 2>&1; then info "✅ 校验和验证通过" else error "❌ 校验和验证失败" @@ -105,278 +181,438 @@ verify_package() { fi fi - # 验证签名 - if [[ -f "${package_path}.sig" ]]; then - info "验证签名..." - 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 "❌ 签名验证失败" - return 1 - fi + # 验证签名(如果存在) + local sig_file="${patch_file}.sig" + if [[ -f "$sig_file" ]] && command -v gpg >/dev/null 2>&1; then + if gpg --verify "$sig_file" "$patch_file" >/dev/null 2>&1; then + info "✅ 签名验证通过" else - warn "GPG未安装,跳过签名验证" + error "❌ 签名验证失败" + return 1 fi fi - # 验证压缩包完整性 - if ! tar -tzf "$package_path" >/dev/null 2>&1; then - error "❌ 压缩包损坏或格式错误" - return 1 - fi - info "✅ 补丁包验证通过" return 0 } -# 备份函数 -create_backup() { - local backup_name="backup_$(date +%Y%m%d_%H%M%S)" - local backup_path="$BACKUP_DIR/$backup_name.tar.gz" - - info "创建应用前备份: $backup_path" - - # 获取需要备份的文件列表 - local files_to_backup=() - while IFS='|' read -r change_type path old_info new_info; do - case "$change_type" in - "MODIFIED"|"DELETED") - local target_file="$path" - if [[ -f "$target_file" ]]; then - files_to_backup+=("$target_file") - fi - ;; - esac - done < "$TEMP_DIR/changes.txt" - - if [[ ${#files_to_backup[@]} -eq 0 ]]; then - info "无需备份文件" - return 0 - fi - - # 创建备份 - tar -czf "$backup_path" "${files_to_backup[@]}" - - local backup_size=$(du -h "$backup_path" | cut -f1) - info "✅ 备份创建完成: $backup_path ($backup_size)" - echo "$backup_path" -} - -# 补丁应用函数 -extract_package() { - local package_path="$1" +# 提取补丁包 +extract_patch() { + local patch_file="$1" local extract_dir="$2" - info "解压补丁包到: $extract_dir" + info "📦 提取补丁包到: $extract_dir" - mkdir -p "$extract_dir" + if [[ ! -d "$extract_dir" ]]; then + mkdir -p "$extract_dir" + fi - if tar -xzf "$package_path" -C "$extract_dir"; then - info "✅ 补丁包解压完成" - return 0 + if tar -xzf "$patch_file" -C "$extract_dir"; then + info "✅ 补丁包提取完成" + + # 验证提取内容 + if [[ -f "$extract_dir/MANIFEST.MF" ]]; then + info "✅ 清单文件验证通过" + return 0 + else + error "❌ 补丁包缺少清单文件" + return 1 + fi else - error "❌ 补丁包解压失败" + error "❌ 补丁包提取失败" return 1 fi } -apply_file_changes() { - local patch_dir="$1" +# 解析清单文件 +parse_manifest() { + local manifest_file="$1" - info "开始应用文件变更..." + info "📋 解析清单文件: $manifest_file" >&2 - local applied_count=0 - local failed_count=0 - - # 读取变更清单 - local manifest_file="$patch_dir/MANIFEST.MF" if [[ ! -f "$manifest_file" ]]; then error "清单文件不存在: $manifest_file" return 1 fi - # 处理每个变更 + # 读取基础信息 + local patch_name=$(grep -i "名称:" "$manifest_file" | cut -d':' -f2- | sed 's/^[[:space:]]*//') + local patch_version=$(grep -i "版本:" "$manifest_file" | cut -d':' -f2- | sed 's/^[[:space:]]*//') + local change_count=$(grep -c -E "^(ADDED|MODIFIED|DELETED)" "$manifest_file" 2>/dev/null || echo "0") + + echo "补丁信息:" + echo " 名称: ${patch_name:-未知}" + echo " 版本: ${patch_version:-未知}" + echo " 变更数: $change_count" + echo "" + + # 提取变更列表 + local changes=() while IFS='|' read -r change_type path extra_info; do - case "$change_type" in - "ADDED"|"MODIFIED") - apply_file_addition_modification "$patch_dir" "$path" "$change_type" - result=$? - ;; - "DELETED") - apply_file_deletion "$path" - result=$? - ;; - *) - warn "未知变更类型: $change_type" - result=1 - ;; - esac - - if [[ $result -eq 0 ]]; then - ((applied_count++)) - else - ((failed_count++)) - fi + changes+=("$change_type|$path|$extra_info") done < <(grep -E "^(ADDED|MODIFIED|DELETED)" "$manifest_file") - info "文件变更应用完成: $applied_count 成功, $failed_count 失败" - - if [[ $failed_count -gt 0 ]]; then - return 1 + if [[ ${#changes[@]} -eq 0 ]]; then + warn "⚠️ 清单中没有找到变更记录" >&2 fi + + # 返回变更数组 + printf "%s\n" "${changes[@]}" return 0 } -apply_file_addition_modification() { - local patch_dir="$1" - local file_path="$2" - local change_type="$3" +# 创建备份 +create_backup() { + local changes_file="$1" + local backup_dir="$2" - local source_file="$patch_dir/files/$file_path" - local target_file="/$file_path" # 绝对路径 + info "💾 创建应用前备份..." >&2 + + if [[ ! -s "$changes_file" ]]; then + warn "无变更需要备份" >&2 + return 0 + fi + + local files_to_backup=() + + # 分析需要备份的文件 + while IFS='|' read -r change_type path extra_info; do + case "$change_type" in + "MODIFIED"|"DELETED") + local target_file="$path" + if [[ -f "$target_file" ]]; then + files_to_backup+=("$target_file") + debug "需要备份: $target_file" >&2 + fi + ;; + esac + done < "$changes_file" + + if [[ ${#files_to_backup[@]} -eq 0 ]]; then + info "⚠️ 无文件需要备份" >&2 + return 0 + fi + + # 创建备份 + local backup_tar="$backup_dir/backup_$(date +%Y%m%d_%H%M%S).tar.gz" + mkdir -p "$(dirname "$backup_tar")" + + local work_dir="$(pwd)" # 默认是当前工作目录 + + if tar -czf "$backup_tar" -C "${work_dir}" "${files_to_backup[@]/#\//}"; then + local size=$(du -h "$backup_tar" | cut -f1) + info "✅ 备份创建完成: $backup_tar ($size)" >&2 + echo "$backup_tar" + return 0 + else + error "❌ 备份创建失败" >&2 + return 1 + fi +} + +# 应用文件变更 +apply_file_changes() { + local extract_dir="$1" + local changes_file="$2" + local dry_run="${3:-false}" + + info "🔄 开始应用文件变更 (干跑模式: $dry_run)" >&2 + + local applied_count=0 + local failed_count=0 + local skip_count=0 + + # 读取变更列表 + while IFS='|' read -r change_type path extra_info; do + case "$change_type" in + "ADDED") + apply_file_addition "$extract_dir" "$path" "$dry_run" + result=$? + ;; + "MODIFIED") + apply_file_modification "$extract_dir" "$path" "$dry_run" + result=$? + ;; + "DELETED") + apply_file_deletion "$path" "$dry_run" + result=$? + ;; + *) + warn "⚠️ 未知变更类型: $change_type" >&2 + result=99 + ;; + esac + + case $result in + 0) ((applied_count++)) ;; + 1) ((failed_count++)) ;; + 2) ((skip_count++)) ;; + esac + done < "$changes_file" + + info "📊 变更应用统计:" >&2 + info " 成功: $applied_count" >&2 + info " 失败: $failed_count" >&2 + info " 跳过: $skip_count" >&2 + info " 总计: $((applied_count + failed_count + skip_count))" >&2 + + if [[ $failed_count -gt 0 ]]; then + error "❌ 存在应用失败的变更" >&2 + return 1 + fi + + if [[ "$dry_run" == "true" ]]; then + info "✅ 干跑模式完成 - 无实际变更" >&2 + return 0 + else + info "✅ 文件变更应用完成" >&2 + return 0 + fi +} + +# 应用文件新增 +apply_file_addition() { + local extract_dir="$1" + local file_path="$2" + local dry_run="$3" + + local source_file="$extract_dir/files/$file_path" + local target_file="$file_path" local target_dir=$(dirname "$target_file") # 检查源文件是否存在 if [[ ! -f "$source_file" ]]; then - error "源文件不存在: $source_file" + error "源文件不存在: $source_file" >&2 return 1 fi + if [[ "$dry_run" == "true" ]]; then + info "📋 [干跑] 新增文件: $file_path" >&2 + return 0 + fi + # 创建目标目录 if [[ ! -d "$target_dir" ]]; then - mkdir -p "$target_dir" - debug "创建目录: $target_dir" + if ! mkdir -p "$target_dir"; then + error "创建目录失败: $target_dir" >&2 + return 1 + fi + debug "创建目录: $target_dir" >&2 fi - # 备份原文件(如果是修改操作) - if [[ "$change_type" == "MODIFIED" ]] && [[ -f "$target_file" ]]; then - local backup_file="$TEMP_DIR/backup/$file_path" - mkdir -p "$(dirname "$backup_file")" - cp -p "$target_file" "$backup_file" - debug "备份原文件: $target_file -> $backup_file" + # 检查目标文件是否已存在 + if [[ -f "$target_file" ]]; then + warn "目标文件已存在,将被覆盖: $target_file" >&2 fi # 复制文件 - if cp -p "$source_file" "$target_file"; then - # 设置权限(从清单中读取) - set_file_permissions "$target_file" "$change_type" - info "✅ 应用文件: $file_path ($change_type)" + if cp "$source_file" "$target_file"; then + # 设置权限 + set_file_permissions "$target_file" "644" + info "✅ 新增文件: $file_path" >&2 return 0 else - error "❌ 文件应用失败: $file_path" + error "❌ 文件新增失败: $file_path" >&2 return 1 fi } +# 应用文件修改 +apply_file_modification() { + local extract_dir="$1" + local file_path="$2" + local dry_run="$3" + + local source_file="$extract_dir/files/$file_path" + local target_file="$file_path" + + info "🔄 开始应用文件修改: $file_path" >&2 + info "当前工作目录: $(pwd)" >&2 + info "源文件: $source_file" >&2 + info "目标文件: $target_file" >&2 + + # 检查源文件 + if [[ ! -f "$source_file" ]]; then + error "源文件不存在: $source_file" >&2 + return 1 + fi + + # 检查目标文件 + if [[ ! -f "$target_file" ]]; then + warn "目标文件不存在,将作为新增处理: $target_file" >&2 + return apply_file_addition "$extract_dir" "$file_path" "$dry_run" + fi + + if [[ "$dry_run" == "true" ]]; then + info "📋 [干跑] 修改文件: $file_path" >&2 + return 0 + fi + + # 备份原文件 + local backup_file="$TEMP_DIR/backup/$file_path" + local backup_dir=$(dirname "$backup_file") + mkdir -p "$backup_dir" + + if ! cp -p "$target_file" "$backup_file"; then + error "备份原文件失败: $target_file" >&2 + return 1 + fi + + # 应用修改 + if cp "$source_file" "$target_file"; then + # 保持原权限 + local original_perm=$(stat -c%a "$backup_file" 2>/dev/null || echo "644") + set_file_permissions "$target_file" "$original_perm" + info "✅ 修改文件: $file_path" >&2 + return 0 + else + error "❌ 文件修改失败: $file_path" >&2 + # 恢复备份 + cp "$backup_file" "$target_file" + return 1 + fi +} + +# 应用文件删除 apply_file_deletion() { local file_path="$1" - local target_file="/$file_path" + local dry_run="$2" + + local target_file="$file_path" if [[ ! -f "$target_file" ]]; then - warn "文件不存在,无需删除: $target_file" + warn "文件不存在,无需删除: $target_file" >&2 + return 2 # 跳过 + fi + + if [[ "$dry_run" == "true" ]]; then + info "📋 [干跑] 删除文件: $file_path" >&2 return 0 fi # 备份文件 local backup_file="$TEMP_DIR/backup/$file_path" - mkdir -p "$(dirname "$backup_file")" - cp -p "$target_file" "$backup_file" + local backup_dir=$(dirname "$backup_file") + mkdir -p "$backup_dir" + + if ! cp -p "$target_file" "$backup_file"; then + error "备份文件失败: $target_file" >&2 + return 1 + fi # 删除文件 if rm -f "$target_file"; then - info "✅ 删除文件: $file_path" - - # 尝试删除空目录 - local parent_dir=$(dirname "$target_file") - while [[ "$parent_dir" != "/" ]] && rmdir "$parent_dir" 2>/dev/null; do - debug "删除空目录: $parent_dir" - parent_dir=$(dirname "$parent_dir") - done + info "✅ 删除文件: $file_path" >&2 + # 尝试清理空目录 + clean_empty_directories "$(dirname "$target_file")" return 0 else - error "❌ 文件删除失败: $file_path" + error "❌ 文件删除失败: $file_path" >&2 return 1 fi } +# 清理空目录 +clean_empty_directories() { + local dir="$1" + + while [[ "$dir" != "/" ]] && [[ -d "$dir" ]]; do + if ! rmdir "$dir" 2>/dev/null; then + break # 目录非空,停止清理 + fi + debug "清理空目录: $dir" >&2 + dir=$(dirname "$dir") + done +} + +# 设置文件权限 set_file_permissions() { - local file_path="$1" - local change_type="$2" + local file="$1" + local permissions="${2:-644}" - # 根据文件类型设置权限 - case "$file_path" in - *.sh|*.bash) - chmod 755 "$file_path" - ;; - *.php|*.py|*.pl) - chmod 644 "$file_path" - ;; - /etc/*|/var/www/*) - chmod 644 "$file_path" - ;; - *) - chmod 644 "$file_path" - ;; - esac - - debug "设置权限: $file_path -> $(stat -c %a "$file_path")" + if chmod "$permissions" "$file" 2>/dev/null; then + debug "设置权限: $file → $permissions" + else + warn "权限设置失败: $file" + fi } # 验证应用结果 verify_application() { - local patch_dir="$1" + local extract_dir="$1" + local changes_file="$2" - info "开始验证应用结果..." + if [[ "$VALIDATION_ENABLED" != "true" ]]; then + info "验证功能已禁用" >&2 + return 0 + fi - local manifest_file="$patch_dir/MANIFEST.MF" + info "🔍 验证应用结果..." >&2 + + local manifest_file="$extract_dir/MANIFEST.MF" local verification_passed=true + local verified_count=0 + local failed_count=0 + # 读取变更列表进行验证 while IFS='|' read -r change_type path extra_info; do case "$change_type" in "ADDED"|"MODIFIED") - if ! verify_file_application "$path" "$change_type" "$patch_dir"; then + if verify_file_application "$extract_dir" "$path" "$change_type"; then + ((verified_count++)) + else + ((failed_count++)) verification_passed=false fi ;; "DELETED") - if ! verify_file_deletion "$path"; then + if verify_file_deletion "$path"; then + ((verified_count++)) + else + ((failed_count++)) verification_passed=false fi ;; esac - done < <(grep -E "^(ADDED|MODIFIED|DELETED)" "$manifest_file") + done < "$changes_file" + + info "📊 验证统计:" >&2 + info " 成功: $verified_count" >&2 + info " 失败: $failed_count" >&2 if $verification_passed; then - info "✅ 应用验证通过" + info "✅ 应用验证通过" >&2 return 0 else - error "❌ 应用验证失败" + error "❌ 应用验证失败" >&2 return 1 fi } +# 验证文件应用 verify_file_application() { - local file_path="$1" - local change_type="$2" - local patch_dir="$3" + local extract_dir="$1" + local file_path="$2" + local change_type="$3" - local target_file="/$file_path" - local source_file="$patch_dir/files/$file_path" + local source_file="$extract_dir/files/$file_path" + local target_file="$file_path" - # 检查文件是否存在 + # 检查目标文件是否存在 if [[ ! -f "$target_file" ]]; then - error "❌ 文件不存在: $target_file" + error "❌ 目标文件不存在: $target_file" return 1 fi - # 检查文件内容 + # 比较文件内容 local source_hash=$(sha256sum "$source_file" | cut -d' ' -f1) local target_hash=$(sha256sum "$target_file" | cut -d' ' -f1) if [[ "$source_hash" != "$target_hash" ]]; then error "❌ 文件内容不匹配: $file_path" + error " 源哈希: $source_hash" + error " 目标哈希: $target_hash" return 1 fi @@ -384,9 +620,10 @@ verify_file_application() { return 0 } +# 验证文件删除 verify_file_deletion() { local file_path="$1" - local target_file="/$file_path" + local target_file="$file_path" if [[ -f "$target_file" ]]; then error "❌ 文件未成功删除: $target_file" @@ -397,69 +634,92 @@ verify_file_deletion() { return 0 } -# 回滚准备 +# 创建回滚点 create_rollback_point() { - local patch_path="$1" - local backup_path="$2" + local patch_file="$1" + local backup_file="$2" + local extract_dir="$3" + + if [[ "$ROLLBACK_ENABLED" != "true" ]]; then + info "回滚功能已禁用" + return 0 + fi + + info "🔄 创建回滚点..." local rollback_info_file="$BACKUP_DIR/rollback_info.json" - local patch_name=$(basename "$patch_path") + local patch_name=$(basename "$patch_file") local timestamp=$(date +%Y%m%d_%H%M%S) + # 读取清单信息 + local manifest_file="$extract_dir/MANIFEST.MF" + local patch_info="" + if [[ -f "$manifest_file" ]]; then + patch_info=$(grep -E "^(名称|版本|描述):" "$manifest_file" | head -3) + fi + local rollback_info=$(cat << EOF { "patch_name": "$patch_name", "apply_time": "$(date -Iseconds)", - "backup_path": "$backup_path", - "rollback_path": "$BACKUP_DIR/rollback_${timestamp}.tar.gz", + "backup_path": "$backup_file", + "rollback_dir": "$BACKUP_DIR/rollback_$timestamp", + "manifest_info": "$(echo "$patch_info" | tr '\n' ';')", "status": "applied" } EOF ) echo "$rollback_info" > "$rollback_info_file" - info "回滚点创建完成: $rollback_info_file" + info "✅ 回滚点创建完成: $rollback_info_file" } -# 通知函数 -send_application_notification() { +# 发送通知 +send_notification() { local status="$1" - local patch_path="$2" + local patch_file="$2" local message="$3" if [[ "$NOTIFICATIONS_ENABLED" != "true" ]]; then return 0 fi + local subject="" + local color="" + case "$status" in "start") - local subject="补丁应用开始" - local color="#3498db" + subject="补丁应用开始" + color="#3498db" ;; "success") - local subject="补丁应用成功" - local color="#2ecc71" + subject="补丁应用成功" + color="#2ecc71" ;; "failure") - local subject="补丁应用失败" - local color="#e74c3c" + subject="补丁应用失败" + color="#e74c3c" ;; *) - local subject="补丁应用通知" - local color="#95a5a6" + subject="补丁应用通知" + color="#95a5a6" ;; esac # Slack通知 - if [[ -n "$SLACK_WEBHOOK" ]]; then - send_slack_notification "$subject" "$message" "$color" + if [[ -n "${SLACK_WEBHOOK:-}" ]]; then + send_slack_notification "$subject" "$message" "$color" "$patch_file" fi + + # 本地日志通知 + log "NOTIFICATION" "$subject: $message" } send_slack_notification() { local subject="$1" local message="$2" local color="$3" + local patch_file="$4" local payload=$(cat << EOF { @@ -471,7 +731,7 @@ send_slack_notification() { "fields": [ { "title": "补丁文件", - "value": "$(basename "$PATCH_PATH")", + "value": "$(basename "$patch_file")", "short": true }, { @@ -492,88 +752,196 @@ EOF ) if command -v curl >/dev/null 2>&1; then - curl -s -X POST -H 'Content-type: application/json' \ - --data "$payload" "$SLACK_WEBHOOK" >/dev/null && \ - info "Slack通知发送成功" + if curl -s -X POST -H 'Content-type: application/json' \ + --data "$payload" "$SLACK_WEBHOOK" >/dev/null; then + info "✅ Slack通知发送成功" + else + warn "⚠️ Slack通知发送失败" + fi fi } # 主应用函数 apply_patch() { - local patch_path="$1" + local patch_file="$1" local dry_run="${2:-false}" + local target_dir="${3:-./}" - info "开始应用补丁包: $patch_path" - send_application_notification "start" "$patch_path" "开始应用补丁" + info "🚀 开始应用补丁包: $patch_file" + info "目标目录: $target_dir" + info "干跑模式: $dry_run" + + # 发送开始通知 + send_notification "start" "$patch_file" "开始应用补丁" # 验证补丁包 - if ! verify_package "$patch_path"; then - error "补丁包验证失败" - send_application_notification "failure" "$patch_path" "补丁包验证失败" + if ! verify_patch_package "$patch_file"; then + send_notification "failure" "$patch_file" "补丁包验证失败" return 1 fi - # 解压补丁包 + # 创建临时目录 local extract_dir="$TEMP_DIR/extract" - if ! extract_package "$patch_path" "$extract_dir"; then - send_application_notification "failure" "$patch_path" "补丁包解压失败" + local changes_file="$TEMP_DIR/changes.txt" + + # 提取补丁包 + info ">>提取补丁包..." + if ! extract_patch "$patch_file" "$extract_dir"; then + send_notification "failure" "$patch_file" "补丁包解压失败" return 1 fi - # 如果是干跑模式,只验证不应用 - if [[ "$dry_run" == "true" ]]; then - info "干跑模式: 只验证不应用" - verify_application "$extract_dir" + # 解析清单文件 + info ">>解析清单文件..." + if ! parse_manifest "$extract_dir/MANIFEST.MF" > "$changes_file"; then + send_notification "failure" "$patch_file" "清单文件解析失败" + return 1 + fi + + # 检查是否有变更 + info ">>检查变更记录..." + if [[ ! -s "$changes_file" ]]; then + warn "⚠️ 补丁包中没有变更记录" + send_notification "success" "$patch_file" "补丁包无变更记录" return 0 fi # 创建备份 - local backup_path=$(create_backup) + local backup_file="" + if [[ "$dry_run" != "true" ]]; then + info ">>创建备份..." + backup_file=$(create_backup "$changes_file" "$BACKUP_DIR") + if [[ $? -ne 0 ]]; then + send_notification "failure" "$patch_file" "备份创建失败" + return 1 + fi + fi # 应用变更 - if ! apply_file_changes "$extract_dir"; then - error "文件变更应用失败" - send_application_notification "failure" "$patch_path" "文件应用失败" + info ">>应用变更..." + if ! apply_file_changes "$extract_dir" "$changes_file" "$dry_run"; then + send_notification "failure" "$patch_file" "文件应用失败" return 1 fi - # 验证应用结果 - if ! verify_application "$extract_dir"; then - error "应用验证失败" - send_application_notification "failure" "$patch_path" "应用验证失败" - return 1 + # 验证应用结果(非干跑模式) + info ">>验证应用结果..." + if [[ "$dry_run" != "true" ]]; then + if ! verify_application "$extract_dir" "$changes_file"; then + send_notification "failure" "$patch_file" "应用验证失败" + return 1 + fi + + # 创建回滚点 + info ">>创建回滚点..." + create_rollback_point "$patch_file" "$backup_file" "$extract_dir" fi - # 创建回滚点 - create_rollback_point "$patch_path" "$backup_path" - - info "✅ 补丁应用完成" - send_application_notification "success" "$patch_path" "补丁应用成功" + send_notification "success" "$patch_file" "补丁应用成功" + info "✅ 补丁应用流程完成" return 0 } +# 显示使用帮助 +usage() { + cat << EOF +用法: $0 [选项] <补丁包路径> + +选项: + -d, --dry-run 干跑模式(只验证不应用) + -t, --target DIR 目标目录(默认: /) + -c, --config FILE 配置文件路径 + -v, --verbose 详细输出 + -q, --quiet 安静模式 + -h, --help 显示此帮助 + +示例: + $0 patch.tar.gz # 应用补丁 + $0 --dry-run patch.tar.gz # 干跑模式 + $0 --target /app patch.tar.gz # 应用到指定目录 + $0 --config custom_config.sh patch.tar.gz + +环境变量: + PATCH_CONFIG 配置文件路径 + BACKUP_DIR 备份目录 + LOG_LEVEL 日志级别 + +报告问题: 请检查日志文件 $LOG_FILE +EOF +} + # 主函数 main() { - local patch_path="${1:-}" - local dry_run="${2:-false}" + local patch_file="" + local dry_run="false" + local target_dir="/" + local verbose_mode="false" + + # 初始化 + load_config + setup_environment + check_dependencies + acquire_lock - if [[ -z "$patch_path" ]]; then - echo "用法: $0 <补丁包路径> [dry-run]" - echo "示例:" - echo " $0 /opt/patches/patch-security-hotfix-1.2.3.tar.gz" - echo " $0 /opt/patches/patch-security-hotfix-1.2.3.tar.gz dry-run" + # 解析命令行参数 + while [[ $# -gt 0 ]]; do + case $1 in + -d|--dry-run) + dry_run="true" + shift + ;; + -t|--target) + target_dir="$2" + shift 2 + ;; + -c|--config) + CONFIG_FILE="$2" + shift 2 + ;; + -v|--verbose) + verbose_mode="true" + shift + ;; + -q|--quiet) + exec >/dev/null 2>&1 + shift + ;; + -h|--help) + usage + exit 0 + ;; + -*) + error "未知选项: $1" + usage + exit 1 + ;; + *) + patch_file="$1" + shift + ;; + esac + done + + # 检查参数 + if [[ -z "$patch_file" ]]; then + error "必须指定补丁包路径" + usage exit 1 fi - # 加载配置 - load_config + if [[ ! -f "$patch_file" ]]; then + error "补丁包不存在: $patch_file" + exit 1 + fi - # 设置环境 - setup_environment - # 应用补丁 - if apply_patch "$patch_path" "$dry_run"; then - info "🎉 补丁应用成功完成" + # 执行补丁应用 + if apply_patch "$patch_file" "$dry_run" "$target_dir"; then + if [[ "$dry_run" == "true" ]]; then + info "🎉 干跑模式完成 - 可安全应用补丁" + else + info "🎉 补丁应用成功完成!" + fi exit 0 else error "💥 补丁应用失败" @@ -582,6 +950,7 @@ main() { } # 异常处理 -trap 'error "脚本执行中断"; cleanup; exit 1' INT TERM +trap 'error "脚本执行中断"; exit 1' INT TERM -main "$@" \ No newline at end of file +# 执行主函数 +main "$@"