#!/bin/bash
set -euo pipefail

# ============================================================
# Nginx 通用升级脚本
# 自动检测安装方式（源码编译/yum/apt/docker），按原方式平滑升级
# ============================================================

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m'

LOG_FILE="/tmp/nginx_upgrade_$(date +%Y%m%d_%H%M%S).log"
BACKUP_DIR=""
BUILD_DIR=""
ROLLBACK_NEEDED=false
NGINX_SBIN=""
NGINX_PREFIX=""
NGINX_PID_PATH=""
NGINX_CONF=""
CURRENT_VERSION=""
TARGET_VERSION=""
CONFIGURE_ARGS=""
INSTALL_METHOD=""  # source / yum / apt / docker
DOCKER_CONTAINER=""

log() { echo -e "${GREEN}[INFO]${NC} $1" | tee -a "$LOG_FILE"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $1" | tee -a "$LOG_FILE"; }
err() { echo -e "${RED}[ERROR]${NC} $1" | tee -a "$LOG_FILE"; }
title() { echo -e "${CYAN}$1${NC}" | tee -a "$LOG_FILE"; }

# --- 回滚函数 ---
rollback() {
    if [ -n "$BACKUP_DIR" ] && [ -d "$BACKUP_DIR" ]; then
        log "从备份恢复: $BACKUP_DIR"
        if [ -f "$BACKUP_DIR/nginx.old" ] && [ -n "$NGINX_SBIN" ]; then
            cp -f "$BACKUP_DIR/nginx.old" "$NGINX_SBIN"
            log "二进制文件已回滚"
            if [ -f "${NGINX_PID_PATH}.oldbin" ]; then
                kill -QUIT $(cat "$NGINX_PID_PATH") 2>/dev/null || true
                kill -HUP $(cat "${NGINX_PID_PATH}.oldbin") 2>/dev/null || true
            elif [ -f "$NGINX_PID_PATH" ]; then
                "$NGINX_SBIN" -s reload 2>/dev/null || true
            fi
            log "回滚完成"
        fi
    else
        err "无备份可用，请手动恢复"
    fi
}

cleanup() {
    if [ "$ROLLBACK_NEEDED" = true ]; then
        err "升级过程中出错，正在执行回滚..."
        rollback
    fi
    if [ -n "${BUILD_DIR:-}" ] && [ -d "$BUILD_DIR" ]; then
        rm -rf "$BUILD_DIR"
    fi
}
trap cleanup EXIT

# --- 检查root权限 ---
check_root() {
    if [ "$(id -u)" -ne 0 ]; then
        err "此脚本需要root权限运行"
        exit 1
    fi
}

# === 检测安装方式 ===
# 全局数组：存储检测到的所有nginx实例
DETECTED_INSTANCES=()
DETECTED_LABELS=()

detect_install_method() {
    log "正在检测Nginx安装方式..."
    local instance_count=0

    # --- 1. 检测Docker容器中的nginx ---
    if command -v docker &>/dev/null; then
        local containers=$(timeout 3 docker ps --format '{{.Names}}' 2>/dev/null || true)
        if [ -n "$containers" ]; then
            while read -r cname; do
                [ -z "$cname" ] && continue
                local cimage=$(docker inspect --format='{{.Config.Image}}' "$cname" 2>/dev/null || true)
                local confidence=""

                # 确认: 镜像名明确是nginx官方镜像
                if echo "$cimage" | grep -qE '^nginx(:|$)' || echo "$cimage" | grep -qE '/nginx(:|$)'; then
                    confidence="confirmed"
                else
                    # 疑似: 通过exec检测容器内是否有nginx进程
                    if timeout 3 docker exec "$cname" nginx -v &>/dev/null 2>&1; then
                        confidence="suspected"
                    fi
                fi

                if [ -n "$confidence" ]; then
                    local cver=$(timeout 3 docker exec "$cname" nginx -v 2>&1 | sed -n 's|.*nginx/\([0-9.]*\).*|\1|p' || echo "未知")
                    instance_count=$((instance_count+1))
                    DETECTED_INSTANCES+=("docker|${cname}|${cver}|${confidence}|${cimage}")
                    if [ "$confidence" = "confirmed" ]; then
                        DETECTED_LABELS+=("Docker容器: ${cname} (镜像: ${cimage}, 版本: ${cver})")
                    else
                        DETECTED_LABELS+=("Docker容器: ${cname} [疑似] (镜像: ${cimage}, 版本: ${cver})")
                    fi
                fi
            done <<< "$containers"
        fi
    fi

    # --- 2. 检测宿主机上的nginx ---
    local host_sbin=""

    # 方式1: 通过PATH查找
    if command -v nginx &>/dev/null; then
        host_sbin=$(which nginx)
    fi

    # 方式2: 从运行中的进程获取（排除容器内进程）
    if [ -z "$host_sbin" ]; then
        local running_pid=$(pgrep -x nginx | head -1)
        if [ -n "$running_pid" ] && [ -f "/proc/$running_pid/exe" ]; then
            local cgroup_info=$(cat /proc/$running_pid/cgroup 2>/dev/null || true)
            if ! echo "$cgroup_info" | grep -qE 'docker|containerd'; then
                host_sbin=$(readlink -f "/proc/$running_pid/exe")
            fi
        fi
    fi

    # 方式3: 搜索本地文件系统
    if [ -z "$host_sbin" ]; then
        host_sbin=$(timeout 10 find / -xdev -name "nginx" -type f -executable \
            -not -path "*/modules/*" -not -path "*/tmp/*" -not -path "*/build*" \
            -not -path "*/overlay*" 2>/dev/null \
            | while read -r f; do
                "$f" -v 2>&1 | grep -q "nginx version" && echo "$f" && break
            done || true)
    fi

    if [ -n "$host_sbin" ] && [ -f "$host_sbin" ]; then
        # 解析软链接
        local real_sbin=$(readlink -f "$host_sbin")
        [ "$real_sbin" != "$host_sbin" ] && host_sbin="$real_sbin"

        local host_ver=$("$host_sbin" -v 2>&1 | sed -n 's|.*nginx/\([0-9.]*\).*|\1|p')
        local host_method=""

        # 判断安装方式
        if command -v rpm &>/dev/null && rpm -qf "$host_sbin" &>/dev/null; then
            if command -v dnf &>/dev/null; then host_method="dnf"; else host_method="yum"; fi
        elif command -v dpkg &>/dev/null && dpkg -S "$host_sbin" &>/dev/null 2>&1; then
            host_method="apt"
        elif command -v apk &>/dev/null && apk info --who-owns "$host_sbin" &>/dev/null 2>&1; then
            host_method="apk"
        elif command -v zypper &>/dev/null && rpm -qf "$host_sbin" &>/dev/null 2>&1; then
            host_method="zypper"
        else
            host_method="source"
        fi

        instance_count=$((instance_count+1))
        DETECTED_INSTANCES+=("${host_method}|${host_sbin}|${host_ver}||")
        local method_label=""
        case "$host_method" in
            source) method_label="源码编译" ;;
            yum|dnf) method_label="YUM/DNF" ;;
            apt) method_label="APT" ;;
            apk) method_label="APK" ;;
            zypper) method_label="Zypper" ;;
        esac
        DETECTED_LABELS+=("宿主机${method_label}: ${host_sbin} (版本: ${host_ver})")
    fi

    # --- 3. 根据检测结果决定下一步 ---
    if [ $instance_count -eq 0 ]; then
        err "未检测到任何Nginx实例"
        read -p "请输入nginx二进制文件完整路径（留空退出）: " manual_path
        if [ -z "$manual_path" ] || [ ! -f "$manual_path" ]; then
            err "无法继续"
            exit 1
        fi
        local manual_ver=$("$manual_path" -v 2>&1 | sed -n 's|.*nginx/\([0-9.]*\).*|\1|p')
        INSTALL_METHOD="source"
        NGINX_SBIN="$manual_path"
        return
    elif [ $instance_count -eq 1 ]; then
        # 只有一个实例，自动选择
        select_instance 0
    else
        # 多个实例，让用户选择
        echo ""
        title "检测到多个Nginx实例:"
        echo ""
        for i in "${!DETECTED_LABELS[@]}"; do
            printf "  [%d] %s\n" $((i+1)) "${DETECTED_LABELS[$i]}"
        done
        echo ""
        read -p "请选择要操作的实例 (1-${instance_count}): " inst_choice
        if ! echo "$inst_choice" | grep -qE '^[0-9]+$' || [ "$inst_choice" -lt 1 ] || [ "$inst_choice" -gt $instance_count ]; then
            err "无效选择"
            exit 1
        fi
        select_instance $((inst_choice-1))
    fi
}

# 根据选择设置全局变量
select_instance() {
    local idx=$1
    local entry="${DETECTED_INSTANCES[$idx]}"
    local method=$(echo "$entry" | cut -d'|' -f1)
    local location=$(echo "$entry" | cut -d'|' -f2)
    local version=$(echo "$entry" | cut -d'|' -f3)
    local confidence=$(echo "$entry" | cut -d'|' -f4)

    INSTALL_METHOD="$method"
    CURRENT_VERSION="$version"

    if [ "$method" = "docker" ]; then
        DOCKER_CONTAINER="$location"
        log "已选择Docker容器: $DOCKER_CONTAINER (版本: $version)"
        if [ "$confidence" = "suspected" ]; then
            warn "该容器为疑似Nginx（非官方nginx镜像），请确认容器内确实运行Nginx"
            read -p "继续？(y/N): " confirm_suspected
            if [[ ! "$confirm_suspected" =~ ^[yY]$ ]]; then
                exit 0
            fi
        fi
    else
        NGINX_SBIN="$location"
        log "已选择宿主机Nginx: $NGINX_SBIN (版本: $version, 方式: $method)"
    fi
}

# === 获取当前nginx详细信息 ===
detect_nginx_info() {
    local v_output=$("$NGINX_SBIN" -V 2>&1)

    CURRENT_VERSION=$("$NGINX_SBIN" -v 2>&1 | sed -n 's|.*nginx/\([0-9.]*\).*|\1|p')
    NGINX_PREFIX=$(echo "$v_output" | sed -n 's|.*--prefix=\([^ ]*\).*|\1|p')
    NGINX_PREFIX=${NGINX_PREFIX:-/etc/nginx}
    NGINX_PID_PATH=$(echo "$v_output" | sed -n 's|.*--pid-path=\([^ ]*\).*|\1|p')
    CONFIGURE_ARGS=$(echo "$v_output" | grep "configure arguments:" | sed 's/configure arguments: //')
    NGINX_CONF=$(echo "$v_output" | sed -n 's|.*--conf-path=\([^ ]*\).*|\1|p')
    NGINX_CONF=${NGINX_CONF:-$NGINX_PREFIX/conf/nginx.conf}

    # PID路径兜底
    if [ -z "$NGINX_PID_PATH" ]; then
        if [ -f "$NGINX_PREFIX/logs/nginx.pid" ]; then
            NGINX_PID_PATH="$NGINX_PREFIX/logs/nginx.pid"
        elif [ -f "/run/nginx.pid" ]; then
            NGINX_PID_PATH="/run/nginx.pid"
        elif [ -f "/var/run/nginx.pid" ]; then
            NGINX_PID_PATH="/var/run/nginx.pid"
        else
            NGINX_PID_PATH="$NGINX_PREFIX/logs/nginx.pid"
        fi
    fi

    echo ""
    title "========== 当前Nginx信息 =========="
    log "安装方式: $INSTALL_METHOD"
    log "二进制路径: $NGINX_SBIN"
    log "当前版本: $CURRENT_VERSION"
    log "安装前缀: $NGINX_PREFIX"
    log "配置文件: $NGINX_CONF"
    log "PID文件: $NGINX_PID_PATH"
    [ -n "$CONFIGURE_ARGS" ] && log "编译参数: $CONFIGURE_ARGS"

    # 显示软链接信息
    local symlinks=$(find /usr/local/sbin /usr/sbin /usr/bin /usr/local/bin -lname "*nginx*" 2>/dev/null || true)
    if [ -n "$symlinks" ]; then
        log "软链接: $symlinks"
    fi
}

# === 获取目标版本 ===
get_target_version() {
    echo ""
    read -p "请输入要升级到的Nginx版本号（例如 1.30.1）: " TARGET_VERSION

    if [ -z "$TARGET_VERSION" ]; then
        err "版本号不能为空"
        exit 1
    fi
    if ! echo "$TARGET_VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then
        err "版本号格式不正确，应为 x.y.z"
        exit 1
    fi
    if [ "$TARGET_VERSION" = "$CURRENT_VERSION" ]; then
        warn "目标版本与当前版本相同，无需升级"
        exit 0
    fi
    log "目标版本: $TARGET_VERSION"
}

# === 备份 ===
do_backup() {
    local backup_base=""
    if [ "$INSTALL_METHOD" = "source" ]; then
        backup_base="$NGINX_PREFIX"
    else
        backup_base="/opt/nginx_backup"
    fi
    BACKUP_DIR="${backup_base}/backup_$(date +%Y%m%d_%H%M%S)"
    mkdir -p "$BACKUP_DIR"
    log "正在备份到: $BACKUP_DIR"

    # 备份二进制
    cp -f "$NGINX_SBIN" "$BACKUP_DIR/nginx.old"

    # 备份配置
    local conf_dir=$(dirname "$NGINX_CONF")
    cp -a "$conf_dir" "$BACKUP_DIR/conf"

    # 备份html（源码安装才有）
    [ -d "$NGINX_PREFIX/html" ] && cp -a "$NGINX_PREFIX/html" "$BACKUP_DIR/html"

    # 记录元信息
    echo "version=$CURRENT_VERSION" > "$BACKUP_DIR/meta.txt"
    echo "method=$INSTALL_METHOD" >> "$BACKUP_DIR/meta.txt"
    echo "sbin=$NGINX_SBIN" >> "$BACKUP_DIR/meta.txt"
    echo "configure=$CONFIGURE_ARGS" >> "$BACKUP_DIR/meta.txt"

    log "备份完成"
}

# ============================================================
#                    源码编译升级流程
# ============================================================

source_check_deps() {
    log "检查编译依赖..."
    local missing=()
    for cmd in gcc make wget tar; do
        command -v $cmd &>/dev/null || missing+=("$cmd")
    done

    if [ ${#missing[@]} -gt 0 ]; then
        warn "缺少依赖: ${missing[*]}，正在安装..."
        if command -v dnf &>/dev/null; then
            dnf install -y gcc gcc-c++ make wget tar pcre-devel zlib-devel openssl-devel >> "$LOG_FILE" 2>&1
        elif command -v yum &>/dev/null; then
            yum install -y gcc gcc-c++ make wget tar pcre-devel zlib-devel openssl-devel >> "$LOG_FILE" 2>&1
        elif command -v apt-get &>/dev/null; then
            apt-get update && apt-get install -y build-essential wget libpcre3-dev zlib1g-dev libssl-dev >> "$LOG_FILE" 2>&1
        elif command -v apk &>/dev/null; then
            apk add gcc g++ make wget tar pcre-dev zlib-dev openssl-dev linux-headers >> "$LOG_FILE" 2>&1
        elif command -v zypper &>/dev/null; then
            zypper install -y gcc gcc-c++ make wget tar pcre-devel zlib-devel libopenssl-devel >> "$LOG_FILE" 2>&1
        else
            err "无法自动安装依赖，请手动安装: ${missing[*]}"
            exit 1
        fi
    fi
    log "依赖检查通过"
}

source_download() {
    BUILD_DIR=$(mktemp -d /tmp/nginx_build_XXXXXX)
    local url="https://nginx.org/download/nginx-${TARGET_VERSION}.tar.gz"
    log "下载源码: $url"

    # 检查磁盘空间（编译至少需要200MB）
    local free_mb=$(df -m /tmp | awk 'NR==2{print $4}')
    if [ -n "$free_mb" ] && [ "$free_mb" -lt 200 ]; then
        err "/tmp 剩余空间不足（${free_mb}MB），编译至少需要200MB"
        exit 1
    fi

    # 兼容老版本wget（不一定支持--show-progress）
    if wget --help 2>&1 | grep -q '\-\-show-progress'; then
        wget -q --show-progress -O "$BUILD_DIR/nginx-${TARGET_VERSION}.tar.gz" "$url" 2>&1 | tee -a "$LOG_FILE" || true
    else
        wget -O "$BUILD_DIR/nginx-${TARGET_VERSION}.tar.gz" "$url" 2>&1 | tee -a "$LOG_FILE" || true
    fi

    # 检查下载结果
    echo ""
    if [ ! -f "$BUILD_DIR/nginx-${TARGET_VERSION}.tar.gz" ]; then
        err "下载失败，请检查版本号或网络"
        exit 1
    fi

    local filesize=$(stat -c%s "$BUILD_DIR/nginx-${TARGET_VERSION}.tar.gz" 2>/dev/null || stat -f%z "$BUILD_DIR/nginx-${TARGET_VERSION}.tar.gz")
    if [ "$filesize" -lt 1024 ]; then
        err "文件异常(${filesize}字节)，版本号可能不存在"
        exit 1
    fi

    tar -zxf "$BUILD_DIR/nginx-${TARGET_VERSION}.tar.gz" -C "$BUILD_DIR"
    [ ! -d "$BUILD_DIR/nginx-${TARGET_VERSION}" ] && err "解压失败" && exit 1
    log "源码准备完成"
}

source_compile() {
    log "开始编译 Nginx ${TARGET_VERSION}..."
    cd "$BUILD_DIR/nginx-${TARGET_VERSION}"

    log "执行 configure..."
    if ! eval "./configure $CONFIGURE_ARGS" >> "$LOG_FILE" 2>&1; then
        err "configure 失败，详见: $LOG_FILE"
        exit 1
    fi

    local cores=$(nproc 2>/dev/null || echo 2)
    log "编译中（${cores}核心并行），请耐心等待..."

    # 后台编译并显示进度
    make -j"$cores" >> "$LOG_FILE" 2>&1 &
    local make_pid=$!
    local elapsed=0
    while kill -0 "$make_pid" 2>/dev/null; do
        sleep 5
        elapsed=$((elapsed+5))
        printf "\r  编译中... 已耗时 ${elapsed}s"
    done
    printf "\r                              \r"
    wait "$make_pid" || { err "编译失败，详见: $LOG_FILE"; exit 1; }

    [ ! -f "objs/nginx" ] && err "编译产物不存在" && exit 1

    local new_ver=$(./objs/nginx -v 2>&1 | sed -n 's|.*nginx/\([0-9.]*\).*|\1|p')
    if [ "$new_ver" != "$TARGET_VERSION" ]; then
        err "版本不匹配: 期望 $TARGET_VERSION, 实际 $new_ver"
        exit 1
    fi
    log "编译成功: $new_ver"
}

source_test_config() {
    log "用新二进制测试现有配置..."
    cd "$BUILD_DIR/nginx-${TARGET_VERSION}"
    if ! ./objs/nginx -t -c "$NGINX_CONF" >> "$LOG_FILE" 2>&1; then
        err "配置文件与新版本不兼容！请先修复配置"
        ./objs/nginx -t -c "$NGINX_CONF" 2>&1 | tee -a "$LOG_FILE"
        exit 1
    fi
    log "配置兼容性测试通过"
}

source_graceful_upgrade() {
    log "开始平滑升级..."
    ROLLBACK_NEEDED=true

    cd "$BUILD_DIR/nginx-${TARGET_VERSION}"
    cp -f objs/nginx "$NGINX_SBIN"
    log "二进制已替换"

    # nginx未运行的情况
    if [ ! -f "$NGINX_PID_PATH" ] || ! kill -0 $(cat "$NGINX_PID_PATH" 2>/dev/null) 2>/dev/null; then
        warn "Nginx当前未运行，直接启动新版本"
        if "$NGINX_SBIN" -c "$NGINX_CONF" >> "$LOG_FILE" 2>&1; then
            ROLLBACK_NEEDED=false
            log "新版本已启动"
            return
        else
            err "启动失败"
            return 1
        fi
    fi

    local old_pid=$(cat "$NGINX_PID_PATH")
    log "当前master PID: $old_pid"

    # 尝试USR2热升级
    if try_usr2_upgrade "$old_pid"; then
        ROLLBACK_NEEDED=false
        log "USR2 热升级完成（零中断）"
        return
    fi

    # USR2失败，回退到优雅重启方式
    warn "USR2热升级失败，回退到优雅重启方式（中断时间极短）"
    fallback_graceful_restart "$old_pid"
}

try_usr2_upgrade() {
    local old_pid=$1

    # 检查nginx是否以完整路径启动（决定USR2能否成功）
    local cmdline=$(tr '\0' ' ' < /proc/$old_pid/cmdline 2>/dev/null | awk '{print $1}')
    if [ -n "$cmdline" ] && [[ "$cmdline" != /* ]]; then
        warn "nginx以相对路径 '$cmdline' 启动，USR2可能失败"
        warn "直接使用优雅重启方式"
        return 1
    fi

    log "发送 USR2 信号..."
    kill -USR2 "$old_pid"

    local i=0
    while [ ! -f "${NGINX_PID_PATH}.oldbin" ] && [ $i -lt 10 ]; do
        sleep 1; i=$((i+1))
    done

    if [ ! -f "${NGINX_PID_PATH}.oldbin" ]; then
        warn "USR2: 等待10秒后新master未启动"
        return 1
    fi

    local new_pid=$(cat "$NGINX_PID_PATH")
    log "新master PID: $new_pid"

    # 优雅关闭旧worker
    log "发送 WINCH 信号，关闭旧worker..."
    kill -WINCH "$old_pid"

    i=0
    while [ $i -lt 60 ]; do
        local workers=$(ps -o pid= --ppid "$old_pid" 2>/dev/null | wc -l || \
                        pgrep -P "$old_pid" 2>/dev/null | wc -l || echo 1)
        [ "$workers" -eq 0 ] && break
        sleep 1; i=$((i+1))
    done

    # 验证新进程
    if ! kill -0 "$new_pid" 2>/dev/null; then
        err "新master异常退出"
        return 1
    fi

    # 关闭旧master
    log "发送 QUIT 信号，关闭旧master..."
    kill -QUIT "$old_pid" 2>/dev/null || true

    i=0
    while kill -0 "$old_pid" 2>/dev/null && [ $i -lt 10 ]; do
        sleep 1; i=$((i+1))
    done
    rm -f "${NGINX_PID_PATH}.oldbin" 2>/dev/null
    return 0
}

fallback_graceful_restart() {
    local old_pid=$1

    log "优雅停止旧进程..."
    kill -QUIT "$old_pid"

    # 等待旧进程退出
    local i=0
    while kill -0 "$old_pid" 2>/dev/null && [ $i -lt 30 ]; do
        sleep 1; i=$((i+1))
    done

    # 如果QUIT没退出，强制停止
    if kill -0 "$old_pid" 2>/dev/null; then
        warn "QUIT超时，发送TERM信号..."
        kill -TERM "$old_pid" 2>/dev/null || true
        sleep 2
    fi

    # 用完整路径启动新版本
    log "启动新版本: $NGINX_SBIN"
    if "$NGINX_SBIN" -c "$NGINX_CONF" >> "$LOG_FILE" 2>&1; then
        ROLLBACK_NEEDED=false
        log "新版本已启动"
    else
        err "新版本启动失败"
        return 1
    fi
}

upgrade_by_source() {
    source_check_deps
    do_backup
    source_download
    source_compile
    source_test_config
    source_graceful_upgrade
}

# ============================================================
#                    YUM/DNF 包管理器升级流程
# ============================================================

upgrade_by_yum() {
    do_backup

    # 检测nginx源
    local repo_file=$(grep -rl "nginx.org" /etc/yum.repos.d/ 2>/dev/null | head -1)
    if [ -z "$repo_file" ]; then
        log "未检测到nginx官方源，正在添加..."
        cat > /etc/yum.repos.d/nginx.repo << 'REPO'
[nginx-stable]
name=nginx stable repo
baseurl=http://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck=1
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true

[nginx-mainline]
name=nginx mainline repo
baseurl=http://nginx.org/packages/mainline/centos/$releasever/$basearch/
gpgcheck=1
enabled=0
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true
REPO
        log "nginx官方源已添加"
    fi

    # 判断目标版本属于stable还是mainline
    local minor=$(echo "$TARGET_VERSION" | cut -d. -f2)
    if [ $((minor % 2)) -eq 1 ]; then
        log "目标版本为mainline分支，启用mainline源..."
        if command -v dnf &>/dev/null; then
            dnf config-manager --set-enabled nginx-mainline 2>/dev/null || \
                sed -i '/\[nginx-mainline\]/,/^$/s/enabled=0/enabled=1/' /etc/yum.repos.d/nginx.repo
        else
            sed -i '/\[nginx-mainline\]/,/^$/s/enabled=0/enabled=1/' /etc/yum.repos.d/nginx.repo
        fi
    fi

    # 检查目标版本是否可用
    log "检查目标版本 $TARGET_VERSION 是否可用..."
    local available=""
    if command -v dnf &>/dev/null; then
        available=$(dnf list available nginx --showduplicates 2>/dev/null | grep "$TARGET_VERSION" || true)
    else
        available=$(yum list available nginx --showduplicates 2>/dev/null | grep "$TARGET_VERSION" || true)
    fi

    if [ -z "$available" ]; then
        err "在YUM源中未找到版本 $TARGET_VERSION"
        err "可用版本:"
        yum list available nginx --showduplicates 2>/dev/null | grep nginx | tail -10 | tee -a "$LOG_FILE"
        exit 1
    fi

    log "开始升级..."
    if command -v dnf &>/dev/null; then
        dnf upgrade -y "nginx-${TARGET_VERSION}" 2>&1 | tee -a "$LOG_FILE"
    else
        yum update -y "nginx-${TARGET_VERSION}" 2>&1 | tee -a "$LOG_FILE"
    fi

    # 重载配置
    if systemctl is-active nginx &>/dev/null; then
        log "重载Nginx配置..."
        nginx -t >> "$LOG_FILE" 2>&1 && systemctl reload nginx
    fi
    log "YUM升级完成"
}

# ============================================================
#                    APT 包管理器升级流程
# ============================================================

upgrade_by_apt() {
    do_backup

    # 检测nginx源
    local has_repo=$(grep -r "nginx.org" /etc/apt/sources.list /etc/apt/sources.list.d/ 2>/dev/null | head -1)
    if [ -z "$has_repo" ]; then
        log "未检测到nginx官方源，正在添加..."
        apt-get install -y curl gnupg2 ca-certificates lsb-release >> "$LOG_FILE" 2>&1
        curl -fsSL https://nginx.org/keys/nginx_signing.key | gpg --dearmor -o /usr/share/keyrings/nginx-archive-keyring.gpg
        local codename=$(lsb_release -cs)
        local os_name=$(. /etc/os-release && echo "$ID")
        echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] http://nginx.org/packages/${os_name} ${codename} nginx" \
            > /etc/apt/sources.list.d/nginx.list
        log "nginx官方源已添加"
    fi

    apt-get update >> "$LOG_FILE" 2>&1

    # 检查目标版本是否可用
    log "检查目标版本 $TARGET_VERSION 是否可用..."
    local available=$(apt-cache showpkg nginx 2>/dev/null | grep "$TARGET_VERSION" || true)
    if [ -z "$available" ]; then
        err "在APT源中未找到版本 $TARGET_VERSION"
        err "可用版本:"
        apt-cache policy nginx 2>/dev/null | tee -a "$LOG_FILE"
        exit 1
    fi

    log "开始升级..."
    apt-get install -y --only-upgrade "nginx=${TARGET_VERSION}*" 2>&1 | tee -a "$LOG_FILE"

    if systemctl is-active nginx &>/dev/null; then
        log "重载Nginx配置..."
        nginx -t >> "$LOG_FILE" 2>&1 && systemctl reload nginx
    fi
    log "APT升级完成"
}

# ============================================================
#                    APK 包管理器升级流程 (Alpine)
# ============================================================

upgrade_by_apk() {
    do_backup
    log "Alpine APK 升级..."

    # Alpine通过更新仓库获取新版本
    apk update >> "$LOG_FILE" 2>&1

    local available=$(apk policy nginx 2>/dev/null | grep "$TARGET_VERSION" || true)
    if [ -z "$available" ]; then
        warn "APK源中未找到精确版本 $TARGET_VERSION，尝试升级到最新可用版本"
    fi

    apk upgrade nginx 2>&1 | tee -a "$LOG_FILE"

    if rc-service nginx status &>/dev/null 2>&1; then
        log "重载Nginx..."
        nginx -t >> "$LOG_FILE" 2>&1 && rc-service nginx reload
    elif systemctl is-active nginx &>/dev/null; then
        nginx -t >> "$LOG_FILE" 2>&1 && systemctl reload nginx
    fi
    log "APK升级完成"
}

# ============================================================
#                    Zypper 包管理器升级流程 (openSUSE/SLES)
# ============================================================

upgrade_by_zypper() {
    do_backup
    log "Zypper 升级..."

    zypper refresh >> "$LOG_FILE" 2>&1

    local available=$(zypper search -s nginx 2>/dev/null | grep "$TARGET_VERSION" || true)
    if [ -z "$available" ]; then
        err "Zypper源中未找到版本 $TARGET_VERSION"
        err "可用版本:"
        zypper search -s nginx 2>/dev/null | tee -a "$LOG_FILE"
        exit 1
    fi

    zypper update -y nginx 2>&1 | tee -a "$LOG_FILE"

    if systemctl is-active nginx &>/dev/null; then
        log "重载Nginx..."
        nginx -t >> "$LOG_FILE" 2>&1 && systemctl reload nginx
    fi
    log "Zypper升级完成"
}

# ============================================================
#                    Docker 升级流程
# ============================================================

upgrade_by_docker() {
    log "Docker方式升级"
    echo ""
    log "检测到Nginx容器: $DOCKER_CONTAINER"

    # 检测是否由docker-compose管理
    local compose_file=""
    local compose_project=""
    local compose_service=""

    # 方式1: 从容器label获取compose信息
    compose_file=$(docker inspect --format='{{index .Config.Labels "com.docker.compose.project.config_files"}}' "$DOCKER_CONTAINER" 2>/dev/null || true)
    compose_project=$(docker inspect --format='{{index .Config.Labels "com.docker.compose.project"}}' "$DOCKER_CONTAINER" 2>/dev/null || true)
    compose_service=$(docker inspect --format='{{index .Config.Labels "com.docker.compose.service"}}' "$DOCKER_CONTAINER" 2>/dev/null || true)

    # 方式2: 从working_dir获取
    if [ -z "$compose_file" ] && [ -n "$compose_project" ]; then
        local working_dir=$(docker inspect --format='{{index .Config.Labels "com.docker.compose.project.working_dir"}}' "$DOCKER_CONTAINER" 2>/dev/null || true)
        if [ -n "$working_dir" ]; then
            for f in docker-compose.yml docker-compose.yaml compose.yml compose.yaml; do
                [ -f "$working_dir/$f" ] && compose_file="$working_dir/$f" && break
            done
        fi
    fi

    if [ -n "$compose_file" ] && [ -f "$compose_file" ]; then
        upgrade_docker_compose "$compose_file" "$compose_service"
    else
        upgrade_docker_plain
    fi
}

# --- docker-compose 方式升级 ---
upgrade_docker_compose() {
    local compose_file=$1
    local service_name=$2

    log "检测到 docker-compose 管理"
    log "  Compose文件: $compose_file"
    log "  服务名: $service_name"
    echo ""

    # 获取当前镜像
    local current_image=$(docker inspect --format='{{.Config.Image}}' "$DOCKER_CONTAINER")
    local new_image="nginx:${TARGET_VERSION}"
    log "  当前镜像: $current_image"
    log "  目标镜像: $new_image"
    echo ""

    echo -e "  ${GREEN}升级方式${NC}: 修改compose文件中的镜像版本，然后重建容器"
    echo -e "  ${YELLOW}注意${NC}: 挂载卷、端口、网络等配置由compose文件管理，不会丢失"
    echo ""
    read -p "确认升级？(y/N): " confirm
    if [[ ! "$confirm" =~ ^[yY]$ ]]; then
        log "用户取消"
        exit 0
    fi

    # 备份compose文件
    cp -f "$compose_file" "${compose_file}.bak.$(date +%Y%m%d_%H%M%S)"
    log "已备份compose文件"

    # 修改镜像版本（只替换版本号，保留仓库地址和后缀）
    local compose_dir=$(dirname "$compose_file")
    local old_image_line=$(grep "image:.*nginx" "$compose_file" | head -1)

    if [ -z "$old_image_line" ]; then
        warn "未能在compose文件中找到nginx镜像配置"
        warn "请手动修改 $compose_file 中的镜像版本为 $TARGET_VERSION"
        exit 1
    fi

    log "当前镜像配置: $old_image_line"

    # 提取当前镜像全名（去掉 image: 前缀和空格）
    local old_full_image=$(echo "$old_image_line" | sed 's/.*image:[[:space:]]*//' | sed 's/[[:space:]]*$//')

    # 智能替换版本号：保留仓库前缀和后缀（如 -alpine）
    # 例: registry.cn/nginx:1.28.0-alpine -> registry.cn/nginx:1.30.1-alpine
    local new_full_image=$(echo "$old_full_image" | sed "s/\([0-9]\+\.[0-9]\+\.[0-9]\+\)/$TARGET_VERSION/")

    echo ""
    echo -e "  镜像变更: ${RED}${old_full_image}${NC} -> ${GREEN}${new_full_image}${NC}"
    echo ""
    read -p "镜像名称是否正确？(y/N): " img_confirm
    if [[ ! "$img_confirm" =~ ^[yY]$ ]]; then
        read -p "请输入完整的目标镜像名称: " new_full_image
    fi

    sed -i "s|${old_full_image}|${new_full_image}|" "$compose_file"
    log "已更新: $old_full_image -> $new_full_image"

    # 拉取新镜像
    log "拉取新镜像..."
    local compose_cmd=""
    if command -v docker-compose &>/dev/null; then
        compose_cmd="docker-compose"
    elif docker compose version &>/dev/null 2>&1; then
        compose_cmd="docker compose"
    else
        err "未找到 docker-compose 或 docker compose 命令"
        exit 1
    fi

    cd "$compose_dir"
    $compose_cmd -f "$compose_file" pull "$service_name" 2>&1 | tee -a "$LOG_FILE"

    # 重建容器
    log "重建容器（自动停止旧容器并启动新容器）..."
    $compose_cmd -f "$compose_file" up -d "$service_name" 2>&1 | tee -a "$LOG_FILE"

    # 验证
    sleep 2
    if docker ps | grep -q "$DOCKER_CONTAINER\|$service_name"; then
        log "docker-compose 升级成功"
    else
        err "容器未正常启动，正在回滚compose文件..."
        local latest_bak=$(ls -t "${compose_file}.bak."* 2>/dev/null | head -1)
        [ -n "$latest_bak" ] && cp -f "$latest_bak" "$compose_file"
        $compose_cmd -f "$compose_file" up -d "$service_name" 2>&1 | tee -a "$LOG_FILE"
        exit 1
    fi
}

# --- 普通docker方式升级 ---
upgrade_docker_plain() {
    log "普通Docker容器（非docker-compose）"
    echo ""

    local current_image=$(docker inspect --format='{{.Config.Image}}' "$DOCKER_CONTAINER")
    local new_image="nginx:${TARGET_VERSION}"
    log "  当前镜像: $current_image"
    log "  目标镜像: $new_image"
    echo ""

    echo -e "  ${YELLOW}注意${NC}: 将停止旧容器，用相同参数启动新容器"
    echo ""
    read -p "确认升级？(y/N): " confirm
    if [[ ! "$confirm" =~ ^[yY]$ ]]; then
        log "用户取消"
        exit 0
    fi

    log "拉取镜像: $new_image"
    docker pull "$new_image" 2>&1 | tee -a "$LOG_FILE"

    # 导出容器运行参数
    local run_cmd=$(docker inspect --format='{{range .Mounts}}-v {{.Source}}:{{.Destination}} {{end}}' "$DOCKER_CONTAINER")
    local ports=$(docker inspect --format='{{range $p, $conf := .NetworkSettings.Ports}}{{range $conf}}-p {{.HostPort}}:{{$p}} {{end}}{{end}}' "$DOCKER_CONTAINER" | sed 's|/tcp||g')
    local container_name=$(docker inspect --format='{{.Name}}' "$DOCKER_CONTAINER" | sed 's|^/||')
    local restart_policy=$(docker inspect --format='{{.HostConfig.RestartPolicy.Name}}' "$DOCKER_CONTAINER")
    local restart_flag=""
    [ -n "$restart_policy" ] && [ "$restart_policy" != "no" ] && restart_flag="--restart=$restart_policy"

    log "停止旧容器..."
    docker stop "$DOCKER_CONTAINER" >> "$LOG_FILE" 2>&1
    docker rename "$DOCKER_CONTAINER" "${container_name}_old_$(date +%s)" >> "$LOG_FILE" 2>&1

    log "启动新容器..."
    eval "docker run -d --name $container_name $restart_flag $ports $run_cmd $new_image" 2>&1 | tee -a "$LOG_FILE"

    if docker ps | grep -q "$container_name"; then
        log "Docker升级成功"
        log "旧容器已重命名保留，确认无误后可删除"
    else
        err "新容器启动失败，正在恢复..."
        docker rm "$container_name" 2>/dev/null || true
        local old_name=$(docker ps -a --format '{{.Names}}' | grep "${container_name}_old_" | head -1)
        [ -n "$old_name" ] && docker rename "$old_name" "$container_name" && docker start "$container_name"
        exit 1
    fi
}

# ============================================================
#                    升级后验证
# ============================================================

post_check() {
    echo ""
    title "========== 升级结果 =========="

    if [ "$INSTALL_METHOD" = "docker" ]; then
        local final_ver=$(docker exec "$DOCKER_CONTAINER" nginx -v 2>&1 | sed -n 's|.*nginx/\([0-9.]*\).*|\1|p' || echo "未知")
    else
        local final_ver=$("$NGINX_SBIN" -v 2>&1 | sed -n 's|.*nginx/\([0-9.]*\).*|\1|p')
    fi

    log "升级方式: $INSTALL_METHOD"
    log "原版本: $CURRENT_VERSION"
    log "新版本: $final_ver"

    if [ "$final_ver" = "$TARGET_VERSION" ]; then
        log "版本验证: 通过"
    else
        err "版本验证: 失败（期望 $TARGET_VERSION，实际 $final_ver）"
    fi

    if [ "$INSTALL_METHOD" != "docker" ]; then
        if [ -f "$NGINX_PID_PATH" ] && kill -0 $(cat "$NGINX_PID_PATH") 2>/dev/null; then
            log "进程状态: 运行中 (PID: $(cat "$NGINX_PID_PATH"))"
        else
            warn "进程状态: 未运行"
        fi
        if "$NGINX_SBIN" -t &>/dev/null; then
            log "配置测试: 通过"
        else
            err "配置测试: 失败"
        fi
    fi

    echo ""
    [ -n "$BACKUP_DIR" ] && log "备份位置: $BACKUP_DIR"
    log "升级日志: $LOG_FILE"
    if [ "$INSTALL_METHOD" = "source" ] && [ -n "$BACKUP_DIR" ]; then
        log "回滚命令: cp $BACKUP_DIR/nginx.old $NGINX_SBIN && nginx -s reload"
    fi
    echo ""
}

# ============================================================
#                         回滚功能
# ============================================================

do_rollback() {
    log "正在查找可用备份..."

    # 查找备份目录
    local backup_base=""
    if [ -n "$NGINX_PREFIX" ]; then
        backup_base="$NGINX_PREFIX"
    fi

    # 同时搜索两个可能的备份位置
    local backups=()
    if [ -n "$backup_base" ] && [ -d "$backup_base" ]; then
        while IFS= read -r dir; do
            backups+=("$dir")
        done < <(find "$backup_base" -maxdepth 1 -type d -name "backup_*" 2>/dev/null | sort -r)
    fi
    if [ -d "/opt/nginx_backup" ]; then
        while IFS= read -r dir; do
            backups+=("$dir")
        done < <(find "/opt/nginx_backup" -maxdepth 1 -type d -name "backup_*" 2>/dev/null | sort -r)
    fi

    if [ ${#backups[@]} -eq 0 ]; then
        err "未找到任何备份目录"
        err "备份通常位于: ${NGINX_PREFIX}/backup_* 或 /opt/nginx_backup/backup_*"
        exit 1
    fi

    echo ""
    title "可用备份列表:"
    echo ""
    for i in "${!backups[@]}"; do
        local bdir="${backups[$i]}"
        local ver="未知"
        local method="未知"
        local btime=$(basename "$bdir" | sed 's/backup_//')
        # 格式化时间: 20260515_113110 -> 2026-05-15 11:31:10
        local ftime=$(echo "$btime" | sed 's/\([0-9]\{4\}\)\([0-9]\{2\}\)\([0-9]\{2\}\)_\([0-9]\{2\}\)\([0-9]\{2\}\)\([0-9]\{2\}\)/\1-\2-\3 \4:\5:\6/')
        if [ -f "$bdir/meta.txt" ]; then
            ver=$(grep "^version=" "$bdir/meta.txt" | cut -d= -f2)
            method=$(grep "^method=" "$bdir/meta.txt" | cut -d= -f2)
        elif [ -f "$bdir/version.txt" ]; then
            ver=$(cat "$bdir/version.txt")
        fi
        printf "  [%d] 版本: ${CYAN}%s${NC} | 方式: %s | 时间: %s\n" $((i+1)) "$ver" "$method" "$ftime"
    done

    echo ""
    read -p "请选择要回滚到的备份编号: " choice

    if ! echo "$choice" | grep -qE '^[0-9]+$' || [ "$choice" -lt 1 ] || [ "$choice" -gt ${#backups[@]} ]; then
        err "无效选择"
        exit 1
    fi

    local selected="${backups[$((choice-1))]}"
    log "选择备份: $selected"

    # 验证备份完整性
    if [ ! -f "$selected/nginx.old" ]; then
        err "备份不完整: 缺少 nginx.old 二进制文件"
        exit 1
    fi

    # 执行回滚
    log "正在恢复二进制文件..."
    cp -f "$selected/nginx.old" "$NGINX_SBIN"

    # 询问是否恢复配置
    if [ -d "$selected/conf" ]; then
        echo ""
        echo -e "${YELLOW}是否恢复配置文件？${NC}"
        echo -e "  ${GREEN}y${NC} = 同时将配置文件恢复到备份时的状态（覆盖当前配置）"
        echo -e "  ${RED}N${NC} = 只回滚二进制版本，保留当前配置文件不动"
        echo ""
        read -p "恢复配置文件？(y/N): " restore_conf
        if [[ "$restore_conf" =~ ^[yY]$ ]]; then
            local conf_dir=$(dirname "$NGINX_CONF")
            cp -a "$selected/conf/"* "$conf_dir/"
            log "配置文件已恢复"
        fi
    fi

    # 测试配置
    if ! "$NGINX_SBIN" -t >> "$LOG_FILE" 2>&1; then
        err "回滚后配置测试失败！"
        "$NGINX_SBIN" -t 2>&1
        exit 1
    fi

    # 重启nginx以加载旧二进制（reload只重载配置，不换二进制）
    if [ -f "$NGINX_PID_PATH" ] && kill -0 $(cat "$NGINX_PID_PATH") 2>/dev/null; then
        log "重启Nginx以加载旧版本二进制..."
        local pid=$(cat "$NGINX_PID_PATH")
        kill -QUIT "$pid" 2>/dev/null || true
        local i=0
        while kill -0 "$pid" 2>/dev/null && [ $i -lt 15 ]; do
            sleep 1; i=$((i+1))
        done
        "$NGINX_SBIN" -c "$NGINX_CONF"
    else
        log "启动Nginx..."
        "$NGINX_SBIN" -c "$NGINX_CONF"
    fi

    local rolled_ver=$("$NGINX_SBIN" -v 2>&1 | sed -n 's|.*nginx/\([0-9.]*\).*|\1|p')
    echo ""
    log "回滚完成！"
    log "当前版本: $rolled_ver"
    if [ -f "$NGINX_PID_PATH" ] && kill -0 $(cat "$NGINX_PID_PATH") 2>/dev/null; then
        log "进程状态: 运行中 (PID: $(cat "$NGINX_PID_PATH"))"
    fi
}

# ============================================================
#                         主流程
# ============================================================

main() {
    echo ""
    echo "============================================"
    echo "       Nginx 通用升级/回滚脚本"
    echo "       支持: 源码 / YUM / APT / APK / Zypper / Docker"
    echo "============================================"
    echo ""

    check_root

    echo -e "请选择操作:"
    echo ""
    echo -e "  [1] ${GREEN}升级 Nginx${NC}"
    echo -e "  [2] ${YELLOW}回滚 Nginx${NC}"
    echo -e "  [3] ${CYAN}清理旧备份${NC}"
    echo ""
    read -p "请输入选择 (1/2/3): " action_choice

    case "$action_choice" in
        1)
            detect_install_method
            if [ "$INSTALL_METHOD" != "docker" ]; then
                detect_nginx_info
            fi
            get_target_version

            echo ""
            title "即将执行升级:"
            echo "  安装方式: $INSTALL_METHOD"
            echo "  ${CURRENT_VERSION} -> ${TARGET_VERSION}"
            [ "$INSTALL_METHOD" = "source" ] && echo "  编译参数: $CONFIGURE_ARGS"
            echo ""

            case "$INSTALL_METHOD" in
                source) upgrade_by_source ;;
                yum)    upgrade_by_yum ;;
                dnf)    upgrade_by_yum ;;
                apt)    upgrade_by_apt ;;
                apk)    upgrade_by_apk ;;
                zypper) upgrade_by_zypper ;;
                docker) upgrade_by_docker ;;
                *)      err "未知安装方式: $INSTALL_METHOD"; exit 1 ;;
            esac

            post_check

            if [ -n "$BUILD_DIR" ] && [ -d "$BUILD_DIR" ]; then
                rm -rf "$BUILD_DIR"
                BUILD_DIR=""
            fi
            log "全部完成！"
            ;;
        2)
            detect_install_method
            if [ "$INSTALL_METHOD" != "docker" ]; then
                detect_nginx_info
            fi
            do_rollback
            ;;
        3)
            detect_install_method
            if [ "$INSTALL_METHOD" != "docker" ]; then
                detect_nginx_info
            fi
            clean_backups
            ;;
        *)
            err "无效选择，请输入 1、2 或 3"
            exit 1
            ;;
    esac
}

# === 清理旧备份 ===
clean_backups() {
    log "正在查找备份..."

    local backup_base=""
    [ -n "$NGINX_PREFIX" ] && backup_base="$NGINX_PREFIX"

    local backups=()
    if [ -n "$backup_base" ] && [ -d "$backup_base" ]; then
        while IFS= read -r dir; do
            backups+=("$dir")
        done < <(find "$backup_base" -maxdepth 1 -type d -name "backup_*" 2>/dev/null | sort -r)
    fi
    if [ -d "/opt/nginx_backup" ]; then
        while IFS= read -r dir; do
            backups+=("$dir")
        done < <(find "/opt/nginx_backup" -maxdepth 1 -type d -name "backup_*" 2>/dev/null | sort -r)
    fi

    if [ ${#backups[@]} -eq 0 ]; then
        log "没有找到任何备份"
        exit 0
    fi

    echo ""
    title "当前备份列表:"
    echo ""
    local total_size=0
    for i in "${!backups[@]}"; do
        local bdir="${backups[$i]}"
        local ver="未知"
        local btime=$(basename "$bdir" | sed 's/backup_//')
        local ftime=$(echo "$btime" | sed 's/\([0-9]\{4\}\)\([0-9]\{2\}\)\([0-9]\{2\}\)_\([0-9]\{2\}\)\([0-9]\{2\}\)\([0-9]\{2\}\)/\1-\2-\3 \4:\5:\6/')
        local size=$(du -sh "$bdir" 2>/dev/null | awk '{print $1}')
        [ -f "$bdir/meta.txt" ] && ver=$(grep "^version=" "$bdir/meta.txt" | cut -d= -f2)
        printf "  [%d] 版本: %s | 时间: %s | 大小: %s\n" $((i+1)) "$ver" "$ftime" "$size"
    done

    echo ""
    echo -e "  ${GREEN}a${NC} = 只保留最新一个备份，删除其余所有"
    echo -e "  ${YELLOW}编号${NC} = 删除指定备份（多个用空格分隔，如: 2 3 4）"
    echo -e "  ${RED}q${NC} = 取消"
    echo ""
    read -p "请输入选择: " clean_choice

    if [ "$clean_choice" = "q" ]; then
        exit 0
    elif [ "$clean_choice" = "a" ]; then
        for i in "${!backups[@]}"; do
            [ $i -eq 0 ] && continue
            rm -rf "${backups[$i]}"
            log "已删除: ${backups[$i]}"
        done
        log "清理完成，保留最新备份: ${backups[0]}"
    else
        for idx in $clean_choice; do
            if echo "$idx" | grep -qE '^[0-9]+$' && [ "$idx" -ge 1 ] && [ "$idx" -le ${#backups[@]} ]; then
                rm -rf "${backups[$((idx-1))]}"
                log "已删除: ${backups[$((idx-1))]}"
            else
                warn "跳过无效编号: $idx"
            fi
        done
        log "清理完成"
    fi
}

main "$@"
