#!/bin/bash set -euo pipefail # System Information Collector # Comprehensive discovery of hardware, OS, network, and storage configuration # Part of the Current State Discovery Framework SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" DISCOVERY_DIR="${SCRIPT_DIR}/results" TIMESTAMP=$(date +%Y%m%d_%H%M%S) REPORT_FILE="${DISCOVERY_DIR}/system_discovery_${TIMESTAMP}.json" # Create discovery directory mkdir -p "$DISCOVERY_DIR" main() { echo "🔍 Starting system information collection..." # Initialize JSON report cat > "$REPORT_FILE" << 'EOF' { "discovery_metadata": { "timestamp": "", "hostname": "", "discovery_version": "1.0" }, "hardware": {}, "operating_system": {}, "network": {}, "storage": {}, "performance": {} } EOF collect_metadata collect_hardware_info collect_os_info collect_network_info collect_storage_info collect_performance_baseline echo "✅ System discovery complete: $REPORT_FILE" generate_summary } collect_metadata() { echo "📋 Collecting metadata..." jq --arg timestamp "$(date -Iseconds)" \ --arg hostname "$(hostname)" \ '.discovery_metadata.timestamp = $timestamp | .discovery_metadata.hostname = $hostname' \ "$REPORT_FILE" > "${REPORT_FILE}.tmp" && mv "${REPORT_FILE}.tmp" "$REPORT_FILE" } collect_hardware_info() { echo "🖥️ Collecting hardware information..." local cpu_info memory_info gpu_info storage_devices # CPU Information cpu_info=$(cat << 'EOF' { "model": "", "cores": 0, "threads": 0, "architecture": "", "flags": [] } EOF ) if [[ -f /proc/cpuinfo ]]; then local cpu_model=$(grep "model name" /proc/cpuinfo | head -1 | cut -d: -f2 | xargs) local cpu_cores=$(grep "cpu cores" /proc/cpuinfo | head -1 | cut -d: -f2 | xargs) local cpu_threads=$(grep "processor" /proc/cpuinfo | wc -l) local cpu_arch=$(uname -m) local cpu_flags=$(grep "flags" /proc/cpuinfo | head -1 | cut -d: -f2 | xargs | tr ' ' '\n' | jq -R . | jq -s .) cpu_info=$(echo "$cpu_info" | jq --arg model "$cpu_model" \ --argjson cores "${cpu_cores:-1}" \ --argjson threads "$cpu_threads" \ --arg arch "$cpu_arch" \ --argjson flags "$cpu_flags" \ '.model = $model | .cores = $cores | .threads = $threads | .architecture = $arch | .flags = $flags') fi # Memory Information memory_info=$(cat << 'EOF' { "total_gb": 0, "available_gb": 0, "swap_gb": 0, "details": {} } EOF ) if [[ -f /proc/meminfo ]]; then local mem_total=$(grep "MemTotal" /proc/meminfo | awk '{print int($2/1024/1024)}') local mem_available=$(grep "MemAvailable" /proc/meminfo | awk '{print int($2/1024/1024)}') local swap_total=$(grep "SwapTotal" /proc/meminfo | awk '{print int($2/1024/1024)}') local mem_details=$(grep -E "(MemTotal|MemAvailable|MemFree|Buffers|Cached|SwapTotal)" /proc/meminfo | awk '{print "\"" tolower($1) "\":" int($2)}' | tr '\n' ',' | sed 's/,$//' | sed 's/://g') memory_info=$(echo "$memory_info" | jq --argjson total "$mem_total" \ --argjson available "$mem_available" \ --argjson swap "$swap_total" \ --argjson details "{$mem_details}" \ '.total_gb = $total | .available_gb = $available | .swap_gb = $swap | .details = $details') fi # GPU Information gpu_info='[]' # Check for NVIDIA GPUs if command -v nvidia-smi &>/dev/null; then local nvidia_gpus=$(nvidia-smi --query-gpu=name,memory.total --format=csv,noheader,nounits 2>/dev/null | awk -F',' '{print "{\"name\":\"" $1 "\",\"memory_mb\":" $2 ",\"vendor\":\"NVIDIA\"}"}' | jq -s .) gpu_info=$(echo "$gpu_info" | jq ". + $nvidia_gpus") fi # Check for AMD/Intel GPUs via lspci local other_gpus=$(lspci | grep -i vga | awk '{print "{\"name\":\"" substr($0, index($0,$5)) "\",\"vendor\":\"" $4 "\",\"detected_via\":\"lspci\"}"}' | jq -s .) gpu_info=$(echo "$gpu_info" | jq ". + $other_gpus") # Storage Devices storage_devices='[]' if command -v lsblk &>/dev/null; then # Get block devices with detailed info while IFS= read -r line; do if [[ "$line" =~ ^([^[:space:]]+)[[:space:]]+([^[:space:]]+)[[:space:]]+([^[:space:]]+)[[:space:]]+([^[:space:]]*)[[:space:]]+([^[:space:]]*) ]]; then local device="${BASH_REMATCH[1]}" local size="${BASH_REMATCH[2]}" local type="${BASH_REMATCH[3]}" local mountpoint="${BASH_REMATCH[4]}" local fstype="${BASH_REMATCH[5]}" # Check if it's rotational (HDD vs SSD) local rotational="unknown" if [[ -f "/sys/block/$device/queue/rotational" ]]; then if [[ $(cat "/sys/block/$device/queue/rotational" 2>/dev/null) == "0" ]]; then rotational="ssd" else rotational="hdd" fi fi local device_info=$(jq -n --arg name "$device" \ --arg size "$size" \ --arg type "$type" \ --arg mount "$mountpoint" \ --arg fs "$fstype" \ --arg rotation "$rotational" \ '{name: $name, size: $size, type: $type, mountpoint: $mount, filesystem: $fs, storage_type: $rotation}') storage_devices=$(echo "$storage_devices" | jq ". + [$device_info]") fi done < <(lsblk -o NAME,SIZE,TYPE,MOUNTPOINT,FSTYPE --noheadings | grep -E "^[a-z]") fi # Combine hardware info local hardware_data=$(jq -n --argjson cpu "$cpu_info" \ --argjson memory "$memory_info" \ --argjson gpu "$gpu_info" \ --argjson storage "$storage_devices" \ '{cpu: $cpu, memory: $memory, gpu: $gpu, storage: $storage}') jq --argjson hardware "$hardware_data" '.hardware = $hardware' "$REPORT_FILE" > "${REPORT_FILE}.tmp" && mv "${REPORT_FILE}.tmp" "$REPORT_FILE" } collect_os_info() { echo "🐧 Collecting operating system information..." local os_info=$(cat << 'EOF' { "distribution": "", "version": "", "kernel": "", "architecture": "", "installed_packages": [], "running_services": [], "firewall_status": "" } EOF ) # OS Distribution and Version local distro="Unknown" local version="Unknown" if [[ -f /etc/os-release ]]; then distro=$(grep "^NAME=" /etc/os-release | cut -d'"' -f2) version=$(grep "^VERSION=" /etc/os-release | cut -d'"' -f2) fi local kernel=$(uname -r) local arch=$(uname -m) # Installed packages (limit to essential packages to avoid huge lists) local packages='[]' if command -v dpkg &>/dev/null; then packages=$(dpkg -l | grep "^ii" | awk '{print $2}' | grep -E "(docker|nginx|apache|mysql|postgresql|redis|nodejs|python)" | jq -R . | jq -s .) elif command -v rpm &>/dev/null; then packages=$(rpm -qa | grep -E "(docker|nginx|apache|mysql|postgresql|redis|nodejs|python)" | jq -R . | jq -s .) fi # Running services local services='[]' if command -v systemctl &>/dev/null; then services=$(systemctl list-units --type=service --state=active --no-pager --no-legend | awk '{print $1}' | sed 's/.service$//' | head -20 | jq -R . | jq -s .) fi # Firewall status local firewall_status="unknown" if command -v ufw &>/dev/null; then if ufw status | grep -q "Status: active"; then firewall_status="ufw_active" else firewall_status="ufw_inactive" fi elif command -v firewall-cmd &>/dev/null; then if firewall-cmd --state 2>/dev/null | grep -q "running"; then firewall_status="firewalld_active" else firewall_status="firewalld_inactive" fi fi os_info=$(echo "$os_info" | jq --arg distro "$distro" \ --arg version "$version" \ --arg kernel "$kernel" \ --arg arch "$arch" \ --argjson packages "$packages" \ --argjson services "$services" \ --arg firewall "$firewall_status" \ '.distribution = $distro | .version = $version | .kernel = $kernel | .architecture = $arch | .installed_packages = $packages | .running_services = $services | .firewall_status = $firewall') jq --argjson os "$os_info" '.operating_system = $os' "$REPORT_FILE" > "${REPORT_FILE}.tmp" && mv "${REPORT_FILE}.tmp" "$REPORT_FILE" } collect_network_info() { echo "🌐 Collecting network configuration..." local network_info=$(cat << 'EOF' { "interfaces": [], "routing": [], "dns_config": {}, "open_ports": [], "docker_networks": [] } EOF ) # Network interfaces local interfaces='[]' if command -v ip &>/dev/null; then while IFS= read -r line; do if [[ "$line" =~ ^[0-9]+:[[:space:]]+([^:]+): ]]; then local iface="${BASH_REMATCH[1]}" local ip_addr=$(ip addr show "$iface" 2>/dev/null | grep "inet " | head -1 | awk '{print $2}' || echo "") local state=$(ip link show "$iface" 2>/dev/null | head -1 | grep -o "state [A-Z]*" | cut -d' ' -f2 || echo "UNKNOWN") local iface_info=$(jq -n --arg name "$iface" --arg ip "$ip_addr" --arg state "$state" \ '{name: $name, ip_address: $ip, state: $state}') interfaces=$(echo "$interfaces" | jq ". + [$iface_info]") fi done < <(ip link show) fi # Routing table local routing='[]' if command -v ip &>/dev/null; then while IFS= read -r line; do local route_info=$(echo "$line" | jq -R .) routing=$(echo "$routing" | jq ". + [$route_info]") done < <(ip route show | head -10) fi # DNS configuration local dns_config='{}' if [[ -f /etc/resolv.conf ]]; then local nameservers=$(grep "nameserver" /etc/resolv.conf | awk '{print $2}' | jq -R . | jq -s .) local domain=$(grep "domain\|search" /etc/resolv.conf | head -1 | awk '{print $2}' || echo "") dns_config=$(jq -n --argjson nameservers "$nameservers" --arg domain "$domain" \ '{nameservers: $nameservers, domain: $domain}') fi # Open ports local open_ports='[]' if command -v ss &>/dev/null; then while IFS= read -r line; do local port_info=$(echo "$line" | jq -R .) open_ports=$(echo "$open_ports" | jq ". + [$port_info]") done < <(ss -tuln | grep LISTEN | head -20) elif command -v netstat &>/dev/null; then while IFS= read -r line; do local port_info=$(echo "$line" | jq -R .) open_ports=$(echo "$open_ports" | jq ". + [$port_info]") done < <(netstat -tuln | grep LISTEN | head -20) fi # Docker networks local docker_networks='[]' if command -v docker &>/dev/null && docker info &>/dev/null; then while IFS= read -r line; do local network_info=$(echo "$line" | jq -R . | jq 'split(" ") | {name: .[0], id: .[1], driver: .[2], scope: .[3]}') docker_networks=$(echo "$docker_networks" | jq ". + [$network_info]") done < <(docker network ls --format "{{.Name}} {{.ID}} {{.Driver}} {{.Scope}}" 2>/dev/null || echo "") fi network_info=$(echo "$network_info" | jq --argjson interfaces "$interfaces" \ --argjson routing "$routing" \ --argjson dns "$dns_config" \ --argjson ports "$open_ports" \ --argjson docker_nets "$docker_networks" \ '.interfaces = $interfaces | .routing = $routing | .dns_config = $dns | .open_ports = $ports | .docker_networks = $docker_nets') jq --argjson network "$network_info" '.network = $network' "$REPORT_FILE" > "${REPORT_FILE}.tmp" && mv "${REPORT_FILE}.tmp" "$REPORT_FILE" } collect_storage_info() { echo "💾 Collecting storage information..." local storage_info=$(cat << 'EOF' { "filesystems": [], "disk_usage": [], "mount_points": [], "docker_volumes": [] } EOF ) # Filesystem information local filesystems='[]' while IFS= read -r line; do local fs_info=$(echo "$line" | awk '{print "{\"filesystem\":\"" $1 "\",\"size\":\"" $2 "\",\"used\":\"" $3 "\",\"available\":\"" $4 "\",\"use_percent\":\"" $5 "\",\"mount\":\"" $6 "\"}"}' | jq .) filesystems=$(echo "$filesystems" | jq ". + [$fs_info]") done < <(df -h | grep -E "^/dev") # Disk usage for important directories local disk_usage='[]' local important_dirs=("/home" "/var" "/opt" "/usr" "/etc") for dir in "${important_dirs[@]}"; do if [[ -d "$dir" ]]; then local usage=$(du -sh "$dir" 2>/dev/null | awk '{print $1}' || echo "unknown") local usage_info=$(jq -n --arg dir "$dir" --arg size "$usage" '{directory: $dir, size: $size}') disk_usage=$(echo "$disk_usage" | jq ". + [$usage_info]") fi done # Mount points with options local mount_points='[]' while IFS= read -r line; do if [[ "$line" =~ ^([^[:space:]]+)[[:space:]]+([^[:space:]]+)[[:space:]]+([^[:space:]]+)[[:space:]]+([^[:space:]]+) ]]; then local device="${BASH_REMATCH[1]}" local mount="${BASH_REMATCH[2]}" local fstype="${BASH_REMATCH[3]}" local options="${BASH_REMATCH[4]}" local mount_info=$(jq -n --arg device "$device" --arg mount "$mount" --arg fstype "$fstype" --arg opts "$options" \ '{device: $device, mountpoint: $mount, filesystem: $fstype, options: $opts}') mount_points=$(echo "$mount_points" | jq ". + [$mount_info]") fi done < <(cat /proc/mounts | grep -E "^/dev") # Docker volumes local docker_volumes='[]' if command -v docker &>/dev/null && docker info &>/dev/null; then while IFS= read -r line; do local vol_name=$(echo "$line" | awk '{print $1}') local vol_driver=$(echo "$line" | awk '{print $2}') local vol_mountpoint=$(docker volume inspect "$vol_name" --format '{{.Mountpoint}}' 2>/dev/null || echo "unknown") local vol_size=$(du -sh "$vol_mountpoint" 2>/dev/null | awk '{print $1}' || echo "unknown") local vol_info=$(jq -n --arg name "$vol_name" --arg driver "$vol_driver" --arg mount "$vol_mountpoint" --arg size "$vol_size" \ '{name: $name, driver: $driver, mountpoint: $mount, size: $size}') docker_volumes=$(echo "$docker_volumes" | jq ". + [$vol_info]") done < <(docker volume ls --format "{{.Name}} {{.Driver}}" 2>/dev/null || echo "") fi storage_info=$(echo "$storage_info" | jq --argjson filesystems "$filesystems" \ --argjson disk_usage "$disk_usage" \ --argjson mount_points "$mount_points" \ --argjson docker_volumes "$docker_volumes" \ '.filesystems = $filesystems | .disk_usage = $disk_usage | .mount_points = $mount_points | .docker_volumes = $docker_volumes') jq --argjson storage "$storage_info" '.storage = $storage' "$REPORT_FILE" > "${REPORT_FILE}.tmp" && mv "${REPORT_FILE}.tmp" "$REPORT_FILE" } collect_performance_baseline() { echo "📊 Collecting performance baseline..." local performance_info=$(cat << 'EOF' { "load_average": {}, "cpu_usage": {}, "memory_usage": {}, "disk_io": {}, "network_stats": {} } EOF ) # Load average local load_avg='{}' if [[ -f /proc/loadavg ]]; then local load_data=$(cat /proc/loadavg) local load_1min=$(echo "$load_data" | awk '{print $1}') local load_5min=$(echo "$load_data" | awk '{print $2}') local load_15min=$(echo "$load_data" | awk '{print $3}') load_avg=$(jq -n --arg l1 "$load_1min" --arg l5 "$load_5min" --arg l15 "$load_15min" \ '{one_minute: $l1, five_minute: $l5, fifteen_minute: $l15}') fi # CPU usage snapshot local cpu_usage='{}' if [[ -f /proc/stat ]]; then local cpu_line=$(grep "^cpu " /proc/stat) cpu_usage=$(echo "$cpu_line" | awk '{print "{\"user\":" $2 ",\"nice\":" $3 ",\"system\":" $4 ",\"idle\":" $5 ",\"iowait\":" $6 "}"}' | jq .) fi # Memory usage local memory_usage='{}' if [[ -f /proc/meminfo ]]; then local mem_total=$(grep "MemTotal" /proc/meminfo | awk '{print $2}') local mem_free=$(grep "MemFree" /proc/meminfo | awk '{print $2}') local mem_available=$(grep "MemAvailable" /proc/meminfo | awk '{print $2}') local mem_used=$((mem_total - mem_free)) memory_usage=$(jq -n --argjson total "$mem_total" --argjson free "$mem_free" --argjson available "$mem_available" --argjson used "$mem_used" \ '{total_kb: $total, free_kb: $free, available_kb: $available, used_kb: $used}') fi # Disk I/O stats local disk_io='[]' if [[ -f /proc/diskstats ]]; then while IFS= read -r line; do if [[ "$line" =~ ^[[:space:]]*[0-9]+[[:space:]]+[0-9]+[[:space:]]+([a-z]+)[[:space:]]+([0-9]+)[[:space:]]+[0-9]+[[:space:]]+([0-9]+)[[:space:]]+[0-9]+[[:space:]]+([0-9]+)[[:space:]]+[0-9]+[[:space:]]+([0-9]+) ]]; then local device="${BASH_REMATCH[1]}" local reads="${BASH_REMATCH[2]}" local read_sectors="${BASH_REMATCH[3]}" local writes="${BASH_REMATCH[4]}" local write_sectors="${BASH_REMATCH[5]}" # Only include main devices, not partitions if [[ ! "$device" =~ [0-9]+$ ]]; then local io_info=$(jq -n --arg dev "$device" --arg reads "$reads" --arg read_sectors "$read_sectors" --arg writes "$writes" --arg write_sectors "$write_sectors" \ '{device: $dev, reads: $reads, read_sectors: $read_sectors, writes: $writes, write_sectors: $write_sectors}') disk_io=$(echo "$disk_io" | jq ". + [$io_info]") fi fi done < <(cat /proc/diskstats) fi # Network stats local network_stats='[]' if [[ -f /proc/net/dev ]]; then while IFS= read -r line; do if [[ "$line" =~ ^[[:space:]]*([^:]+):[[:space:]]*([0-9]+)[[:space:]]+[0-9]+[[:space:]]+[0-9]+[[:space:]]+[0-9]+[[:space:]]+[0-9]+[[:space:]]+[0-9]+[[:space:]]+[0-9]+[[:space:]]+[0-9]+[[:space:]]+([0-9]+) ]]; then local interface="${BASH_REMATCH[1]}" local rx_bytes="${BASH_REMATCH[2]}" local tx_bytes="${BASH_REMATCH[3]}" # Skip loopback if [[ "$interface" != "lo" ]]; then local net_info=$(jq -n --arg iface "$interface" --arg rx "$rx_bytes" --arg tx "$tx_bytes" \ '{interface: $iface, rx_bytes: $rx, tx_bytes: $tx}') network_stats=$(echo "$network_stats" | jq ". + [$net_info]") fi fi done < <(tail -n +3 /proc/net/dev) fi performance_info=$(echo "$performance_info" | jq --argjson load "$load_avg" \ --argjson cpu "$cpu_usage" \ --argjson memory "$memory_usage" \ --argjson disk_io "$disk_io" \ --argjson network "$network_stats" \ '.load_average = $load | .cpu_usage = $cpu | .memory_usage = $memory | .disk_io = $disk_io | .network_stats = $network') jq --argjson performance "$performance_info" '.performance = $performance' "$REPORT_FILE" > "${REPORT_FILE}.tmp" && mv "${REPORT_FILE}.tmp" "$REPORT_FILE" } generate_summary() { echo "" echo "📋 SYSTEM DISCOVERY SUMMARY" echo "==========================" # Extract key information for summary local hostname=$(jq -r '.discovery_metadata.hostname' "$REPORT_FILE") local cpu_model=$(jq -r '.hardware.cpu.model' "$REPORT_FILE") local memory_gb=$(jq -r '.hardware.memory.total_gb' "$REPORT_FILE") local os_distro=$(jq -r '.operating_system.distribution' "$REPORT_FILE") local storage_count=$(jq '.hardware.storage | length' "$REPORT_FILE") local network_interfaces=$(jq '.network.interfaces | length' "$REPORT_FILE") local docker_containers=$(jq '.network.docker_networks | length' "$REPORT_FILE") echo "Hostname: $hostname" echo "CPU: $cpu_model" echo "Memory: ${memory_gb}GB" echo "OS: $os_distro" echo "Storage Devices: $storage_count" echo "Network Interfaces: $network_interfaces" echo "Docker Networks: $docker_containers" echo "" echo "Full report: $REPORT_FILE" echo "Next: Run service_inventory_collector.sh" } # Execute main function main "$@"