#!/usr/bin/env bash
# Dedicated server inventory and non-destructive hardware test.
# Usage: sudo bash dedicated-server-test.sh [--info-only|--quick|--full] [--install-deps]

set -uo pipefail
export LC_ALL=C

MODE=quick
INSTALL_DEPS=0
DISK_DIR=${DISK_TEST_DIR:-/tmp}
REPORT=${REPORT:-"./hardware-test-$(hostname -s 2>/dev/null || echo server)-$(date +%Y%m%d-%H%M%S).log"}
TEMP_FILE=""

while (($#)); do
  case "$1" in
    --info-only) MODE=info ;;
    --quick) MODE=quick ;;
    --full) MODE=full ;;
    --install-deps) INSTALL_DEPS=1 ;;
    --disk-dir) shift; DISK_DIR=${1:?Missing path after --disk-dir} ;;
    --report) shift; REPORT=${1:?Missing path after --report} ;;
    -h|--help) sed -n '2,20p' "$0"; exit 0 ;;
    *) echo "Unknown option: $1"; exit 2 ;;
  esac
  shift
done

if [[ ${EUID:-$(id -u)} -ne 0 ]]; then
  echo "Run as root: sudo bash $0"
  exit 1
fi

mkdir -p "$(dirname "$REPORT")" "$DISK_DIR" || exit 1
touch "$REPORT" || { echo "Cannot write report: $REPORT"; exit 1; }
exec > >(tee -a "$REPORT") 2>&1
trap '[[ -n $TEMP_FILE ]] && rm -f -- "$TEMP_FILE"' EXIT INT TERM

has() { command -v "$1" >/dev/null 2>&1; }
section() { printf '\n\033[1;36m===== %s =====\033[0m\n' "$1"; }
item() { printf '%-18s %s\n' "$1" "$2"; }
sysval() { [[ -r $1 ]] && tr -d '\0' < "$1" | sed 's/[[:space:]]*$//' || echo N/A; }

if [[ $INSTALL_DEPS -eq 1 ]]; then
  section "Installing optional tools"
  if has apt-get; then
    apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y smartmontools dmidecode pciutils ethtool sysbench stress-ng fio nvme-cli lm-sensors
  elif has dnf; then
    dnf install -y smartmontools dmidecode pciutils ethtool sysbench stress-ng fio nvme-cli lm_sensors
  elif has yum; then
    yum install -y smartmontools dmidecode pciutils ethtool sysbench stress-ng fio nvme-cli lm_sensors
  else
    echo "No supported package manager found; continuing with installed tools."
  fi
fi

section "Overview"
item "Mode" "$MODE"
item "Started" "$(date '+%F %T %Z')"
item "Report" "$REPORT"
item "Disk test path" "$DISK_DIR"
echo "No raw-disk writes are performed. Temporary files are removed automatically."

section "System"
item "Hostname" "$(hostname -f 2>/dev/null || hostname)"
item "OS" "$(. /etc/os-release 2>/dev/null; echo "${PRETTY_NAME:-unknown}")"
item "Kernel" "$(uname -srmo)"
item "Uptime" "$(uptime -p 2>/dev/null || uptime)"
item "Virtualization" "$(systemd-detect-virt 2>/dev/null || echo unknown)"
item "Load" "$(cut -d' ' -f1-3 /proc/loadavg)"

section "CPU"
if has lscpu; then
  lscpu | grep -E '^(Architecture|CPU\(s\)|On-line CPU|Thread|Core|Socket|Model name|CPU max MHz|CPU min MHz|L1d cache|L1i cache|L2 cache|L3 cache|NUMA node)'
else
  grep -m1 -E 'model name|Hardware' /proc/cpuinfo
  item "Logical CPUs" "$(getconf _NPROCESSORS_ONLN)"
fi

section "Memory"
free -h
if has dmidecode; then
  echo
  dmidecode -t memory 2>/dev/null | awk -F: '
    /Memory Device$/ {loc=size=type=speed=maker=part=""}
    /^[[:space:]]*Locator:/ && loc=="" {loc=$2; sub(/^[ \t]+/,"",loc)}
    /^[[:space:]]*Size:/ && size=="" {size=$2; sub(/^[ \t]+/,"",size)}
    /^[[:space:]]*Type:/ && type=="" {type=$2; sub(/^[ \t]+/,"",type)}
    /^[[:space:]]*(Configured Memory Speed|Speed):/ && speed=="" {speed=$2; sub(/^[ \t]+/,"",speed)}
    /^[[:space:]]*Manufacturer:/ {maker=$2; sub(/^[ \t]+/,"",maker)}
    /^[[:space:]]*Part Number:/ {part=$2; sub(/^[ \t]+/,"",part); if(size !~ /No Module/) printf "%s | %s | %s | %s | %s | %s\n",loc,size,type,speed,maker,part}
  '
else
  echo "dmidecode not installed; module details unavailable."
fi

section "Board and firmware"
item "Vendor" "$(sysval /sys/class/dmi/id/sys_vendor)"
item "Product" "$(sysval /sys/class/dmi/id/product_name)"
item "Board" "$(sysval /sys/class/dmi/id/board_name)"
item "Board serial" "$(sysval /sys/class/dmi/id/board_serial)"
item "BIOS vendor" "$(sysval /sys/class/dmi/id/bios_vendor)"
item "BIOS version" "$(sysval /sys/class/dmi/id/bios_version)"
item "BIOS date" "$(sysval /sys/class/dmi/id/bios_date)"

section "Storage layout"
lsblk -e 7 -o NAME,PATH,MODEL,SERIAL,TYPE,SIZE,ROTA,TRAN,FSTYPE,MOUNTPOINTS 2>/dev/null || lsblk
echo
df -hT -x tmpfs -x devtmpfs -x squashfs 2>/dev/null || df -hT

section "Disk health"
mapfile -t DISKS < <(lsblk -dn -o PATH,TYPE 2>/dev/null | awk '$2=="disk" {print $1}')
if has smartctl; then
  for disk in "${DISKS[@]}"; do
    echo "--- $disk ---"
    smartctl -i -H -A "$disk" 2>&1 | grep -Ei 'Device Model|Model Number|Serial Number|Firmware Version|User Capacity|Rotation Rate|SMART overall|SMART Health|Critical Warning|Temperature:|Temperature_Celsius|Power_On_Hours|Power Cycle|Percentage Used|Data Units (Read|Written)|Media and Data Integrity Errors|Error Information Log Entries|Reallocated_Sector|Current_Pending|Offline_Uncorrectable' || true
  done
else
  echo "smartctl not installed; install smartmontools or use --install-deps."
fi
has nvme && nvme list 2>/dev/null || true

section "PCI devices"
if has lspci; then
  lspci -nn | grep -Ei 'ethernet|network|raid|sata|non-volatile|vga|3d|display|usb' || lspci -nn
else
  echo "lspci not installed."
fi

section "Network interfaces"
for path in /sys/class/net/*; do
  iface=${path##*/}
  [[ $iface == lo || $iface == veth* || $iface == docker* || $iface == br-* || $iface == virbr* || $iface == cali* || $iface == flannel* || $iface == cni* ]] && continue
  echo "--- $iface ---"
  ip -br address show dev "$iface" 2>/dev/null || true
  item "MAC" "$(sysval "$path/address")"
  item "Operstate" "$(sysval "$path/operstate")"
  if has ethtool; then
    ethtool "$iface" 2>/dev/null | grep -E 'Speed:|Duplex:|Auto-negotiation:|Link detected:' || true
    ethtool -i "$iface" 2>/dev/null | grep -E 'driver:|version:|firmware-version:|bus-info:' || true
  fi
done
ip route 2>/dev/null || true

section "Sensors"
if has sensors; then
  sensors 2>/dev/null || true
else
  for zone in /sys/class/thermal/thermal_zone*; do
    [[ -r $zone/temp ]] || continue
    awk -v n="$(sysval "$zone/type")" -v t="$(cat "$zone/temp")" 'BEGIN {printf "%-24s %.1f C\n",n,t/1000}'
  done
fi

section "Hardware errors from current boot"
ERRORS=$(dmesg --level=err,crit,alert,emerg 2>/dev/null | grep -Ei 'mce|machine check|hardware error|edac|ecc|I/O error|medium error|nvme.*error|ata.*error|pcie.*error|thermal|overheat' | tail -80 || true)
[[ -n $ERRORS ]] && echo "$ERRORS" || echo "No obvious hardware errors found in accessible kernel logs."

if [[ $MODE != info ]]; then
  section "CPU benchmark"
  duration=10; [[ $MODE == full ]] && duration=30
  if has sysbench; then
    sysbench cpu --threads="$(getconf _NPROCESSORS_ONLN)" --time="$duration" run | grep -E 'events per second:|total time:|total number of events:' || true
  elif has stress-ng; then
    stress-ng --cpu 0 --timeout "${duration}s" --metrics-brief 2>&1 || true
  else
    echo "sysbench/stress-ng not installed; skipped."
  fi

  section "Memory benchmark"
  size=4G; [[ $MODE == full ]] && size=16G
  if has sysbench; then
    sysbench memory --threads="$(getconf _NPROCESSORS_ONLN)" --memory-total-size="$size" run | grep -E 'transferred|total time:|total number of events:' || true
  else
    echo "sysbench not installed; skipped."
  fi

  section "Filesystem benchmark"
  test_mb=512; [[ $MODE == full ]] && test_mb=2048
  available=$(df -Pk "$DISK_DIR" | awk 'NR==2 {print $4}')
  if [[ ${available:-0} -lt $((test_mb * 1024 + 262144)) ]]; then
    echo "Skipped: less than $((test_mb + 256)) MiB free in $DISK_DIR."
  else
    TEMP_FILE="$DISK_DIR/.dedicated-server-test.$$"
    echo "Sequential write ($test_mb MiB):"
    dd if=/dev/zero of="$TEMP_FILE" bs=1M count="$test_mb" conv=fdatasync status=progress 2>&1
    echo "Sequential read:"
    dd if="$TEMP_FILE" of=/dev/null bs=4M iflag=direct status=progress 2>&1 || dd if="$TEMP_FILE" of=/dev/null bs=4M status=progress 2>&1
    rm -f -- "$TEMP_FILE"; TEMP_FILE=""
  fi

  if [[ $MODE == full ]]; then
    section "Combined stability test"
    if has stress-ng; then
      stress-ng --cpu 0 --vm 2 --vm-bytes 60% --verify --timeout 60s --metrics-brief 2>&1 || true
    else
      echo "stress-ng not installed; skipped."
    fi
  fi
fi

section "Final check"
has sensors && sensors 2>/dev/null || true
dmesg --level=err,crit,alert,emerg 2>/dev/null | tail -60 || true
item "Finished" "$(date '+%F %T %Z')"
item "Report" "$REPORT"
echo "Review SMART warnings, kernel errors, temperatures, and benchmark consistency."
