Click any finding name to jump to the evidence section it came from.
'
echo '
'
if [[ -s "$FINDINGS" ]]; then cat "$FINDINGS"; else
echo '
No findings were generated.
'; fi
echo '
'
}
emit_tail() {
local sannote=""
if [[ $DO_SANITIZE -eq 1 ]]; then
sannote='
Sanitized report. Serial numbers, MAC addresses, UUIDs, WWNs, IP addresses,
SSIDs, machine/boot IDs, the hostname and the username have been redacted. Redaction is
pattern-based and best-effort - review before publishing, especially free-text log excerpts.
'
fi
cat <
${sannote}
Generated by goat-check.sh v${VERSION}. Evidence blocks are raw tool output; the findings table
applies heuristics (SMART thresholds, NVMe endurance, battery wear, temperature and throttle
limits, ECC and PCIe AER counters, deduplicated log patterns). A FAIL on SMART, ECC, media
errors or filesystem corruption should be treated as urgent - back up data first.
Repeating kernel messages are collapsed to unique patterns before being counted, so a single
fault that logs thousands of lines is reported as one finding rather than thousands.
↑ Top
HTMLTAIL
}
FINALIZED=0
finalize() {
[[ $FINALIZED -eq 1 ]] && return
FINALIZED=1
close_category
{ emit_head; cat "$BODY"; emit_tail; } > "$REPORT" 2>/dev/null
ln -sfn "$REPORT" "$LATEST" 2>/dev/null
chown "$REAL_USER":"$REAL_USER" "$REPORT" 2>/dev/null
chown -h "$REAL_USER":"$REAL_USER" "$LATEST" 2>/dev/null
chmod 644 "$REPORT" 2>/dev/null
rm -rf "$TMPD" 2>/dev/null
local overall ocol
if (( F_FAIL > 0 )); then overall="ACTION REQUIRED"; ocol="$C_RED"
elif (( F_WARN > 0 )); then overall="ATTENTION ADVISED"; ocol="$C_YEL"
else overall="HEALTHY"; ocol="$C_GRN"; fi
echo
printf '%s\n' "${C_BOLD}${C_WHT}================================ SUMMARY ================================${C_RESET}"
printf ' Overall verdict : %s%s%s\n' "$ocol$C_BOLD" "$overall" "$C_RESET"
printf ' Findings : %sFAIL %s%s %sWARN %s%s %sOK %s%s %sINFO %s%s\n' \
"$C_RED" "$F_FAIL" "$C_RESET" "$C_YEL" "$F_WARN" "$C_RESET" "$C_GRN" "$F_OK" "$C_RESET" "$C_CYA" "$F_INFO" "$C_RESET"
printf ' Checks executed : %s total (%s%s ok%s, %s%s problem%s, %s%s skipped%s)\n' \
"$CHECK_TOTAL" "$C_GRN" "$C_OK" "$C_RESET" "$C_YEL" "$C_WARN" "$C_RESET" "$C_GRY" "$C_SKIP" "$C_RESET"
printf ' Elapsed : %s seconds\n' "$(( $(date +%s) - START_EPOCH ))"
[[ $DO_SANITIZE -eq 1 ]] && printf ' Sanitization : %sENABLED%s - serials, MACs, UUIDs, IPs, hostname and username redacted\n' "$C_CYA$C_BOLD" "$C_RESET"
printf '%s\n' "${C_BOLD}${C_WHT}=========================================================================${C_RESET}"
echo
printf '%s\n' "${C_GRN}${C_BOLD}##########################################################################${C_RESET}"
printf '%s\n' "${C_GRN}${C_BOLD}# HTML REPORT IS READY #${C_RESET}"
printf '%s\n' "${C_GRN}${C_BOLD}##########################################################################${C_RESET}"
echo
printf ' %sReport file:%s %s%s%s\n' "$C_BOLD" "$C_RESET" "$C_CYA$C_BOLD" "$REPORT" "$C_RESET"
printf ' %sAlways-latest symlink:%s %s\n' "$C_DIM" "$C_RESET" "$LATEST"
echo
printf ' %sOpen it in Firefox with:%s\n' "$C_BOLD" "$C_RESET"
echo
printf ' %s%sfirefox "file://%s"%s\n' "$C_YEL" "$C_BOLD" "$REPORT" "$C_RESET"
echo
printf ' %s(alternatives: xdg-open "%s" | firefox "file://%s")%s\n' "$C_DIM" "$REPORT" "$LATEST" "$C_RESET"
printf ' %sInside the report, the green "Download report" button (top right) saves a copy anywhere.%s\n' "$C_DIM" "$C_RESET"
echo
printf '%s\n' "${C_GRN}${C_BOLD}##########################################################################${C_RESET}"
echo
if [[ $DO_OPEN -eq 1 ]] && have firefox; then
info "Launching Firefox as ${REAL_USER}..."
as_user nohup firefox "file://$REPORT" >/dev/null 2>&1 &
fi
}
trap 'echo; warn "Interrupted - writing the partial report..."; finalize; exit 130' INT TERM
trap 'finalize' EXIT
#=============================================================================#
# PHASE 1: TOOLING #
#=============================================================================#
banner "goat-check v${VERSION} - full hardware health diagnostics"
info "Host : $HOSTN"
info "Kernel : $(uname -r)"
info "Date : $(date)"
info "Report file : $REPORT"
info "Mode : $([[ $DO_STRESS -eq 1 ]] && echo 'FULL + stress/benchmark tests' || echo 'standard (use --stress for load tests)')"
info "Sanitize : $([[ $DO_SANITIZE -eq 1 ]] && echo 'ENABLED - identifying data will be redacted' || echo 'disabled (use --sanitize before sharing)')"
info "Per-check timeout: ${TIMEOUT}s"
# hddtemp removed: gone from Ubuntu 22.04+, superseded by the drivetemp module.
PKGS=(
smartmontools nvme-cli hdparm sdparm lsscsi util-linux parted gdisk
lm-sensors fancontrol
dmidecode lshw pciutils usbutils hwinfo inxi cpuid
procps psmisc sysstat iotop
memtester stress-ng sysbench fio
upower acpi acpitool powertop
ethtool iproute2 net-tools dnsutils iputils-ping curl wget wireless-tools iw
mdadm lvm2 xfsprogs e2fsprogs btrfs-progs cryptsetup-bin
fwupd rasdaemon edac-utils
alsa-utils pulseaudio-utils
systemd-coredump
bc gawk file jq
mesa-utils vainfo
)
if [[ $DO_INSTALL -eq 1 ]]; then
step "PHASE 1/3 - installing diagnostic tooling"
export DEBIAN_FRONTEND=noninteractive
if [[ ${EUID:-$(id -u)} -ne 0 ]]; then
warn "Not root - skipping package installation."
elif ! have apt-get; then
warn "apt-get not found - this does not look like a Debian-based system. Skipping installation."
else
info "Refreshing package lists (apt-get update)..."
if apt-get update -qq 2>&1 | tail -n 3; then ok "Package lists updated."
else warn "apt-get update reported problems - continuing with cached lists."; fi
info "Checking which of the ${#PKGS[@]} packages exist in this release's archive..."
AVAIL=(); UNAVAIL=()
for p in "${PKGS[@]}"; do
if apt-cache show "$p" >/dev/null 2>&1; then AVAIL+=("$p"); else UNAVAIL+=("$p"); fi
done
if (( ${#UNAVAIL[@]} > 0 )); then
warn "Not in this release's archive, will be skipped: ${UNAVAIL[*]}"
fi
ok "${#AVAIL[@]} package(s) available for installation."
info "Installing (single batch)..."
if apt-get install -y -qq --no-install-recommends "${AVAIL[@]}" >/dev/null 2>&1; then
ok "All available packages installed successfully in one pass."
else
warn "Batch install failed - falling back to per-package installation."
FAILED=()
for p in "${AVAIL[@]}"; do
if dpkg -s "$p" >/dev/null 2>&1; then
printf ' %s[have]%s %s\n' "$C_GRY" "$C_RESET" "$p"
elif apt-get install -y -qq --no-install-recommends "$p" >/dev/null 2>&1; then
printf ' %s[ ok ]%s %s\n' "$C_GRN" "$C_RESET" "$p"
else
printf ' %s[fail]%s %s\n' "$C_YEL" "$C_RESET" "$p"; FAILED+=("$p")
fi
done
(( ${#FAILED[@]} > 0 )) && warn "Failed to install: ${FAILED[*]}" || ok "All packages present after fallback pass."
fi
fi
else
step "PHASE 1/3 - package installation skipped (--no-install)"
fi
if [[ $DO_MODPROBE -eq 1 && ${EUID:-$(id -u)} -eq 0 ]]; then
info "Loading common sensor modules (non-persistent)..."
for m in coretemp k10temp drivetemp nct6775 it87 acpi_power_meter; do
modprobe -q "$m" 2>/dev/null && printf ' %s[loaded]%s %s\n' "$C_GRN" "$C_RESET" "$m"
done
fi
if [[ $DO_SENSORS_DETECT -eq 1 ]] && have sensors-detect; then
info "Running sensors-detect --auto (this writes /etc/modules)..."
yes '' | sensors-detect --auto >/dev/null 2>&1 && ok "sensors-detect finished." || warn "sensors-detect failed."
have systemctl && systemctl restart kmod 2>/dev/null
fi
have rasdaemon && systemctl start rasdaemon 2>/dev/null
if [[ $DO_SANITIZE -eq 1 ]]; then
info "Building sanitization ruleset from this machine's identifiers..."
build_sanitize_rules
ok "$(grep -c . "$SAN_SED" 2>/dev/null || echo 0) redaction rules active."
fi
if have smartctl; then
info "Probing storage devices for a working SMART access method (USB bridges included)..."
smart_cache
for d in $(disks_list); do
t="$(smart_type_of "$d")"
if [[ "$t" == "UNSUPPORTED" ]]; then
printf ' %s[none]%s %s - no SMART access method found\n' "$C_YEL" "$C_RESET" "$d"
else
printf ' %s[ ok ]%s %s via %s\n' "$C_GRN" "$C_RESET" "$d" "${t:-auto-detect}"
fi
done
fi
step "PHASE 2/3 - running diagnostics"
#=============================================================================#
# PHASE 2: DIAGNOSTICS #
#=============================================================================#
#--------------------------------------------------------- 1. SYSTEM OVERVIEW -
category "System Overview & Identity"
run_check "Operating system release" - 'cat /etc/os-release; echo; lsb_release -a 2>/dev/null'
run_check "Host, kernel & architecture" - 'hostnamectl 2>/dev/null; echo; uname -a; echo; cat /proc/version'
run_check "Uptime, load average & users" - 'uptime; echo; cat /proc/loadavg; echo; who -a 2>/dev/null | head -n 20'
run_check "Virtualization detection" - 'systemd-detect-virt 2>/dev/null || true; grep -iE "hypervisor" /proc/cpuinfo | head -1 | none_if_empty "No hypervisor CPU flag - running on bare metal."'
run_check "DMI: system / baseboard / chassis" dmidecode 'dmidecode -t system -t baseboard -t chassis'
run_check "DMI: BIOS / firmware" dmidecode 'dmidecode -t bios'
run_check "Hardware inventory (lshw)" lshw 'lshw -short'
run_check "Full hardware detail (lshw)" lshw 'lshw -sanitize' 180
run_check "System summary (inxi)" inxi 'inxi -Fxxxz --no-host -c 0' 120
run_check "Boot history" - 'journalctl --list-boots --no-pager 2>/dev/null | tail -n 15; echo; last -x reboot 2>/dev/null | head -n 15'
#-------------------------------------------------------------------- 2. CPU --
category "CPU / Processor"
run_check "CPU topology & features (lscpu)" lscpu 'lscpu; echo; lscpu -e 2>/dev/null'
run_check "CPU frequency & governors" - 'cpufreq_report'
run_check "CPU details from DMI" dmidecode 'dmidecode -t processor'
run_check "Speculative-execution vulnerabilities" - 'grep -r . /sys/devices/system/cpu/vulnerabilities/ 2>/dev/null | sed "s|/sys/devices/system/cpu/vulnerabilities/||" | sort'
run_check "Microcode revision & currency" - 'microcode_report'
run_check "Thermal & power throttle counters" - 'throttle_report'
run_check "Machine-check / hardware errors in log" - 'dmesg -T 2>/dev/null | grep -iE "machine check|\bmce\b|hardware error|thermal throttl|temperature above" | tail -n 40 | none_if_empty "No machine-check or hardware-error messages this boot."'
run_check "Top CPU consumers" ps 'ps -eo pid,user,pcpu,pmem,etime,stat,comm --sort=-pcpu | head -n 16'
run_check "CPU statistics (mpstat)" mpstat 'mpstat -P ALL 1 3'
run_check "Interrupts & context switches" - 'vmstat 1 3; echo; head -n 25 /proc/interrupts'
if [[ $DO_STRESS -eq 1 ]]; then
run_check "STRESS: all-core CPU load (30s)" stress-ng 'stress-ng --cpu 0 --cpu-method matrixprod --timeout 30s --metrics-brief --times' 120
run_check "BENCH: sysbench CPU (single + multi)" sysbench 'sysbench cpu --cpu-max-prime=20000 --threads=1 run | tail -n 20; echo; sysbench cpu --cpu-max-prime=20000 --threads=$(nproc) run | tail -n 20' 180
run_check "Post-load temperatures" sensors 'sensors'
run_check "Post-load throttle counters" - 'throttle_report'
fi
analyze_cpu_load
#----------------------------------------------------------------- 3. MEMORY --
category "Memory (RAM / Swap / ECC)"
run_check "Memory usage overview" - 'free -h; echo; free -m'
run_check "/proc/meminfo" - 'cat /proc/meminfo'
run_check "Installed DIMMs (DMI type 17)" dmidecode 'dmidecode -t memory'
run_check "Memory array capabilities" dmidecode 'dmidecode -t 16'
run_check "Swap configuration" - 'swapon --show 2>/dev/null; echo; cat /proc/swaps; echo; echo "vm.swappiness = $(cat /proc/sys/vm/swappiness 2>/dev/null)"'
run_check "ECC / EDAC error counters" - 'edac_report'
run_check "OOM-killer history" - 'dmesg -T 2>/dev/null | grep -iE "out of memory|oom-killer|killed process" | tail -n 25 | none_if_empty "No OOM-killer events this boot."'
run_check "Top memory consumers" ps 'ps -eo pid,user,pmem,rss,vsz,etime,comm --sort=-pmem | head -n 16'
run_check "Memory pressure / slab summary" - 'vmstat -s 2>/dev/null | head -n 25; echo; slabtop -o 2>/dev/null | head -n 15'
if [[ $DO_STRESS -eq 1 ]]; then
run_check "STRESS: memtester (guarded)" memtester "memtester_guarded ${MEMTEST_SIZE}" 900
run_check "STRESS: stress-ng VM stressor (guarded)" stress-ng 'mem_stress' 150
fi
analyze_memory
#--------------------------------------------------- 4. STORAGE & FILESYSTEMS -
category "Storage: Disks, SMART & Filesystems"
run_check "Block device tree" lsblk 'lsblk -o NAME,TYPE,SIZE,FSTYPE,LABEL,MOUNTPOINT,ROTA,TRAN,MODEL,SERIAL'
run_check "SMART health summary (all drives)" smartctl 'smart_summary'
run_check "SMART device scan" smartctl 'smartctl --scan-open'
run_check "SMART full attribute dump" smartctl 'smart_report' 240
run_check "SMART self-test history" smartctl 'smart_selftest_log'
run_check "SMART device statistics" smartctl 'smart_devstat'
run_check "NVMe controller & health logs" nvme 'nvme_report' 180
run_check "Disk parameters (hdparm -I)" hdparm 'for d in $(disks_list); do echo "### $d"; hdparm -I "$d" 2>&1 | head -n 45; echo; done'
run_check "TRIM / discard configuration" - 'trim_report'
run_check "Filesystem usage (space)" df 'df -hT -x tmpfs -x devtmpfs'
run_check "Filesystem usage (inodes)" df 'df -ihT -x tmpfs -x devtmpfs'
run_check "Mounted filesystems & options" findmnt 'findmnt --real -o TARGET,SOURCE,FSTYPE,OPTIONS'
run_check "ext2/3/4 filesystem state" tune2fs 'fs_report'
run_check "Partition tables" parted 'for d in $(disks_list); do echo "### $d"; parted -s "$d" print 2>&1; echo; done'
run_check "RAID / LVM / ZFS / LUKS status" - 'raid_report'
run_check "Storage I/O errors in kernel log" - 'dmesg -T 2>/dev/null | grep -iE "I/O error|blk_update_request|buffer I/O|ata[0-9]+[.:].*(error|failed|exception)|hard resetting link|medium error|SError|EXT4-fs error" | tail -n 40 | none_if_empty "No storage I/O errors this boot."'
run_check "Disk I/O statistics" iostat 'iostat -xh 1 3'
run_check "Log & cache disk consumption" - 'log_usage_report'
if [[ $DO_STRESS -eq 1 ]]; then
run_check "BENCH: sequential/cached read (hdparm)" hdparm 'bench_hdparm' 300
run_check "BENCH: random 4k read IOPS (fio)" fio 'bench_disk' 180
run_check "STRESS: SMART short self-test (polled)" smartctl 'smart_selftest' 700
fi
analyze_smart
analyze_selftest_age
analyze_disk_space
analyze_trim
analyze_logs_disk
#--------------------------------------------------------- 5. THERMAL & FANS --
category "Thermal, Fans & Cooling"
run_check "Sensor readings (lm-sensors)" sensors 'sensors -A'
run_check "Raw sensor values" sensors 'sensors -u'
run_check "Thermal zones & cooling devices" - 'thermal_report'
run_check "Fan tachometers & PWM" - 'fan_report'
run_check "Drive temperatures" smartctl 'drive_temps'
run_check "Thermal events in kernel log" - 'dmesg -T 2>/dev/null | grep -iE "thermal|temperature|critical temp" | tail -n 30 | none_if_empty "No thermal messages in the kernel log."'
analyze_temps
#------------------------------------------------------- 6. BATTERY & POWER ---
category "Battery & Power Supply"
run_check "Power supply sysfs dump" - 'power_report'
run_check "Battery details (upower)" upower 'upower -e 2>/dev/null | while read -r p; do echo "### $p"; upower -i "$p"; echo; done | none_if_empty "upower reports no power devices."'
run_check "ACPI battery/thermal/adapter" acpi 'acpi -V 2>&1'
run_check "AC adapter state" - 'grep -H . /sys/class/power_supply/A{C,DP,C0}*/online 2>/dev/null | none_if_empty "No AC adapter node exposed (normal on desktops)."'
run_check "Power consumption estimate" powertop 'timeout 25 powertop --time=15 --csv=/dev/stdout 2>/dev/null | head -n 60' 60
run_check "Suspend/resume & power events" - 'journalctl -b --no-pager 2>/dev/null | grep -iE "suspend|resume|hibernat|battery|acpi" | tail -n 30 | none_if_empty "No suspend/resume or ACPI power events this boot."'
analyze_battery
#--------------------------------------------------------- 7. GPU & DISPLAY ---
category "GPU & Display"
run_check "Graphics controllers (lspci)" lspci 'lspci -nnk | grep -iA3 -E "vga|3d|display"'
run_check "DRM devices" - 'ls -l /sys/class/drm 2>/dev/null; echo; for c in /sys/class/drm/card*/device/{vendor,device}; do [ -r "$c" ] && echo "$c = $(cat $c)"; done 2>/dev/null'
run_check "OpenGL renderer (glxinfo)" glxinfo 'as_user glxinfo -B 2>&1'
run_check "NVIDIA GPU status" nvidia-smi 'nvidia-smi -q 2>&1 | head -n 80'
run_check "AMD/Intel GPU sysfs telemetry" - 'for f in /sys/class/drm/card*/device/gpu_busy_percent /sys/class/drm/card*/device/mem_info_vram_used /sys/class/drm/card*/device/hwmon/hwmon*/temp1_input /sys/class/drm/card*/device/hwmon/hwmon*/power1_average; do [ -r "$f" ] && echo "$f = $(cat $f)"; done 2>/dev/null | none_if_empty "No AMD/Intel GPU telemetry nodes exposed."'
run_check "VA-API video acceleration" vainfo 'as_user vainfo 2>&1 | head -n 30'
run_check "GPU / DRM errors in kernel log" - 'dmesg -T 2>/dev/null | grep -iE "drm|nouveau|amdgpu|i915|xe |nvidia" | grep -iE "error|fail|warn|reset|hang|GPU HANG" | tail -n 25 | none_if_empty "No GPU or DRM errors this boot."'
#---------------------------------------------------------------- 8. AUDIO ----
category "Audio Devices"
run_check "Audio hardware & sound server" - 'audio_report' 90
run_check "Audio errors in kernel log" - 'dmesg -T 2>/dev/null | grep -iE "snd|azx|hda|alsa|sof-audio" | grep -iE "error|fail|timeout|no response" | tail -n 20 | none_if_empty "No audio subsystem errors this boot."'
#---------------------------------------------------------------- 9. NETWORK --
category "Network Interfaces & Connectivity"
run_check "Interface addresses & routes" ip 'ip -br addr; echo; ip route; echo; ip -6 route 2>/dev/null | head -n 10'
run_check "Link statistics (errors/drops)" ip 'ip -s -s link'
run_check "Per-NIC driver, speed & errors" - 'net_report'
run_check "Wireless status" - 'iw dev 2>/dev/null; echo; iwconfig 2>/dev/null; echo; rfkill list 2>/dev/null'
run_check "DNS configuration" - 'cat /etc/resolv.conf 2>/dev/null; echo; resolvectl status 2>/dev/null | head -n 30'
run_check "Connectivity test (ICMP/DNS/HTTP)" - 'ping -c3 -W3 1.1.1.1 2>&1; echo; getent hosts deb.debian.org 2>&1; echo; curl -sSI -m 10 https://deb.debian.org 2>&1 | head -n 5' 60
run_check "Listening sockets" ss 'ss -tulpn 2>/dev/null | head -n 40'
run_check "Network errors in kernel log" - 'dmesg -T 2>/dev/null | grep -iE "eth[0-9]|enp|wlp|wlan|link is|carrier|NIC Link" | tail -n 30 | none_if_empty "No network interface messages this boot."'
analyze_network
#------------------------------------------- 10. PERIPHERALS, PCIe & FIRMWARE -
category "Peripherals, PCIe Buses & Firmware"
run_check "PCI devices with drivers" lspci 'lspci -nnk'
run_check "PCI bus tree" lspci 'lspci -tv'
run_check "PCIe link speed vs capability" lspci 'pcie_link_report' 120
run_check "PCIe AER error counters" - 'aer_report'
run_check "USB device tree" lsusb 'lsusb -t; echo; lsusb'
run_check "USB enumeration errors" - 'dmesg -T 2>/dev/null | grep -iE "usb.*(cannot enable|device descriptor read|unable to enumerate|error -71|error -110|over-current)" | tail -n 20 | none_if_empty "No USB enumeration errors this boot."'
run_check "SCSI / storage controllers" lsscsi 'lsscsi -s 2>&1; echo; lsblk -S 2>/dev/null'
run_check "Input devices" - 'cat /proc/bus/input/devices 2>/dev/null | grep -E "^N:|^H:|^P:" | head -n 50'
run_check "Firmware devices (fwupd)" fwupdmgr 'fwupdmgr get-devices 2>&1' 90 2
run_check "Available firmware updates" fwupdmgr 'fwupdmgr get-updates 2>&1' 90 2
run_check "Missing firmware blobs in log" - 'dmesg -T 2>/dev/null | grep -iE "firmware|microcode" | grep -iE "fail|missing|error|direct load" | tail -n 25 | none_if_empty "No firmware load failures."'
run_check "Loaded kernel modules" lsmod 'lsmod | head -n 45'
run_check "Kernel taint decode" - 'taint_decode'
analyze_pcie
analyze_firmware_age
#----------------------------------------------- 11. KERNEL, LOGS & SERVICES --
category "Kernel Logs, Services & Software Health"
run_check "Kernel messages, deduplicated" - 'dmesg_dedup_report'
run_check "Kernel errors (dmesg err+)" - 'dmesg -T -l err,crit,alert,emerg 2>/dev/null | tail -n 60 | none_if_empty "No error-level kernel messages."'
run_check "Kernel warnings (dmesg warn)" - 'dmesg -T -l warn 2>/dev/null | tail -n 40 | none_if_empty "No warning-level kernel messages."'
run_check "Journal errors, this boot" journalctl 'journalctl -p 3 -b --no-pager 2>/dev/null | tail -n 60 | none_if_empty "No priority-3 journal entries this boot."'
run_check "Journal errors, previous boot" journalctl 'journalctl -p 3 -b -1 --no-pager 2>/dev/null | tail -n 40 | none_if_empty "No previous boot recorded, or no errors in it."'
run_check "Failed systemd units" systemctl 'systemctl --failed --no-pager; echo; systemctl list-units --state=failed --no-pager'
run_check "Slowest boot units" systemd-analyze 'systemd-analyze 2>&1; echo; systemd-analyze blame 2>/dev/null | head -n 20'
run_check "Core dumps" coredumpctl 'coredumpctl list --no-pager 2>&1 | tail -n 20'
run_check "Segfaults grouped by component" - 'echo "count faulting object"; seg_summary | none_if_empty "No segfaults this boot."'
run_check "Kernel BUG / oops / panic" - 'journalctl -b --no-pager 2>/dev/null | grep -iE "general protection|kernel BUG|Oops|kernel panic|call trace|soft lockup|hard LOCKUP" | tail -n 30 | none_if_empty "No kernel BUG, oops, panic or lockup this boot."'
run_check "Pending package upgrades" apt 'apt list --upgradable 2>/dev/null | head -n 40' 90
run_check "Package database integrity" dpkg 'dpkg -C 2>&1 | head -n 30 | none_if_empty "dpkg database is consistent - no broken or half-configured packages."'
run_check "Reboot required?" - '[ -f /var/run/reboot-required ] && { echo "YES - reboot required"; cat /var/run/reboot-required.pkgs 2>/dev/null; } || echo "No reboot flag present."'
analyze_kernel_logs
analyze_segfaults
analyze_services
analyze_updates
step "PHASE 3/3 - building HTML report"
finalize
exit 0