chore: 针对备份及还原处理

This commit is contained in:
2025-11-15 16:15:58 +08:00
parent cbc4529a34
commit c0252a2dd2
21 changed files with 4618 additions and 44 deletions

View File

@@ -0,0 +1,16 @@
# TODO: 创建一个基于alpine的docker镜像用于运行patch工具
# 镜像名称patch-tools-alpine
# 基础镜像alpine:3.18
# 安装依赖bash, tar, gzip, zip, diffutils, patch
# 复制脚本patch_config.sh, patch.sh
# 入口点:/bin/bash /patch.sh
services:
patch-tools:
image: ubuntu/squid:latest
container_name: patch-tools-alpine
volumes:
- ./:/working_dir/scripts
- ../patch-dir:/working_dir/patchs
- ../../ftp-src/app:/working_dir/old-shop-src
- ../../src/app:/working_dir/new-shop-src

View File

@@ -0,0 +1,223 @@
#!/bin/bash
# install_patch_system.sh - 补丁管理系统安装脚本
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
INSTALL_DIR="/opt/patch-management"
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"; }
info() { log "INFO: $1"; }
error() { log "ERROR: $1"; exit 1; }
warn() { log "WARNING: $1"; }
# 检测是否在Docker容器中运行
is_docker_environment() {
# 检查多种Docker环境标识
if [[ -f /.dockerenv ]]; then
return 0
fi
if grep -q docker /proc/1/cgroup 2>/dev/null; then
return 0
fi
if grep -q lxc /proc/1/cgroup 2>/dev/null; then
return 0
fi
if [[ -n "${container:-}" ]]; then
return 0
fi
if [[ -n "${DOCKER_CONTAINER:-}" ]]; then
return 0
fi
# 检查容器相关的环境变量组合
local env_vars=(
"HOSTNAME"
"HOME"
"USER"
)
for var in "${env_vars[@]}"; do
if [[ -n "${!var:-}" ]] && [[ "${!var}" =~ ^[a-f0-9]{12,}$ ]]; then
# 检查环境变量值是否像容器ID
if [[ ${!var} =~ ^[a-f0-9]{12,64}$ ]]; then
return 0
fi
fi
done
return 1
}
# 获取适当的命令前缀在Docker中使用空前缀否则使用sudo
get_cmd_prefix() {
if is_docker_environment; then
echo ""
else
echo "sudo"
fi
}
install_dependencies() {
info "安装系统依赖..."
local sudo_prefix
sudo_prefix=$(get_cmd_prefix)
if command -v apt-get >/dev/null 2>&1; then
# Debian/Ubuntu
$sudo_prefix apt-get update
$sudo_prefix apt-get install -y tar gzip jq coreutils findutils util-linux bc
elif command -v yum >/dev/null 2>&1; then
# CentOS/RHEL
$sudo_prefix yum install -y tar gzip jq coreutils findutils util-linux bc
else
warn "无法自动安装依赖,请手动安装: tar, gzip, jq, coreutils, findutils, util-linux, bc"
fi
# 安装GPG用于签名验证
if command -v apt-get >/dev/null 2>&1; then
$sudo_prefix apt-get install -y gnupg
elif command -v yum >/dev/null 2>&1; then
$sudo_prefix yum install -y gnupg
fi
}
create_directories() {
info "创建目录结构..."
local sudo_prefix
sudo_prefix=$(get_cmd_prefix)
$sudo_prefix mkdir -p "$INSTALL_DIR"
$sudo_prefix mkdir -p "/var/backups/patch"
$sudo_prefix mkdir -p "/var/log/patch_system"
$sudo_prefix mkdir -p "/etc/patch/keys"
# 设置权限
$sudo_prefix chmod 755 "$INSTALL_DIR"
$sudo_prefix chmod 755 "/var/backups/patch"
$sudo_prefix chmod 755 "/var/log/patch_system"
$sudo_prefix chmod 700 "/etc/patch/keys"
}
install_scripts() {
info "安装脚本文件..."
local sudo_prefix
sudo_prefix=$(get_cmd_prefix)
# 复制脚本文件, 存在则覆盖
$sudo_prefix cp --force "$SCRIPT_DIR/patch_generator.sh" "$INSTALL_DIR/"
$sudo_prefix cp --force "$SCRIPT_DIR/patch_fnc.sh" "$INSTALL_DIR/"
$sudo_prefix cp --force "$SCRIPT_DIR/patch_applier.sh" "$INSTALL_DIR/"
$sudo_prefix cp --force "$SCRIPT_DIR/patch_rollback.sh" "$INSTALL_DIR/"
$sudo_prefix cp --force "$SCRIPT_DIR/patch_verifier.sh" "$INSTALL_DIR/"
$sudo_prefix cp --force "$SCRIPT_DIR/patch_workflow.sh" "$INSTALL_DIR/"
$sudo_prefix cp --force "$SCRIPT_DIR/patch_config.sh" "$INSTALL_DIR/"
info "脚本文件已安装在: $INSTALL_DIR/"
# 设置执行权限
$sudo_prefix chmod +x "$INSTALL_DIR"/*.sh
# 创建符号链接
$sudo_prefix ln -sf "$INSTALL_DIR/patch_workflow.sh" "/usr/local/bin/patch-mgmt"
info "符号链接已创建: /usr/local/bin/patch-mgmt"
}
setup_cron() {
info "设置定时任务..."
if is_docker_environment; then
info "Docker环境跳过系统定时任务设置"
info "如需定时任务请考虑使用宿主机的crontab或Docker运行参数"
return 0
fi
local sudo_prefix
sudo_prefix=$(get_cmd_prefix)
local cron_job="0 2 * * * $INSTALL_DIR/patch_verifier.sh /opt/patches batch > /var/log/patch_system/batch_verify.log 2>&1"
if ! crontab -l 2>/dev/null | grep -q "patch_verifier.sh"; then
(crontab -l 2>/dev/null; echo "$cron_job") | crontab -
info "定时任务已添加"
else
info "定时任务已存在"
fi
}
generate_keys() {
info "生成签名密钥..."
local key_dir="/etc/patch/keys"
local sudo_prefix
sudo_prefix=$(get_cmd_prefix)
if [[ ! -f "$key_dir/private.pem" ]]; then
$sudo_prefix mkdir -p "$key_dir"
# 生成RSA密钥对
openssl genrsa -out "$key_dir/private.pem" 4096
openssl rsa -in "$key_dir/private.pem" -pubout -out "$key_dir/public.pem"
$sudo_prefix chmod 600 "$key_dir/private.pem"
$sudo_prefix chmod 644 "$key_dir/public.pem"
info "密钥对已生成: $key_dir/"
else
info "密钥对已存在"
fi
}
main() {
info "开始安装企业级补丁管理系统"
echo "========================================"
# 检查运行环境
if is_docker_environment; then
info "检测到Docker容器环境将以root权限执行不适用sudo"
SUDO_CMD=""
else
info "检测到主机环境将使用sudo执行管理员操作"
SUDO_CMD="sudo"
# 在非Docker环境中建议非root用户运行
if [[ $EUID -eq 0 ]]; then
warn "检测到以root用户运行建议在非Docker环境中使用具备sudo权限的普通用户"
fi
fi
# 安装依赖
install_dependencies
# 创建目录
create_directories
# 安装脚本
install_scripts
# 设置定时任务
setup_cron
# 生成密钥
generate_keys
info "🎉 补丁管理系统安装完成!"
echo ""
echo "📁 安装目录: $INSTALL_DIR"
echo "🔧 使用命令: patch-mgmt"
echo "📋 配置文件: $INSTALL_DIR/patch_config.sh"
echo ""
echo "💡 下一步操作:"
echo " 1. 编辑配置文件: $INSTALL_DIR/patch_config.sh"
echo " 2. 测试系统: patch-mgmt --help"
echo " 3. 配置通知: 修改SLACK_WEBHOOK等设置"
}
main "$@"

View File

@@ -0,0 +1,587 @@
#!/bin/bash
# patch_applier.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_apply_$(date +%Y%m%d_%H%M%S).log"}
: ${BACKUP_DIR:="/var/backups/patch"}
: ${TEMP_DIR:="/tmp/patch_apply_$$"}
}
setup_environment() {
mkdir -p "$(dirname "$LOG_FILE")"
mkdir -p "$BACKUP_DIR"
mkdir -p "$TEMP_DIR"
info "日志文件: $LOG_FILE"
info "备份目录: $BACKUP_DIR"
info "临时目录: $TEMP_DIR"
}
cleanup() {
if [[ -d "$TEMP_DIR" ]]; then
rm -rf "$TEMP_DIR"
info "临时目录已清理: $TEMP_DIR"
fi
}
trap cleanup EXIT
# 验证函数
verify_package() {
local package_path="$1"
info "开始验证补丁包: $package_path"
# 检查文件存在性
if [[ ! -f "$package_path" ]]; then
error "补丁包不存在: $package_path"
return 1
fi
# 检查文件大小
local size=$(stat -c%s "$package_path" 2>/dev/null || echo "0")
if [[ $size -eq 0 ]]; then
error "补丁包为空: $package_path"
return 1
fi
# 验证校验和
if [[ -f "${package_path}.sha256" ]]; then
info "验证校验和..."
if sha256sum -c "${package_path}.sha256" >/dev/null 2>&1; then
info "✅ 校验和验证通过"
else
error "❌ 校验和验证失败"
return 1
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
else
warn "GPG未安装跳过签名验证"
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"
local extract_dir="$2"
info "解压补丁包到: $extract_dir"
mkdir -p "$extract_dir"
if tar -xzf "$package_path" -C "$extract_dir"; then
info "✅ 补丁包解压完成"
return 0
else
error "❌ 补丁包解压失败"
return 1
fi
}
apply_file_changes() {
local patch_dir="$1"
info "开始应用文件变更..."
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
# 处理每个变更
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
done < <(grep -E "^(ADDED|MODIFIED|DELETED)" "$manifest_file")
info "文件变更应用完成: $applied_count 成功, $failed_count 失败"
if [[ $failed_count -gt 0 ]]; then
return 1
fi
return 0
}
apply_file_addition_modification() {
local patch_dir="$1"
local file_path="$2"
local change_type="$3"
local source_file="$patch_dir/files/$file_path"
local target_file="/$file_path" # 绝对路径
local target_dir=$(dirname "$target_file")
# 检查源文件是否存在
if [[ ! -f "$source_file" ]]; then
error "源文件不存在: $source_file"
return 1
fi
# 创建目标目录
if [[ ! -d "$target_dir" ]]; then
mkdir -p "$target_dir"
debug "创建目录: $target_dir"
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"
fi
# 复制文件
if cp -p "$source_file" "$target_file"; then
# 设置权限(从清单中读取)
set_file_permissions "$target_file" "$change_type"
info "✅ 应用文件: $file_path ($change_type)"
return 0
else
error "❌ 文件应用失败: $file_path"
return 1
fi
}
apply_file_deletion() {
local file_path="$1"
local target_file="/$file_path"
if [[ ! -f "$target_file" ]]; then
warn "文件不存在,无需删除: $target_file"
return 0
fi
# 备份文件
local backup_file="$TEMP_DIR/backup/$file_path"
mkdir -p "$(dirname "$backup_file")"
cp -p "$target_file" "$backup_file"
# 删除文件
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
return 0
else
error "❌ 文件删除失败: $file_path"
return 1
fi
}
set_file_permissions() {
local file_path="$1"
local change_type="$2"
# 根据文件类型设置权限
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")"
}
# 验证应用结果
verify_application() {
local patch_dir="$1"
info "开始验证应用结果..."
local manifest_file="$patch_dir/MANIFEST.MF"
local verification_passed=true
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
verification_passed=false
fi
;;
"DELETED")
if ! verify_file_deletion "$path"; then
verification_passed=false
fi
;;
esac
done < <(grep -E "^(ADDED|MODIFIED|DELETED)" "$manifest_file")
if $verification_passed; then
info "✅ 应用验证通过"
return 0
else
error "❌ 应用验证失败"
return 1
fi
}
verify_file_application() {
local file_path="$1"
local change_type="$2"
local patch_dir="$3"
local target_file="/$file_path"
local source_file="$patch_dir/files/$file_path"
# 检查文件是否存在
if [[ ! -f "$target_file" ]]; then
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"
return 1
fi
debug "✅ 文件验证通过: $file_path"
return 0
}
verify_file_deletion() {
local file_path="$1"
local target_file="/$file_path"
if [[ -f "$target_file" ]]; then
error "❌ 文件未成功删除: $target_file"
return 1
fi
debug "✅ 文件删除验证通过: $file_path"
return 0
}
# 回滚准备
create_rollback_point() {
local patch_path="$1"
local backup_path="$2"
local rollback_info_file="$BACKUP_DIR/rollback_info.json"
local patch_name=$(basename "$patch_path")
local timestamp=$(date +%Y%m%d_%H%M%S)
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",
"status": "applied"
}
EOF
)
echo "$rollback_info" > "$rollback_info_file"
info "回滚点创建完成: $rollback_info_file"
}
# 通知函数
send_application_notification() {
local status="$1"
local patch_path="$2"
local message="$3"
if [[ "$NOTIFICATIONS_ENABLED" != "true" ]]; then
return 0
fi
case "$status" in
"start")
local subject="补丁应用开始"
local color="#3498db"
;;
"success")
local subject="补丁应用成功"
local color="#2ecc71"
;;
"failure")
local subject="补丁应用失败"
local color="#e74c3c"
;;
*)
local subject="补丁应用通知"
local color="#95a5a6"
;;
esac
# Slack通知
if [[ -n "$SLACK_WEBHOOK" ]]; then
send_slack_notification "$subject" "$message" "$color"
fi
}
send_slack_notification() {
local subject="$1"
local message="$2"
local color="$3"
local payload=$(cat << EOF
{
"attachments": [
{
"color": "$color",
"title": "$subject",
"text": "$message",
"fields": [
{
"title": "补丁文件",
"value": "$(basename "$PATCH_PATH")",
"short": true
},
{
"title": "应用时间",
"value": "$(date)",
"short": true
},
{
"title": "应用主机",
"value": "$(hostname)",
"short": true
}
]
}
]
}
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通知发送成功"
fi
}
# 主应用函数
apply_patch() {
local patch_path="$1"
local dry_run="${2:-false}"
info "开始应用补丁包: $patch_path"
send_application_notification "start" "$patch_path" "开始应用补丁"
# 验证补丁包
if ! verify_package "$patch_path"; then
error "补丁包验证失败"
send_application_notification "failure" "$patch_path" "补丁包验证失败"
return 1
fi
# 解压补丁包
local extract_dir="$TEMP_DIR/extract"
if ! extract_package "$patch_path" "$extract_dir"; then
send_application_notification "failure" "$patch_path" "补丁包解压失败"
return 1
fi
# 如果是干跑模式,只验证不应用
if [[ "$dry_run" == "true" ]]; then
info "干跑模式: 只验证不应用"
verify_application "$extract_dir"
return 0
fi
# 创建备份
local backup_path=$(create_backup)
# 应用变更
if ! apply_file_changes "$extract_dir"; then
error "文件变更应用失败"
send_application_notification "failure" "$patch_path" "文件应用失败"
return 1
fi
# 验证应用结果
if ! verify_application "$extract_dir"; then
error "应用验证失败"
send_application_notification "failure" "$patch_path" "应用验证失败"
return 1
fi
# 创建回滚点
create_rollback_point "$patch_path" "$backup_path"
info "✅ 补丁应用完成"
send_application_notification "success" "$patch_path" "补丁应用成功"
return 0
}
# 主函数
main() {
local patch_path="${1:-}"
local dry_run="${2:-false}"
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"
exit 1
fi
# 加载配置
load_config
# 设置环境
setup_environment
# 应用补丁
if apply_patch "$patch_path" "$dry_run"; then
info "🎉 补丁应用成功完成"
exit 0
else
error "💥 补丁应用失败"
exit 1
fi
}
# 异常处理
trap 'error "脚本执行中断"; cleanup; exit 1' INT TERM
main "$@"

View File

@@ -0,0 +1,177 @@
#!/bin/bash
# patch_config.sh - 企业级补丁配置
# ==============================================================================
# 基础配置 - 定义补丁的基本信息
# ==============================================================================
# 基础配置
PATCH_NAME="security-hotfix-2025"
PATCH_VERSION="1.0.0"
PATCH_DESCRIPTION="紧急安全漏洞修复"
PATCH_AUTHOR="企业DevOps团队"
PATCH_EMAIL="devops@aigc-quickapp.com"
# ==============================================================================
# 文件筛选配置 - 定义哪些文件需要被包含或排除
# ==============================================================================
## 包含的文件模式
INCLUDE_PATTERNS=(
".+/.*" # 匹配所有子目录下的文件
)
## 排除的文件模式
EXCLUDE_PATTERNS=(
"*.log"
"*.tmp"
"*.bak"
"*.swp"
"*.swx"
"^.github/"
"/node_modules/*"
"/__pycache__/*"
# 排除以下一级文件夹
"^.git/"
"^cache/"
"^temp/"
"^tmp/"
"^logs/"
"^runtime/"
"^uploads/"
"^attachment/"
"^h5/"
"^hwapp/"
".DS_Store"
"Thumbs.db"
)
# ==============================================================================
# 文件大小限制 - 定义补丁处理的文件大小范围
# ==============================================================================
# 文件大小限制
MAX_FILE_SIZE="20MB" # 最大文件大小
MIN_FILE_SIZE="0KB" # 最小文件大小
# ==============================================================================
# 时间过滤 - 定义补丁处理的文件时间范围
# ==============================================================================
# 时间过滤
MODIFIED_AFTER="2000-01-01T00:00:00Z" # 仅包含修改时间在该时间之后的文件
CREATED_AFTER="2000-01-01T00:00:00Z" # 仅包含创建时间在该时间之后的文件
# ==============================================================================
# 比较方法配置 - 定义补丁处理的比较方式
# ==============================================================================
# 比较方法配置
# 比较内容和时间
COMPARISON_METHOD="content" # 比较方法: content, time, both
HASH_ALGORITHM="sha256" # 哈希算法,用于文件内容比较
TIME_PRECISION="second" # 时间精度,用于文件时间比较
COMPARE_PERMISSIONS=false # 是否比较文件权限
COMPARE_OWNERSHIP=false # 是否比较文件所有者
# ==============================================================================
# 增量配置 - 定义是否启用增量补丁
# ==============================================================================
# 增量配置
INCREMENTAL_ENABLED=true # 是否启用增量补丁
BASE_VERSION="1.0.0" # 基础版本,用于增量比较
DETECT_RENAMES=true # 是否检测文件重命名
BINARY_DIFF=true # 是否启用二进制差异比较
CHUNK_SIZE="8KB" # 二进制差异比较的块大小
# ==============================================================================
# 压缩配置 - 定义补丁压缩方式和级别
# ==============================================================================
# 压缩配置
COMPRESSION_ENABLED=true # 是否启用压缩
COMPRESSION_ALGORITHM="gzip" # gzip, bzip2, xz
COMPRESSION_LEVEL=6 # 压缩级别1-9
PER_FILE_OPTIMIZATION=true # 是否对每个文件单独压缩
# ==============================================================================
# 安全配置 - 定义补丁签名和加密方式
# ==============================================================================
# 安全配置
SIGNING_ENABLED=true # 是否启用签名
SIGNING_ALGORITHM="rsa" # 签名算法rsa, ecdsa
PRIVATE_KEY="/etc/patch/keys/private.pem" # 私钥文件路径
PUBLIC_KEY="/etc/patch/keys/public.pem" # 公钥文件路径
ENCRYPTION_ENABLED=false # 是否启用加密
ENCRYPTION_ALGORITHM="aes-256-gcm" # 加密算法aes-256-gcm, aes-256-cbc
ENCRYPTION_KEY="/etc/patch/keys/encryption.key" # 加密密钥文件路径
ENCRYPTION_IV="/etc/patch/keys/encryption.iv" # 加密初始化向量文件路径
# ==============================================================================
# 备份配置 - 定义是否启用备份和备份策略
# ==============================================================================
# 备份配置
BACKUP_ENABLED=true # 是否启用备份
BACKUP_STRATEGY="full" # 备份策略full, incremental, differential
BACKUP_RETENTION_DAYS=30 # 备份保留天数
# ==============================================================================
# 回滚配置 - 定义是否启用自动回滚和回滚策略
# ==============================================================================
# 回滚配置
ROLLBACK_ENABLED=true # 是否启用自动回滚
ROLLBACK_AUTO_GENERATE=true # 是否自动生成回滚脚本
ROLLBACK_STRATEGY="snapshot" # 回滚策略snapshot, restore
# ==============================================================================
# 通知配置 - 定义是否启用通知和通知渠道
# ==============================================================================
# 通知配置
NOTIFICATIONS_ENABLED=false # 是否启用通知
SLACK_WEBHOOK="https://hooks.slack.com/services/..." # Slack Webhook URL
EMAIL_RECIPIENTS="devops@company.com,team@company.com" # 邮箱接收人列表
# ==============================================================================
# 性能配置 - 定义并行处理和资源限制
# ==============================================================================
# 性能配置
PARALLEL_PROCESSING=false # 是否启用并行处理
MAX_WORKERS=4 # 最大工作线程数
MEMORY_LIMIT="2GB" # 内存限制
IO_BUFFER_SIZE="64KB" # IO缓冲区大小
# ==============================================================================
# 输出配置 - 定义补丁输出格式和目录
# ==============================================================================
# 输出配置
OUTPUT_FORMAT="tar.gz" # 输出格式tar.gz, zip
OUTPUT_DIRECTORY="/opt/patches" # 输出目录
NAMING_PATTERN="patch-{name}-{version}-{timestamp}-{git_commit}.{format}" # 文件名命名模式包含占位符举例patch-app-1.0.0-20250101T000000Z-abc123.tar.gz
# ==============================================================================
# 日志配置 - 定义日志级别、文件路径和大小
# ==============================================================================
# 日志配置
LOG_LEVEL="INFO" # 日志级别DEBUG, INFO, WARN, ERROR
LOG_FILE="/var/log/patch_system/patch.log" # 日志文件路径
LOG_MAX_SIZE="100MB" # 日志文件最大大小
LOG_BACKUP_COUNT=10 # 日志文件备份数量
# ==============================================================================
# 长路径支持 - 定义是否启用长路径支持
# ==============================================================================
# 长路径支持
LONG_PATH_SUPPORT=true # 是否启用长路径支持
TAR_OPTIONS="--force-local" # tar命令选项--force-local 强制使用本地文件系统

View File

@@ -0,0 +1,202 @@
#!/bin/bash
# patch_config.sh - 企业级补丁配置
# ==============================================================================
# 基础配置 - 定义补丁的基本信息
# ==============================================================================
# 基础配置
PATCH_NAME="security-hotfix-2025"
PATCH_VERSION="1.0.0"
PATCH_DESCRIPTION="紧急安全漏洞修复"
PATCH_AUTHOR="企业DevOps团队"
PATCH_EMAIL="devops@aigc-quickapp.com"
# ==============================================================================
# 文件筛选配置 - 定义哪些文件需要被包含或排除
# ==============================================================================
## 包含的文件模式
INCLUDE_PATTERNS=(
".+/.*" # 匹配所有子目录下的文件
"^.well-known/"
"^addon/"
"^addons/"
"^app/"
"^config/"
"^extend/"
"^public/"
"^templates/"
"^vendor/"
"^web/"
"^webapi/"
"^.404.html"
"^index.php"
"^install.php"
"^install.lock"
"^.env"
"^.env.test"
"^.env.production"
"^.env.staging"
"^.env.development"
"^.env.local"
"^.htaccess"
"^.user.ini"
"^composer.json"
"^composer.lock"
)
## 排除的文件模式
EXCLUDE_PATTERNS=(
"*.log"
"*.tmp"
"*.bak"
"*.swp"
"*.swx"
"^.github/"
"/node_modules/*"
"/__pycache__/*"
# 排除以下一级文件夹
"^.git/"
"^cache/"
"^temp/"
"^tmp/"
"^logs/"
"^runtime/"
"^uploads/"
"^attachment/"
"^h5/"
"^hwapp/"
".DS_Store"
"Thumbs.db"
)
# ==============================================================================
# 文件大小限制 - 定义补丁处理的文件大小范围
# ==============================================================================
# 文件大小限制
MAX_FILE_SIZE="20MB" # 最大文件大小
MIN_FILE_SIZE="0KB" # 最小文件大小
# ==============================================================================
# 时间过滤 - 定义补丁处理的文件时间范围
# ==============================================================================
# 时间过滤
MODIFIED_AFTER="2000-01-01T00:00:00Z" # 仅包含修改时间在该时间之后的文件
CREATED_AFTER="2000-01-01T00:00:00Z" # 仅包含创建时间在该时间之后的文件
# ==============================================================================
# 比较方法配置 - 定义补丁处理的比较方式
# ==============================================================================
# 比较方法配置
# 比较内容和时间
COMPARISON_METHOD="content" # 比较方法: content, time, both
HASH_ALGORITHM="sha256" # 哈希算法,用于文件内容比较
TIME_PRECISION="second" # 时间精度,用于文件时间比较
COMPARE_PERMISSIONS=false # 是否比较文件权限
COMPARE_OWNERSHIP=false # 是否比较文件所有者
# ==============================================================================
# 增量配置 - 定义是否启用增量补丁
# ==============================================================================
# 增量配置
INCREMENTAL_ENABLED=true # 是否启用增量补丁
BASE_VERSION="1.0.0" # 基础版本,用于增量比较
DETECT_RENAMES=true # 是否检测文件重命名
BINARY_DIFF=true # 是否启用二进制差异比较
CHUNK_SIZE="8KB" # 二进制差异比较的块大小
# ==============================================================================
# 压缩配置 - 定义补丁压缩方式和级别
# ==============================================================================
# 压缩配置
COMPRESSION_ENABLED=true # 是否启用压缩
COMPRESSION_ALGORITHM="gzip" # gzip, bzip2, xz
COMPRESSION_LEVEL=6 # 压缩级别1-9
PER_FILE_OPTIMIZATION=true # 是否对每个文件单独压缩
# ==============================================================================
# 安全配置 - 定义补丁签名和加密方式
# ==============================================================================
# 安全配置
SIGNING_ENABLED=true # 是否启用签名
SIGNING_ALGORITHM="rsa" # 签名算法rsa, ecdsa
PRIVATE_KEY="/etc/patch/keys/private.pem" # 私钥文件路径
PUBLIC_KEY="/etc/patch/keys/public.pem" # 公钥文件路径
ENCRYPTION_ENABLED=false # 是否启用加密
ENCRYPTION_ALGORITHM="aes-256-gcm" # 加密算法aes-256-gcm, aes-256-cbc
ENCRYPTION_KEY="/etc/patch/keys/encryption.key" # 加密密钥文件路径
ENCRYPTION_IV="/etc/patch/keys/encryption.iv" # 加密初始化向量文件路径
# ==============================================================================
# 备份配置 - 定义是否启用备份和备份策略
# ==============================================================================
# 备份配置
BACKUP_ENABLED=true # 是否启用备份
BACKUP_STRATEGY="full" # 备份策略full, incremental, differential
BACKUP_RETENTION_DAYS=30 # 备份保留天数
# ==============================================================================
# 回滚配置 - 定义是否启用自动回滚和回滚策略
# ==============================================================================
# 回滚配置
ROLLBACK_ENABLED=true # 是否启用自动回滚
ROLLBACK_AUTO_GENERATE=true # 是否自动生成回滚脚本
ROLLBACK_STRATEGY="snapshot" # 回滚策略snapshot, restore
# ==============================================================================
# 通知配置 - 定义是否启用通知和通知渠道
# ==============================================================================
# 通知配置
NOTIFICATIONS_ENABLED=false # 是否启用通知
SLACK_WEBHOOK="https://hooks.slack.com/services/..." # Slack Webhook URL
EMAIL_RECIPIENTS="devops@company.com,team@company.com" # 邮箱接收人列表
# ==============================================================================
# 性能配置 - 定义并行处理和资源限制
# ==============================================================================
# 性能配置
PARALLEL_PROCESSING=false # 是否启用并行处理
MAX_WORKERS=4 # 最大工作线程数
MEMORY_LIMIT="2GB" # 内存限制
IO_BUFFER_SIZE="64KB" # IO缓冲区大小
# ==============================================================================
# 输出配置 - 定义补丁输出格式和目录
# ==============================================================================
# 输出配置
OUTPUT_FORMAT="tar.gz" # 输出格式tar.gz, zip
OUTPUT_DIRECTORY="/opt/patches" # 输出目录
NAMING_PATTERN="patch-{name}-{version}-{timestamp}-{git_commit}.{format}" # 文件名命名模式包含占位符举例patch-app-1.0.0-20250101T000000Z-abc123.tar.gz
# ==============================================================================
# 日志配置 - 定义日志级别、文件路径和大小
# ==============================================================================
# 日志配置
LOG_LEVEL="INFO" # 日志级别DEBUG, INFO, WARN, ERROR
LOG_FILE="/var/log/patch_system/patch.log" # 日志文件路径
LOG_MAX_SIZE="100MB" # 日志文件最大大小
LOG_BACKUP_COUNT=10 # 日志文件备份数量
# ==============================================================================
# 长路径支持 - 定义是否启用长路径支持
# ==============================================================================
# 长路径支持
LONG_PATH_SUPPORT=true # 是否启用长路径支持
TAR_OPTIONS="--force-local" # tar命令选项--force-local 强制使用本地文件系统

View File

@@ -0,0 +1,126 @@
#!/bin/bash
# patch_fnc.sh - 企业级增量补丁包生成脚本公共函数
# 用于并行查找提供给xargs使用
# 文件工具函数
parse_size() {
local size_str="$1"
local unit=$(echo "$size_str" | sed 's/[0-9.]//g' | tr 'a-z' 'A-Z')
local value=$(echo "$size_str" | sed 's/[^0-9.]//g')
case "$unit" in
"KB") echo "$(echo "$value * 1024" | bc)" ;;
"MB") echo "$(echo "$value * 1024 * 1024" | bc)" ;;
"GB") echo "$(echo "$value * 1024 * 1024 * 1024" | bc)" ;;
*) echo "$value" ;;
esac
}
get_file_hash() {
local file_path="$1"
local algorithm="${2:-sha256}"
case "$algorithm" in
"md5") md5sum "$file_path" | cut -d' ' -f1 ;;
"sha1") sha1sum "$file_path" | cut -d' ' -f1 ;;
"sha256") sha256sum "$file_path" | cut -d' ' -f1 ;;
*) sha256sum "$file_path" | cut -d' ' -f1 ;;
esac
}
get_file_info() {
local file_path="$1"
if [[ ! -f "$file_path" ]]; then
error "文件不存在: $file_path"
return 1
fi
local stat_output
if stat --version 2>&1 | grep -q GNU; then
# GNU stat
stat_output=$(stat -c "%s|%Y|%Z|%a|%u|%g" "$file_path")
else
# BSD stat
stat_output=$(stat -f "%z|%m|%c|%p|%u|%g" "$file_path")
fi
echo "$stat_output"
}
# 文件筛选
should_include_file() {
local file_path="$1"
local file_info="$2"
IFS='|' read -r size mtime ctime permissions uid gid <<< "$file_info"
# 文件大小过滤
local max_size=$(parse_size "$MAX_FILE_SIZE")
local min_size=$(parse_size "$MIN_FILE_SIZE")
if [[ $size -gt $max_size ]] || [[ $size -lt $min_size ]]; then
warn "文件大小超出范围: $file_path ($size bytes, 限制:$min_size-$max_size bytes)"
return 1
fi
# 包含模式匹配
local include_match=false
for pattern in "${INCLUDE_PATTERNS[@]}"; do
if [[ "$file_path" == $pattern ]] || [[ "$file_path" =~ $pattern ]]; then
include_match=true
debug "文件匹配包含模式<$pattern>: $file_path"
break
fi
done
if [[ ${#INCLUDE_PATTERNS[@]} -gt 0 ]] && ! $include_match; then
warn "文件不匹配任何包含模式: $file_path"
return 1
fi
# 排除模式匹配
for pattern in "${EXCLUDE_PATTERNS[@]}"; do
if [[ "$file_path" == $pattern ]] || [[ "$file_path" =~ $pattern ]]; then
warn "文件匹配排除模式<$pattern>: $file_path"
return 1
fi
done
# 时间过滤
if [[ -n "$MODIFIED_AFTER" ]]; then
local filter_time=$(date -d "$MODIFIED_AFTER" +%s 2>/dev/null || date +%s)
if [[ $mtime -lt $filter_time ]]; then
local mtime_str=$(date -d @$mtime +"%Y-%m-%d %H:%M:%S")
warn "文件修改时间过早: $file_path ($mtime_str)"
return 1
fi
fi
# 权限过滤
if [[ "$PERMISSIONS_FILTER" == "true" ]]; then
if [[ $permissions -ne 644 ]] && [[ $permissions -ne 755 ]]; then
warn "文件权限不符合要求: $file_path ($permissions)"
return 1
fi
fi
# 用户过滤
if [[ "$UID_FILTER" == "true" ]]; then
if [[ $uid -ne 0 ]] && [[ $uid -ne 1000 ]]; then
warn "文件所有者不符合要求: $file_path ($uid)"
return 1
fi
fi
# 组过滤
if [[ "$GID_FILTER" == "true" ]]; then
if [[ $gid -ne 0 ]] && [[ $gid -ne 1000 ]]; then
warn "文件组不符合要求: $file_path ($gid)"
return 1
fi
fi
return 0
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,356 @@
#!/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"
# 颜色定义
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_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"}
}
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
# 回滚点管理
get_available_rollback_points() {
info "可用的回滚点:"
if [[ ! -f "$ROLLBACK_INFO_FILE" ]]; then
warn "未找到回滚点信息文件"
return 1
fi
# 读取回滚点信息
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
else
warn "回滚点信息不完整或备份文件不存在"
return 1
fi
}
# 验证回滚包
verify_rollback_package() {
local rollback_path="$1"
info "验证回滚包: $rollback_path"
if [[ ! -f "$rollback_path" ]]; then
error "回滚包不存在: $rollback_path"
return 1
fi
if ! tar -tzf "$rollback_path" >/dev/null 2>&1; then
error "回滚包损坏或格式错误"
return 1
fi
info "✅ 回滚包验证通过"
return 0
}
# 回滚执行函数
perform_rollback() {
local rollback_path="$1"
local dry_run="${2:-false}"
info "开始执行回滚: $rollback_path"
# 验证回滚包
if ! verify_rollback_package "$rollback_path"; then
return 1
fi
# 解压回滚包
local extract_dir="$TEMP_DIR/rollback_extract"
if ! tar -xzf "$rollback_path" -C "$extract_dir"; then
error "回滚包解压失败"
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
fi
# 执行实际回滚
if execute_file_rollback "$extract_dir"; then
info "✅ 回滚执行完成"
# 清理回滚点
cleanup_rollback_point
send_rollback_notification "success" "$rollback_path" "回滚成功"
return 0
else
error "❌ 回滚执行失败"
send_rollback_notification "failure" "$rollback_path" "回滚失败"
return 1
fi
}
execute_file_rollback() {
local extract_dir="$1"
info "开始执行文件回滚..."
local rollback_count=0
local error_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_dir=$(dirname "$target_file")
# 创建目标目录
mkdir -p "$target_dir"
# 备份当前文件(如果存在)
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"
fi
# 恢复文件
if cp -p "$backup_file" "$target_file"; then
info "✅ 回滚文件: $relative_path"
((rollback_count++))
else
error "❌ 回滚失败: $relative_path"
((error_count++))
fi
done
info "回滚完成: $rollback_count 个文件成功, $error_count 个文件失败"
if [[ $error_count -gt 0 ]]; then
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"
fi
}
# 通知函数
send_rollback_notification() {
local status="$1"
local rollback_path="$2"
local message="$3"
if [[ "$NOTIFICATIONS_ENABLED" != "true" ]]; then
return 0
fi
case "$status" in
"success")
local subject="补丁回滚成功"
local color="#2ecc71"
;;
"failure")
local subject="补丁回滚失败"
local color="#e74c3c"
;;
*)
local subject="补丁回滚通知"
local color="#95a5a6"
;;
esac
# Slack通知
if [[ -n "$SLACK_WEBHOOK" ]]; then
send_slack_rollback_notification "$subject" "$message" "$color" "$rollback_path"
fi
}
send_slack_rollback_notification() {
local subject="$1"
local message="$2"
local color="$3"
local rollback_path="$4"
local payload=$(cat << EOF
{
"attachments": [
{
"color": "$color",
"title": "$subject",
"text": "$message",
"fields": [
{
"title": "回滚包",
"value": "$(basename "$rollback_path")",
"short": true
},
{
"title": "回滚时间",
"value": "$(date)",
"short": true
},
{
"title": "回滚主机",
"value": "$(hostname)",
"short": true
}
]
}
]
}
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通知发送成功"
fi
}
# 主回滚函数
rollback_patch() {
local rollback_path="${1:-}"
local dry_run="${2:-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 "无法获取回滚点信息"
return 1
fi
fi
# 执行回滚
if perform_rollback "$rollback_path" "$dry_run"; then
info "🎉 回滚操作成功完成"
return 0
else
error "💥 回滚操作失败"
return 1
fi
}
# 主函数
main() {
local rollback_path="${1:-}"
local dry_run="${2:-false}"
# 加载配置
load_config
# 设置环境
setup_environment
# 显示使用信息
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
fi
# 执行回滚
if rollback_patch "$rollback_path" "$dry_run"; then
exit 0
else
exit 1
fi
}
# 异常处理
trap 'error "脚本执行中断"; cleanup; exit 1' INT TERM
main "$@"

View File

@@ -0,0 +1,538 @@
#!/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 "$@"

View File

@@ -0,0 +1,153 @@
#!/bin/bash
# patch_workflow.sh - 完整补丁管理工作流
set -euo pipefail
# SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SCRIPT_DIR="/opt/patch-management"
# 颜色定义
GREEN='\033[0;32m'
RED='\033[0;31m'
NC='\033[0m'
log() { echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')]${NC} $1"; }
error() { echo -e "${RED}[ERROR]${NC} $1"; }
# 完整工作流
full_patch_workflow() {
local source_dir="$1"
local target_dir="$2"
local patch_name="$3"
log "开始完整补丁管理工作流"
echo "========================================"
# 1. 生成补丁包
log "步骤1: 生成补丁包"
if ! "$SCRIPT_DIR/patch_generator.sh" "$source_dir" "$target_dir" "$patch_name"; then
error "补丁包生成失败"
return 1
fi
# 获取生成的补丁包路径
local patch_file=$(find "/opt/patches" -name "*${patch_name}*" -type f | head -1)
if [[ -z "$patch_file" ]]; then
error "未找到补丁包文件"
return 1
fi
# 2. 验证补丁包
log "步骤2: 验证补丁包"
if ! "$SCRIPT_DIR/patch_verifier.sh" "$patch_file" "pre-apply"; then
error "补丁包验证失败"
return 1
fi
# 3. 应用补丁包(干跑模式)
log "步骤3: 干跑模式应用补丁"
if ! "$SCRIPT_DIR/patch_applier.sh" "$patch_file" "dry-run"; then
error "干跑模式应用失败"
return 1
fi
# 4. 实际应用补丁包
read -p "是否继续实际应用补丁? (y/N): " confirm
if [[ "$confirm" == "y" || "$confirm" == "Y" ]]; then
log "步骤4: 实际应用补丁"
if ! "$SCRIPT_DIR/patch_applier.sh" "$patch_file"; then
error "补丁应用失败"
return 1
fi
else
log "操作取消"
return 0
fi
# 5. 应用后验证
log "步骤5: 应用后验证"
if ! "$SCRIPT_DIR/patch_verifier.sh" "$patch_file" "post-apply"; then
error "应用后验证失败"
return 1
fi
log "🎉 完整补丁管理工作流完成"
return 0
}
# 回滚工作流
rollback_workflow() {
local rollback_file="${1:-}"
log "开始回滚工作流"
echo "========================================"
# 1. 干跑模式回滚
log "步骤1: 干跑模式回滚"
if ! "$SCRIPT_DIR/patch_rollback.sh" "$rollback_file" "dry-run"; then
error "干跑模式回滚失败"
return 1
fi
# 2. 实际回滚
read -p "是否继续实际回滚? (y/N): " confirm
if [[ "$confirm" == "y" || "$confirm" == "Y" ]]; then
log "步骤2: 实际回滚"
if ! "$SCRIPT_DIR/patch_rollback.sh" "$rollback_file"; then
error "回滚失败"
return 1
fi
else
log "操作取消"
return 0
fi
log "✅ 回滚工作流完成"
return 0
}
# 主函数
main() {
case "${1:-}" in
"generate")
shift
full_patch_workflow "$@"
;;
"apply")
shift
"$SCRIPT_DIR/patch_applier.sh" "$@"
;;
"rollback")
shift
rollback_workflow "$@"
;;
"verify")
shift
"$SCRIPT_DIR/patch_verifier.sh" "$@"
;;
"batch-verify")
shift
"$SCRIPT_DIR/patch_verifier.sh" "$1" "standalone" "batch"
;;
*)
echo "用法: $0 <command> [args]"
echo "命令:"
echo " generate <源目录> <目标目录> <补丁名称> # 完整工作流"
echo " apply <补丁包路径> [dry-run] # 应用补丁"
echo " rollback [回滚包路径] # 回滚补丁"
echo " verify <补丁包路径> [验证类型] # 验证补丁"
echo " batch-verify <目录> # 批量验证"
echo ""
echo "示例:"
echo " $0 generate /old/version /new/version security-hotfix"
echo " $0 apply /opt/patches/patch.tar.gz dry-run"
echo " $0 rollback /var/backups/patch/backup.tar.gz"
exit 1
;;
esac
}
# 异常处理
trap 'error "脚本执行中断"; cleanup; exit 1' INT TERM
main "$@"

View File

@@ -0,0 +1,210 @@
#!/bin/bash
# shop_src_patch_config.sh - 针对Shop项目的补丁配置
# ==============================================================================
# 基础配置 - 定义补丁的基本信息
# ==============================================================================
# 基础配置
PATCH_NAME="shop-src-feat-fix-2025"
PATCH_VERSION="1.0.0"
PATCH_DESCRIPTION="Shop项目功能修复补丁"
PATCH_AUTHOR="企业DevOps团队"
PATCH_EMAIL="devops@aigc-quickapp.com"
# ==============================================================================
# 文件筛选配置 - 定义哪些文件需要被包含或排除
# ==============================================================================
## 包含的文件模式
INCLUDE_PATTERNS=(
"*.php"
"*.js"
"*.css"
"*.html"
"*.conf"
"*.json"
"*.yml"
"*.yaml"
"*.py"
".well-known/*"
"addon/*"
"addons/*"
"app/*"
"config/*"
"extend/*"
"h5/*"
"hwapp/*"
"public/*"
"templates/*"
"web/*"
"webapi/*"
".404.html"
"index.php"
"install.php"
"install.lock"
".env"
".env.test"
".env.production"
".env.staging"
".env.development"
".env.local"
".htaccess"
".user.ini"
"composer.json"
"composer.lock"
)
## 排除的文件模式
EXCLUDE_PATTERNS=(
"*.log"
"*.tmp"
"*.bak"
"*.swp"
".git/*"
"node_modules/*"
"__pycache__/*"
'/cache/', # 排除缓存目录
'/temp/', # 排除临时目录
'/tmp/', # 排除临时目录
'/logs/', # 排除日志目录
'/runtime/', # 排除运行时目录
'/uploads/', # 排除上传目录
'/attachment/', # 排除附件目录
"*.min.js"
"*.min.css"
"test/*"
"temp/*"
".DS_Store"
"Thumbs.db"
)
# ==============================================================================
# 文件大小限制 - 定义补丁处理的文件大小范围
# ==============================================================================
# 文件大小限制
MAX_FILE_SIZE="20MB" # 最大文件大小
MIN_FILE_SIZE="1KB" # 最小文件大小
# ==============================================================================
# 时间过滤 - 定义补丁处理的文件时间范围
# ==============================================================================
# 时间过滤
MODIFIED_AFTER="2025-01-01T00:00:00Z" # 仅包含修改时间在该时间之后的文件
CREATED_AFTER="2025-01-01T00:00:00Z" # 仅包含创建时间在该时间之后的文件
# ==============================================================================
# 比较方法配置 - 定义补丁处理的比较方式
# ==============================================================================
# 比较方法配置
# 比较内容和时间
COMPARISON_METHOD="both" # content, time, both
HASH_ALGORITHM="sha256" # 哈希算法,用于文件内容比较
TIME_PRECISION="second" # 时间精度,用于文件时间比较
COMPARE_PERMISSIONS=true # 是否比较文件权限
COMPARE_OWNERSHIP=false # 是否比较文件所有者
# ==============================================================================
# 增量配置 - 定义是否启用增量补丁
# ==============================================================================
# 增量配置
INCREMENTAL_ENABLED=true # 是否启用增量补丁
BASE_VERSION="1.0.0" # 基础版本,用于增量比较
DETECT_RENAMES=true # 是否检测文件重命名
BINARY_DIFF=true # 是否启用二进制差异比较
CHUNK_SIZE="8KB" # 二进制差异比较的块大小
# ==============================================================================
# 压缩配置 - 定义补丁压缩方式和级别
# ==============================================================================
# 压缩配置
COMPRESSION_ENABLED=true # 是否启用压缩
COMPRESSION_ALGORITHM="gzip" # gzip, bzip2, xz
COMPRESSION_LEVEL=6 # 压缩级别1-9
PER_FILE_OPTIMIZATION=true # 是否对每个文件单独压缩
# ==============================================================================
# 安全配置 - 定义补丁签名和加密方式
# ==============================================================================
# 安全配置
SIGNING_ENABLED=true # 是否启用签名
SIGNING_ALGORITHM="rsa" # 签名算法rsa, ecdsa
PRIVATE_KEY="/etc/patch/keys/private.pem" # 私钥文件路径
PUBLIC_KEY="/etc/patch/keys/public.pem" # 公钥文件路径
ENCRYPTION_ENABLED=false # 是否启用加密
ENCRYPTION_ALGORITHM="aes-256-gcm" # 加密算法aes-256-gcm, aes-256-cbc
ENCRYPTION_KEY="/etc/patch/keys/encryption.key" # 加密密钥文件路径
ENCRYPTION_IV="/etc/patch/keys/encryption.iv" # 加密初始化向量文件路径
# ==============================================================================
# 备份配置 - 定义是否启用备份和备份策略
# ==============================================================================
# 备份配置
BACKUP_ENABLED=true # 是否启用备份
BACKUP_STRATEGY="full" # 备份策略full, incremental, differential
BACKUP_RETENTION_DAYS=30 # 备份保留天数
# ==============================================================================
# 回滚配置 - 定义是否启用自动回滚和回滚策略
# ==============================================================================
# 回滚配置
ROLLBACK_ENABLED=true # 是否启用自动回滚
ROLLBACK_AUTO_GENERATE=true # 是否自动生成回滚脚本
ROLLBACK_STRATEGY="snapshot" # 回滚策略snapshot, restore
# ==============================================================================
# 通知配置 - 定义是否启用通知和通知渠道
# ==============================================================================
# 通知配置
NOTIFICATIONS_ENABLED=false # 是否启用通知
SLACK_WEBHOOK="https://hooks.slack.com/services/..." # Slack Webhook URL
EMAIL_RECIPIENTS="devops@company.com,team@company.com" # 邮箱接收人列表
# ==============================================================================
# 性能配置 - 定义并行处理和资源限制
# ==============================================================================
# 性能配置
PARALLEL_PROCESSING=true # 是否启用并行处理
MAX_WORKERS=4 # 最大工作线程数
MEMORY_LIMIT="2GB" # 内存限制
IO_BUFFER_SIZE="64KB" # IO缓冲区大小
# ==============================================================================
# 输出配置 - 定义补丁输出格式和目录
# ==============================================================================
# 输出配置
OUTPUT_FORMAT="tar.gz" # 输出格式tar.gz, zip
OUTPUT_DIRECTORY="/opt/patches" # 输出目录
NAMING_PATTERN="patch-{name}-{version}-{timestamp}-{git_commit}.{format}" # 文件名命名模式包含占位符举例patch-app-1.0.0-20250101T000000Z-abc123.tar.gz
# ==============================================================================
# 日志配置 - 定义日志级别、文件路径和大小
# ==============================================================================
# 日志配置
LOG_LEVEL="INFO" # 日志级别DEBUG, INFO, WARNING, ERROR, CRITICAL
LOG_FILE="/var/log/patch_system/patch.log" # 日志文件路径
LOG_MAX_SIZE="100MB" # 日志文件最大大小
LOG_BACKUP_COUNT=10 # 日志文件备份数量
# ==============================================================================
# 长路径支持 - 定义是否启用长路径支持
# ==============================================================================
# 长路径支持
LONG_PATH_SUPPORT=true # 是否启用长路径支持
TAR_OPTIONS="--force-local" # tar命令选项--force-local 强制使用本地文件系统