#!/bin/bash # ============================================================================= # Drive Health Checker (v8) # # A "pretty" script to list drives, let the user select one, # and run a SMART health check. # # v8 Update: # - Replaced string joining with printf for a perfectly # aligned, multi-column menu (ID, LABEL, DEVICE, SIZE, MODEL). # - Reworked list-building logic to use parallel arrays. # ============================================================================= # --- Define Colors --- if command -v tput &> /dev/null; then GREEN=$(tput setaf 2) RED=$(tput setaf 1) YELLOW=$(tput setaf 3) BLUE=$(tput setaf 4) BOLD=$(tput bold) NC=$(tput sgr0) # No Color else GREEN='\033[0;32m' RED='\033[0;31m' YELLOW='\033[1;33m' BLUE='\033[0;34m' BOLD='\033[1m' NC='\033[0m' fi # --- Helper Function for Headers --- print_header() { echo -e "\n${BLUE}${BOLD}--- $1 ---${NC}" } # --- 1. System Checks --- # Check for root privileges if [ "$EUID" -ne 0 ]; then echo -e "${RED}${BOLD}Error:${NC} This script must be run with sudo for drive access." echo -e "Please run as: ${GREEN}sudo $0${NC}" exit 1 fi # Check for dependencies if ! command -v smartctl &> /dev/null || ! command -v lsblk &> /dev/null; then echo -e "${YELLOW}Warning:${NC} 'smartctl' or 'lsblk' command not found." echo -e "This script requires ${BOLD}smartmontools${NC} and ${BOLD}util-linux${NC}." echo -e "As a Manjaro user, you can install them by running:" echo -e "${GREEN}sudo pacman -S smartmontools util-linux${NC}" exit 1 fi # --- 2. Get Drive List (once) --- # --- UPDATED SECTION --- print_header "Building Drive List (this may take a second)..." # Declare parallel arrays to hold drive components declare -a drive_names=() declare -a drive_labels=() declare -a drive_sizes=() declare -a drive_models=() # Get a list of just the disk names (e.g., sda, sdb) disks=$(lsblk -d -n -o NAME -e 7,11) if [ -z "$disks" ]; then echo -e "${RED}No physical drives found.${NC}" exit 1 fi # Loop through each disk to build the component lists for disk in $disks; do # Add device name drive_names+=("$disk") # Get Size and Model # We use 'read' to handle models with spaces read -r size model < <(lsblk -d -n -o SIZE,MODEL "/dev/$disk") drive_sizes+=("$size") drive_models+=("$model") # Get the first available filesystem LABEL label=$(lsblk -n -o LABEL "/dev/$disk" | grep -v "^$" | head -n 1) # Format label for display (or use a placeholder) if [ -n "$label" ]; then drive_labels+=("($label)") else drive_labels+=("( )") # Placeholder for alignment fi done # --- END UPDATED SECTION --- # --- 3. Main Loop --- while true; do clear print_header "Available Physical Drives" # --- UPDATED MENU PRINTING --- # Print the header printf " ${BOLD}%-4s %-16s %-8s %-7s %s${NC}\n" "ID)" "LABEL" "DEVICE" "SIZE" "MODEL" printf " ${BOLD}%-4s %-16s %-8s %-7s %s${NC}\n" "---" "---------------" "------" "------" "-----------------" # Loop and print each drive using printf for alignment for ((i=0; i<${#drive_names[@]}; i++)); do id="$((i+1))" # Print the formatted line printf " ${YELLOW}%-4s${NC} ${YELLOW}%-16s${NC} %-8s %-7s %s\n" \ "$id)" \ "${drive_labels[$i]}" \ "${drive_names[$i]}" \ "${drive_sizes[$i]}" \ "${drive_models[$i]}" done # --- END UPDATED MENU --- echo -e " ${YELLOW}q)${NC} Quit" echo "" # Add a newline # Prompt user read -p "${YELLOW}${BOLD}Select a drive (number) or 'q' to quit: ${NC}" selection # Process input case "$selection" in q|Q|quit|Quit) echo -e "${GREEN}Exiting.${NC}" break ;; *) # Check if it's a valid number if ! [[ "$selection" =~ ^[0-9]+$ ]] || [ "$selection" -lt 1 ] || [ "$selection" -gt ${#drive_names[@]} ]; then echo -e "\n${RED}Invalid selection.${NC} Press Enter to try again." read -p "" continue # Restart the loop fi # --- 4. It's a valid number, get the drive --- # --- UPDATED PARSING --- # Convert selection to zero-based index ((idx = selection - 1)) # Get components from our parallel arrays device_name="${drive_names[$idx]}" model_name="${drive_models[$idx]}" label_name="${drive_labels[$idx]}" device_path="/dev/$device_name" # Build a cleaner header for the report print_header "Health Report for $device_path (${BOLD}${label_name} ${model_name}${NC})" # --- END UPDATED PARSING --- if ! smartctl -i "$device_path" | grep -q "SMART support is: Available"; then echo -e "${YELLOW}Warning:${NC} This drive (${BOLD}$device_path${NC}) does not report SMART support." echo "Cannot perform health check." else # --- 5. Run Health Check --- smartctl -s on "$device_path" &> /dev/null echo -e "Running SMART health check..." attributes=$(smartctl -A "$device_path") health_result=$(smartctl -H "$device_path" | grep 'overall-health' | awk '{print $6}') temp=$(echo "$attributes" | grep -i 'Temperature_Celsius' | awk '{print $10}') power_on_hours=$(echo "$attributes" | grep -i 'Power_On_Hours' | awk '{print $10}') serial_number=$(smartctl -i "$device_path" | grep 'Serial Number:' | awk '{print $3}') reallocated_sectors=$(echo "$attributes" | grep -i 'Reallocated_Sector_Ct' | awk '{print $10}') pending_sectors=$(echo "$attributes" | grep -i 'Current_Pending_Sector_Ct' | awk '{print $10}') uncorrectable_sectors=$(echo "$attributes" | grep -i 'Offline_Uncorrectable' | awk '{print $10}') [ -z "$reallocated_sectors" ] && reallocated_sectors=0 [ -z "$pending_sectors" ] && pending_sectors=0 [ -z "$uncorrectable_sectors" ] && uncorrectable_sectors=0 # --- 6. Display Verdict --- echo -e "\n${BOLD}Quick Summary:${NC}" echo -e " ${BLUE}Serial Number:${NC} $serial_number" echo -e " ${BLUE}Power On Hours:${NC} $power_on_hours" echo -e " ${BLUE}Temperature:${NC} ${temp}°C" echo -e "\n ${BOLD}Bad Sector Analysis:${NC}" echo -e " ${BLUE}Reallocated Sectors:${NC} ${reallocated_sectors}" echo -e " ${BLUE}Pending Sectors:${NC} ${pending_sectors}" echo -e " ${BLUE}Uncorrectable Sectors:${NC} ${uncorrectable_sectors}" echo -e "\n${BOLD}--- VERDICT ---${NC}" if [ "$health_result" = "PASSED" ] && [ "$pending_sectors" -eq 0 ] && [ "$uncorrectable_sectors" -eq 0 ]; then echo -e "${GREEN}✅ PASSED${NC}" echo -e "The drive's overall SMART health assessment is ${BOLD}PASSED${NC}." if [ "$reallocated_sectors" -ne 0 ]; then echo -e "${YELLOW}Note:${NC} The drive has reallocated ${BOLD}$reallocated_sectors${NC} sectors. This is not an" echo -e "immediate failure, but you should monitor this value." fi elif [ "$health_result" = "PASSED" ]; then echo -e "${YELLOW}⚠️ WARNING${NC}" echo -e "The drive's overall SMART health is ${BOLD}PASSED${NC}, but it reports critical issues!" if [ "$pending_sectors" -ne 0 ]; then echo -e "${RED}Critical:${NC} ${BOLD}$pending_sectors${NC} pending sectors. The drive is struggling to read data." fi if [ "$uncorrectable_sectors" -ne 0 ]; then echo -e "${RED}Critical:${NC} ${BOLD}$uncorrectable_sectors${NC} uncorrectable sectors. ${BOLD}Data has been lost.${NC}" fi echo -e "${YELLOW}ACTION REQUIRED: Back up any critical data immediately.${NC}" else echo -e "${RED}❌ FAILED${NC}" echo -e "The drive's overall SMART health assessment is ${BOLD}${health_result}${NC}." echo -e "${YELLOW}ACTION REQUIRED: Back up any critical data immediately" echo -e "and consider replacing this drive.${NC}" fi # Ask to show full report echo "" # Newline read -p "${YELLOW}Show full SMART report for $device_path? (y/N): ${NC}" show_full if [[ "$show_full" =~ ^[Yy]$ ]]; then print_header "Full SMART Report for $device_path" echo "Look for 'Percentage_Used' or 'Media_Wearout_Indicator' on SSDs." smartctl -a "$device_path" | less fi fi # --- 7. Pause before looping --- echo -e "\n${YELLOW}Press Enter to return to the drive list...${NC}" read -p "" ;; esac done exit 0