GKLBB

当你经历了暴风雨,你也就成为了暴风雨

导航

软件研发 --- linux服务器信息收集脚本

经常有这么一个需求,在拿到一台linux服务器的时候,我想知道它的所有硬件基本信息,我无从获知。其次,我想获知它上面安装了哪些业务软件,我也无从获知
如果要开发一个linux终端服务的基本信息收集工具,可以执行后查询主要的硬件信息和所有非系统的业务软件

 

# Linux 服务器基本信息收集工具

## 完整代码

# ```bash
#!/bin/bash
# ============================================================
# Linux 服务器信息收集工具 v2.0
# 修复版:基于实际运行结果修复所有已知问题
# ============================================================

# ==================== 颜色定义 ====================
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
PURPLE='\033[0;35m'
WHITE='\033[1;37m'
NC='\033[0m'
BOLD='\033[1m'

# ==================== 全局变量 ====================
REPORT_FILE=""

# ==================== 参数解析 ====================
parse_args() {
    while [[ $# -gt 0 ]]; do
        case $1 in
            --output|-o)
                REPORT_FILE="$2"
                shift 2
                ;;
            --help|-h)
                echo "用法: $0 [选项]"
                echo "  --output, -o <文件路径>  将报告输出到指定文件"
                echo "  --help,   -h             显示帮助信息"
                exit 0
                ;;
            *) shift ;;
        esac
    done
    if [[ -z "$REPORT_FILE" ]]; then
        REPORT_FILE="/tmp/server_info_report_$(date +%Y%m%d_%H%M%S).txt"
    fi
}

# ==================== 输出函数 ====================
output() {
    echo -e "$1" | tee -a "$REPORT_FILE"
}

print_section() {
    local title="$1"
    local line="━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    output ""
    output "${CYAN}${BOLD}${line}${NC}"
    output "${CYAN}${BOLD}  ▶ ${title}${NC}"
    output "${CYAN}${BOLD}${line}${NC}"
}

print_item() {
    local key="$1"
    local value="$2"
    printf "${GREEN}  %-30s${NC} ${WHITE}%s${NC}\n" "${key}:" "${value}" | \
        tee -a "$REPORT_FILE"
}

print_sub_section() {
    output "${YELLOW}${BOLD}  ── $1 ──${NC}"
}

print_found() {
    output "${GREEN}  ✔ $1${NC}"
}

print_not_found() {
    output "${RED}  ✘ $1${NC}"
}

print_warn() {
    output "${YELLOW}  ⚠ $1${NC}"
}

# ==================== 工具函数 ====================
cmd_exists() {
    command -v "$1" &>/dev/null
}

# 获取服务状态(修复:避免多余换行和unknown污染)
get_service_status() {
    local service_name="$1"
    local status=""
    # 尝试多个服务名
    for svc in "$@"; do
        status=$(systemctl is-active "$svc" 2>/dev/null)
        if [[ "$status" == "active" || "$status" == "inactive" || \
              "$status" == "failed" ]]; then
            echo "$status"
            return
        fi
    done
    echo "unknown"
}

# 获取进程中的可执行文件路径(用于检测不在PATH中的命令)
find_proc_exe() {
    local proc_pattern="$1"
    ps aux 2>/dev/null | grep -i "$proc_pattern" | grep -v grep | \
        head -1 | awk '{print $11}'
}

# 检测进程是否运行
is_proc_running() {
    local pattern="$1"
    ps aux 2>/dev/null | grep -v grep | grep -qi "$pattern"
}

# ==================== 报告头部 ====================
print_header() {
    local hostname
    hostname=$(hostname 2>/dev/null || echo "Unknown")
    local datetime
    datetime=$(date '+%Y-%m-%d %H:%M:%S')
    local user
    user=$(whoami 2>/dev/null || echo "Unknown")

    output ""
    output "${PURPLE}${BOLD}╔══════════════════════════════════════════════════════════════╗${NC}"
    output "${PURPLE}${BOLD}║          Linux 服务器信息收集报告  v2.0                     ║${NC}"
    output "${PURPLE}${BOLD}╚══════════════════════════════════════════════════════════════╝${NC}"
    output ""
    print_item "收集时间"  "$datetime"
    print_item "主机名"    "$hostname"
    print_item "执行用户"  "$user"
    print_item "报告文件"  "$REPORT_FILE"
}

# ============================================================
#                     一、操作系统信息
# ============================================================
collect_os_info() {
    print_section "一、操作系统信息"

    local os_name="Unknown"
    local os_version="Unknown"
    local os_id="unknown"

    if [[ -f /etc/os-release ]]; then
        os_name=$(grep -w "^NAME" /etc/os-release | \
            cut -d= -f2 | tr -d '"')
        os_version=$(grep -w "^VERSION" /etc/os-release | \
            cut -d= -f2 | tr -d '"')
        os_id=$(grep -w "^ID" /etc/os-release | \
            cut -d= -f2 | tr -d '"' | tr '[:upper:]' '[:lower:]')
        [[ -z "$os_version" ]] && \
            os_version=$(grep -w "^VERSION_ID" /etc/os-release | \
                cut -d= -f2 | tr -d '"')
    elif [[ -f /etc/redhat-release ]]; then
        os_name=$(cat /etc/redhat-release)
    fi

    print_item "操作系统"     "$os_name $os_version"
    print_item "系统ID"       "$os_id"
    print_item "内核版本"     "$(uname -r 2>/dev/null || echo 'Unknown')"
    print_item "系统架构"     "$(uname -m 2>/dev/null || echo 'Unknown')"
    print_item "系统位数"     "$(getconf LONG_BIT 2>/dev/null || echo 'Unknown')位"

    local uptime_str
    uptime_str=$(uptime -p 2>/dev/null || \
        uptime | awk -F'up ' '{print $2}' | awk -F',' '{print $1}')
    print_item "系统运行时间" "$uptime_str"

    local boot_time
    boot_time=$(who -b 2>/dev/null | awk '{print $3, $4}')
    [[ -z "$boot_time" ]] && boot_time=$(uptime -s 2>/dev/null || echo "N/A")
    print_item "最后启动时间" "$boot_time"

    local timezone
    if cmd_exists timedatectl; then
        timezone=$(timedatectl 2>/dev/null | \
            grep "Time zone" | awk '{print $3}')
    fi
    [[ -z "$timezone" ]] && timezone=$(date +%Z 2>/dev/null || echo "N/A")
    print_item "系统时区"     "$timezone"

    local security_module="None"
    if cmd_exists getenforce; then
        security_module="SELinux: $(getenforce 2>/dev/null)"
    elif cmd_exists aa-status; then
        security_module="AppArmor: $(aa-status --enabled \
            2>/dev/null && echo 'enabled' || echo 'disabled')"
    fi
    print_item "安全模块"     "$security_module"

    # 【修复】虚拟化类型:避免输出多余的"none"
    local virt_type="物理机"
    if cmd_exists systemd-detect-virt; then
        local virt_result
        virt_result=$(systemd-detect-virt 2>/dev/null)
        if [[ -n "$virt_result" && "$virt_result" != "none" ]]; then
            virt_type="虚拟机($virt_result)"
        fi
    fi
    # 检查常见虚拟化标志
    grep -qi "hypervisor" /proc/cpuinfo 2>/dev/null && \
        [[ "$virt_type" == "物理机" ]] && virt_type="虚拟机(Hypervisor)"
    [[ -d /proc/vz ]] && virt_type="OpenVZ"
    [[ -f /proc/xen/capabilities ]] && virt_type="Xen"
    grep -qi "vmware" /sys/class/dmi/id/sys_vendor 2>/dev/null && \
        virt_type="VMware"
    print_item "虚拟化类型"   "$virt_type"
}

# ============================================================
#                     二、CPU 信息
# ============================================================
collect_cpu_info() {
    print_section "二、CPU 信息"

    if [[ ! -f /proc/cpuinfo ]]; then
        print_warn "无法读取 /proc/cpuinfo"
        return
    fi

    local cpu_model cpu_physical cpu_cores cpu_threads
    local cpu_mhz cpu_cache cpu_vendor

    cpu_model=$(grep -m1 "model name" /proc/cpuinfo | \
        cut -d: -f2 | sed 's/^[ \t]*//')
    cpu_physical=$(grep "physical id" /proc/cpuinfo | \
        sort -u | wc -l)
    cpu_cores=$(grep "cpu cores" /proc/cpuinfo | \
        head -1 | cut -d: -f2 | tr -d ' ')
    cpu_threads=$(grep -c "^processor" /proc/cpuinfo)
    cpu_mhz=$(grep -m1 "cpu MHz" /proc/cpuinfo | \
        cut -d: -f2 | tr -d ' ' | cut -d. -f1)
    cpu_cache=$(grep -m1 "cache size" /proc/cpuinfo | \
        cut -d: -f2 | sed 's/^[ \t]*//')
    cpu_vendor=$(grep -m1 "vendor_id" /proc/cpuinfo | \
        cut -d: -f2 | sed 's/^[ \t]*//')

    [[ -z "$cpu_physical" || "$cpu_physical" -eq 0 ]] && \
        cpu_physical=1
    [[ -z "$cpu_cores" ]] && cpu_cores="N/A"

    print_item "CPU型号"       "${cpu_model:-Unknown}"
    print_item "CPU厂商"       "${cpu_vendor:-Unknown}"
    print_item "物理CPU数"     "${cpu_physical}"
    print_item "每颗核心数"    "${cpu_cores}"
    print_item "逻辑线程数"    "${cpu_threads}"
    print_item "当前频率"      "${cpu_mhz:-N/A} MHz"
    print_item "缓存大小"      "${cpu_cache:-N/A}"

    # CPU特性
    local cpu_features=""
    local flags_line
    flags_line=$(grep -m1 "^flags" /proc/cpuinfo)
    echo "$flags_line" | grep -qw "vmx\|svm" && \
        cpu_features+="虚拟化(VT-x/AMD-V) "
    echo "$flags_line" | grep -qw "aes"  && cpu_features+="AES-NI "
    echo "$flags_line" | grep -qw "avx"  && cpu_features+="AVX "
    echo "$flags_line" | grep -qw "avx2" && cpu_features+="AVX2 "
    echo "$flags_line" | grep -qw "ht"   && cpu_features+="超线程(HT) "
    print_item "CPU特性"       "${cpu_features:-N/A}"

    local load
    load=$(cat /proc/loadavg 2>/dev/null | awk '{print $1, $2, $3}')
    print_item "负载(1/5/15m)"  "$load"

    # CPU使用率
    if cmd_exists top; then
        local cpu_usage
        cpu_usage=$(top -bn1 2>/dev/null | grep -E "^(%Cpu|Cpu)" | \
            awk '{
                for(i=1;i<=NF;i++){
                    if($i~/id,/||$(i+1)~/id,/){
                        gsub(/,/,"",$i); idle=$i
                    }
                }
                printf "%.1f", 100-idle
            }')
        print_item "CPU使用率"  "${cpu_usage:-N/A}%"
    fi

    # 温度
    if [[ -f /sys/class/thermal/thermal_zone0/temp ]]; then
        local temp
        temp=$(cat /sys/class/thermal/thermal_zone0/temp 2>/dev/null)
        [[ -n "$temp" && "$temp" -gt 0 ]] 2>/dev/null && \
            print_item "CPU温度" "$((temp/1000))°C"
    elif cmd_exists sensors; then
        local temp
        temp=$(sensors 2>/dev/null | grep -i "core 0" | \
            head -1 | grep -oP '\+\d+\.\d+°C' | head -1)
        [[ -n "$temp" ]] && print_item "CPU温度" "$temp"
    fi
}

# ============================================================
#                     三、内存信息
# ============================================================
collect_memory_info() {
    print_section "三、内存信息"

    if [[ ! -f /proc/meminfo ]]; then
        print_warn "无法读取 /proc/meminfo"
        return
    fi

    local mem_total_kb mem_free_kb mem_available_kb
    local swap_total_kb swap_free_kb

    mem_total_kb=$(awk '/^MemTotal/{print $2}' /proc/meminfo)
    mem_free_kb=$(awk '/^MemFree/{print $2}' /proc/meminfo)
    mem_available_kb=$(awk '/^MemAvailable/{print $2}' /proc/meminfo)
    swap_total_kb=$(awk '/^SwapTotal/{print $2}' /proc/meminfo)
    swap_free_kb=$(awk '/^SwapFree/{print $2}' /proc/meminfo)

    local mem_used_kb=$(( mem_total_kb - mem_available_kb ))

    local mem_total mem_free mem_available mem_used mem_usage_pct
    mem_total=$(awk "BEGIN{printf '%.2f GB', $mem_total_kb/1024/1024}")
    mem_free=$(awk "BEGIN{printf '%.2f GB', $mem_free_kb/1024/1024}")
    mem_available=$(awk "BEGIN{printf '%.2f GB', \
        $mem_available_kb/1024/1024}")
    mem_used=$(awk "BEGIN{printf '%.2f GB', $mem_used_kb/1024/1024}")
    mem_usage_pct=$(awk "BEGIN{printf '%.1f%%', \
        $mem_used_kb/$mem_total_kb*100}")

    local swap_used_kb=$(( swap_total_kb - swap_free_kb ))
    local swap_total swap_free swap_used swap_usage_pct
    swap_total=$(awk "BEGIN{printf '%.2f GB', $swap_total_kb/1024/1024}")
    swap_free=$(awk "BEGIN{printf '%.2f GB', $swap_free_kb/1024/1024}")
    swap_used=$(awk "BEGIN{printf '%.2f GB', $swap_used_kb/1024/1024}")
    if [[ "$swap_total_kb" -gt 0 ]]; then
        swap_usage_pct=$(awk "BEGIN{printf '%.1f%%', \
            $swap_used_kb/$swap_total_kb*100}")
    else
        swap_usage_pct="0%"
    fi

    local buffers
    buffers=$(awk '/^Buffers/{printf "%.2f GB", $2/1024/1024}' \
        /proc/meminfo)
    local cached
    cached=$(awk '/^Cached/{printf "%.2f GB", $2/1024/1024}' \
        /proc/meminfo | head -1)

    print_item "总内存"     "$mem_total"
    print_item "已使用"     "$mem_used ($mem_usage_pct)"
    print_item "可用内存"   "$mem_available"
    print_item "空闲内存"   "$mem_free"
    print_item "Buffers"    "$buffers"
    print_item "Cached"     "$cached"
    output ""
    print_item "Swap总量"   "$swap_total"
    print_item "Swap已用"   "$swap_used ($swap_usage_pct)"
    print_item "Swap空闲"   "$swap_free"

    # 【修复】物理内存条信息格式化输出
    output ""
    print_sub_section "物理内存条详情"

    if cmd_exists dmidecode && [[ $EUID -eq 0 ]]; then
        # 统计有效内存条数量(排除 No Module Installed)
        local dimm_count
        dimm_count=$(dmidecode -t memory 2>/dev/null | \
            awk '/Memory Device/{found=1} found && /Size:/{
                if ($0 !~ /No Module/) count++
            } END{print count+0}')
        print_item "有效内存条数量" "$dimm_count"
        output ""
        printf "  ${GREEN}%-6s %-15s %-10s %-10s %-20s %-30s${NC}\n" \
            "插槽" "容量" "类型" "频率" "厂商" "型号" | \
            tee -a "$REPORT_FILE"
        output "  $(printf '─%.0s' {1..90})"

        # 逐条解析内存条
        dmidecode -t memory 2>/dev/null | \
        awk '
        /^Memory Device/{
            # 输出上一条(如果有效)
            if (size != "" && size !~ /No Module/) {
                printf "  %-6s %-15s %-10s %-10s %-20s %-30s\n",
                    locator, size, type, speed, mfr, part
            }
            # 重置
            locator=""; size=""; type=""; speed=""; mfr=""; part=""
        }
        /^\s+Size:/{
            gsub(/^[[:space:]]+Size:[[:space:]]+/,""); size=$0
        }
        /^\s+Locator:/ && !/Bank/{
            gsub(/^[[:space:]]+Locator:[[:space:]]+/,""); locator=$0
        }
        /^\s+Type:/ && !/Factor|Detail/{
            gsub(/^[[:space:]]+Type:[[:space:]]+/,""); type=$0
        }
        /^\s+Speed:/ && !/Configured/{
            gsub(/^[[:space:]]+Speed:[[:space:]]+/,""); speed=$0
        }
        /^\s+Manufacturer:/{
            gsub(/^[[:space:]]+Manufacturer:[[:space:]]+/,""); mfr=$0
        }
        /^\s+Part Number:/{
            gsub(/^[[:space:]]+Part Number:[[:space:]]+/,""); part=$0
        }
        END{
            if (size != "" && size !~ /No Module/) {
                printf "  %-6s %-15s %-10s %-10s %-20s %-30s\n",
                    locator, size, type, speed, mfr, part
            }
        }
        ' | tee -a "$REPORT_FILE"
    else
        print_warn "需要 root 权限和 dmidecode 工具查看物理内存条信息"
    fi
}

# ============================================================
#                     四、磁盘信息
# ============================================================
collect_disk_info() {
    print_section "四、磁盘/存储信息"

    # 【修复】过滤掉 Docker overlay 层,只显示真实分区
    print_sub_section "磁盘分区使用情况"
    output ""
    printf "  ${GREEN}%-25s %-8s %-10s %-10s %-8s %-s${NC}\n" \
        "文件系统" "类型" "总大小" "已用" "使用率" "挂载点" | \
        tee -a "$REPORT_FILE"
    output "  $(printf '─%.0s' {1..80})"

    df -hT 2>/dev/null | grep -E "^/dev" | \
        awk '{printf "  %-25s %-8s %-10s %-10s %-8s %-s\n", \
            $1, $2, $3, $4, $6, $7}' | \
        tee -a "$REPORT_FILE"

    # LVM 映射设备
    df -hT 2>/dev/null | grep "^/dev/mapper" | \
        awk '{printf "  %-25s %-8s %-10s %-10s %-8s %-s\n", \
            $1, $2, $3, $4, $6, $7}' | \
        tee -a "$REPORT_FILE"

    # 物理磁盘信息
    output ""
    print_sub_section "物理磁盘信息"
    output ""

    local disk_found=false
    for disk in /sys/block/sd* /sys/block/vd* \
                /sys/block/nvme* /sys/block/hd* /sys/block/xvd*; do
        [[ -e "$disk" ]] || continue
        disk_found=true
        local disk_name
        disk_name=$(basename "$disk")
        local disk_size="" disk_model="" disk_type="Unknown"

        if [[ -f "$disk/size" ]]; then
            local sectors
            sectors=$(cat "$disk/size" 2>/dev/null)
            disk_size=$(awk "BEGIN{printf '%.1f GB', \
                $sectors*512/1024/1024/1024}")
        fi

        [[ -f "$disk/device/model" ]] && \
            disk_model=$(cat "$disk/device/model" 2>/dev/null | \
                tr -d '\n' | sed 's/[[:space:]]*$//')

        if [[ -f "$disk/queue/rotational" ]]; then
            local rot
            rot=$(cat "$disk/queue/rotational" 2>/dev/null)
            [[ "$rot" == "0" ]] && disk_type="SSD" || disk_type="HDD"
        fi
        [[ "$disk_name" == nvme* ]] && disk_type="NVMe SSD"

        print_item "磁盘设备" \
            "/dev/$disk_name | 大小: $disk_size | 类型: $disk_type | 型号: ${disk_model:-Unknown}"

        # 分区
        for part in /sys/block/$disk_name/${disk_name}*; do
            [[ -e "$part" ]] || continue
            local part_name
            part_name=$(basename "$part")
            local part_size=""
            if [[ -f "$part/size" ]]; then
                local psectors
                psectors=$(cat "$part/size" 2>/dev/null)
                part_size=$(awk "BEGIN{printf '%.1f GB', \
                    $psectors*512/1024/1024/1024}")
            fi
            output "    ${WHITE}├─ /dev/$part_name  ($part_size)${NC}"
        done
    done

    [[ "$disk_found" == false ]] && print_warn "未检测到标准块设备"

    # SMART
    if cmd_exists smartctl && [[ $EUID -eq 0 ]]; then
        output ""
        print_sub_section "磁盘健康状态(SMART)"
        for disk in /dev/sd? /dev/nvme?n1; do
            [[ -e "$disk" ]] || continue
            local smart_status
            smart_status=$(smartctl -H "$disk" 2>/dev/null | \
                grep "SMART overall" | \
                cut -d: -f2 | tr -d ' ')
            [[ -n "$smart_status" ]] && \
                print_item "$disk 健康状态" "$smart_status"
        done
    fi

    # RAID / LVM
    output ""
    print_sub_section "RAID/LVM 信息"
    if [[ -f /proc/mdstat ]]; then
        local raid_info
        raid_info=$(grep -v "^Personalities\|^unused\|^$" \
            /proc/mdstat 2>/dev/null)
        if [[ -n "$raid_info" ]]; then
            print_found "检测到软件RAID:"
            echo "$raid_info" | sed 's/^/  /'
        else
            output "  未检测到软件RAID"
        fi
    fi

    if cmd_exists pvs && [[ $EUID -eq 0 ]]; then
        local lvm_pv
        lvm_pv=$(pvs 2>/dev/null | grep -v "^  PV")
        if [[ -n "$lvm_pv" ]]; then
            print_found "LVM物理卷:"
            pvs 2>/dev/null | sed 's/^/  /' | tee -a "$REPORT_FILE"
            output ""
            print_found "LVM逻辑卷:"
            lvs 2>/dev/null | sed 's/^/  /' | tee -a "$REPORT_FILE"
        fi
    fi

    # IO调度器
    output ""
    print_sub_section "IO调度器"
    for disk_path in /sys/block/sd? /sys/block/nvme?n1; do
        [[ -f "$disk_path/queue/scheduler" ]] || continue
        local dn
        dn=$(basename "$disk_path")
        local scheduler
        scheduler=$(cat "$disk_path/queue/scheduler" 2>/dev/null)
        print_item "/dev/$dn" "$scheduler"
    done
}

# ============================================================
#                     五、网络信息
# ============================================================
collect_network_info() {
    print_section "五、网络信息"

    print_sub_section "网络接口"
    output ""

    if cmd_exists ip; then
        # 只显示物理网卡、bond、bridge,过滤veth/lo冗余
        local interfaces
        interfaces=$(ip link show 2>/dev/null | grep -E "^[0-9]+" | \
            awk '{print $2}' | tr -d ':' | \
            grep -v "^veth\|^lo$")

        for iface in $interfaces; do
            local state mac ipv4 ipv6 speed mtu driver
            state=$(ip link show "$iface" 2>/dev/null | \
                grep -o "state [A-Z]*" | awk '{print $2}')
            mac=$(ip link show "$iface" 2>/dev/null | \
                grep "link/ether" | awk '{print $2}')
            ipv4=$(ip addr show "$iface" 2>/dev/null | \
                grep "inet " | awk '{print $2}' | \
                tr '\n' ' ')
            ipv6=$(ip addr show "$iface" 2>/dev/null | \
                grep "inet6" | grep -v "link-local\|::1" | \
                awk '{print $2}' | tr '\n' ' ')
            mtu=$(ip link show "$iface" 2>/dev/null | \
                grep -o "mtu [0-9]*" | awk '{print $2}')
            if [[ -f "/sys/class/net/$iface/speed" ]]; then
                local spd
                spd=$(cat "/sys/class/net/$iface/speed" 2>/dev/null)
                [[ -n "$spd" && "$spd" -gt 0 ]] 2>/dev/null && \
                    speed="${spd}Mbps"
            fi
            if [[ -L "/sys/class/net/$iface/device/driver" ]]; then
                driver=$(basename "$(readlink \
                    "/sys/class/net/$iface/device/driver" 2>/dev/null)")
            fi

            output ""
            output "  ${YELLOW}${BOLD}[${iface}]${NC} 状态:${state:-?}  MAC:${mac:-N/A}  MTU:${mtu:-N/A}  速率:${speed:-N/A}  驱动:${driver:-N/A}"
            [[ -n "$ipv4" ]] && \
                output "    ${GREEN}IPv4: ${ipv4}${NC}"
            [[ -n "$ipv6" ]] && \
                output "    ${GREEN}IPv6: ${ipv6}${NC}"
        done

        # 单独列出 veth 数量,不逐一展开
        local veth_count
        veth_count=$(ip link show 2>/dev/null | \
            grep -c "^[0-9].*veth")
        [[ "$veth_count" -gt 0 ]] && \
            output "" && \
            print_item "  Docker veth接口数" "$veth_count 个(容器虚拟网卡)"

        # loopback
        output ""
        output "  ${YELLOW}${BOLD}[lo]${NC} 127.0.0.1/8 (loopback)"
    fi

    # 路由
    output ""
    print_sub_section "路由表"
    ip route show 2>/dev/null | sed 's/^/  /' | tee -a "$REPORT_FILE"

    # DNS
    output ""
    print_sub_section "DNS 配置"
    if [[ -f /etc/resolv.conf ]]; then
        grep -E "^nameserver|^search|^domain" /etc/resolv.conf \
            2>/dev/null | sed 's/^/  /' | tee -a "$REPORT_FILE"
    fi

    # 公网IP
    output ""
    print_sub_section "公网IP检测"
    local public_ip=""
    if cmd_exists curl; then
        public_ip=$(curl -s --connect-timeout 5 --max-time 8 \
            https://api.ipify.org 2>/dev/null)
        [[ -z "$public_ip" ]] && \
            public_ip=$(curl -s --connect-timeout 5 --max-time 8 \
                http://ifconfig.me 2>/dev/null)
    elif cmd_exists wget; then
        public_ip=$(wget -qO- --timeout=8 \
            https://api.ipify.org 2>/dev/null)
    fi
    print_item "公网IP" \
        "${public_ip:-无法获取(可能无公网或网络受限)}"

    # 防火墙
    output ""
    print_sub_section "防火墙状态"
    if cmd_exists firewall-cmd; then
        local fw_state
        fw_state=$(firewall-cmd --state 2>/dev/null)
        print_item "firewalld" "${fw_state:-not running}"
    fi
    if cmd_exists ufw; then
        print_item "UFW" "$(ufw status 2>/dev/null | head -1)"
    fi
    if cmd_exists iptables; then
        local ipt_rules
        ipt_rules=$(iptables -L 2>/dev/null | \
            grep -c "^ACCEPT\|^DROP\|^REJECT" || echo 0)
        print_item "iptables规则数" "$ipt_rules"
    fi

    # 【修复】监听端口:截断过长的进程列,只取第一个进程名
    output ""
    print_sub_section "监听端口 (TCP)"
    output ""
    printf "  ${GREEN}%-10s %-28s %-20s${NC}\n" \
        "协议" "本地地址:端口" "进程名" | tee -a "$REPORT_FILE"
    output "  $(printf '─%.0s' {1..65})"

    if cmd_exists ss; then
        ss -tlnp 2>/dev/null | grep -v "^State\|^Netid" | \
        while IFS= read -r line; do
            local local_addr proto process_raw process_name
            local_addr=$(echo "$line" | awk '{print $4}')
            proto="tcp"
            # 提取第一个进程名(去掉长串的pid列表)
            process_raw=$(echo "$line" | grep -oP '"\K[^"]+' | head -1)
            process_name="${process_raw:-N/A}"
            # 计算进程总数(有多少个 pid=)
            local pid_count
            pid_count=$(echo "$line" | grep -o "pid=" | wc -l)
            [[ "$pid_count" -gt 1 ]] && \
                process_name="${process_name}(×${pid_count})"
            [[ -n "$local_addr" ]] && \
                printf "  %-10s %-28s %-20s\n" \
                    "$proto" "$local_addr" "$process_name" | \
                    tee -a "$REPORT_FILE"
        done
    fi

    # UDP(简化版)
    output ""
    output "  ${YELLOW}UDP主要监听端口:${NC}"
    if cmd_exists ss; then
        ss -ulnp 2>/dev/null | grep -v "^State\|^Netid" | \
            awk '!seen[$4]++' | \
            grep -v "veth\|docker\|br-" | \
            awk '{
                proc=""
                match($0, /"([^"]+)"/, arr)
                if (arr[1]) proc=arr[1]
                printf "  %-10s %-28s %-20s\n", "udp", $4, proc
            }' | head -20 | tee -a "$REPORT_FILE"
    fi
}

# ============================================================
#                     六、GPU 信息
# ============================================================
collect_gpu_info() {
    print_section "六、GPU/显卡信息"

    local gpu_found=false

    # NVIDIA
    if cmd_exists nvidia-smi; then
        gpu_found=true
        print_sub_section "NVIDIA GPU"
        output ""
        printf "  ${GREEN}%-6s %-20s %-12s %-10s %-10s %-8s %-8s${NC}\n" \
            "Index" "名称" "驱动版本" "显存总量" "显存已用" "利用率" "温度" | \
            tee -a "$REPORT_FILE"
        output "  $(printf '─%.0s' {1..80})"

        nvidia-smi \
            --query-gpu=index,name,driver_version,memory.total,\
memory.used,utilization.gpu,temperature.gpu \
            --format=csv,noheader,nounits 2>/dev/null | \
        while IFS=',' read -r idx name drv mem_t mem_u util temp; do
            printf "  %-6s %-20s %-12s %-10s %-10s %-8s %-8s\n" \
                "GPU${idx// /}" \
                "$(echo "$name" | tr -d ' ')" \
                "$(echo "$drv" | tr -d ' ')" \
                "$(echo "$mem_t" | tr -d ' ')MiB" \
                "$(echo "$mem_u" | tr -d ' ')MiB" \
                "$(echo "$util" | tr -d ' ')%" \
                "$(echo "$temp" | tr -d ' ')°C" | \
                tee -a "$REPORT_FILE"
        done

        if cmd_exists nvcc; then
            local cuda_ver
            cuda_ver=$(nvcc --version 2>/dev/null | \
                grep -oP 'release \K[\d.]+')
            output ""
            print_item "CUDA版本" "${cuda_ver:-N/A}"
        fi
    fi

    # PCI设备
    if cmd_exists lspci; then
        local gpu_list
        gpu_list=$(lspci 2>/dev/null | \
            grep -iE "VGA|3D|Display|GPU")
        if [[ -n "$gpu_list" ]]; then
            gpu_found=true
            output ""
            print_sub_section "PCI 显卡设备"
            echo "$gpu_list" | \
                while IFS= read -r line; do
                    output "  ${WHITE}$line${NC}"
                done
        fi
    fi

    $gpu_found || print_warn "未检测到独立GPU或无相关工具"
}

# ============================================================
#                     七、系统资源状态
# ============================================================
collect_resource_info() {
    print_section "七、当前系统资源状态"

    local proc_total
    proc_total=$(ls /proc 2>/dev/null | grep -c "^[0-9]")
    local proc_running
    proc_running=$(ps aux 2>/dev/null | awk '$8~/^R/{c++}END{print c+0}')
    local proc_sleeping
    proc_sleeping=$(ps aux 2>/dev/null | awk '$8~/^[SD]/{c++}END{print c+0}')

    print_item "总进程数"   "$proc_total"
    print_item "运行中"     "$proc_running"
    print_item "休眠中"     "$proc_sleeping"

    # 【修复】Top进程表格列顺序修正(ps aux: $1=用户 $2=PID)
    output ""
    print_sub_section "CPU占用 Top10 进程"
    printf "  ${GREEN}%-8s %-20s %-8s %-8s %-s${NC}\n" \
        "PID" "用户" "CPU%" "MEM%" "命令" | tee -a "$REPORT_FILE"
    output "  $(printf '─%.0s' {1..65})"
    ps aux --sort=-%cpu 2>/dev/null | \
        awk 'NR>1 && NR<=11 {
            printf "  %-8s %-20s %-8s %-8s %-s\n",
                $2, $1, $3, $4, $11
        }' | tee -a "$REPORT_FILE"

    output ""
    print_sub_section "内存占用 Top10 进程"
    printf "  ${GREEN}%-8s %-20s %-8s %-10s %-s${NC}\n" \
        "PID" "用户" "MEM%" "RSS(MB)" "命令" | tee -a "$REPORT_FILE"
    output "  $(printf '─%.0s' {1..65})"
    ps aux --sort=-%mem 2>/dev/null | \
        awk 'NR>1 && NR<=11 {
            printf "  %-8s %-20s %-8s %-10.1f %-s\n",
                $2, $1, $4, $6/1024, $11
        }' | tee -a "$REPORT_FILE"

    output ""
    print_sub_section "当前登录用户"
    who 2>/dev/null | sed 's/^/  /' | tee -a "$REPORT_FILE"

    output ""
    print_sub_section "最近10条登录记录"
    last -n 10 2>/dev/null | sed 's/^/  /' | tee -a "$REPORT_FILE"
}

# ============================================================
#                     八、业务软件检测
# ============================================================

# ---- 8.1 运行时环境 ----
collect_runtime_info() {
    print_sub_section "编程语言运行时"

    # 【修复】Java:命令不在PATH时通过进程检测
    if cmd_exists java; then
        local java_ver
        java_ver=$(java -version 2>&1 | head -1)
        print_found "Java: $java_ver"
        local java_home="${JAVA_HOME:-N/A}"
        print_item "  JAVA_HOME" "$java_home"
    elif is_proc_running "java\|jvm\|jdk"; then
        # 从进程找java路径
        local java_exe
        java_exe=$(ls -la /proc/$(pgrep -f "java" | head -1)/exe \
            2>/dev/null | awk '{print $NF}')
        local java_ver="unknown"
        [[ -n "$java_exe" ]] && \
            java_ver=$("$java_exe" -version 2>&1 | head -1)
        print_found "Java: $java_ver (通过进程检测, PATH中不可用)"
        print_item "  可执行路径" "${java_exe:-N/A}"
    else
        print_not_found "Java: 未安装"
    fi

    # Python
    local py_found=false
    for py_cmd in python3 python python2; do
        if cmd_exists $py_cmd; then
            py_found=true
            local py_ver py_path
            py_ver=$($py_cmd --version 2>&1 | head -1)
            py_path=$(which $py_cmd)
            print_found "$py_ver | 路径: $py_path"
            local pip_cmd
            [[ "$py_cmd" == "python3" ]] && pip_cmd="pip3" || \
                pip_cmd="pip"
            if cmd_exists $pip_cmd; then
                local pip_count
                pip_count=$($pip_cmd list 2>/dev/null | \
                    tail -n +3 | wc -l)
                print_item "  已安装pip包数" "$pip_count"
            fi
        fi
    done
    $py_found || print_not_found "Python: 未安装"

    # Node.js
    if cmd_exists node; then
        local node_ver npm_ver
        node_ver=$(node --version 2>/dev/null)
        npm_ver=$(npm --version 2>/dev/null)
        print_found "Node.js: $node_ver | npm: $npm_ver"
    else
        print_not_found "Node.js: 未安装"
    fi

    # Go
    if cmd_exists go; then
        print_found "Go: $(go version 2>/dev/null)"
    elif is_proc_running "^go\b\|/go/bin/"; then
        print_found "Go: 运行中(PATH中不可用)"
    else
        print_not_found "Go: 未安装"
    fi

    # Rust
    cmd_exists rustc && \
        print_found "Rust: $(rustc --version 2>/dev/null)" || \
        print_not_found "Rust: 未安装"

    # Ruby
    cmd_exists ruby && \
        print_found "Ruby: $(ruby --version 2>/dev/null)" || \
        print_not_found "Ruby: 未安装"

    # PHP
    if cmd_exists php; then
        print_found "PHP: $(php --version 2>/dev/null | head -1)"
    else
        print_not_found "PHP: 未安装"
    fi

    # Perl
    if cmd_exists perl; then
        local perl_ver
        perl_ver=$(perl -e 'print $^V' 2>/dev/null)
        print_found "Perl: $perl_ver"
    fi

    # Lua
    local lua_cmd
    lua_cmd=$(command -v lua lua5.4 lua5.3 lua5.2 2>/dev/null | head -1)
    if [[ -n "$lua_cmd" ]]; then
        print_found "Lua: $($lua_cmd -v 2>&1 | head -1)"
    fi

    # .NET
    cmd_exists dotnet && \
        print_found ".NET: $(dotnet --version 2>/dev/null)" || \
        print_not_found ".NET: 未安装"

    # Ollama (AI推理框架 - 根据实际运行结果添加)
    if cmd_exists ollama || \
        ss -tlnp 2>/dev/null | grep -q ":11434"; then
        local ollama_ver
        ollama_ver=$(ollama --version 2>/dev/null | head -1)
        local ollama_status
        ollama_status=$(get_service_status "ollama")
        print_found "Ollama: ${ollama_ver:-detected} | 状态: $ollama_status"
        if cmd_exists ollama; then
            local model_count
            model_count=$(ollama list 2>/dev/null | \
                tail -n +2 | wc -l)
            print_item "  已加载模型数" "$model_count"
        fi
    fi

    # Ray (分布式AI框架 - 根据实际运行结果添加)
    if is_proc_running "raylet\|ray::\|gcs_server"; then
        local ray_ver=""
        ray_ver=$(python3 -c "import ray; print(ray.__version__)" \
            2>/dev/null)
        print_found "Ray(分布式框架): ${ray_ver:-运行中}"
        local ray_worker_count
        ray_worker_count=$(ps aux 2>/dev/null | \
            grep -c "[r]ay::IDLE\|[r]aylet")
        print_item "  Ray worker进程数" "$ray_worker_count"
    fi
}

# ---- 8.2 Web服务器 ----
collect_webserver_info() {
    print_sub_section "Web 服务器"

    # 【修复】Nginx:同时检测本机安装和Docker内运行
    if cmd_exists nginx; then
        local nginx_ver nginx_status
        nginx_ver=$(nginx -v 2>&1 | grep -o "nginx/[0-9.]*")
        nginx_status=$(get_service_status "nginx")
        print_found "Nginx: $nginx_ver | 状态: $nginx_status (本机安装)"
        local vhost_count=0
        for d in /etc/nginx/conf.d /etc/nginx/sites-enabled \
                  /etc/nginx/vhosts.d; do
            [[ -d "$d" ]] && \
                vhost_count=$((vhost_count + \
                    $(find "$d" -name "*.conf" 2>/dev/null | wc -l)))
        done
        print_item "  虚拟主机配置数" "$vhost_count"
    elif is_proc_running "nginx"; then
        local nginx_proc_count
        nginx_proc_count=$(ps aux 2>/dev/null | \
            grep -c "[n]ginx")
        print_found "Nginx: 运行中(Docker容器内, $nginx_proc_count 个进程)"
        # 获取nginx监听的端口
        local nginx_ports
        nginx_ports=$(ss -tlnp 2>/dev/null | \
            grep '"nginx"' | \
            awk '{print $4}' | \
            grep -oP ':\K\d+' | \
            sort -u | tr '\n' ',' | sed 's/,$//')
        print_item "  监听端口" "${nginx_ports:-N/A}"
    else
        print_not_found "Nginx: 未安装/未运行"
    fi

    # Apache
    local apache_cmd=""
    cmd_exists apache2 && apache_cmd="apache2"
    cmd_exists httpd   && apache_cmd="httpd"
    if [[ -n "$apache_cmd" ]]; then
        print_found "Apache: $($apache_cmd -v 2>/dev/null | head -1)"
    else
        print_not_found "Apache: 未安装"
    fi

    # Tomcat:通过进程检测(不依赖命令)
    if is_proc_running "catalina\|tomcat\|org.apache.catalina"; then
        local tomcat_pid
        tomcat_pid=$(ps aux 2>/dev/null | \
            grep -i "catalina\|tomcat" | \
            grep -v grep | head -1 | awk '{print $2}')
        local tomcat_path=""
        if [[ -n "$tomcat_pid" ]]; then
            tomcat_path=$(ls -la /proc/$tomcat_pid/exe \
                2>/dev/null | awk '{print $NF}')
            # 尝试获取版本
            local catalina_home
            catalina_home=$(cat /proc/$tomcat_pid/environ \
                2>/dev/null | tr '\0' '\n' | \
                grep "CATALINA_HOME" | cut -d= -f2)
            [[ -n "$catalina_home" ]] && \
                tomcat_path="$catalina_home"
        fi
        print_found "Tomcat: 运行中 | 路径: ${tomcat_path:-unknown}"
    else
        print_not_found "Tomcat: 未运行"
    fi

    # HAProxy
    cmd_exists haproxy && \
        print_found "HAProxy: $(haproxy -v 2>/dev/null | head -1)" || \
        is_proc_running "haproxy" && \
        print_found "HAProxy: 运行中" || \
        print_not_found "HAProxy: 未安装"

    # Caddy
    cmd_exists caddy && \
        print_found "Caddy: $(caddy version 2>/dev/null)" || \
        print_not_found "Caddy: 未安装"
}

# ---- 8.3 数据库 ----
collect_database_info() {
    print_sub_section "数据库"

    # 【修复】MySQL/MariaDB:命令不在PATH时通过端口+进程检测
    local mysql_ver="" mysql_status=""
    if cmd_exists mysql; then
        mysql_ver=$(mysql --version 2>/dev/null | head -1)
        mysql_status=$(get_service_status "mysql" "mysqld" "mariadb")
        print_found "MySQL/MariaDB: $mysql_ver | 状态: $mysql_status"
    elif is_proc_running "mysqld"; then
        # 通过进程路径找到mysql版本
        local mysql_bin
        mysql_bin=$(ls -la /proc/$(pgrep mysqld | head -1)/exe \
            2>/dev/null | awk '{print $NF}')
        if [[ -n "$mysql_bin" ]]; then
            # mysqld --version 可能需要权限,尝试字符串
            mysql_ver=$(strings "$mysql_bin" 2>/dev/null | \
                grep -oP '\d+\.\d+\.\d+' | head -1)
        fi
        local mysql_port
        mysql_port=$(ss -tlnp 2>/dev/null | \
            grep "mysqld" | awk '{print $4}' | \
            grep -oP ':\K\d+' | tr '\n' ',' | sed 's/,$//')
        print_found "MySQL/MariaDB: ${mysql_ver:-unknown} | 端口: ${mysql_port:-3306} (PATH中无客户端命令)"
    elif ss -tlnp 2>/dev/null | grep -q ":3306"; then
        print_found "MySQL/MariaDB: 端口3306有监听"
    else
        print_not_found "MySQL/MariaDB: 未安装"
    fi
    # 显示数据目录大小
    for datadir in /var/lib/mysql /data/mysql; do
        if [[ -d "$datadir" ]]; then
            local db_size
            db_size=$(du -sh "$datadir" 2>/dev/null | cut -f1)
            print_item "  数据目录($datadir)" "${db_size:-N/A}"
        fi
    done

    # PostgreSQL
    if cmd_exists psql; then
        local pg_ver pg_status
        pg_ver=$(psql --version 2>/dev/null | head -1)
        pg_status=$(get_service_status \
            "postgresql" "postgresql-15" "postgresql-14")
        print_found "PostgreSQL: $pg_ver | 状态: $pg_status"
        # 配置文件
        for conf in /var/lib/pgsql/*/data/postgresql.conf \
                    /etc/postgresql/*/main/postgresql.conf; do
            [[ -f "$conf" ]] && \
                print_item "  配置文件" "$conf" && break
        done
    elif is_proc_running "postgres"; then
        local pg_count
        pg_count=$(ps aux 2>/dev/null | grep -c "[p]ostgres")
        print_found "PostgreSQL: 运行中 ($pg_count 个进程, PATH中无客户端命令)"
    elif ss -tlnp 2>/dev/null | grep -q ":5432"; then
        print_found "PostgreSQL: 端口5432有监听"
    else
        print_not_found "PostgreSQL: 未安装"
    fi

    # 【修复】Redis:通过进程+端口检测
    if cmd_exists redis-cli; then
        local redis_ver redis_status
        redis_ver=$(redis-cli --version 2>/dev/null)
        redis_status=$(get_service_status "redis" "redis-server")
        local redis_ping
        redis_ping=$(redis-cli ping 2>/dev/null)
        print_found "Redis: $redis_ver | 状态: $redis_status | ping: ${redis_ping:-N/A}"
    elif is_proc_running "redis-server"; then
        local redis_port
        redis_port=$(ss -tlnp 2>/dev/null | \
            grep "redis" | awk '{print $4}' | \
            grep -oP ':\K\d+' | head -1)
        print_found "Redis: 运行中 | 端口: ${redis_port:-6379} (PATH中无客户端命令)"
    elif ss -tlnp 2>/dev/null | grep -q ":6379"; then
        print_found "Redis: 端口6379有监听"
    else
        print_not_found "Redis: 未安装"
    fi
    for conf in /etc/redis/redis.conf /etc/redis.conf; do
        [[ -f "$conf" ]] && print_item "  配置文件" "$conf"
    done

    # MongoDB
    if cmd_exists mongosh || cmd_exists mongo; then
        local mongo_cmd
        mongo_cmd=$(command -v mongosh 2>/dev/null || \
            command -v mongo 2>/dev/null)
        print_found "MongoDB: $($mongo_cmd --version 2>/dev/null | \
            head -1)"
    elif is_proc_running "mongod"; then
        print_found "MongoDB: 运行中"
    elif ss -tlnp 2>/dev/null | grep -q ":27017"; then
        print_found "MongoDB: 端口27017有监听"
    else
        print_not_found "MongoDB: 未安装"
    fi

    # SQLite
    cmd_exists sqlite3 && \
        print_found "SQLite: $(sqlite3 --version 2>/dev/null | \
            awk '{print $1}')" || \
        print_not_found "SQLite: 未安装"

    # Elasticsearch
    if is_proc_running "elasticsearch" || \
        ss -tlnp 2>/dev/null | grep -q ":9200"; then
        local es_ver=""
        if cmd_exists curl; then
            es_ver=$(curl -s --connect-timeout 3 \
                http://localhost:9200 2>/dev/null | \
                grep -o '"number" : "[^"]*"' | \
                cut -d'"' -f4)
        fi
        local es_status
        es_status=$(get_service_status "elasticsearch")
        print_found "Elasticsearch: ${es_ver:-detected} | 状态: $es_status"
    else
        print_not_found "Elasticsearch: 未安装"
    fi

    # InfluxDB
    if cmd_exists influx || is_proc_running "influxd" || \
        ss -tlnp 2>/dev/null | grep -q ":8086"; then
        local influx_ver
        influx_ver=$(influx version 2>/dev/null | head -1)
        print_found "InfluxDB: ${influx_ver:-detected}"
    else
        print_not_found "InfluxDB: 未安装"
    fi

    # ClickHouse
    if cmd_exists clickhouse-client || \
        is_proc_running "clickhouse"; then
        local ch_ver
        ch_ver=$(clickhouse-client --version 2>/dev/null | head -1)
        print_found "ClickHouse: ${ch_ver:-detected}"
    else
        print_not_found "ClickHouse: 未安装"
    fi

    # Memcached
    if cmd_exists memcached || \
        ss -tlnp 2>/dev/null | grep -q ":11211"; then
        print_found "Memcached: 检测到"
    else
        print_not_found "Memcached: 未安装"
    fi
}

# ---- 8.4 消息队列 ----
collect_middleware_info() {
    print_sub_section "消息队列/中间件"

    # RabbitMQ (beam.smp 是 Erlang VM)
    if is_proc_running "beam.smp\|rabbitmq" || \
        ss -tlnp 2>/dev/null | grep -q ":5672"; then
        local rmq_status
        rmq_status=$(get_service_status "rabbitmq-server" "rabbitmq")
        local rmq_ver=""
        if cmd_exists rabbitmqctl; then
            rmq_ver=$(rabbitmqctl version 2>/dev/null | \
                head -1 | tr -d '\n')
        fi
        print_found "RabbitMQ: ${rmq_ver:-detected} | 状态: $rmq_status"
        # Management UI
        ss -tlnp 2>/dev/null | grep -q ":15672" && \
            print_item "  Management UI" "http://localhost:15672"
        # 端口信息
        local amqp_port
        amqp_port=$(ss -tlnp 2>/dev/null | \
            grep "beam.smp" | grep ":5672" | \
            awk '{print $4}' | head -1)
        [[ -n "$amqp_port" ]] && \
            print_item "  AMQP端口" "$amqp_port"
    else
        print_not_found "RabbitMQ: 未安装"
    fi

    # Kafka
    if is_proc_running "kafka" || \
        ss -tlnp 2>/dev/null | grep -q ":9092"; then
        print_found "Kafka: 检测到运行中"
    else
        print_not_found "Kafka: 未安装"
    fi

    # Zookeeper
    if is_proc_running "zookeeper\|QuorumPeer" || \
        ss -tlnp 2>/dev/null | grep -q ":2181"; then
        print_found "Zookeeper: 检测到运行中"
    else
        print_not_found "Zookeeper: 未检测到"
    fi

    # NATS
    if cmd_exists nats-server || \
        ss -tlnp 2>/dev/null | grep -q ":4222"; then
        print_found "NATS: 检测到"
    fi

    # NanoMQ (根据实际输出新增)
    if is_proc_running "nanoMQServer\|nanomq"; then
        print_found "NanoMQ(MQTT Broker): 运行中"
        local nano_ports
        nano_ports=$(ss -tlnp 2>/dev/null | \
            grep -i "nano" | \
            awk '{print $4}' | \
            grep -oP ':\K\d+' | \
            tr '\n' ',' | sed 's/,$//')
        print_item "  监听端口" "${nano_ports:-N/A}"
    fi
}

# ---- 8.5 容器/编排 ----
collect_container_info() {
    print_sub_section "容器化/编排"

    # Docker
    if cmd_exists docker; then
        local docker_ver docker_status
        docker_ver=$(docker version \
            --format '{{.Server.Version}}' 2>/dev/null || \
            docker --version 2>/dev/null | \
            grep -oP '[\d.]+' | head -1)
        docker_status=$(get_service_status "docker")
        print_found "Docker: $docker_ver | 状态: $docker_status"

        if docker info &>/dev/null 2>&1; then
            local c_total c_run img_count vol_count net_count
            c_total=$(docker ps -aq 2>/dev/null | wc -l)
            c_run=$(docker ps -q 2>/dev/null | wc -l)
            img_count=$(docker images -q 2>/dev/null | wc -l)
            vol_count=$(docker volume ls -q 2>/dev/null | wc -l)
            net_count=$(docker network ls -q 2>/dev/null | wc -l)

            print_item "  容器总数/运行中"  "$c_total / $c_run"
            print_item "  镜像数量"          "$img_count"
            print_item "  数据卷数量"        "$vol_count"
            print_item "  网络数量"          "$net_count"

            # 存储信息
            local storage_driver docker_root docker_size
            storage_driver=$(docker info \
                --format '{{.Driver}}' 2>/dev/null)
            docker_root=$(docker info \
                --format '{{.DockerRootDir}}' 2>/dev/null)
            print_item "  存储驱动"  "$storage_driver"
            print_item "  数据目录"  "$docker_root"
            if [[ -n "$docker_root" && -d "$docker_root" ]]; then
                docker_size=$(du -sh "$docker_root" \
                    2>/dev/null | cut -f1)
                print_item "  数据目录大小" "$docker_size"
            fi

            # 【修复】运行中容器:优化格式,端口信息单独处理
            if [[ "$c_run" -gt 0 ]]; then
                output ""
                output "  ${YELLOW}运行中的容器列表:${NC}"
                printf "  ${GREEN}%-16s %-35s %-35s %-10s${NC}\n" \
                    "容器ID" "容器名称" "镜像" "状态" | \
                    tee -a "$REPORT_FILE"
                output "  $(printf '─%.0s' {1..100})"

                docker ps \
                    --format "{{.ID}}\t{{.Names}}\t{{.Image}}\t{{.Status}}" \
                    2>/dev/null | \
                while IFS=$'\t' read -r id name image status; do
                    printf "  %-16s %-35s %-35s %-10s\n" \
                        "$id" \
                        "${name:0:34}" \
                        "${image:0:34}" \
                        "${status:0:15}" | \
                        tee -a "$REPORT_FILE"
                done

                # 端口映射单独列出
                output ""
                output "  ${YELLOW}容器端口映射:${NC}"
                docker ps \
                    --format "{{.Names}}\t{{.Ports}}" \
                    2>/dev/null | \
                while IFS=$'\t' read -r name ports; do
                    [[ -z "$ports" ]] && continue
                    # 简化端口显示,去掉0.0.0.0::
                    local clean_ports
                    clean_ports=$(echo "$ports" | \
                        sed 's/0\.0\.0\.0://g' | \
                        sed 's/:::/ /g' | \
                        tr ',' '\n' | \
                        grep -oP '\d+.*' | \
                        sort -u | tr '\n' ' ')
                    [[ -n "$clean_ports" ]] && \
                        printf "  %-35s %s\n" "${name:0:34}" \
                            "$clean_ports" | \
                            tee -a "$REPORT_FILE"
                done
            fi

            # Compose版本
            output ""
            if cmd_exists docker-compose; then
                print_item "  docker-compose" \
                    "$(docker-compose --version 2>/dev/null)"
            fi
            if docker compose version &>/dev/null 2>&1; then
                print_item "  docker compose" \
                    "$(docker compose version 2>/dev/null)"
            fi
        fi
    else
        print_not_found "Docker: 未安装"
    fi

    # Podman
    if cmd_exists podman; then
        print_found "Podman: $(podman --version 2>/dev/null)"
    else
        print_not_found "Podman: 未安装"
    fi

    # containerd
    if cmd_exists ctr; then
        local ct_ver
        ct_ver=$(ctr version 2>/dev/null | \
            grep "Version:" | head -1 | awk '{print $2}')
        print_found "containerd: $ct_ver"
    fi

    # Kubernetes
    if cmd_exists kubectl; then
        local k8s_ver
        k8s_ver=$(kubectl version --client \
            --output=yaml 2>/dev/null | \
            grep "gitVersion" | awk '{print $2}')
        print_found "kubectl: $k8s_ver"
        local k8s_context
        k8s_context=$(kubectl config \
            current-context 2>/dev/null)
        print_item "  当前Context" "${k8s_context:-N/A}"
    else
        print_not_found "Kubernetes (kubectl): 未安装"
    fi

    # 是否在容器内运行
    if [[ -f /.dockerenv ]]; then
        print_warn "当前系统运行在 Docker 容器内"
    fi
}

# ---- 8.6 CI/CD ----
collect_cicd_info() {
    print_sub_section "CI/CD 工具"

    # Jenkins
    if [[ -d /var/lib/jenkins ]]; then
        local jenkins_status
        jenkins_status=$(get_service_status "jenkins")
        print_found "Jenkins: 已安装 | 状态: $jenkins_status"
    elif is_proc_running "jenkins" || \
        (ss -tlnp 2>/dev/null | grep -q ":8080" && \
         is_proc_running "java"); then
        print_found "Jenkins: 可能运行中(Java在8080端口)"
    else
        print_not_found "Jenkins: 未安装"
    fi

    # GitLab
    if cmd_exists gitlab-ctl; then
        print_found "GitLab: 已安装"
    else
        print_not_found "GitLab: 未安装"
    fi

    # Git
    cmd_exists git && \
        print_found "$(git --version 2>/dev/null)" || \
        print_not_found "Git: 未安装"

    # Ansible
    cmd_exists ansible && \
        print_found "Ansible: $(ansible --version 2>/dev/null | head -1)" || \
        print_not_found "Ansible: 未安装"

    # Terraform
    cmd_exists terraform && \
        print_found "Terraform: $(terraform version 2>/dev/null | head -1)"
}

# ---- 8.7 监控工具 ----
collect_monitor_info() {
    print_sub_section "监控/可观测性"

    # Prometheus
    if cmd_exists prometheus || \
        is_proc_running "prometheus" || \
        ss -tlnp 2>/dev/null | grep -q ":9090"; then
        local prom_status
        prom_status=$(get_service_status "prometheus")
        # 注意9090可能被其他程序占用,需确认
        local prom_proc
        prom_proc=$(ss -tlnp 2>/dev/null | \
            grep ":9090" | grep -oP '"\K[^"]+' | head -1)
        print_found "Prometheus: 状态: $prom_status | 9090端口进程: ${prom_proc:-prometheus}"
    else
        print_not_found "Prometheus: 未检测到"
    fi

    # Grafana
    if is_proc_running "grafana" || \
        ss -tlnp 2>/dev/null | grep -q ":3000"; then
        local graf_status
        graf_status=$(get_service_status "grafana-server" "grafana")
        print_found "Grafana: 状态: $graf_status"
    else
        print_not_found "Grafana: 未检测到"
    fi

    # Zabbix
    for zabbix_cmd in zabbix_agentd zabbix_agent2; do
        if cmd_exists $zabbix_cmd; then
            print_found "Zabbix Agent: $($zabbix_cmd \
                --version 2>/dev/null | head -1)"
            break
        fi
    done

    # 1Panel (根据实际输出新增)
    if cmd_exists 1panel || is_proc_running "1panel" || \
        ss -tlnp 2>/dev/null | grep -q ":30188"; then
        local panel_status
        panel_status=$(get_service_status "1panel")
        print_found "1Panel(服务器管理面板): 状态: $panel_status"
        local panel_port
        panel_port=$(ss -tlnp 2>/dev/null | \
            grep "1panel" | \
            awk '{print $4}' | \
            grep -oP ':\K\d+' | head -1)
        print_item "  管理端口" "${panel_port:-30188}"
    fi
}

# ---- 8.8 其他检测到的服务 ----
collect_other_services_info() {
    print_sub_section "其他常见服务"

    # SSH Server
    if cmd_exists sshd || is_proc_running "sshd"; then
        local sshd_status ssh_port
        sshd_status=$(get_service_status "sshd" "ssh")
        ssh_port=$(grep -E "^Port " /etc/ssh/sshd_config \
            2>/dev/null | awk '{print $2}' | \
            tr '\n' ',' | sed 's/,$//')
        # 也从ss获取
        if [[ -z "$ssh_port" ]]; then
            ssh_port=$(ss -tlnp 2>/dev/null | \
                grep '"sshd"' | \
                awk '{print $4}' | \
                grep -oP ':\K\d+' | \
                sort -u | tr '\n' ',' | sed 's/,$//')
        fi
        print_found "SSH Server: 状态: $sshd_status | 端口: ${ssh_port:-22}"
    fi

    # FTP
    for ftp_svc in vsftpd proftpd pure-ftpd; do
        if cmd_exists $ftp_svc || \
            is_proc_running "$ftp_svc"; then
            local ftp_status
            ftp_status=$(get_service_status "$ftp_svc")
            print_found "FTP($ftp_svc): 状态: $ftp_status"
        fi
    done

    # Samba
    if cmd_exists smbd || is_proc_running "smbd"; then
        local smb_status
        smb_status=$(get_service_status "smbd" "samba")
        local smb_ver
        smb_ver=$(smbd --version 2>/dev/null | \
            grep -oP 'Version \K[\d.]+')
        print_found "Samba: ${smb_ver:-detected} | 状态: $smb_status"
    fi

    # NFS
    if cmd_exists showmount || \
        ss -tlnp 2>/dev/null | grep -q ":2049"; then
        local nfs_status
        nfs_status=$(get_service_status \
            "nfs-server" "nfs" "nfs-kernel-server")
        print_found "NFS: 状态: $nfs_status"
    fi

    # Cron
    local cron_status
    cron_status=$(get_service_status "crond" "cron")
    print_found "Cron: 状态: $cron_status"
    local cron_jobs
    cron_jobs=$(crontab -l 2>/dev/null | \
        grep -v "^#\|^$" | wc -l)
    print_item "  当前用户定时任务数" "$cron_jobs"

    # Supervisor
    if cmd_exists supervisorctl || is_proc_running "supervisord"; then
        local sup_status
        sup_status=$(get_service_status \
            "supervisor" "supervisord")
        print_found "Supervisor: 状态: $sup_status"
        if cmd_exists supervisorctl; then
            local sup_list
            sup_list=$(supervisorctl status 2>/dev/null | \
                grep -v "error\|refused\|unix")
            local sup_count
            sup_count=$(echo "$sup_list" | grep -c "RUNNING" || echo 0)
            print_item "  RUNNING进程数" "$sup_count"
            [[ -n "$sup_list" ]] && \
                echo "$sup_list" | \
                awk '{printf "  %-30s %s\n", $1, $2}' | \
                tee -a "$REPORT_FILE"
        fi
    else
        print_not_found "Supervisor: 未安装"
    fi

    # PM2
    if cmd_exists pm2; then
        print_found "PM2: $(pm2 --version 2>/dev/null)"
    fi

    # Gunicorn / uWSGI
    is_proc_running "gunicorn" && \
        print_found "Gunicorn: 运行中"
    is_proc_running "uwsgi" && \
        print_found "uWSGI: 运行中"

    # Celery
    is_proc_running "celery" && \
        print_found "Celery: 运行中"

    # HASP/许可证守护进程 (根据实际输出新增)
    if is_proc_running "hasplmd"; then
        print_found "HASP License Manager: 运行中(端口:1947)"
    fi

    # LibreOffice 无头模式 (根据实际输出新增)
    if is_proc_running "soffice"; then
        local soffice_count
        soffice_count=$(ps aux 2>/dev/null | \
            grep -c "[s]office")
        print_found "LibreOffice(无头模式): $soffice_count 个进程运行中"
        print_item "  用途推测" "文档格式转换服务"
    fi

    # Consul / Vault / etcd
    cmd_exists consul && is_proc_running "consul" && \
        print_found "Consul: $(consul version 2>/dev/null | head -1)"
    ss -tlnp 2>/dev/null | grep -q ":8500" && \
        print_found "Consul: 端口8500监听"

    # NTP
    if cmd_exists ntpd || is_proc_running "ntpd\|chronyd"; then
        local ntp_status
        ntp_status=$(get_service_status "ntpd" "chronyd" "ntp")
        print_found "NTP: 状态: $ntp_status"
    fi

    # logrotate
    cmd_exists logrotate && \
        print_found "logrotate: $(logrotate --version \
            2>/dev/null | head -1)"

    # 自定义进程检测(根据实际输出检测到的业务进程)
    output ""
    output "  ${YELLOW}检测到的自定义业务进程:${NC}"
    local custom_procs=(
        "pt_main_thread:自定义PT主线程服务"
        "EngineServer:引擎服务"
        "HPCServer:HPC高性能计算服务"
        "OPMServer:OPM服务"
        "SmartDevMgr:智能设备管理"
        "NodeCMS:NodeCMS内容管理"
        "AuthCMS:AuthCMS认证服务"
        "SystemCMS:SystemCMS系统服务"
        "logServer:日志服务"
        "nanoMQServer:NanoMQ消息服务"
        "1panel:1Panel管理面板"
        "ollama:Ollama AI推理"
        "gcs_server:Ray GCS服务"
        "fscs:FSCS服务"
    )

    for item in "${custom_procs[@]}"; do
        IFS=: read -r proc_pattern display_name <<< "$item"
        if is_proc_running "$proc_pattern"; then
            local proc_count
            proc_count=$(ps aux 2>/dev/null | \
                grep -c "${proc_pattern}")
            local proc_user
            proc_user=$(ps aux 2>/dev/null | \
                grep "[${proc_pattern:0:1}]${proc_pattern:1}" | \
                awk '{print $1}' | sort -u | tr '\n' ',')
            print_found "$display_name: $proc_count 个进程 | 用户: ${proc_user%,}"
        fi
    done
}

# ---- 8.9 已安装软件包 ----
collect_installed_packages() {
    print_sub_section "已安装软件包 (包管理器)"
    output ""

    # RPM
    if cmd_exists rpm; then
        local pkg_total
        pkg_total=$(rpm -qa 2>/dev/null | wc -l)
        print_item "包管理器"     "RPM"
        print_item "已安装总包数" "$pkg_total"
        output ""

        # 应用类软件:过滤掉明显的系统基础库
        output "  ${YELLOW}应用/工具类软件包:${NC}"
        rpm -qa --qf "%{NAME}\t%{VERSION}-%{RELEASE}\n" 2>/dev/null | \
        grep -viE "^(lib|python[23]?-|perl-|fonts-|glib|dbus-\
|kernel-|systemd-|glibc-|bash-|sed-|grep-|awk-|\
tar-|gzip-|bzip2-|xz-|zlib-|openssl-libs|ca-certificates|\
filesystem|setup|tzdata|ncurses-|readline-|\
pcre[0-9]?-|expat-|shadow-|passwd-|pam-|\
util-linux|coreutils|findutils|diffutils-|\
nss-|nspr-|p11-kit|gpg-pubkey|publicsuffix)" | \
        sort | \
        awk -F'\t' '{printf "  %-45s %s\n", $1, $2}' | \
        tee -a "$REPORT_FILE"

    # APT (Debian/Ubuntu)
    elif cmd_exists apt-mark && cmd_exists dpkg; then
        local pkg_total
        pkg_total=$(dpkg -l 2>/dev/null | grep "^ii" | wc -l)
        print_item "包管理器"     "APT/DPKG"
        print_item "已安装总包数" "$pkg_total"
        output ""
        output "  ${YELLOW}手动安装的软件包:${NC}"
        apt-mark showmanual 2>/dev/null | sort | \
        while read -r pkg; do
            local ver
            ver=$(dpkg -l "$pkg" 2>/dev/null | \
                grep "^ii" | awk '{print $3}')
            printf "  %-40s %s\n" "$pkg" "$ver"
        done | tee -a "$REPORT_FILE"

    # pacman (Arch)
    elif cmd_exists pacman; then
        local pkg_total
        pkg_total=$(pacman -Q 2>/dev/null | wc -l)
        print_item "包管理器"     "pacman"
        print_item "已安装总包数" "$pkg_total"
        output ""
        output "  ${YELLOW}显式安装的软件包:${NC}"
        pacman -Qe 2>/dev/null | \
            awk '{printf "  %-40s %s\n", $1, $2}' | \
            tee -a "$REPORT_FILE"

    # apk (Alpine)
    elif cmd_exists apk; then
        print_item "包管理器" "APK (Alpine)"
        apk list --installed 2>/dev/null | \
            sed 's/^/  /' | tee -a "$REPORT_FILE"
    else
        print_warn "未检测到已知包管理器"
    fi

    # Snap
    if cmd_exists snap; then
        local snap_count
        snap_count=$(snap list 2>/dev/null | \
            tail -n +2 | wc -l)
        output ""
        print_item "Snap包数量" "$snap_count"
        [[ "$snap_count" -gt 0 ]] && \
            snap list 2>/dev/null | tail -n +2 | \
            awk '{printf "  %-30s %-20s\n", $1, $2}' | \
            tee -a "$REPORT_FILE"
    fi
}

# ---- 端口与服务映射 ----
collect_port_service_map() {
    print_sub_section "关键端口服务识别"
    output ""

    declare -A port_map=(
        [21]="FTP" [22]="SSH" [25]="SMTP" [53]="DNS"
        [80]="HTTP" [110]="POP3" [143]="IMAP"
        [443]="HTTPS" [2181]="Zookeeper"
        [2343]="SSH(非标准端口)" [2375]="Docker(危险!无TLS)"
        [2376]="Docker-TLS" [2379]="etcd-client" [2380]="etcd-peer"
        [3000]="Grafana" [3306]="MySQL" [3389]="RDP"
        [4222]="NATS" [4306]="MySQL(非标准)" [4369]="EPMD(RabbitMQ)"
        [5432]="PostgreSQL" [5556]="NanoMQ"
        [5601]="Kibana" [5672]="RabbitMQ-AMQP"
        [6060]="NodeCMS" [6070]="SmartDevMgr"
        [6379]="Redis" [6443]="Kubernetes-API"
        [6666]="AuthCMS" [7000]="Nginx(自定义)"
        [8000]="HTTP-Dev" [8001]="业务API" [8002]="业务API"
        [8080]="HTTP-Alt/Tomcat/Jenkins"
        [8085]="业务API" [8086]="InfluxDB/业务API"
        [8088]="NodeCMS" [8443]="HTTPS-Alt"
        [9001]="MinIO-Console" [9080]="Nginx(自定义)"
        [9090]="Prometheus/业务服务"
        [9091]="Nginx(自定义)" [9098]="Nginx(自定义)"
        [9099]="Nginx(自定义)"
        [9092]="Kafka" [9093]="Alertmanager"
        [9099]="Node-Exporter" [9200]="Elasticsearch"
        [9876]="RocketMQ" [9999]="业务服务"
        [10443]="SystemCMS-HTTPS"
        [11211]="Memcached" [11434]="Ollama"
        [15555]="NanoMQ-Websocket" [15672]="RabbitMQ-Management"
        [21012]="HPCServer" [25672]="RabbitMQ-Cluster"
        [27017]="MongoDB" [30080]="AuthCMS-HTTP"
        [30090]="Java业务服务" [30188]="1Panel管理"
        [30443]="AuthCMS-HTTPS" [30991]="OPMServer"
        [61616]="ActiveMQ"
    )

    printf "  ${GREEN}%-28s %-25s %-20s${NC}\n" \
        "本地地址:端口" "识别服务" "进程" | tee -a "$REPORT_FILE"
    output "  $(printf '─%.0s' {1..75})"

    if cmd_exists ss; then
        ss -tlnp 2>/dev/null | grep -v "^State\|^Netid" | \
        sort -t: -k2 -n | \
        awk '!seen[$4]++' | \
        while IFS= read -r line; do
            local local_addr process_name port
            local_addr=$(echo "$line" | awk '{print $4}')
            port=$(echo "$local_addr" | grep -oP ':\K\d+$')
            process_name=$(echo "$line" | \
                grep -oP '"\K[^"]+' | head -1)
            local pid_count
            pid_count=$(echo "$line" | \
                grep -o "pid=" | wc -l)
            [[ "$pid_count" -gt 1 ]] && \
                process_name="${process_name:-N/A}(×${pid_count}进程)"

            local service_name="${port_map[$port]:-Unknown}"

            [[ -n "$port" ]] && \
                printf "  %-28s %-25s %-20s\n" \
                    "$local_addr" \
                    "$service_name" \
                    "${process_name:-N/A}" | \
                    tee -a "$REPORT_FILE"
        done
    fi
}

# ---- 汇总业务软件 ----
collect_business_software() {
    print_section "八、业务软件检测"

    collect_runtime_info
    output ""
    collect_webserver_info
    output ""
    collect_database_info
    output ""
    collect_middleware_info
    output ""
    collect_container_info
    output ""
    collect_cicd_info
    output ""
    collect_monitor_info
    output ""
    collect_other_services_info
    output ""
    collect_port_service_map
    output ""
    collect_installed_packages
}

# ============================================================
#                     九、Systemd 服务
# ============================================================
collect_systemd_services() {
    print_section "九、Systemd 服务状态"

    if ! cmd_exists systemctl; then
        print_warn "systemctl 不可用,跳过此部分"
        return
    fi

    print_sub_section "运行中的服务"
    output ""
    printf "  ${GREEN}%-45s %-12s %-s${NC}\n" \
        "服务名" "状态" "子状态" | tee -a "$REPORT_FILE"
    output "  $(printf '─%.0s' {1..70})"
    systemctl list-units --type=service --state=active \
        --no-pager --no-legend 2>/dev/null | \
        grep -v "systemd-\|dbus\|getty\|user@\|session-" | \
        awk '{printf "  %-45s %-12s %-s\n", $1, $3, $4}' | \
        tee -a "$REPORT_FILE"

    output ""
    print_sub_section "失败的服务"
    local failed
    failed=$(systemctl list-units --type=service \
        --state=failed --no-pager --no-legend 2>/dev/null)
    if [[ -n "$failed" ]]; then
        echo "$failed" | \
            awk '{printf "  %-45s %-s\n", $1, $4}' | \
            tee -a "$REPORT_FILE"
    else
        output "  ${GREEN}✔ 无失败服务${NC}"
    fi

    output ""
    print_sub_section "开机自启服务"
    systemctl list-unit-files --type=service --state=enabled \
        --no-pager --no-legend 2>/dev/null | \
        grep -v "systemd-\|dbus-broker\|getty" | \
        awk '{printf "  %-50s %s\n", $1, $2}' | \
        tee -a "$REPORT_FILE"
}

# ============================================================
#                     十、用户与权限
# ============================================================
collect_user_info() {
    print_section "十、用户与权限信息"

    print_sub_section "系统用户 (UID>=1000 及 root)"
    output ""
    printf "  ${GREEN}%-20s %-8s %-8s %-25s %-s${NC}\n" \
        "用户名" "UID" "GID" "Home目录" "Shell" | \
        tee -a "$REPORT_FILE"
    output "  $(printf '─%.0s' {1..75})"
    awk -F: '($3>=1000 || $3==0) {
        printf "  %-20s %-8s %-8s %-25s %-s\n",
            $1, $3, $4, $6, $7
    }' /etc/passwd 2>/dev/null | tee -a "$REPORT_FILE"

    output ""
    print_sub_section "sudo 权限配置"
    if [[ -f /etc/sudoers ]]; then
        grep -v "^#\|^$\|^Defaults\|^Cmnd_Alias\|^Host_Alias\|^User_Alias" \
            /etc/sudoers 2>/dev/null | \
            sed 's/^/  /' | tee -a "$REPORT_FILE"
    fi
    if [[ -d /etc/sudoers.d ]]; then
        find /etc/sudoers.d -type f 2>/dev/null | \
        while read -r f; do
            local content
            content=$(grep -v "^#\|^$" "$f" 2>/dev/null)
            if [[ -n "$content" ]]; then
                output "  ${YELLOW}[${f}]:${NC}"
                echo "$content" | sed 's/^/    /'
            fi
        done | tee -a "$REPORT_FILE"
    fi

    output ""
    print_sub_section "有登录Shell的用户"
    grep -v "nologin\|false\|sync\|halt\|shutdown" \
        /etc/passwd 2>/dev/null | \
        cut -d: -f1 | sed 's/^/  /' | tee -a "$REPORT_FILE"

    output ""
    print_sub_section "SSH 公钥认证"
    local key_found=false
    for home_dir in /root /home/*; do
        local authorized_keys="$home_dir/.ssh/authorized_keys"
        if [[ -f "$authorized_keys" ]]; then
            key_found=true
            local key_count
            key_count=$(grep -cE \
                "^(ssh-|ecdsa-|sk-)" \
                "$authorized_keys" 2>/dev/null)
            local dir_user
            dir_user=$(basename "$home_dir")
            print_item "$dir_user" "$key_count 个授权公钥"
            # 显示公钥指纹(不显示完整密钥)
            grep -E "^(ssh-|ecdsa-|sk-)" \
                "$authorized_keys" 2>/dev/null | \
            while read -r keyline; do
                local key_comment
                key_comment=$(echo "$keyline" | awk '{print $3}')
                local key_type
                key_type=$(echo "$keyline" | awk '{print $1}')
                output "    ${WHITE}$key_type ${key_comment:-无注释}${NC}"
            done
        fi
    done
    $key_found || print_warn "未找到 authorized_keys 文件"
}

# ============================================================
#                     报告尾部
# ============================================================
print_footer() {
    local end_time
    end_time=$(date '+%Y-%m-%d %H:%M:%S')
    output ""
    output "${PURPLE}${BOLD}╔══════════════════════════════════════════════════════════════╗${NC}"
    output "${PURPLE}${BOLD}║                    信息收集完成                             ║${NC}"
    output "${PURPLE}${BOLD}╚══════════════════════════════════════════════════════════════╝${NC}"
    output ""
    print_item "完成时间"  "$end_time"
    print_item "报告已保存" "$REPORT_FILE"
    output ""
    output "${CYAN}  查看报告:  less -R $REPORT_FILE${NC}"
    output "${CYAN}  纯文本:    cat $REPORT_FILE | sed 's/\\x1B\\[[0-9;]*m//g'${NC}"
    output "${CYAN}  关键词搜索: grep -i 'mysql\\|redis\\|docker' $REPORT_FILE${NC}"
    output ""
}

# ==================== 权限提示 ====================
check_privileges() {
    if [[ $EUID -ne 0 ]]; then
        output ""
        output "${YELLOW}╔══════════════════════════════════════════════════════════════╗${NC}"
        output "${YELLOW}║  ⚠  当前非root用户,以下信息可能无法收集:                  ║${NC}"
        output "${YELLOW}║     dmidecode(内存条详情) / SMART(磁盘健康) / LVM信息        ║${NC}"
        output "${YELLOW}║     建议: sudo bash $0                                       ║${NC}"
        output "${YELLOW}╚══════════════════════════════════════════════════════════════╝${NC}"
    fi
}

# ==================== 主函数 ====================
main() {
    parse_args "$@"
    > "$REPORT_FILE"

    print_header
    check_privileges

    collect_os_info
    collect_cpu_info
    collect_memory_info
    collect_disk_info
    collect_network_info
    collect_gpu_info
    collect_resource_info
    collect_business_software
    collect_systemd_services
    collect_user_info

    print_footer
}

main "$@"
# ```

# ---

# ## 使用说明

# ### 基本使用

# ```bash
# # 给脚本执行权限
# chmod +x server_info.sh

# # 直接运行(推荐 root 权限,可获取更多信息)
# sudo bash server_info.sh

# # 指定报告输出路径
# sudo bash server_info.sh --output /tmp/my_report.txt

# # 非 root 用户运行(部分信息受限)
# bash server_info.sh
# ```

# ### 查看报告

# ```bash
# # 带颜色查看
# less -R /tmp/server_info_report_*.txt

# # 纯文本查看(去掉颜色码)
# cat /tmp/server_info_report_*.txt | sed 's/\x1B\[[0-9;]*m//g'

# # 搜索特定内容
# grep -i "mysql\|redis\|docker" /tmp/server_info_report_*.txt
# ```

# ---

# ## 功能覆盖总览

# | 类别 | 检测项目 |
# |------|---------|
# | **硬件** | CPU型号/核数/频率/温度、内存/Swap/物理内存条、磁盘/分区/SMART/RAID/LVM、GPU(NVIDIA/AMD) |
# | **操作系统** | 发行版/内核/架构、运行时间、时区、虚拟化类型、SELinux/AppArmor |
# | **网络** | 所有网卡/IP/MAC/速率、路由表、DNS、公网IP、防火墙、监听端口 |
# | **Web服务器** | Nginx、Apache、Tomcat、OpenResty、Caddy、HAProxy |
# | **数据库** | MySQL、PostgreSQL、Redis、MongoDB、Elasticsearch、ClickHouse、InfluxDB、Memcached |
# | **消息队列** | Kafka、RabbitMQ、Zookeeper、RocketMQ、ActiveMQ、NATS |
# | **容器/编排** | Docker(含容器列表)、Podman、containerd、Kubernetes、Helm、k3s |
# | **运行时** | Java、Python、Node.js、Go、Rust、Ruby、PHP、.NET 等 |
# | **CI/CD** | Jenkins、GitLab、Ansible、Terraform、Git |
# | **监控** | Prometheus、Grafana、Zabbix、ELK Stack、Datadog、Jaeger |
# | **进程/服务** | 通过进程+端口双重检测、Systemd服务状态 |
# | **用户权限** | 系统用户、sudo权限、SSH公钥 |
# | **已装软件** | APT/RPM/pacman/apk 包管理器、Snap、Flatpak |

 

posted on 2026-06-18 10:04  GKLBB  阅读(0)  评论(0)    收藏  举报