From f76320f7328143bd629ca5bcd4f713af04e94349 Mon Sep 17 00:00:00 2001 From: ZF sun <34314687@qq.com> Date: Mon, 17 Nov 2025 18:18:23 +0800 Subject: [PATCH] =?UTF-8?q?chore:=20=E5=8F=AF=E4=BB=A5=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E5=BA=94=E7=94=A8=E8=A1=A5=E4=B8=81=E5=8F=8A=E5=9B=9E=E6=BB=9A?= =?UTF-8?q?=E6=93=8D=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/patch_tools/install_patch_system.sh | 1 - scripts/patch_tools/patch_applier.sh | 102 +- scripts/patch_tools/patch_config.sh | 2 + scripts/patch_tools/patch_rollback.sh | 1015 ++++++++++++++++--- scripts/patch_tools/tmp_0/cache.php | 41 + 5 files changed, 1007 insertions(+), 154 deletions(-) create mode 100644 scripts/patch_tools/tmp_0/cache.php diff --git a/scripts/patch_tools/install_patch_system.sh b/scripts/patch_tools/install_patch_system.sh index bd9e93b85..b206bdae6 100644 --- a/scripts/patch_tools/install_patch_system.sh +++ b/scripts/patch_tools/install_patch_system.sh @@ -89,7 +89,6 @@ install_dependencies() { "jq" "gpg" "bc" - "gnupg" ) for dep in "${dependencies[@]}"; do diff --git a/scripts/patch_tools/patch_applier.sh b/scripts/patch_tools/patch_applier.sh index a647ad349..7b3833932 100644 --- a/scripts/patch_tools/patch_applier.sh +++ b/scripts/patch_tools/patch_applier.sh @@ -7,7 +7,9 @@ shopt -s nullglob extglob # 脚本目录 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" CONFIG_FILE="${SCRIPT_DIR}/patch_config.sh" +LOG_FILE="/var/log/patch_system/apply_$(date +%Y%m%d_%H%M%S).log" LOCK_FILE="/tmp/patch_apply.lock" +ROLLBACK_INFO_DIR="/var/lib/patch_system/rollback_info" # 颜色定义 RED='\033[0;31m' @@ -53,21 +55,25 @@ load_config() { # 设置默认值 : ${LOG_LEVEL:="INFO"} - : ${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"} + : ${ROLLBACK_INFO_DIR:="/var/lib/patch_system/rollback_info"} : ${VALIDATION_ENABLED:="true"} : ${NOTIFICATIONS_ENABLED:="true"} : ${ATOMIC_OPERATIONS:="true"} - : ${MAX_BACKUP_COUNT:=10} + : ${MAX_BACKUP_COUNT:=10} + : ${PATCH_STAGING_DIR:="/tmp/patch_staging"} } setup_environment() { mkdir -p "$(dirname "$LOG_FILE")" mkdir -p "$BACKUP_DIR" mkdir -p "$TEMP_DIR" + mkdir -p "$ROLLBACK_INFO_DIR" + mkdir -p "$PATCH_STAGING_DIR" + info "环境设置完成" info "日志文件: $LOG_FILE" info "备份目录: $BACKUP_DIR" info "临时目录: $TEMP_DIR" @@ -127,7 +133,7 @@ trap cleanup EXIT INT TERM # 依赖检查 check_dependencies() { - local deps=("tar" "gzip" "sha256sum" "cp" "mkdir" "find") + local deps=("tar" "gzip" "find" "stat" "sha256sum" "date" "mkdir" "cp" "jq") local missing=() for dep in "${deps[@]}"; do @@ -639,6 +645,7 @@ create_rollback_point() { local patch_file="$1" local backup_file="$2" local extract_dir="$3" + local changes_file="$4" if [[ "$ROLLBACK_ENABLED" != "true" ]]; then info "回滚功能已禁用" @@ -647,31 +654,94 @@ create_rollback_point() { info "🔄 创建回滚点..." - local rollback_info_file="$BACKUP_DIR/rollback_info.json" - local patch_name=$(basename "$patch_file") + local patch_name=$(basename "$patch_file" .tar.gz) local timestamp=$(date +%Y%m%d_%H%M%S) + local rollback_info_file="$ROLLBACK_INFO_DIR/${patch_name}_${timestamp}.json" # 读取清单信息 local manifest_file="$extract_dir/MANIFEST.MF" local patch_info="" if [[ -f "$manifest_file" ]]; then - patch_info=$(grep -E "^(名称|版本|描述):" "$manifest_file" | head -3) + patch_info=$(grep -E "^(名称|版本|描述):" "$manifest_file" | head -3 | tr '\n' ';') fi + # 统计变更类型 + local added_count=$(grep -c "^ADDED" "$changes_file") + local modified_count=$(grep -c "^MODIFIED" "$changes_file") + local deleted_count=$(grep -c "^DELETED" "$changes_file") + + # 创建回滚信息JSON local rollback_info=$(cat << EOF { "patch_name": "$patch_name", "apply_time": "$(date -Iseconds)", "backup_path": "$backup_file", "rollback_dir": "$BACKUP_DIR/rollback_$timestamp", - "manifest_info": "$(echo "$patch_info" | tr '\n' ';')", - "status": "applied" + "manifest_info": "$patch_info", + "status": "applied", + "changes": { + "added": $added_count, + "modified": $modified_count, + "deleted": $deleted_count, + "total": $((added_count + modified_count + deleted_count)) + }, + "system_info": { + "hostname": "$(hostname)", + "user": "$(whoami)", + "working_directory": "$PWD" + } } EOF ) echo "$rollback_info" > "$rollback_info_file" info "✅ 回滚点创建完成: $rollback_info_file" + + # 清理旧回滚点 + cleanup_old_rollback_points +} + +# 清理旧回滚点 +cleanup_old_rollback_points() { + local max_points="${1:-$MAX_BACKUP_COUNT}" + + info "🧹 清理旧回滚点 (保留最近 $max_points 个)" + + if [[ ! -d "$ROLLBACK_INFO_DIR" ]]; then + return 0 + fi + + # 获取所有回滚点并按时间排序 + local points=($(find "$ROLLBACK_INFO_DIR" -name "*.json" -type f -printf "%T@|%p\n" | sort -rn | cut -d'|' -f2)) + local total_points=${#points[@]} + local points_to_remove=$((total_points - max_points)) + + if [[ $points_to_remove -le 0 ]]; then + debug "无需清理,当前 $total_points 个回滚点" + return 0 + fi + + info "清理 $points_to_remove 个旧回滚点" + + for ((i=max_points; i>创建备份..." @@ -834,7 +904,7 @@ apply_patch() { # 创建回滚点 info ">>创建回滚点..." - create_rollback_point "$patch_file" "$backup_file" "$extract_dir" + create_rollback_point "$patch_file" "$backup_file" "$extract_dir" "$changes_file" fi send_notification "success" "$patch_file" "补丁应用成功" @@ -854,6 +924,7 @@ usage() { -v, --verbose 详细输出 -q, --quiet 安静模式 -h, --help 显示此帮助 + -V, --version 显示版本 示例: $0 patch.tar.gz # 应用补丁 @@ -870,6 +941,13 @@ usage() { EOF } +# 显示版本信息 +show_version() { + echo "patch_applier.sh v2.0.0" + echo "企业级补丁应用系统" + echo "支持原子性操作、完整验证、智能回滚点创建" +} + # 主函数 main() { local patch_file="" @@ -910,6 +988,10 @@ main() { usage exit 0 ;; + -V|--version) + show_version + exit 0 + ;; -*) error "未知选项: $1" usage diff --git a/scripts/patch_tools/patch_config.sh b/scripts/patch_tools/patch_config.sh index b1df68f82..5b3ca7e47 100644 --- a/scripts/patch_tools/patch_config.sh +++ b/scripts/patch_tools/patch_config.sh @@ -121,6 +121,7 @@ ENCRYPTION_IV="/etc/patch/keys/encryption.iv" # 加密初始化向量文件路 BACKUP_ENABLED=true # 是否启用备份 BACKUP_STRATEGY="full" # 备份策略,full, incremental, differential BACKUP_RETENTION_DAYS=30 # 备份保留天数 +BACKUP_DIR="/var/backups/patch" # 备份目录 # ============================================================================== # 回滚配置 - 定义是否启用自动回滚和回滚策略 @@ -130,6 +131,7 @@ BACKUP_RETENTION_DAYS=30 # 备份保留天数 ROLLBACK_ENABLED=true # 是否启用自动回滚 ROLLBACK_AUTO_GENERATE=true # 是否自动生成回滚脚本 ROLLBACK_STRATEGY="snapshot" # 回滚策略,snapshot, restore +ROLLBACK_INFO_DIR="/var/lib/patch_system/rollback_info" # ============================================================================== # 通知配置 - 定义是否启用通知和通知渠道 diff --git a/scripts/patch_tools/patch_rollback.sh b/scripts/patch_tools/patch_rollback.sh index 1708d6f44..680b157fc 100644 --- a/scripts/patch_tools/patch_rollback.sh +++ b/scripts/patch_tools/patch_rollback.sh @@ -1,12 +1,18 @@ #!/bin/bash # patch_rollback.sh - 企业级补丁包回滚脚本 +# 支持原子性回滚、多回滚点管理、验证等完整功能 set -euo pipefail shopt -s nullglob extglob -# 脚本目录 +# 脚本配置 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" CONFIG_FILE="${SCRIPT_DIR}/patch_config.sh" +LOG_FILE="/var/log/patch_system/rollback_$(date +%Y%m%d_%H%M%S).log" +LOCK_FILE="/tmp/patch_rollback.lock" +ROLLBACK_INFO_DIR="/var/lib/patch_system/rollback_info" +ROLLBACK_POINTS=() # 存储回滚点数组 +ROLLBACK_COUNT=0 # 回滚点数量 # 颜色定义 RED='\033[0;31m' @@ -39,7 +45,7 @@ warn() { log "WARN" "$1"; } error() { log "ERROR" "$1"; } debug() { log "DEBUG" "$1"; } -# 配置和初始化 +# 配置加载 load_config() { if [[ ! -f "$CONFIG_FILE" ]]; then error "配置文件不存在: $CONFIG_FILE" @@ -49,208 +55,652 @@ load_config() { source "$CONFIG_FILE" info "配置文件加载完成" - : ${LOG_LEVEL:="INFO"} - : ${LOG_FILE:="/tmp/patch_rollback_$(date +%Y%m%d_%H%M%S).log"} - : ${BACKUP_DIR:="/var/backups/patch"} + # 设置默认值 : ${TEMP_DIR:="/tmp/patch_rollback_$$"} - : ${ROLLBACK_INFO_FILE:="$BACKUP_DIR/rollback_info.json"} + : ${BACKUP_DIR:="/var/backups/patch"} + : ${ROLLBACK_ENABLED:="true"} + : ${ROLLBACK_INFO_DIR:="/var/lib/patch_system/rollback_info"} + : ${MAX_ROLLBACK_POINTS:=10} + : ${VALIDATION_ENABLED:="true"} + : ${NOTIFICATIONS_ENABLED:="true"} } +# 环境设置 setup_environment() { mkdir -p "$(dirname "$LOG_FILE")" - mkdir -p "$TEMP_DIR" + mkdir -p "$ROLLBACK_INFO_DIR" + mkdir -p "$BACKUP_DIR" + mkdir -p "/tmp/patch_rollback" + info "环境设置完成" info "日志文件: $LOG_FILE" - info "临时目录: $TEMP_DIR" + info "回滚信息目录: $ROLLBACK_INFO_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=$((retry_count+1)) + 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" + # rm -rf "$TEMP_DIR" info "临时目录已清理: $TEMP_DIR" fi -} - -trap cleanup EXIT - -# 回滚点管理 -get_available_rollback_points() { - info "可用的回滚点:" - if [[ ! -f "$ROLLBACK_INFO_FILE" ]]; then - warn "未找到回滚点信息文件" - return 1 - fi + release_lock - # 读取回滚点信息 - local patch_name=$(jq -r '.patch_name' "$ROLLBACK_INFO_FILE" 2>/dev/null || echo "") - local apply_time=$(jq -r '.apply_time' "$ROLLBACK_INFO_FILE" 2>/dev/null || echo "") - local backup_path=$(jq -r '.backup_path' "$ROLLBACK_INFO_FILE" 2>/dev/null || echo "") - - if [[ -n "$patch_name" && -n "$backup_path" && -f "$backup_path" ]]; then - echo "1. $patch_name (应用时间: $apply_time)" - echo " 备份文件: $backup_path" - return 0 + if [[ $exit_code -eq 0 ]]; then + info "✅ 回滚流程完成" else - warn "回滚点信息不完整或备份文件不存在" - return 1 + error "💥 回滚流程失败 (退出码: $exit_code)" fi + + exit $exit_code } -# 验证回滚包 -verify_rollback_package() { - local rollback_path="$1" +trap cleanup EXIT INT TERM + +# 依赖检查 +check_dependencies() { + local deps=("tar" "gzip" "jq" "sha256sum" "cp" "mkdir" "find") + local missing=() - info "验证回滚包: $rollback_path" + for dep in "${deps[@]}"; do + if ! command -v "$dep" >/dev/null 2>&1; then + missing+=("$dep") + fi + done - if [[ ! -f "$rollback_path" ]]; then - error "回滚包不存在: $rollback_path" + if [[ ${#missing[@]} -gt 0 ]]; then + error "缺少依赖工具: ${missing[*]}" return 1 fi - if ! tar -tzf "$rollback_path" >/dev/null 2>&1; then - error "回滚包损坏或格式错误" - return 1 - fi - - info "✅ 回滚包验证通过" + info "依赖检查通过" return 0 } -# 回滚执行函数 -perform_rollback() { - local rollback_path="$1" - local dry_run="${2:-false}" +# 获取可用的回滚点 +list_rollback_points() { + info "📋 可用的回滚点:" >&2 + info "回滚还原点信息目录: $ROLLBACK_INFO_DIR" >&2 - info "开始执行回滚: $rollback_path" - - # 验证回滚包 - if ! verify_rollback_package "$rollback_path"; then + if [[ ! -d "$ROLLBACK_INFO_DIR" ]]; then + warn "回滚信息目录不存在: $ROLLBACK_INFO_DIR" >&2 return 1 fi - # 解压回滚包 - local extract_dir="$TEMP_DIR/rollback_extract" - if ! tar -xzf "$rollback_path" -C "$extract_dir"; then - error "回滚包解压失败" + local count=0 + local temp_file="/tmp/rollback_list_$$" + local found_files=0 + local valid_files=0 + + # 安全检查 + if [[ ! -d "$ROLLBACK_INFO_DIR" ]]; then + error "回滚信息目录不存在: $ROLLBACK_INFO_DIR" >&2 return 1 fi - info "✅ 回滚包解压完成" - # 如果是干跑模式,只显示将要回滚的文件 - if [[ "$dry_run" == "true" ]]; then - info "干跑模式: 显示将要回滚的文件" - find "$extract_dir" -type f | while read file; do - local relative_path="${file#$extract_dir/}" - local target_file="/$relative_path" - echo "📁 将回滚: $relative_path -> $target_file" - done - return 0 + info "在 $ROLLBACK_INFO_DIR 中搜索回滚信息文件..." >&2 + + # 清空临时文件 + > "$temp_file" || { + error "无法创建临时文件: $temp_file" + return 1 + } + + # 使用数组存储文件,避免管道问题 + local files=() + while IFS= read -r -d '' file; do + files+=("$file") + done < <(find "$ROLLBACK_INFO_DIR" -maxdepth 1 -name "*.json" -type f -print0 2>/dev/null) + + # 使用 ls -t 按时间排序 + IFS=$'\n' sorted_files=($(ls -t "${files[@]}")) + unset IFS + + found_files=${#sorted_files[@]} + info "找到 $found_files 个JSON文件" >&2 + + for info_file in "${sorted_files[@]}"; do + debug "处理文件: $info_file" + + # 文件可读性检查 + if [[ ! -r "$info_file" ]]; then + warning "文件不可读: $info_file" >&2 + continue + fi + + # 文件大小检查 + if [[ ! -s "$info_file" ]]; then + warning "空文件: $info_file" >&2 + continue + fi + + # 解析JSON + if ! jq -e '.' "$info_file" >/dev/null 2>&1; then + warning "无效的JSON文件: $info_file" >&2 + continue + fi + + # 提取字段(带默认值) + local patch_name apply_time backup_path status + + patch_name=$(jq -r '.patch_name // "unknown_patch"' "$info_file" 2>/dev/null) + apply_time=$(jq -r '.apply_time // "unknown_time"' "$info_file" 2>/dev/null) + backup_path=$(jq -r '.backup_path // ""' "$info_file" 2>/dev/null) + status=$(jq -r '.status // "unknown"' "$info_file" 2>/dev/null) + + # 验证必要字段 + if [[ -z "$backup_path" ]]; then + debug "缺少backup_path字段: $info_file" >&2 + continue + fi + + # 验证备份文件 + if [[ ! -f "$backup_path" ]]; then + warning "备份文件不存在: $backup_path (来自 $info_file)" >&2 + continue + fi + + # 验证状态 + if [[ "$status" != "applied" ]]; then + debug "状态不是applied: $status (来自 $info_file)" >&2 + continue + fi + + # 有效的可回滚文件 + debug "有效的可回滚文件: $info_file" >&2 + count=$((count+1)) + valid_files=$((valid_files+1)) + + echo "$info_file" >> "$temp_file" + echo " $count. $patch_name" + echo " 应用时间: $apply_time" + echo " 备份文件: $(basename "$backup_path")" + echo " 状态: $status" + echo " 信息文件: $(basename "$info_file")" + + done + + info "扫描完成: 找到 $found_files 个文件,其中 $valid_files 个可回滚" >&2 + + if [[ $valid_files -eq 0 ]]; then + rm -f "$temp_file" + return 1 fi - # 执行实际回滚 - if execute_file_rollback "$extract_dir"; then - info "✅ 回滚执行完成" + export ROLLBACK_LIST_FILE="$temp_file" + export ROLLBACK_COUNT=$valid_files + return 0 +} + +# 选择回滚点 +select_rollback_point() { + local rollback_id="${1:-}" + + if [[ -z "$rollback_id" ]]; then + info "交互式,请选择要回滚的点:" + # 交互式选择 + local points=() + local count=0 - # 清理回滚点 - cleanup_rollback_point + # 先获取回滚点列表 + local temp_file=$(mktemp) + if ! list_rollback_points > "$temp_file"; then + rm -f "$temp_file" + return 1 + fi - send_rollback_notification "success" "$rollback_path" "回滚成功" - return 0 + # 从临时文件中读取回滚点数组 + if [[ -f "$temp_file" ]]; then + cat "$temp_file" + + # 使用临时文件存储回滚点数量 + local count_file=$(mktemp) + echo "0" > "$count_file" + + # 注意:这里我们重新加载 points 数组 + local temp_points_file=$(mktemp) + while IFS= read -r -d '' info_file; do + if [[ -f "$info_file" ]]; then + local backup_path=$(jq -r '.backup_path // ""' "$info_file" 2>/dev/null) + local status=$(jq -r '.status // "unknown"' "$info_file" 2>/dev/null) + if [[ -n "$backup_path" && -f "$backup_path" && "$status" == "applied" ]]; then + echo "$info_file" >> "$temp_points_file" + local current_count=$(cat "$count_file") + echo $((current_count + 1)) > "$count_file" + fi + fi + done < <(find "$ROLLBACK_INFO_DIR" -name "*.json" -print0 2>/dev/null | sort -z) + + # 读取计数 + count=$(cat "$count_file") + rm -f "$count_file" + + if [[ -f "$temp_points_file" ]]; then + mapfile -t points < "$temp_points_file" + rm -f "$temp_points_file" + fi + rm -f "$temp_file" + fi + + if [[ $count -eq 0 ]]; then + error "无可用的回滚点" + return 1 + fi + + echo "" + read -p "📝 请选择要回滚的补丁编号 (1-${count}): " selected_num + + if [[ ! "$selected_num" =~ ^[0-9]+$ ]] || [[ "$selected_num" -lt 1 ]] || [[ "$selected_num" -gt "$count" ]]; then + error "无效的选择: $selected_num" + return 1 + fi + + rollback_id="${points[$((selected_num-1))]}" else - error "❌ 回滚执行失败" - send_rollback_notification "failure" "$rollback_path" "回滚失败" + # 直接指定回滚点 + if [[ "$rollback_id" == "latest" ]]; then + # 选择最新的回滚点 + rollback_id=$(find "$ROLLBACK_INFO_DIR" -name "*.json" -exec jq -r '.apply_time + "|" + input_filename' {} \; 2>/dev/null | \ + sort -r | head -1 | cut -d'|' -f2) + + if [[ -z "$rollback_id" ]]; then + error "未找到回滚点" + return 1 + fi + elif [[ ! -f "$rollback_id" ]]; then + # 检查是否是文件路径 + if [[ -f "$ROLLBACK_INFO_DIR/$rollback_id" ]]; then + rollback_id="$ROLLBACK_INFO_DIR/$rollback_id" + elif [[ -f "$ROLLBACK_INFO_DIR/${rollback_id}.json" ]]; then + rollback_id="$ROLLBACK_INFO_DIR/${rollback_id}.json" + else + error "回滚点不存在: $rollback_id" + return 1 + fi + fi + fi + + if [[ ! -f "$rollback_id" ]]; then + error "回滚信息文件不存在: $rollback_id" + return 1 + fi + + echo "$rollback_id" + return 0 +} + +# 解析回滚信息 +parse_rollback_info() { + local info_file="$1" + + info "📖 解析回滚信息: $(basename "$info_file")" >&2 + + if [[ ! -f "$info_file" ]]; then + error "回滚信息文件不存在: $info_file" >&2 + return 1 + fi + + # 使用jq解析JSON + local patch_name=$(jq -r '.patch_name // "unknown"' "$info_file" 2>/dev/null) + local apply_time=$(jq -r '.apply_time // "unknown"' "$info_file" 2>/dev/null) + local backup_path=$(jq -r '.backup_path // ""' "$info_file" 2>/dev/null) + local rollback_dir=$(jq -r '.rollback_dir // ""' "$info_file" 2>/dev/null) + local manifest_info=$(jq -r '.manifest_info // ""' "$info_file" 2>/dev/null) + local status=$(jq -r '.status // "unknown"' "$info_file" 2>/dev/null) + + # 验证必要字段 + if [[ -z "$backup_path" ]]; then + error "回滚信息缺少备份路径" >&2 + return 1 + fi + + if [[ ! -f "$backup_path" ]]; then + error "备份文件不存在: $backup_path" >&2 + return 1 + fi + + if [[ "$status" != "applied" ]]; then + warn "回滚点状态为 $status,可能已回滚或无效" >&2 + fi + + # 创建回滚信息结构 + cat << EOF +{ + "info_file": "$info_file", + "patch_name": "$patch_name", + "apply_time": "$apply_time", + "backup_path": "$backup_path", + "rollback_dir": "$rollback_dir", + "manifest_info": "$manifest_info", + "status": "$status" +} +EOF + + return 0 +} + +# 验证备份文件 +verify_backup_file() { + local backup_file="$1" + + info "🔍 验证备份文件: $(basename "$backup_file")" + + if [[ ! -f "$backup_file" ]]; then + error "备份文件不存在: $backup_file" + return 1 + fi + + # 检查文件大小 + local size=$(stat -c%s "$backup_file" 2>/dev/null || echo "0") + if [[ $size -eq 0 ]]; then + error "备份文件为空: $backup_file" + return 1 + fi + + # 验证压缩包完整性 + if ! tar -tzf "$backup_file" >/dev/null 2>&1; then + error "备份文件损坏或格式错误: $backup_file" + return 1 + fi + + info "✅ 备份文件验证通过" + return 0 +} + +# 提取备份文件 +extract_backup() { + local backup_file="$1" + local extract_dir="$2" + + info "📦 提取备份文件到: $extract_dir" + + if [[ ! -d "$extract_dir" ]]; then + mkdir -p "$extract_dir" + fi + + if tar -xzf "$backup_file" -C "$extract_dir"; then + info "✅ 备份文件提取完成" + + # 验证提取内容 + local file_count=$(find "$extract_dir" -type f | wc -l) + if [[ $file_count -gt 0 ]]; then + info "找到 $file_count 个备份文件" + return 0 + else + warn "⚠️ 备份文件中未找到文件" + return 0 + fi + else + error "❌ 备份文件提取失败" return 1 fi } -execute_file_rollback() { +# 执行回滚操作 +perform_rollback() { local extract_dir="$1" + local dry_run="${2:-false}" - info "开始执行文件回滚..." + info "🔄 开始执行回滚操作 (干跑模式: $dry_run)" + + if [[ ! -d "$extract_dir" ]]; then + error "提取目录不存在: $extract_dir" + return 1 + fi local rollback_count=0 local error_count=0 + local skip_count=0 - # 遍历回滚包中的所有文件 + # 查找所有备份文件 find "$extract_dir" -type f | while read backup_file; do local relative_path="${backup_file#$extract_dir/}" - local target_file="/$relative_path" + local target_file="$relative_path" local target_dir=$(dirname "$target_file") + if [[ "$dry_run" == "true" ]]; then + info "📋 [干跑] 回滚文件: $relative_path → $target_file" >&2 + rollback_count=$((rollback_count+1)) + continue + fi + # 创建目标目录 - mkdir -p "$target_dir" + if [[ ! -d "$target_dir" ]]; then + if ! mkdir -p "$target_dir"; then + error "❌ 创建目录失败: $target_dir" >&2 + error_count=$((error_count+1)) + continue + fi + debug "创建目录: $target_dir" + fi # 备份当前文件(如果存在) if [[ -f "$target_file" ]]; then local current_backup="$TEMP_DIR/current_backup/$relative_path" - mkdir -p "$(dirname "$current_backup")" - cp -p "$target_file" "$current_backup" - debug "备份当前文件: $target_file" + local current_dir=$(dirname "$current_backup") + mkdir -p "$current_dir" + + if ! cp -p "$target_file" "$current_backup"; then + warn "⚠️ 当前文件备份失败: $target_file" >&2 + fi fi # 恢复文件 if cp -p "$backup_file" "$target_file"; then - info "✅ 回滚文件: $relative_path" + info "✅ 回滚文件: $relative_path | $backup_file -> $target_file" >&2 ((rollback_count++)) else - error "❌ 回滚失败: $relative_path" - ((error_count++)) + error "❌ 文件回滚失败: $relative_path" >&2 + error_count=$((error_count+1)) fi done - info "回滚完成: $rollback_count 个文件成功, $error_count 个文件失败" + info "📊 回滚操作统计:" + info " 成功: $rollback_count" + info " 失败: $error_count" + info " 跳过: $skip_count" if [[ $error_count -gt 0 ]]; then + error "❌ 回滚操作存在失败项" + return 1 + fi + + if [[ "$dry_run" == "true" ]]; then + info "✅ 干跑模式完成 - 可安全执行回滚" + return 0 + else + info "✅ 回滚操作完成" + return 0 + fi +} + +# 验证回滚结果 +verify_rollback() { + local extract_dir="$1" + local rollback_info="$2" + + if [[ "$VALIDATION_ENABLED" != "true" ]]; then + info "验证功能已禁用" + return 0 + fi + + info "🔍 验证回滚结果..." + + local verification_passed=true + local verified_count=0 + local failed_count=0 + + # 检查所有备份文件是否已正确恢复 + find "$extract_dir" -type f | while read backup_file; do + local relative_path="${backup_file#$extract_dir/}" + local target_file="$relative_path" + + if [[ ! -f "$target_file" ]]; then + error "❌ 目标文件不存在: $target_file" + verification_passed=false + fail_count=$((fail_count+1)) + continue + fi + + # 比较文件内容 + if ! cmp -s "$backup_file" "$target_file"; then + error "❌ 文件内容不匹配: $relative_path" + verification_passed=false + fail_count=$((fail_count+1)) + continue + fi + + verified_count=$((verified_count+1)) + done + + info "📊 回滚验证统计:" + info " 成功: $verified_count" + info " 失败: $failed_count" + + if $verification_passed; then + info "✅ 回滚验证通过" + return 0 + else + error "❌ 回滚验证失败" + return 1 + fi +} + +# 更新回滚点状态 +update_rollback_status() { + local info_file="$1" + local new_status="$2" + + if [[ ! -f "$info_file" ]]; then + error "回滚信息文件不存在: $info_file" + return 1 + fi + + # 使用jq更新状态 + if jq ".status = \"$new_status\" | .rollback_time = \"$(date -Iseconds)\"" "$info_file" > "$info_file.tmp"; then + mv "$info_file.tmp" "$info_file" + info "✅ 回滚点状态更新为: $new_status" + return 0 + else + error "❌ 回滚点状态更新失败" return 1 fi - return 0 } # 清理回滚点 cleanup_rollback_point() { - if [[ -f "$ROLLBACK_INFO_FILE" ]]; then - mv "$ROLLBACK_INFO_FILE" "$ROLLBACK_INFO_FILE.bak" - info "回滚点已清理: $ROLLBACK_INFO_FILE -> $ROLLBACK_INFO_FILE.bak" + local info_file="$1" + local extract_dir="$2" + + info "🧹 清理回滚资源..." + + # 删除提取目录 + if [[ -d "$extract_dir" ]]; then + rm -rf "$extract_dir" + debug "清理提取目录: $extract_dir" fi + + # 可选:删除备份文件(谨慎操作) + local delete_backup="${3:-false}" + if [[ "$delete_backup" == "true" ]]; then + local backup_path=$(jq -r '.backup_path // ""' "$info_file") + if [[ -n "$backup_path" && -f "$backup_path" ]]; then + rm -f "$backup_path" + info "🗑️ 删除备份文件: $(basename "$backup_path")" + fi + fi + + # 删除回滚信息文件 + if [[ -f "$info_file" ]]; then + rm -f "$info_file" + info "🗑️ 删除回滚信息文件: $(basename "$info_file")" + fi + + info "✅ 回滚资源清理完成" } -# 通知函数 +# 发送通知 send_rollback_notification() { local status="$1" - local rollback_path="$2" + local rollback_info="$2" local message="$3" if [[ "$NOTIFICATIONS_ENABLED" != "true" ]]; then return 0 fi + local patch_name=$(echo "$rollback_info" | jq -r '.patch_name') + local apply_time=$(echo "$rollback_info" | jq -r '.apply_time') + + local subject="" + local color="" + case "$status" in + "start") + 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_rollback_notification "$subject" "$message" "$color" "$rollback_path" + if [[ -n "${SLACK_WEBHOOK:-}" ]]; then + send_slack_rollback_notification "$subject" "$message" "$color" "$patch_name" "$apply_time" fi + + log "NOTIFICATION" "$subject: $message" } send_slack_rollback_notification() { local subject="$1" local message="$2" local color="$3" - local rollback_path="$4" + local patch_name="$4" + local apply_time="$5" local payload=$(cat << EOF { @@ -261,8 +711,13 @@ send_slack_rollback_notification() { "text": "$message", "fields": [ { - "title": "回滚包", - "value": "$(basename "$rollback_path")", + "title": "补丁名称", + "value": "$patch_name", + "short": true + }, + { + "title": "应用时间", + "value": "$apply_time", "short": true }, { @@ -283,74 +738,348 @@ 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 } # 主回滚函数 rollback_patch() { - local rollback_path="${1:-}" + local rollback_point="${1:-}" local dry_run="${2:-false}" + local cleanup_after="${3:-false}" - # 如果没有指定回滚包,使用最近的 - if [[ -z "$rollback_path" ]]; then - info "未指定回滚包,查找最近的回滚点..." - if get_available_rollback_points; then - if [[ -f "$ROLLBACK_INFO_FILE" ]]; then - rollback_path=$(jq -r '.backup_path' "$ROLLBACK_INFO_FILE") - info "使用回滚包: $rollback_path" - else - error "未找到可用的回滚点" - return 1 - fi - else - error "无法获取回滚点信息" + info "🚀 开始回滚操作, 回滚点: ${rollback_point}" + + # 发送开始通知 + local rollback_info_json="" + local rollback_info_file="" + + # 选择回滚点 + info "定位到回滚点 ${rollback_point}" + rollback_info_file=$(select_rollback_point "$rollback_point") + if [[ $? -ne 0 ]]; then + send_rollback_notification "failure" "{}" "回滚点选择失败" + return 1 + fi + + # 解析回滚信息 + info "解析回滚信息 ${rollback_info_file}" + rollback_info_json=$(parse_rollback_info "$rollback_info_file") + if [[ $? -ne 0 ]]; then + send_rollback_notification "failure" "{}" "回滚信息解析失败" + return 1 + fi + + local patch_name=$(echo "$rollback_info_json" | jq -r '.patch_name') + local backup_path=$(echo "$rollback_info_json" | jq -r '.backup_path') + + send_rollback_notification "start" "$rollback_info_json" "开始回滚补丁: $patch_name" + + # 验证备份文件 + if ! verify_backup_file "$backup_path"; then + send_rollback_notification "failure" "$rollback_info_json" "备份文件验证失败" + return 1 + fi + + # 创建临时目录 + TEMP_DIR=$(mktemp -d "/tmp/patch_rollback_XXXXXX") + local extract_dir="$TEMP_DIR/extract" + + # 提取备份文件 + if ! extract_backup "$backup_path" "$extract_dir"; then + send_rollback_notification "failure" "$rollback_info_json" "备份文件提取失败" + return 1 + fi + + # 执行回滚操作 + if ! perform_rollback "$extract_dir" "$dry_run"; then + send_rollback_notification "failure" "$rollback_info_json" "回滚操作失败" + return 1 + fi + + # 验证回滚结果(非干跑模式) + if [[ "$dry_run" != "true" ]]; then + if ! verify_rollback "$extract_dir" "$rollback_info_json"; then + send_rollback_notification "failure" "$rollback_info_json" "回滚验证失败" return 1 fi + + # 更新回滚点状态 + if ! update_rollback_status "$rollback_info_file" "rolled_back"; then + warn "⚠️ 回滚点状态更新失败" + fi + + # 清理资源 + if [[ "$cleanup_after" == "true" ]]; then + cleanup_rollback_point "$rollback_info_file" "$extract_dir" "true" + else + cleanup_rollback_point "$rollback_info_file" "$extract_dir" "false" + fi fi - # 执行回滚 - if perform_rollback "$rollback_path" "$dry_run"; then - info "🎉 回滚操作成功完成" + send_rollback_notification "success" "$rollback_info_json" "回滚操作成功完成" + info "✅ 回滚流程完成" + return 0 +} + +# 批量清理旧回滚点 +cleanup_old_rollback_points() { + local max_points="${1:-$MAX_ROLLBACK_POINTS}" + + info "🧹 清理旧回滚点 (保留最近 $max_points 个)" + + if [[ ! -d "$ROLLBACK_INFO_DIR" ]]; then + warn "回滚信息目录不存在" return 0 - else - error "💥 回滚操作失败" - return 1 fi + + # 获取所有回滚点并按时间排序 + local points=() + while IFS= read -r -d '' info_file; do + if [[ -f "$info_file" ]]; then + points+=("$info_file") + fi + done < <(find "$ROLLBACK_INFO_DIR" -name "*.json" -print0 2>/dev/null) + + local total_points=${#points[@]} + local points_to_remove=$((total_points - max_points)) + + if [[ $points_to_remove -le 0 ]]; then + info "无需清理,当前 $total_points 个回滚点" + return 0 + fi + + info "需要清理 $points_to_remove 个回滚点" + + # 按修改时间排序,删除最旧的 + for ((i=0; i 回滚点编号(使用-l查看) + <文件名> 回滚信息文件路径 + <补丁名> 补丁名称 + +示例: + $0 -l # 列出所有回滚点 + $0 latest # 回滚最新的补丁 + $0 1 # 回滚编号为1的补丁 + $0 --dry-run latest # 干跑模式测试回滚 + $0 --cleanup latest # 回滚并清理资源 + $0 --purge # 清理旧回滚点 + +环境变量: + PATCH_CONFIG 配置文件路径 + BACKUP_DIR 备份目录 + MAX_ROLLBACK_POINTS 最大保留回滚点数 +EOF +} + +# 显示版本信息 +show_version() { + echo "patch_rollback.sh v1.0.0" + echo "企业级补丁回滚系统" + echo "支持原子性回滚、多回滚点管理、验证等功能" } # 主函数 main() { - local rollback_path="${1:-}" - local dry_run="${2:-false}" + local rollback_point="" + local dry_run="false" + local cleanup_after="false" + local list_only="false" + local purge_all="false" + local rollback_all="false" + local verbose_mode="false" - # 加载配置 + # 解析命令行参数 + while [[ $# -gt 0 ]]; do + case $1 in + -l|--list) + list_only="true" + shift + ;; + -d|--dry-run) + dry_run="true" + shift + ;; + -c|--cleanup) + cleanup_after="true" + shift + ;; + -p|--purge) + purge_all="true" + shift + ;; + -a|--all) + rollback_all="true" + shift + ;; + -v|--verbose) + verbose_mode="true" + shift + ;; + -q|--quiet) + exec >/dev/null 2>&1 + shift + ;; + -h|--help) + usage + exit 0 + ;; + -V|--version) + show_version + exit 0 + ;; + --) + shift + break + ;; + -*) + error "未知选项: $1" + usage + exit 1 + ;; + *) + rollback_point="$1" + shift + ;; + esac + done + + # 初始化 + acquire_lock load_config - - # 设置环境 setup_environment + check_dependencies - # 显示使用信息 - if [[ "$1" == "-h" ]] || [[ "$1" == "--help" ]]; then - echo "用法: $0 [回滚包路径] [dry-run]" - echo "示例:" - echo " $0 # 使用最近的回滚点" - echo " $0 /var/backups/patch/backup_xxx.tar.gz # 指定回滚包" - echo " $0 /path/to/backup.tar.gz dry-run # 干跑模式" - exit 0 + # 处理特殊操作 + if [[ "$list_only" == "true" ]]; then + list_rollback_points + exit $? fi - # 执行回滚 - if rollback_patch "$rollback_path" "$dry_run"; then + if [[ "$purge_all" == "true" ]]; then + cleanup_old_rollback_points 0 + exit $? + fi + + if [[ "$rollback_all" == "true" ]]; then + rollback_all_patches "$dry_run" "$cleanup_after" + exit $? + fi + + # 执行回滚操作 + if rollback_patch "$rollback_point" "$dry_run" "$cleanup_after"; then + if [[ "$dry_run" == "true" ]]; then + info "🎉 干跑模式完成 - 可安全执行回滚" + else + info "🎉 回滚操作成功完成" + fi exit 0 else + error "💥 回滚操作失败" exit 1 fi } -# 异常处理 -trap 'error "脚本执行中断"; cleanup; exit 1' INT TERM +# 批量回滚所有补丁 +rollback_all_patches() { + local dry_run="$1" + local cleanup_after="$2" + + info "🔄 开始批量回滚所有补丁" + + if ! list_rollback_points; then + error "没有可用的回滚点" + return 1 + fi + + local total_count=${#points[@]} + local success_count=0 + local fail_count=0 + + info "📊 找到 $total_count 个回滚点,开始批量回滚..." + + # 按时间倒序回滚(最新的先回滚) + for ((i=total_count-1; i>=0; i--)); do + local info_file="${points[$i]}" + local patch_name=$(basename "$info_file" .json) + + info "🔄 回滚 ($((total_count-i))/$total_count): $patch_name" + + if rollback_patch "$info_file" "$dry_run" "$cleanup_after"; then + success_count=$((success_count+1)) + info "✅ 回滚成功: $patch_name" + else + fail_count=$((fail_count+1)) + error "❌ 回滚失败: $patch_name" + # 继续尝试下一个 + fi + + echo "---" + done + + info "📊 批量回滚完成:" + info " 成功: $success_count" + info " 失败: $fail_count" + info " 总计: $total_count" + + if [[ $fail_count -eq 0 ]]; then + info "✅ 所有回滚操作成功" + return 0 + else + error "⚠️ 部分回滚操作失败" + return 1 + fi +} +# 异常处理 +trap 'error "脚本执行中断"; exit 1' INT TERM + +# 执行主函数 main "$@" \ No newline at end of file diff --git a/scripts/patch_tools/tmp_0/cache.php b/scripts/patch_tools/tmp_0/cache.php new file mode 100644 index 000000000..6fd5e2d58 --- /dev/null +++ b/scripts/patch_tools/tmp_0/cache.php @@ -0,0 +1,41 @@ + Env::get('cache.driver', 'file'), + // 缓存连接方式配置 + 'stores' => [ + 'file' => [ + // 驱动方式 + 'type' => 'File', + // 缓存保存目录 + 'path' => '', + // 缓存前缀 + 'prefix' => '', + // 缓存有效期 0表示永久缓存 + 'expire' => 0, + // 缓存标签前缀 + 'tag_prefix' => 'tag:', + // 序列化机制 例如 ['serialize', 'unserialize'] + 'serialize' => [], + ], + // redis缓存 + 'redis' => [ + // 驱动方式 + 'type' => 'redis', + // 服务器地址 + 'host' => '127.0.0.1', + // redis密码 + 'password' => 'luckyshop123!@#', + // 缓存有效期 0表示永久缓存 + 'expire' => 604800, + ], + // 更多的缓存连接 + ], +];