软件研发 --- 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 |
免责声明
本文档所有内容仅供安全研究、学术交流与技术学习使用,严禁用于任何未经授权的逆向破解、网络攻击、隐私窃取、恶意软件开发及其他违反《中华人民共和国网络安全法》《数据安全法》等法律法规的行为,使用者应确保已获得目标软件权利人的合法授权并自行承担因使用本文档内容所产生的一切法律责任与后果,作者不对任何直接或间接损害承担任何责任,继续阅读即视为您已知悉并同意上述全部条款。
浙公网安备 33010602011771号