From Colossal Cheetah, 3 Years ago, written in Plain Text.
Embed
  1. #!/bin/bash
  2.  
  3. ###### ZPool & SMART status report with FreeNAS config backup
  4. ### Original script by joeschmuck, modified by Bidelu0hm, then by melp (me)
  5.  
  6. ### At a minimum, enter email address in user-definable parameter section. Feel free to edit other user parameters as needed.
  7. ### If you find any errors, feel free to contact me on the FreeNAS forums (username melp) or email me at jason at jro dot io.
  8.  
  9. ### Version: v1.3
  10. ### Changelog:
  11. # v1.3:
  12. #   - Added scrub duration column
  13. #   - Fixed for FreeNAS 11.1 (thanks reven!)
  14. #   - Fixed fields parsed out of zpool status
  15. #   - Buffered zpool status to reduce calls to script
  16. # v1.2:
  17. #   - Added switch for power-on time format
  18. #   - Slimmed down table columns
  19. #   - Fixed some shellcheck errors & other misc stuff
  20. #   - Added .tar.gz to backup file attached to email
  21. #   - (Still coming) Better SSD SMART support
  22. # v1.1:
  23. #   - Config backup now attached to report email
  24. #   - Added option to turn off config backup
  25. #   - Added option to save backup configs in a specified directory
  26. #   - Power-on hours in SMART summary table now listed as YY-MM-DD-HH
  27. #   - Changed filename of config backup to exclude timestamp (just uses datestamp now)
  28. #   - Config backup and checksum files now zipped (was just .tar before; now .tar.gz)
  29. #   - Fixed degrees symbol in SMART table (rendered weird for a lot of people); replaced with a *
  30. #   - Added switch to enable or disable SSDs in SMART table (SSD reporting still needs work)
  31. #   - Added most recent Extended & Short SMART tests in drive details section (only listed one before, whichever was more recent)
  32. #   - Reformatted user-definable parameters section
  33. #   - Added more general comments to code
  34. # v1.0:
  35. #   - Initial release
  36.  
  37. ### TODO:
  38. # - Fix SSD SMART reporting
  39.                                                                                
  40. # - Add support for conveyance test
  41.  
  42.  
  43. ###### User-definable Parameters
  44. ### Email Address
  45.  
  46. ### Global table colors
  47. okColor="#c9ffcc"       # Hex code for color to use in SMART Status column if drives pass (default is light green, #c9ffcc)
  48. warnColor="#ffd6d6"     # Hex code for WARN color (default is light red, #ffd6d6)
  49. critColor="#ff0000"     # Hex code for CRITICAL color (default is bright red, #ff0000)
  50. altColor="#f4f4f4"      # Table background alternates row colors between white and this color (default is light gray, #f4f4f4)
  51.  
  52.  
  53. ### SMART status summary table settings
  54. includeSSD="true"      # [NOTE: Currently this is pretty much useless] Change to "true" to include SSDs in SMART status summary table; "false" to disable
  55. tempWarn=40             # Drive temp (in C) at which WARNING color will be used
  56. tempCrit=45             # Drive temp (in C) at which CRITICAL color will be used
  57. sectorsCrit=10          # Number of sectors per drive with errors before CRITICAL color will be used
  58. testAgeWarn=7           # Maximum age (in days) of last SMART test before CRITICAL color will be used
  59. powerTimeFormat="ymdh"  # Format for power-on hours string, valid options are "ymdh", "ymd", "ym", or "y" (year month day hour)
  60.  
  61.  
  62. ###### Auto-generated Parameters
  63. host=$(hostname -s)
  64. logfile="/tmp/smart_report.tmp"
  65. subject="Status Report for ${host}"
  66. boundary="gc0p4Jq0M2Yt08jU534c0p"
  67. if [ "$includeSSD" == "true" ]; then
  68.     drives=$(for drive in $(/bin/lsblk -d | awk  '{ print $1 }' | awk '{ if(NR>1)print }' | sort); do
  69.         if [ "$(/usr/sbin/smartctl -i /dev/"${drive}" | grep "SMART support is: Enabled")" ]; then
  70.             printf "%s " "${drive}"
  71.         fi
  72.     done | awk '{for (i=NF; i!=0 ; i--) print $i }')
  73. else
  74.     drives=$(for drive in $(/bin/lsblk -d | awk  '{ print $1 }' | awk '{ if(NR>1)print }' | sort); do
  75.         if [ "$(/usr/sbin/smartctl -i /dev/"${drive}" | grep "SMART support is: Enabled")" ] && ! [ "$(/usr/sbin/smartctl -i /dev/"${drive}" | grep "Solid State Device")" ]; then
  76.             printf "%s " "${drive}"
  77.         fi
  78.     done | awk '{for (i=NF; i!=0 ; i--) print $i }')
  79. fi
  80.  
  81.  
  82.  
  83. ###### Email pre-formatting
  84. ### Set email headers
  85. (
  86.     echo "From: ${email}"
  87.     echo "To: ${email}"
  88.     echo "Subject: ${subject}"
  89.     echo "MIME-Version: 1.0"
  90.     echo "Content-Type: multipart/mixed; boundary=${boundary}"
  91. ) > "$logfile"
  92.  
  93. (
  94.  echo "--${boundary}"
  95.  echo "Content-Type: text/html"
  96. ) >> "$logfile"
  97.  
  98. ###### Report Summary Section (html tables)
  99. ### SMART status summary table
  100. (
  101.     # Write HTML table headers to log file
  102.     echo "<br><br>"
  103.     echo "<table style=\"border: 1px solid black; border-collapse: collapse;\">"
  104.     echo "<tr><th colspan=\"15\" style=\"text-align:center; font-size:20px; height:40px; font-family:courier;\">SMART Status Report Summary</th></tr>"
  105.     echo "<tr>"
  106.     echo "  <th style=\"text-align:center; width:100px; height:60px; border:1px solid black; border-collapse:collapse; font-family:courier;\">Device</th>"
  107.     echo "  <th style=\"text-align:center; width:130px; height:60px; border:1px solid black; border-collapse:collapse; font-family:courier;\">Serial<br>Number</th>"
  108.     echo "  <th style=\"text-align:center; width:80px; height:60px; border:1px solid black; border-collapse:collapse; font-family:courier;\">SMART<br>Status</th>"
  109.     echo "  <th style=\"text-align:center; width:80px; height:60px; border:1px solid black; border-collapse:collapse; font-family:courier;\">Temp</th>"
  110.     echo "  <th style=\"text-align:center; width:120px; height:60px; border:1px solid black; border-collapse:collapse; font-family:courier;\">Power-On<br>Time</th>"
  111.     echo "  <th style=\"text-align:center; width:100px; height:60px; border:1px solid black; border-collapse:collapse; font-family:courier;\">Start/Stop<br>Count</th>"
  112.     echo "  <th style=\"text-align:center; width:80px; height:60px; border:1px solid black; border-collapse:collapse; font-family:courier;\">Spin<br>Retry<br>Count</th>"
  113.     echo "  <th style=\"text-align:center; width:80px; height:60px; border:1px solid black; border-collapse:collapse; font-family:courier;\">Realloc'd<br>Sectors</th>"
  114.     echo "  <th style=\"text-align:center; width:80px; height:60px; border:1px solid black; border-collapse:collapse; font-family:courier;\">Realloc<br>Events</th>"
  115.     echo "  <th style=\"text-align:center; width:80px; height:60px; border:1px solid black; border-collapse:collapse; font-family:courier;\">Current<br>Pending<br>Sectors</th>"
  116.     echo "  <th style=\"text-align:center; width:120px; height:60px; border:1px solid black; border-collapse:collapse; font-family:courier;\">Offline<br>Uncorrectable<br>Sectors</th>"
  117.     echo "  <th style=\"text-align:center; width:80px; height:60px; border:1px solid black; border-collapse:collapse; font-family:courier;\">UltraDMA<br>CRC<br>Errors</th>"
  118.     echo "  <th style=\"text-align:center; width:80px; height:60px; border:1px solid black; border-collapse:collapse; font-family:courier;\">Seek<br>Error<br>Health</th>"
  119.     echo "  <th style=\"text-align:center; width:100px; height:60px; border:1px solid black; border-collapse:collapse; font-family:courier;\">Last Test<br>Age (days)</th>"
  120.     echo "  <th style=\"text-align:center; width:100px; height:60px; border:1px solid black; border-collapse:collapse; font-family:courier;\">Last Test<br>Type</th></tr>"
  121.     echo "</tr>"
  122. ) >> "$logfile"
  123. for drive in $drives; do
  124.     (
  125.         # For each drive detected, run "smartctl -A -i" and parse its output. This whole section is a single, long statement, so I'll make all comments here.
  126.         # Start by passing awk variables (all the -v's) used in other parts of the script. Other variables are calculated in-line with other smartctl calls.
  127.         # Next, pull values out of the original "smartctl -A -i" statement by searching for the text between the //'s.
  128.         # After parsing the output, compute other values (last test's age, on time in YY-MM-DD-HH).
  129.         # After these computations, determine the row's background color (alternating as above, subbing in other colors from the palate as needed).
  130.         # Finally, print the HTML code for the current row of the table with all the gathered data.
  131.         /usr/sbin/smartctl -A -i /dev/"$drive" | \
  132.         awk -v device="$drive" -v tempWarn="$tempWarn" -v tempCrit="$tempCrit" -v sectorsCrit="$sectorsCrit" -v testAgeWarn="$testAgeWarn" \
  133.         -v okColor="$okColor" -v warnColor="$warnColor" -v critColor="$critColor" -v altColor="$altColor" -v powerTimeFormat="$powerTimeFormat" \
  134.         -v lastTestHours="$(/usr/sbin/smartctl -l selftest /dev/"$drive" | grep "# 1" | awk '{print $9}')" \
  135.         -v lastTestType="$(/usr/sbin/smartctl -l selftest /dev/"$drive" | grep "# 1" | awk '{print $3}')" \
  136.         -v smartStatus="$(/usr/sbin/smartctl -H /dev/"$drive" | grep "SMART overall-health" | awk '{print $6}')" ' \
  137.         /Serial Number:/{serial=$3} \
  138.         /Temperature_Celsius/{temp=($10 + 0)} \
  139.         /Power_On_Hours/{onHours=$10} \
  140.         /Start_Stop_Count/{startStop=$10} \
  141.         /Spin_Retry_Count/{spinRetry=$10} \
  142.         /Reallocated_Sector/{reAlloc=$10} \
  143.         /Reallocated_Event_Count/{reAllocEvent=$10} \
  144.         /Current_Pending_Sector/{pending=$10} \
  145.         /Offline_Uncorrectable/{offlineUnc=$10} \
  146.         /UDMA_CRC_Error_Count/{crcErrors=$10} \
  147.         /Seek_Error_Rate/{seekErrorHealth=$4} \
  148.         END {
  149.             testAge=int((onHours - lastTestHours) / 24);
  150.             yrs=int(onHours / 8760);
  151.             mos=int((onHours % 8760) / 730);
  152.             dys=int(((onHours % 8760) % 730) / 24);
  153.             hrs=((onHours % 8760) % 730) % 24;
  154.             if (powerTimeFormat == "ymdh") onTime=yrs "y " mos "m " dys "d " hrs "h";
  155.             else if (powerTimeFormat == "ymd") onTime=yrs "y " mos "m " dys "d";
  156.             else if (powerTimeFormat == "ym") onTime=yrs "y " mos "m";
  157.             else if (powerTimeFormat == "y") onTime=yrs "y";
  158.             else onTime=yrs "y " mos "m " dys "d " hrs "h ";
  159.             if ((substr(device,3) + 0) % 2 == 1) bgColor = "#ffffff"; else bgColor = altColor;
  160.             if (smartStatus != "PASSED") smartStatusColor = critColor; else smartStatusColor = okColor;
  161.             if (temp >= tempCrit) tempColor = critColor; else if (temp >= tempWarn) tempColor = warnColor; else tempColor = bgColor;
  162.             if (spinRetry != "0") spinRetryColor = warnColor; else spinRetryColor = bgColor;
  163.             if ((reAlloc + 0) > sectorsCrit) reAllocColor = critColor; else if (reAlloc != 0) reAllocColor = warnColor; else reAllocColor = bgColor;
  164.             if (reAllocEvent != "0") reAllocEventColor = warnColor; else reAllocEventColor = bgColor;
  165.             if ((pending + 0) > sectorsCrit) pendingColor = critColor; else if (pending != 0) pendingColor = warnColor; else pendingColor = bgColor;
  166.             if ((offlineUnc + 0) > sectorsCrit) offlineUncColor = critColor; else if (offlineUnc != 0) offlineUncColor = warnColor; else offlineUncColor = bgColor;
  167.             if (crcErrors != "0") crcErrorsColor = warnColor; else crcErrorsColor = bgColor;
  168.             if ((seekErrorHealth + 0) < 100) seekErrorHealthColor = warnColor; else seekErrorHealthColor = bgColor;
  169.             if (testAge > testAgeWarn) testAgeColor = warnColor; else testAgeColor = bgColor;
  170.             printf "<tr style=\"background-color:%s;\">\n" \
  171.                 "<td style=\"text-align:center; height:25px; border:1px solid black; border-collapse:collapse; font-family:courier;\">/dev/%s</td>\n" \
  172.                 "<td style=\"text-align:center; height:25px; border:1px solid black; border-collapse:collapse; font-family:courier;\">%s</td>\n" \
  173.                 "<td style=\"text-align:center; background-color:%s; height:25px; border:1px solid black; border-collapse:collapse; font-family:courier;\">%s</td>\n" \
  174.                 "<td style=\"text-align:center; background-color:%s; height:25px; border:1px solid black; border-collapse:collapse; font-family:courier;\">%d*C</td>\n" \
  175.                 "<td style=\"text-align:center; height:25px; border:1px solid black; border-collapse:collapse; font-family:courier;\">%s</td>\n" \
  176.                 "<td style=\"text-align:center; height:25px; border:1px solid black; border-collapse:collapse; font-family:courier;\">%s</td>\n" \
  177.                 "<td style=\"text-align:center; background-color:%s; height:25px; border:1px solid black; border-collapse:collapse; font-family:courier;\">%s</td>\n" \
  178.                 "<td style=\"text-align:center; background-color:%s; height:25px; border:1px solid black; border-collapse:collapse; font-family:courier;\">%s</td>\n" \
  179.                 "<td style=\"text-align:center; background-color:%s; height:25px; border:1px solid black; border-collapse:collapse; font-family:courier;\">%s</td>\n" \
  180.                 "<td style=\"text-align:center; background-color:%s; height:25px; border:1px solid black; border-collapse:collapse; font-family:courier;\">%s</td>\n" \
  181.                 "<td style=\"text-align:center; background-color:%s; height:25px; border:1px solid black; border-collapse:collapse; font-family:courier;\">%s</td>\n" \
  182.                 "<td style=\"text-align:center; background-color:%s; height:25px; border:1px solid black; border-collapse:collapse; font-family:courier;\">%s</td>\n" \
  183.                 "<td style=\"text-align:center; background-color:%s; height:25px; border:1px solid black; border-collapse:collapse; font-family:courier;\">%s%%</td>\n" \
  184.                 "<td style=\"text-align:center; background-color:%s; height:25px; border:1px solid black; border-collapse:collapse; font-family:courier;\">%d</td>\n" \
  185.                 "<td style=\"text-align:center; height:25px; border:1px solid black; border-collapse:collapse; font-family:courier;\">%s</td>\n" \
  186.             "</tr>\n", bgColor, device, serial, smartStatusColor, smartStatus, tempColor, temp, onTime, startStop, spinRetryColor, spinRetry, reAllocColor, reAlloc, \
  187.             reAllocEventColor, reAllocEvent, pendingColor, pending, offlineUncColor, offlineUnc, crcErrorsColor, crcErrors, seekErrorHealthColor, seekErrorHealth, \
  188.             testAgeColor, testAge, lastTestType;
  189.         }'
  190.     ) >> "$logfile"
  191. done
  192. # End SMART summary table and summary section
  193. (
  194.     echo "</table>"
  195.     echo "<br><br>"
  196. ) >> "$logfile"
  197.  
  198. # Drive usage
  199. (
  200.         echo "<b>RAID Usage</b><br>"
  201.         echo "<pre>"
  202.         df -h /dev/md0
  203.         echo "</pre>"
  204.         echo "<br>"
  205. ) >> "$logfile"
  206.  
  207.  
  208. ###### Detailed Report Section (monospace text)
  209. echo "<pre style=\"font-size:14px\">" >> "$logfile"
  210.  
  211. ### SMART status for each drive
  212. for drive in $drives; do
  213.     # Gather brand and serial number of each drive
  214.     brand="$(/usr/sbin/smartctl -i /dev/"$drive" | grep "Model Family" | awk '{print $3, $4, $5}')"
  215.     serial="$(/usr/sbin/smartctl -i /dev/"$drive" | grep "Serial Number" | awk '{print $3}')"
  216.     (
  217.         # Create a simple header and drop the output of some basic smartctl commands
  218.         echo "<br>"
  219.         echo "<b>########## SMART status report for ${drive} drive (${brand}: ${serial}) ##########</b>"
  220.         /usr/sbin/smartctl -H -A -l error /dev/"$drive"
  221.         /usr/sbin/smartctl -l selftest /dev/"$drive" | grep "Extended \\|Num" | cut -c6- | head -2
  222.         /usr/sbin/smartctl -l selftest /dev/"$drive" | grep "Short \\|Num" | cut -c6- | head -2 | tail -n -1
  223.         echo "<br><br>"
  224.     ) >> "$logfile"
  225. done
  226. # Remove some un-needed junk from the output
  227. sed -i '' -e '/smartctl 6.6/d' "$logfile"
  228. sed -i '' -e '/Copyright/d' "$logfile"
  229. sed -i '' -e '/=== START OF READ/d' "$logfile"
  230. sed -i '' -e '/SMART Attributes Data/d' "$logfile"
  231. sed -i '' -e '/Vendor Specific SMART/d' "$logfile"
  232. sed -i '' -e '/SMART Error Log Version/d' "$logfile"
  233.  
  234. ### End details section, close MIME section
  235. (
  236.     echo "</pre>"
  237.     echo "--${boundary}--"
  238. )  >> "$logfile"
  239.  
  240. ### Send report
  241. /usr/sbin/sendmail -t -oi < "$logfile"
  242. #rm "$logfile"
  243.  
  244.  
  245.