#!/bin/bash
################################################################################################################
#
# Check all installed disks with the smartctl overall-check
# Returns CRITICAL if smartctl exits with an bitcode from 0 to 5 or if it is 7 -> man smartclt
#
# Date modified: 2021-09-01
# Version: 0.9
# Copyright: Timme Hosting GmbH & Co. KG (c) 2021
# Author: Phil Thiele
#
################################################################################################################
DISKS=0
UNKNOWN=0
WARNING=0
FAILED=0
RESULT=''
RES_UNKNOWN=()
RES_WARNING=()
RES_CRITICAL=()
ERRNO=0
# Validate if there are NVMe disks are installed
IS_NVME=$(test -d /sys/class/nvme && printf "%d" 1 || printf "%d" 0)

# If one or more NVMe is present set additional path
[[ $IS_NVME -eq 1 ]] && NVME=/sys/class/nvme

CMD='/usr/sbin/smartctl'

if [[ ! -x "${CMD}" ]]; then
   echo "[ ${CMD} ]: Command not found"
   exit 1
fi

# Determine available block devices
BLK_DEV=()
TO_CHECK=(/sys/class/block)
# Only append path of NVME if there are NVMe disks installed on target host
[[ $IS_NVME -eq 1 ]] && [ ! -z "${NVME}" ] && TO_CHECK+=($NVME)
for i in $(ls "${TO_CHECK[@]}"); do
   # Only match devices like sda,sdb,nvme1,nvme0
   if [[ "$i" =~ ^(sd[a-z]|nvme[0-9]+)$ ]]; then
      BLK_DEV+=("${i}")
      ((DISKS++)) # Increase counter for each found disk
   fi
done

# Terminate the script with a critical if the number of disks is odd, that should never be the case, unless a hard drive has completely failed.
odd_number_disks=$((DISKS % 2))
if [[ $odd_number_disks -gt 0 ]]; then
  echo "A hard drive appears to have completely failed, please check!"
  exit 2
fi

# Iterate over each disk inside array BLK_DEV[@] and scan it for any errors
for d in "${BLK_DEV[@]}"; do

   # Precheck for e.g. IPMI emulated disk. Device names like "nvme0" will be ignored
   # because lsblk does not recognize them as a regular block device
   /usr/bin/env lsblk "/dev/${d}" >/dev/null 2>/dev/null
   blkstat=$?
   if [[ $blkstat -gt 0 ]] && [[ ! "$d" =~ ^nvme[0-9]+$ ]]; then
      continue
   fi

   # Do SMART/Health Check
   "${CMD}" -H "/dev/${d}" >/dev/null # Just retrieve the exit code
   status=$?                          # Save the exit code
   [[ ! $IS_NVME ]] && IS_SSD=$(cat /sys/block/${d}/queue/rotational)

   # Iterate each bit defined by smartmontools -> see manual
   for ((i = 0; i < 8; i++)); do

      # Opening disk device failed
      smart_stat=$((status & 2 ** i && 1))
      if [[ $i -eq 1 ]] && [[ $smart_stat -eq 1 ]]; then
         RES_UNKNOWN+=("[${d}] Failed to open block device, ")
         ((UNKNOWN++))
      fi

      # Do nothing on Bit 5 because disk is OK (see man smartctl)
      if [[ $i -eq 5 ]]; then
         continue
      fi

      # Check Lifetime and available Spare
      if ((IS_NVME && i == 3 && smart_stat == 1)); then
         AVAIL_SPARE=$("${CMD}" -x "/dev/${d}" | grep -si 'available spare:' | awk -F':' '{print $2}' | tr -d ' %')
         AVAIL_SPARE_THRESHOLD=$("${CMD}" -x "/dev/${d}" | grep -si 'available spare threshold:' | awk -F':' '{print $2}' | tr -d ' %')
         PERCENT_USED=$("${CMD}" -x "/dev/${d}" | grep -si 'percentage used:' | awk -F':' '{print $2}' | tr -d ' %')

         # Max lifetime reached
         if ((PERCENT_USED > 100)); then
            RES_WARNING+=("[${d}] Max lifetime reached. Percentage used: ${PERCENT_USED}% / Available Spare: ${AVAIL_SPARE}%,")
            ((WARNING++))
         fi

         # Available Spare reached its threshold
         if ((AVAIL_SPARE <= AVAIL_SPARE_THRESHOLD)); then
            RES_CRITICAL+=("[${d}] Disk health is critical - available Spare reached or exceeded its threshold! Available Spare: ${AVAIL_SPARE}% / Threshold: ${AVAIL_SPARE_THRESHOLD}%,")
            ((FAILED++))
         fi
         continue
      fi

      # Other errors defined by smartmontools -> see manual
      if ([[ ! $i -eq 3 ]] || [[ ! $i -eq 1 ]]) && [[ $smart_stat -eq 1 ]]; then
         RES_CRITICAL+=("[${d}] SMART Check failed with Bit: ${i},")
         ((FAILED++))
      fi
   done
done

if [[ $FAILED -gt 0 ]]; then
   # Set the Exitcode to critical for nagios if a harddisk failed the check
   RESULT="CRITICAL - Disk/-s failed SMART check: ${RES_CRITICAL[@]}"
   ERRNO=2
   echo "${RESULT%?}"
   exit $ERRNO
elif [[ $WARNING -gt 0 ]]; then
   RESULT="WARNING - Disk/-s failed SMART check: ${RES_WARNING[@]}"
   ERRNO=1
   echo "${RESULT%?}"
   exit $ERRNO
elif [[ $UNKNOWN -gt 0 ]]; then
   RESULT="UNKNOWN - Unable to run SMART check: ${RES_UNKNOWN[@]}"
   ERRNO=3
   echo "${RESULT%?}"
   exit $ERRNO
elif [[ $DISKS == 0 ]]; then
   RESULT="UNKNOWN - No Disk/-s found"
   ERRNO=3
   echo "${RESULT}"
   exit $ERRNO
else
   RESULT="OK - All disks are healthy"
   ERRNO=0
   echo "${RESULT}"
   exit $ERRNO
fi
