├── set_hdd_erc.sh ├── ups_report.sh ├── get_hdd_temp.sh ├── save_config.sh ├── save_config_enc.sh ├── zpool_report.sh ├── get-system-temps.pl ├── smart_report.sh └── README.md /set_hdd_erc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # https://www.smartmontools.org/wiki/FAQ#WhatiserrorrecoverycontrolERCandwhyitisimportanttoenableitfortheSATAdisksinRAID 4 | 5 | # ERC timeout values, in tenths of a second. The defaults below are 7 seconds for both reads and writes: 6 | 7 | readsetting=70 8 | writesetting=70 9 | 10 | # We need a list of the SMART-enabled drives on the system. Choose one of these 11 | # three methods to provide the list. Comment out the two unused sections of code. 12 | 13 | # 1. A string constant; just key in the devices you want to report on here: 14 | #drives="da1 da2 da3 da4 da5 da6 da7 da8 ada0" 15 | 16 | # 2. A systcl-based technique suggested on the FreeNAS forum: 17 | #drives=$(for drive in $(sysctl -n kern.disks); do \ 18 | #if [ "$(/usr/local/sbin/smartctl -i /dev/${drive} | grep "SMART support is: Enabled" | awk '{print $3}')" ] 19 | #then printf ${drive}" "; fi done | awk '{for (i=NF; i!=0 ; i--) print $i }') 20 | 21 | # 3. A smartctl-based function: 22 | get_smart_drives() 23 | { 24 | gs_drives=$(/usr/local/sbin/smartctl --scan | grep "dev" | awk '{print $1}' | sed -e 's/\/dev\///' | tr '\n' ' ') 25 | 26 | gs_smartdrives="" 27 | 28 | for gs_drive in $gs_drives; do 29 | gs_smart_flag=$(/usr/local/sbin/smartctl -i /dev/"$gs_drive" | grep "SMART support is: Enabled" | awk '{print $4}') 30 | if [ "$gs_smart_flag" = "Enabled" ]; then 31 | gs_smartdrives=$gs_smartdrives" "${gs_drive} 32 | fi 33 | done 34 | 35 | eval "$1=\$gs_smartdrives" 36 | } 37 | 38 | drives="" 39 | get_smart_drives drives 40 | 41 | # end of method 3. 42 | 43 | set_erc() 44 | { 45 | echo "Drive: /dev/$1" 46 | /usr/local/sbin/smartctl -q silent -l scterc,"${readsetting}","${writesetting}" /dev/"$1" 47 | /usr/local/sbin/smartctl -l scterc /dev/"$1" | grep "SCT\|Write\|Read" 48 | } 49 | 50 | for drive in $drives; do 51 | set_erc "$drive" 52 | done 53 | -------------------------------------------------------------------------------- /ups_report.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Send UPS report to designated email address 4 | # Reference: http://networkupstools.org/docs/developer-guide.chunked/apas01.html 5 | 6 | ### Parameters ### 7 | 8 | # Specify your email address here: 9 | email="" 10 | 11 | # Set to a value greater than zero to include all available UPSC 12 | # variables in the report: 13 | senddetail=0 14 | 15 | freenashost=$(hostname -s) 16 | freenashostuc=$(hostname -s | tr '[:lower:]' '[:upper:]') 17 | boundary="===== MIME boundary; FreeNAS server ${freenashost} =====" 18 | logfile="/tmp/ups_report.tmp" 19 | subject="UPS Status Report for ${freenashostuc}" 20 | 21 | ### Set email headers ### 22 | printf "%s\n" "To: ${email} 23 | Subject: ${subject} 24 | Mime-Version: 1.0 25 | Content-Type: multipart/mixed; boundary=\"$boundary\" 26 | 27 | --${boundary} 28 | Content-Type: text/html; charset=\"US-ASCII\" 29 | Content-Transfer-Encoding: 7bit 30 | Content-Disposition: inline 31 |
" >> ${logfile}
32 | 
33 | # Get a list of all ups devices installed on the system:
34 | 
35 | upslist=$(upsc -l "${freenashost}")
36 | 
37 | ### Set email body ###
38 | (
39 |  date "+Time: %Y-%m-%d %H:%M:%S"
40 |  echo ""
41 |  for ups in $upslist; do
42 |    ups_type=$(upsc "${ups}" device.type 2> /dev/null | tr '[:lower:]' '[:upper:]')
43 |    ups_mfr=$(upsc "${ups}" ups.mfr 2> /dev/null)
44 |    ups_model=$(upsc "${ups}" ups.model 2> /dev/null)
45 |    ups_serial=$(upsc "${ups}" ups.serial 2> /dev/null)
46 |    ups_status=$(upsc "${ups}" ups.status 2> /dev/null)
47 |    ups_load=$(upsc "${ups}" ups.load 2> /dev/null)
48 |    ups_realpower=$(upsc "${ups}" ups.realpower 2> /dev/null)
49 |    ups_realpowernominal=$(upsc "${ups}" ups.realpower.nominal 2> /dev/null)
50 |    ups_batterycharge=$(upsc "${ups}" battery.charge 2> /dev/null)
51 |    ups_batteryruntime=$(upsc "${ups}" battery.runtime 2> /dev/null)
52 |    ups_batteryvoltage=$(upsc "${ups}" battery.voltage 2> /dev/null)
53 |    ups_inputvoltage=$(upsc "${ups}" input.voltage 2> /dev/null)
54 |    ups_outputvoltage=$(upsc "${ups}" output.voltage 2> /dev/null)
55 |    printf "=== %s %s, model %s, serial number %s\n\n" "${ups_mfr}" "${ups_type}" "${ups_model}" "${ups_serial} ==="
56 |    echo "Name: ${ups}"
57 |    echo "Status: ${ups_status}"
58 |    echo "Output Load: ${ups_load}%"
59 |    if [ ! -z "${ups_realpower}" ]; then
60 |      echo "Real Power: ${ups_realpower}W"
61 |    fi
62 |    if [ ! -z "${ups_realpowernominal}" ]; then
63 |      echo "Real Power: ${ups_realpowernominal}W (nominal)"
64 |    fi
65 |    if [ ! -z "${ups_inputvoltage}" ]; then
66 |      echo "Input Voltage: ${ups_inputvoltage}V"
67 |    fi
68 |    if [ ! -z "${ups_outputvoltage}" ]; then
69 |      echo "Output Voltage: ${ups_outputvoltage}V"
70 |    fi
71 |    echo "Battery Runtime: ${ups_batteryruntime}s"
72 |    echo "Battery Charge: ${ups_batterycharge}%"
73 |    echo "Battery Voltage: ${ups_batteryvoltage}V"
74 |    echo ""
75 |    if [ $senddetail -gt 0 ]; then
76 |      echo "=== ALL AVAILABLE UPS VARIABLES ==="
77 |      upsc "${ups}"
78 |      echo ""
79 |    fi
80 |  done
81 | ) >> ${logfile}
82 | 
83 | printf "%s\n" "
84 | --${boundary}--" >> ${logfile} 85 | 86 | ### Send report ### 87 | if [ -z "${email}" ]; then 88 | echo "No email address specified, information available in ${logfile}" 89 | else 90 | sendmail -t -oi < ${logfile} 91 | rm ${logfile} 92 | fi 93 | 94 | -------------------------------------------------------------------------------- /get_hdd_temp.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Display current temperature of CPU(s) and all SMART-enabled drives 4 | 5 | # Optionally uses IPMI to report temperatures of the system CPU(s) 6 | # 7 | # If IPMI is disabled (see 'use_ipmi' below) then the script uses 8 | # sysctl to report the CPU temperatures. To use IPMI, you must 9 | # provide the IPMI host, user name, and user password file. 10 | 11 | # Full path to 'smartctl' program: 12 | smartctl=/usr/local/sbin/smartctl 13 | 14 | # IPMI support: set to a postive value to use IPMI for CPU temp 15 | # reporting, set to zero to disable IPMI and use 'sysctl' instead: 16 | use_ipmi=0 17 | 18 | # IP address or DNS-resolvable hostname of IPMI server: 19 | ipmihost=192.168.1.x 20 | 21 | # IPMI username: 22 | ipmiuser=root 23 | 24 | # IPMI password file. This is a file containing the IPMI user's password 25 | # on a single line and should have 0600 permissions: 26 | ipmipwfile=/root/ipmi_password 27 | 28 | # Full path to 'ipmitool' program: 29 | ipmitool=/usr/local/bin/ipmitool 30 | 31 | # We need a list of the SMART-enabled drives on the system. Choose one of these 32 | # three methods to provide the list. Comment out the two unused sections of code. 33 | 34 | # 1. A string constant; just key in the devices you want to report on here: 35 | #drives="da1 da2 da3 da4 da5 da6 da7 da8 ada0" 36 | 37 | # 2. A systcl-based technique suggested on the FreeNAS forum: 38 | #drives=$(for drive in $(sysctl -n kern.disks); do \ 39 | #if [ "$(/usr/local/sbin/smartctl -i /dev/${drive} | grep "SMART support is: Enabled" | awk '{print $3}')" ] 40 | #then printf ${drive}" "; fi done | awk '{for (i=NF; i!=0 ; i--) print $i }') 41 | 42 | # 3. A smartctl-based function: 43 | 44 | get_smart_drives() 45 | { 46 | gs_smartdrives="" 47 | gs_drives=$("$smartctl" --scan | awk '{print $1}') 48 | 49 | for gs_drive in $gs_drives; do 50 | gs_smart_flag=$("$smartctl" -i "$gs_drive" | egrep "SMART support is:[[:blank:]]+Enabled" | awk '{print $4}') 51 | if [ "$gs_smart_flag" = "Enabled" ]; then 52 | gs_smartdrives="$gs_smartdrives $gs_drive" 53 | fi 54 | done 55 | echo "$gs_smartdrives" 56 | } 57 | 58 | drives=$(get_smart_drives) 59 | 60 | # end of method 3. 61 | 62 | ############################# 63 | # CPU temperatures: 64 | ############################# 65 | 66 | if [ "$use_ipmi" -eq 0 ]; then 67 | cpucores=$(sysctl -n hw.ncpu) 68 | printf '=== CPU (%s) ===\n' "$cpucores" 69 | cpucores=$((cpucores - 1)) 70 | for core in $(seq 0 $cpucores); do 71 | temp=$(sysctl -n dev.cpu."$core".temperature|sed 's/\..*$//g') 72 | if [ "$temp" -lt 0 ]; then 73 | temp="--n/a--" 74 | else 75 | temp="${temp}C" 76 | fi 77 | printf 'CPU %2.2s: %5s\n' "$core" "$temp" 78 | done 79 | echo "" 80 | else 81 | cpucores=$("$ipmitool" -I lanplus -H "$ipmihost" -U "$ipmiuser" -f "$ipmipwfile" sdr elist all | grep -c -i "cpu.*temp") 82 | 83 | printf '=== CPU (%s) ===\n' "$cpucores" 84 | if [ "$cpucores" -eq 1 ]; then 85 | temp=$("$ipmitool" -I lanplus -H "$ipmihost" -U "$ipmiuser" -f "$ipmipwfile" sdr elist all | grep "CPU Temp" | awk '{print $10}') 86 | if [ "$temp" -lt 0 ]; then 87 | temp="-n/a-" 88 | else 89 | temp="${temp}C" 90 | fi 91 | printf 'CPU %2s: %5s\n' "$core" "$temp" 92 | else 93 | for core in $(seq 1 "$cpucores"); do 94 | temp=$("$ipmitool" -I lanplus -H "$ipmihost" -U "$ipmiuser" -f "$ipmipwfile" sdr elist all | grep "CPU${core} Temp" | awk '{print $10}') 95 | if [ "$temp" -lt 0 ]; then 96 | temp="-n/a-" 97 | else 98 | temp="${temp}C" 99 | fi 100 | printf 'CPU %2s: [%s]\n' "$core" "$temp" 101 | done 102 | fi 103 | echo "" 104 | fi 105 | 106 | ############################# 107 | # Drive temperatures: 108 | ############################# 109 | 110 | echo "=== DRIVES ===" 111 | 112 | for drive in $drives; do 113 | serial=$("$smartctl" -i "$drive" | grep -i "serial number" | awk '{print $NF}') 114 | capacity=$("$smartctl" -i "$drive" | grep "User Capacity" | awk '{print $5 $6}') 115 | temp=$("$smartctl" -A "$drive" | grep "194 Temperature" | awk '{print $10}') 116 | if [ -z "$temp" ]; then 117 | temp=$("$smartctl" -A "$drive" | grep "190 Temperature_Case" | awk '{print $10}') 118 | fi 119 | if [ -z "$temp" ]; then 120 | temp=$("$smartctl" -A "$drive" | grep "190 Airflow_Temperature" | awk '{print $10}') 121 | fi 122 | if [ -z "$temp" ]; then 123 | temp=$("$smartctl" -A "$drive" | grep "Current Drive Temperature" | awk '{print $4}') 124 | fi 125 | if [ -z "$temp" ]; then 126 | temp="-n/a-" 127 | else 128 | temp="${temp}C" 129 | fi 130 | dfamily=$("$smartctl" -i "$drive" | grep "Model Family" | awk '{print $3, $4, $5, $6, $7}' | sed -e 's/[[:space:]]*$//') 131 | dmodel=$("$smartctl" -i "$drive" | grep "Device Model" | awk '{print $3, $4, $5, $6, $7}' | sed -e 's/[[:space:]]*$//') 132 | if [ -z "$dfamily" ]; then 133 | dinfo="$dmodel" 134 | else 135 | dinfo="$dfamily ($dmodel)" 136 | fi 137 | if [ -z "$dfamily" ]; then 138 | vendor=$("$smartctl" -i "$drive" | grep "Vendor:" | awk '{print $NF}') 139 | product=$("$smartctl" -i "$drive" | grep "Product:" | awk '{print $NF}') 140 | revision=$("$smartctl" -i "$drive" | grep "Revision:" | awk '{print $NF}') 141 | dinfo="$vendor $product $revision" 142 | fi 143 | printf '%6.6s: %5s %-8s %-20.20s %s\n' "$(basename "$drive")" "$temp" "$capacity" "$serial" "$dinfo" 144 | done 145 | 146 | -------------------------------------------------------------------------------- /save_config.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ##### 4 | # Backup the TrueNAS/FreeNAS configuration database and password secret encryption files 5 | ##### 6 | 7 | # REQUIRED: Specify the dataset on your system where you want the configuration files copied. 8 | # Don't include the trailing slash. 9 | 10 | # Example: configdir="/mnt/tank/sysadmin/config" 11 | 12 | configdir="" 13 | 14 | # Remove this code once you've defined configdir above... :-) 15 | 16 | if [ -z "${configdir}" ]; then 17 | echo "Edit script and specify the target directory ('configdir') before using $0" 18 | exit 2 19 | fi 20 | 21 | # Optional: Set non-zero 'do_tar' flag to have both files stored in a tarball as typically 22 | # needed when restoring a configuration. 23 | do_tar=1 24 | 25 | # Optional: specify your email address here if you want to receive a notification message. 26 | notifyemail="" 27 | 28 | # Optional: specify the short name of your ESXi host if you are running FreeNAS 29 | # as a VM and you want to back up the ESXi host's configuration 30 | esxihost="" 31 | 32 | # Get the date and version of TrueNAS/FreeNAS: 33 | 34 | rundate=$(date) 35 | 36 | osvers=$(grep -i truenas /etc/version) 37 | if [ -z "${osvers}" ]; then 38 | osvers=$(grep -i freenas /etc/version) 39 | if [ -z "${osvers}" ]; then 40 | osvers="UNKNOWN" 41 | else 42 | osvers="FreeNAS" 43 | fi 44 | else 45 | osvers="TrueNAS" 46 | fi 47 | 48 | # Form a unique, timestamped filename for the backup configuration database and tarball 49 | 50 | P1=$(hostname -s) 51 | P2=$(< /etc/version sed -e 's/)//;s/(//;s/ /-/' | tr -d '\n') 52 | P3=$(date +%Y%m%d%H%M%S) 53 | fnconfigdest_base="$P1"-"$P2"-"$P3" 54 | fnconfigdestdb="${configdir}"/"${fnconfigdest_base}".db 55 | fnconfigtarball="${configdir}"/"${fnconfigdest_base}".tar 56 | 57 | # Copy the source database and password encryption secret key to the destination: 58 | 59 | echo "Backup ${osvers} configuration database file: ${fnconfigdestdb}" 60 | 61 | cp -f /data/pwenc_secret "$configdir" 62 | /usr/local/bin/sqlite3 /data/freenas-v1.db ".backup main '${fnconfigdestdb}'" 63 | l_status=$? 64 | 65 | # Validate the configuration file and create tarball: 66 | 67 | if [ $l_status -eq 0 ]; then 68 | dbstatus=$(sqlite3 "$fnconfigdestdb" "pragma integrity_check;") 69 | printf 'sqlite3 status: [%s]\n' "${dbstatus}" 70 | if [ "${dbstatus}" = "ok" ]; then 71 | l_status=0 72 | if [ $do_tar -ne 0 ]; then 73 | # Save the config DB w/ its original name in the tarball -- makes restoring them easier: 74 | cp -f "${fnconfigdestdb}" "${configdir}"/freenas-v1.db 75 | tar -cvf "${fnconfigtarball}" -C "${configdir}" freenas-v1.db pwenc_secret 76 | l_status=$? 77 | printf 'tar status: [%s]\n' "${l_status}" 78 | fi 79 | else 80 | l_status=1 81 | fi 82 | fi 83 | 84 | if [ $l_status -eq 0 ]; then 85 | echo "Success backing up configuration files to directory ${configdir}" 86 | else 87 | echo "Error backing up configuration files to directory ${configdir}" 88 | fi 89 | l_status=$? 90 | 91 | # Backup the VMware ESXi host configuration: 92 | 93 | if [ -n "${esxihost}" ]; then 94 | esxihostname=$(ssh root@"${esxihost}" hostname) 95 | esxiversion=$(ssh root@"${esxihost}" uname -a | sed -e "s|VMkernel ||;s|$esxihostname ||") 96 | esxiconfig_url=$(ssh root@"${esxihost}" vim-cmd hostsvc/firmware/backup_config | awk '{print $7}' | sed -e "s|*|$esxihostname|") 97 | esxiconfig_date=$(date +%Y%m%d%H%M%S) 98 | esxiconfig_file="${configdir}"/"${esxihost}"-configBundle-"${esxiconfig_date}".tgz 99 | 100 | echo "Downloading $esxiconfig_url to $esxiconfig_file" 101 | wget --no-check-certificate --output-document="${esxiconfig_file}" "${esxiconfig_url}" 102 | fi 103 | 104 | # Send email notification if indicated: 105 | 106 | if [ -n "${notifyemail}" ]; then 107 | freenashostuc=$(hostname -s | tr '[:lower:]' '[:upper:]') 108 | freenashostname=$(hostname) 109 | freenasversion=$(< /etc/version sed -e 's/)//;s/(//;s/ /-/' | tr -d '\n') 110 | boundary="===== MIME boundary; ${osvers} server ${freenashostname} =====" 111 | logfile="/tmp/save_config.tmp" 112 | if [ $l_status -eq 0 ]; then 113 | subject="${osvers} configuration saved on server ${freenashostuc}" 114 | else 115 | subject="${osvers} configuration backup failed on server ${freenashostuc}" 116 | fi 117 | 118 | printf "%s\n" "To: ${notifyemail} 119 | Subject: ${subject} 120 | Mime-Version: 1.0 121 | Content-Type: multipart/mixed; boundary=\"$boundary\" 122 | 123 | --${boundary} 124 | Content-Type: text/html; charset=\"US-ASCII\" 125 | Content-Transfer-Encoding: 7bit 126 | Content-Disposition: inline 127 |
" > ${logfile} 
128 |   
129 | (
130 |     if [ $l_status -eq 0 ]; then
131 |       echo "Configuration file saved successfully on ${rundate}"
132 |     else
133 |       echo "Configuration backup failed with status=${l_status} on ${rundate}"
134 |     fi
135 |     echo ""
136 |     echo "--- ${osvers} ---"
137 |     echo "Server: ${freenashostname}"
138 |     echo "Version: ${freenasversion}"
139 |     echo "Files:"
140 | 	echo "  ${fnconfigdestdb}"
141 | 	echo "  ${configdir}/pwenc_secret"
142 |     if [ "$do_tar" -ne 0 ]; then	
143 | 	   echo "  ${fnconfigtarball}"
144 | 	fi 
145 |     if [ -n "${esxihost}" ]; then
146 |       echo ""
147 |       echo "--- ESXi ---"
148 |       echo "Server: ${esxihostname}"
149 |       echo "Version: ${esxiversion}"
150 |       echo "File: ${esxiconfig_file}"
151 |     fi
152 | ) >> ${logfile}
153 |   
154 |   printf "%s\n" "
155 | --${boundary}--" >> ${logfile} 156 | 157 | sendmail -t -oi < ${logfile} 158 | rm ${logfile} 159 | fi 160 | 161 | 162 | -------------------------------------------------------------------------------- /save_config_enc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ################################################# 4 | # Backup FreeNAS configuration files 5 | # 6 | # Copies the FreeNAS sqlite3 configuration and password secret 7 | # seed files to the location you specify in the 'configdir' 8 | # variable below. 9 | # 10 | # OPTIONAL: 11 | # 12 | # By specifying your email address in the 'email' variable, you may choose to 13 | # have the configuration file emailed to you in an encrypted tarball. 14 | # 15 | ################################################# 16 | 17 | rundate=$(date) 18 | 19 | # Optional: specify your email address here if you want to the script to email 20 | # you the configuration file in an encrypted tarball. 21 | # 22 | # Leave the email address blank to simply copy the configuration file to the 23 | # destination you specify with the 'configdir' setting below. 24 | email="" 25 | 26 | # Specify the dataset on your system where you want the configuration files copied. 27 | # Don't include the trailing slash. 28 | 29 | # Example: configdir=/mnt/tank/sysadmin/config 30 | configdir="" 31 | 32 | # OpenSSL encryption passphrase file. Enter the passphrase on the the first line in 33 | # the file. This file should have 0600 permissions. 34 | enc_passphrasefile=/root/config_passphrase 35 | 36 | # FreeNAS hostname: 37 | freenashost=$(hostname -s) 38 | 39 | # FreeBSD version: 40 | fbsd_relver=$(uname -K) 41 | 42 | # MIME boundary 43 | mime_boundary="==>>> MIME boundary; FreeNAS server [${freenashost}] <<<==" 44 | 45 | ################################################# 46 | # Append file attachment to current email message 47 | ################################################# 48 | 49 | append_file() 50 | { 51 | l_mimetype="" 52 | 53 | if [ -f "$1" ]; then 54 | l_mimetype=$(file --mime-type "$1" | sed 's/.*: //') 55 | 56 | printf '%s\n' "--${mime_boundary} 57 | Content-Type: $l_mimetype 58 | Content-Transfer-Encoding: base64 59 | Content-Disposition: attachment; filename=\"$(basename "$1")\" 60 | " 61 | base64 "$1" 62 | echo 63 | fi 64 | } 65 | 66 | ################################################# 67 | # Backup the FreeNAS configuration file 68 | ################################################# 69 | 70 | fnconfigdest_version=$(< /etc/version sed -e 's/)//;s/(//;s/ /-/' | tr -d '\n') 71 | fnconfigdest_date=$(date +%Y%m%d%H%M%S) 72 | fnconfigdest_base="$freenashost"-"$fnconfigdest_version"-"$fnconfigdest_date".db 73 | fnconfigdest="$configdir"/"$fnconfigdest_base" 74 | fnconfigtarball=./"$freenashost"-"$fnconfigdest_version"-"$fnconfigdest_date".tar.gz 75 | fnconfigtarballenc=./"$freenashost"-"$fnconfigdest_version"-"$fnconfigdest_date".tar.gz.enc 76 | 77 | echo "Backup configuration database file: $fnconfigdest" 78 | 79 | # Copy the source database and password encryption secret seed file to the destination: 80 | 81 | /usr/local/bin/sqlite3 /data/freenas-v1.db ".backup main '${fnconfigdest}'" 82 | l_status=$? 83 | cp -f /data/pwenc_secret "$configdir" 84 | 85 | if [ -z "$email" ]; then 86 | # No email message requested, show status and exit: 87 | echo "Configuration file copied with status ${l_status}" 88 | exit $l_status 89 | fi 90 | 91 | ######################################################### 92 | # Send email message with encrypted config files attached 93 | ######################################################### 94 | 95 | fnconfigtarball=./"$freenashost"-"$fnconfigdest_version"-"$fnconfigdest_date".tar.gz 96 | fnconfigtarballenc=./"$freenashost"-"$fnconfigdest_version"-"$fnconfigdest_date".tar.gz.enc 97 | 98 | # Validate the configuration file and create tarball: 99 | 100 | if [ $l_status -eq 0 ]; then 101 | dbstatus=$(sqlite3 "$fnconfigdest" "pragma integrity_check;") 102 | printf 'sqlite3 status: [%s]\n' "$dbstatus" 103 | if [ "$dbstatus" = "ok" ]; then 104 | tar -czvf "$fnconfigtarball" -C "$configdir" "$fnconfigdest_base" pwenc_secret 105 | l_status=$? 106 | printf 'tar status: [%s]\n' "$l_status" 107 | else 108 | l_status=1 109 | fi 110 | if [ $l_status -eq 0 ]; then 111 | if [ "$fbsd_relver" -ge 1200000 ]; then 112 | openssl enc -e -aes-256-cbc -md sha512 -pbkdf2 -iter 128000 -salt -S "$(openssl rand -hex 8)" -pass file:"$enc_passphrasefile" -in "$fnconfigtarball" -out "$fnconfigtarballenc" 113 | else 114 | openssl enc -e -aes-256-cbc -md sha512 -salt -S "$(openssl rand -hex 4)" -pass file:"$enc_passphrasefile" -in "$fnconfigtarball" -out "$fnconfigtarballenc" 115 | fi 116 | l_status=$? 117 | printf 'openssl status: [%s]\n' "$l_status" 118 | fi 119 | fi 120 | 121 | freenashostuc=$(hostname -s | tr '[:lower:]' '[:upper:]') 122 | freenashostname=$(hostname) 123 | freenasversion=$(cat /etc/version) 124 | if [ $l_status -eq 0 ]; then 125 | subject="FreeNAS configuration saved on server ${freenashostuc}" 126 | savestatus="FreeNAS configuration file saved successfully on ${rundate}" 127 | else 128 | subject="FreeNAS configuration backup failed on server ${freenashostuc}" 129 | savestatus="FreeNAS configuration backup failed with status=${l_status} on ${rundate}" 130 | fi 131 | logfile="/tmp/save_config_enc.tmp" 132 | { 133 | printf '%s\n' "From: root 134 | To: ${email} 135 | Subject: ${subject} 136 | Mime-Version: 1.0 137 | Content-Type: multipart/mixed; boundary=\"$mime_boundary\" 138 | 139 | --${mime_boundary} 140 | Content-Type: text/plain; charset=\"US-ASCII\" 141 | Content-Transfer-Encoding: 7bit 142 | Content-Disposition: inline 143 | 144 | ${savestatus} 145 | 146 | Server: ${freenashostname} 147 | Version: ${freenasversion} 148 | File: ${fnconfigdest} 149 | " 150 | 151 | if [ $l_status -eq 0 ]; then 152 | append_file "$fnconfigtarballenc" 153 | fi 154 | 155 | # print last boundary with closing -- 156 | printf '%s\n' "--${mime_boundary}--" 157 | } > "$logfile" 158 | 159 | sendmail -t -oi < "$logfile" 160 | rm "$logfile" 161 | rm "$fnconfigtarball" 162 | rm "$fnconfigtarballenc" 163 | 164 | 165 | 166 | -------------------------------------------------------------------------------- /zpool_report.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ### Parameters ### 4 | 5 | # Specify your email address here: 6 | email="" 7 | 8 | # zpool output changed from FreeNAS version 11.0 to 11.1, breaking 9 | # our parsing of the scrubErrors and scrubDate variables. Added a 10 | # conditional to test for the FreeNAS version and parse accordingly. 11 | # This changed again with the release of TrueNAS. Ironically, back to 12 | # the way parsing worked with older versions of FreeNAS. 13 | # 14 | # We obtain the FreeBSD version using uname, as suggested by user 15 | # Chris Moore on the FreeBSD forum. 16 | # 17 | # 'uname -K' gives 7-digit OS release and version, e.g.: 18 | # 19 | # FreeBSD 11.0 1100512 20 | # FreeBSD 11.1 1101505 21 | # FreeBSD 12.2 1202000 22 | # 23 | # If a scrub runs longer than 24 hours, we have two additional tokens to parse in the 24 | # zpool status scan line output ("'x' days"): 25 | # 26 | # 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 27 | # scan: scrub repaired 0B in 1 days 11:56:46 with 0 errors on Wed Dec 9 06:07:04 2020 28 | # 29 | # 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 30 | # scan: scrub repaired 0B in 00:09:11 with 0 errors on Sun Dec 13 17:31:24 2020 31 | 32 | fbsd_relver=$(uname -K) 33 | 34 | freenashost=$(hostname -s | tr '[:lower:]' '[:upper:]') 35 | boundary="===== MIME boundary; FreeNAS server ${freenashost} =====" 36 | logfile="/tmp/zpool_report.tmp" 37 | subject="ZPool Status Report for ${freenashost}" 38 | pools=$(zpool list -H -o name) 39 | usedWarn=75 40 | usedCrit=90 41 | scrubAgeWarn=30 42 | warnSymbol="?" 43 | critSymbol="!" 44 | 45 | ### Set email headers ### 46 | printf "%s\n" "To: ${email} 47 | Subject: ${subject} 48 | Mime-Version: 1.0 49 | Content-Type: multipart/mixed; boundary=\"$boundary\" 50 | 51 | --${boundary} 52 | Content-Type: text/html; charset=\"US-ASCII\" 53 | Content-Transfer-Encoding: 7bit 54 | Content-Disposition: inline 55 |
" >> ${logfile}
 56 | 
 57 | ###### summary ######
 58 | (
 59 |   echo "########## ZPool status report summary for all pools on server ${freenashost} ##########"
 60 |   echo ""
 61 |   echo "+--------------+--------+------+------+------+----+----+--------+------+-----+"
 62 |   echo "|Pool Name     |Status  |Read  |Write |Cksum |Used|Frag|Scrub   |Scrub |Last |"
 63 |   echo "|              |        |Errors|Errors|Errors|    |    |Repaired|Errors|Scrub|"
 64 |   echo "|              |        |      |      |      |    |    |Bytes   |      |Age  |"
 65 |   echo "+--------------+--------+------+------+------+----+----+--------+------+-----+"
 66 | ) >> ${logfile}
 67 | 
 68 | for pool in $pools; do
 69 |   if [ "$fbsd_relver" -ge 1101000 ]; then
 70 |     frag="$(zpool list -H -o frag "$pool")"   
 71 |   else
 72 |     if [ "${pool}" = "freenas-boot" ] || [ "${pool}" = "boot-pool" ]; then
 73 |       frag=""
 74 |     else
 75 |       frag="$(zpool list -H -o frag "$pool")"
 76 |     fi
 77 |   fi
 78 | 
 79 |   status="$(zpool list -H -o health "$pool")"
 80 |   errors="$(zpool status "$pool" | grep -E "(ONLINE|DEGRADED|FAULTED|UNAVAIL|REMOVED)[ \t]+[0-9]+")"
 81 |   readErrors=0
 82 |   for err in $(echo "$errors" | awk '{print $3}'); do
 83 |     if echo "$err" | grep -E -q "[^0-9]+"; then
 84 |       readErrors=1000
 85 |       break
 86 |     fi
 87 |     readErrors=$((readErrors + err))
 88 |   done
 89 |   writeErrors=0
 90 |   for err in $(echo "$errors" | awk '{print $4}'); do
 91 |     if echo "$err" | grep -E -q "[^0-9]+"; then
 92 |       writeErrors=1000
 93 |       break
 94 |     fi
 95 |     writeErrors=$((writeErrors + err))
 96 |   done
 97 |   cksumErrors=0
 98 |   for err in $(echo "$errors" | awk '{print $5}'); do
 99 |     if echo "$err" | grep -E -q "[^0-9]+"; then
100 |       cksumErrors=1000
101 |       break
102 |     fi
103 |     cksumErrors=$((cksumErrors + err))
104 |   done
105 |   if [ "$readErrors" -gt 999 ]; then readErrors=">1K"; fi
106 |   if [ "$writeErrors" -gt 999 ]; then writeErrors=">1K"; fi
107 |   if [ "$cksumErrors" -gt 999 ]; then cksumErrors=">1K"; fi
108 |   used="$(zpool list -H -p -o capacity "$pool")"
109 |   scrubRepBytes="N/A"
110 |   scrubErrors="N/A"
111 |   scrubAge="N/A"
112 |   if [ "$(zpool status "$pool" | grep "scan" | awk '{print $2}')" = "scrub" ]; then
113 |     parseLong=0
114 |     if [ "$fbsd_relver" -gt 1101000 ] && [ "$fbsd_relver" -lt 1200000 ]; then
115 |       parseLong=$((parseLong+1))
116 |     fi
117 |     if [ "$(zpool status "$pool" | grep "scan" | awk '{print $7}')" = "days" ]; then
118 |       parseLong=$((parseLong+1))
119 |     fi 
120 |     scrubRepBytes="$(zpool status "$pool" | grep "scan" | awk '{print $4}')"
121 |     if [ $parseLong -gt 0 ]; then
122 |       scrubErrors="$(zpool status "$pool" | grep "scan" | awk '{print $10}')"
123 |       scrubDate="$(zpool status "$pool" | grep "scan" | awk '{print $17"-"$14"-"$15"_"$16}')"
124 |     else
125 |       scrubErrors="$(zpool status "$pool" | grep "scan" | awk '{print $8}')"
126 |       scrubDate="$(zpool status "$pool" | grep "scan" | awk '{print $15"-"$12"-"$13"_"$14}')"
127 |     fi
128 |     scrubTS="$(date -j -f "%Y-%b-%e_%H:%M:%S" "$scrubDate" "+%s")"
129 |     currentTS="$(date "+%s")"
130 |     scrubAge=$((((currentTS - scrubTS) + 43200) / 86400))
131 |   fi
132 |   if [ "$status" = "FAULTED" ] || [ "$used" -gt "$usedCrit" ]; then
133 |     symbol="$critSymbol"  
134 |   elif [ "$scrubErrors" != "N/A" ] && [ "$scrubErrors" != "0" ]; then
135 |     symbol="$critSymbol"
136 |   elif [ "$status" != "ONLINE" ] \
137 |   || [ "$readErrors" != "0" ] \
138 |   || [ "$writeErrors" != "0" ] \
139 |   || [ "$cksumErrors" != "0" ] \
140 |   || [ "$used" -gt "$usedWarn" ] \
141 |   || [ "$(echo "$scrubAge" | awk '{print int($1)}')" -gt "$scrubAgeWarn" ]; then
142 |     symbol="$warnSymbol"  
143 |   elif [ "$scrubRepBytes" != "0" ] &&  [ "$scrubRepBytes" != "0B" ] && [ "$scrubRepBytes" != "N/A" ]; then
144 |     symbol="$warnSymbol"
145 |   else
146 |     symbol=" "
147 |   fi
148 |   (
149 |   printf "|%-12s %1s|%-8s|%6s|%6s|%6s|%3s%%|%4s|%8s|%6s|%5s|\n" \
150 |   "$pool" "$symbol" "$status" "$readErrors" "$writeErrors" "$cksumErrors" \
151 |   "$used" "$frag" "$scrubRepBytes" "$scrubErrors" "$scrubAge"
152 |   ) >> ${logfile}
153 |   done
154 | 
155 | (
156 |   echo "+--------------+--------+------+------+------+----+----+--------+------+-----+"
157 | ) >> ${logfile}
158 | 
159 | ###### for each pool ######
160 | for pool in $pools; do
161 |   (
162 |   echo ""
163 |   echo "########## ZPool status report for ${pool} ##########"
164 |   echo ""
165 |   zpool status -v "$pool"
166 |   ) >> ${logfile}
167 | done
168 | 
169 | printf "%s\n" "
170 | --${boundary}--" >> ${logfile} 171 | 172 | ### Send report ### 173 | if [ -z "${email}" ]; then 174 | echo "No email address specified, information available in ${logfile}" 175 | else 176 | sendmail -t -oi < ${logfile} 177 | rm ${logfile} 178 | fi 179 | -------------------------------------------------------------------------------- /get-system-temps.pl: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/perl 2 | ############################################################################### 3 | # 4 | # get-system-temps.pl 5 | # 6 | # Displays CPU and drive temperatures 7 | # 8 | # Drive information reported includes the device ID, temperature, capacity, type 9 | # (SDD or HDD), serial number, model, and, if available, the model family. 10 | # 11 | # Optionally uses IPMI to report CPU temperatures. Otherwise, these are pulled 12 | # from sysctl. IPMI is more accurate in that it reports the temperature of each 13 | # socketed CPU in the system, even on virtualized instances, whereas the CPU 14 | # temperatures typically aren't available from sysctl in this case. 15 | # 16 | # Requires the smartmontools, available at: https://www.smartmontools.org/ 17 | # 18 | # Keith Nash, July 2017 19 | # 20 | ############################################################################### 21 | 22 | use strict; 23 | use warnings; 24 | 25 | # Get system's hostname: 26 | 27 | my $hostname = qx(hostname); 28 | chomp($hostname); 29 | 30 | # Full path to the smartctl program: 31 | 32 | my $smartctl = "/usr/local/sbin/smartctl"; 33 | 34 | # IPMI setup: 35 | 36 | # Toggle IPMI support on or off: 37 | # 1 = on: use IPMI 38 | # 0 = off: use sysctl instead of IPMI 39 | 40 | my $useipmi = 0; 41 | 42 | # IPMI username and password file. The password file is a text file with the 43 | # IPMI user's password on the first line. Be sure to set permissions to 0600 44 | # on the password file. 45 | # 46 | # You may not need credentials on some systems. In this case, ignore these 47 | # variables and modify the ipmitool variable below to suit your environment, 48 | # removing the '-I lanplus' and user credential options (-U and -f) as needed. 49 | 50 | my $ipmiuser = "root"; 51 | my $ipmipwfile = "/root/ipmi_password"; 52 | 53 | # The IPMI host must be either an IP address or a DNS-resolvable hostname. If you 54 | # have multiple systems, leave the variable blank and edit the conditional below 55 | # to specify the IPMI host according to the host on which you are running the script: 56 | 57 | my $ipmihost = ""; 58 | 59 | if ($useipmi && $ipmihost eq "") 60 | { 61 | if ($hostname =~ /bandit/) 62 | { 63 | $ipmihost="falcon.ipmi.spearfoot.net" 64 | } 65 | elsif ($hostname =~ /boomer/) 66 | { 67 | $ipmihost="felix.ipmi.spearfoot.net" 68 | } 69 | elsif ($hostname =~ /bacon/) 70 | { 71 | $ipmihost="fritz.ipmi.spearfoot.net" 72 | } 73 | else 74 | { 75 | die "No IPMI host specified!\n" 76 | } 77 | } 78 | 79 | # Full path to ipmitool program, including options and credentials: 80 | 81 | my $ipmitool = "/usr/local/bin/ipmitool -I lanplus -H $ipmihost -U $ipmiuser -f $ipmipwfile"; 82 | 83 | main(); 84 | 85 | ############################################################################### 86 | # 87 | # main 88 | # 89 | ############################################################################### 90 | sub main 91 | { 92 | printf("==========\n\n"); 93 | 94 | if ($useipmi) 95 | { 96 | printf("%s (IPMI host: %s)\n\n",$hostname,$ipmihost); 97 | } 98 | else 99 | { 100 | printf("%s\n\n",$hostname); 101 | } 102 | 103 | display_cpu_temps(); 104 | display_drive_info(); 105 | } 106 | 107 | ############################################################################### 108 | # 109 | # display_cpu_temps 110 | # 111 | ############################################################################### 112 | sub display_cpu_temps 113 | { 114 | my $temp; 115 | my $cpucores=0; 116 | 117 | if ($useipmi) 118 | { 119 | $cpucores = qx($ipmitool sdr | grep -c -i "cpu.*temp"); 120 | } 121 | else 122 | { 123 | $cpucores = qx(sysctl -n hw.ncpu); 124 | } 125 | 126 | printf("=== CPU (%d) ===\n",$cpucores); 127 | 128 | if ($useipmi) 129 | { 130 | if ($cpucores > 1) 131 | { 132 | for (my $core=1; $core <= $cpucores; $core++) 133 | { 134 | $temp=qx($ipmitool sdr | grep -i "CPU$core Temp" | awk '{print \$4}'); 135 | chomp($temp); 136 | printf("CPU %2u: %3sC\n",$core,$temp); 137 | } 138 | } 139 | else 140 | { 141 | $temp=qx($ipmitool sdr | grep -i "CPU Temp" | awk '{print \$4}'); 142 | chomp($temp); 143 | printf("CPU %2u: %3sC\n",1,$temp); 144 | } 145 | } 146 | else 147 | { 148 | for (my $core=0; $core < $cpucores; $core++) 149 | { 150 | $temp = qx(sysctl -n dev.cpu.$core.temperature); 151 | $temp =~ s/[^\-[:digit:]\.]//g; 152 | chomp($temp); 153 | if ($temp <= 0) 154 | { 155 | printf("CPU %2u: -N/A-\n",$core); 156 | } 157 | else 158 | { 159 | printf("CPU %2u: %3sC\n",$core,$temp); 160 | } 161 | } 162 | } 163 | } 164 | 165 | ############################################################################### 166 | # 167 | # display_drive_info 168 | # 169 | ############################################################################### 170 | sub display_drive_info 171 | { 172 | my $drive_id; 173 | my $drive_model; 174 | my $drive_family; 175 | my $drive_serial; 176 | my $drive_capacity; 177 | my $drive_temp; 178 | my $drive_is_ssd; 179 | my $drive_family_display; 180 | 181 | printf("\n=== Drives ===\n"); 182 | 183 | my @smart_drive_list = get_smart_drives(); 184 | 185 | foreach my $drive (@smart_drive_list) 186 | { 187 | ($drive_model, $drive_family, $drive_serial, $drive_capacity, $drive_temp, $drive_is_ssd) = get_drive_info($drive); 188 | 189 | if ($drive =~ /\/dev\/(.*)/) 190 | { 191 | $drive_id = $1; 192 | } 193 | else 194 | { 195 | $drive_id = $drive; 196 | } 197 | 198 | if ($drive_family eq "") 199 | { 200 | $drive_family_display = ""; 201 | } 202 | else 203 | { 204 | $drive_family_display = "(" . $drive_family . ")"; 205 | } 206 | 207 | printf("%6.6s: %3uC [%8.8s %s] %-20.20s %s %s\n", 208 | $drive_id, 209 | $drive_temp, 210 | $drive_capacity, 211 | $drive_is_ssd ? "SSD" : "HDD", 212 | $drive_serial, 213 | $drive_model, 214 | $drive_family_display); 215 | } 216 | } 217 | 218 | ############################################################################### 219 | # 220 | # get_smart_drives 221 | # 222 | ############################################################################### 223 | sub get_smart_drives 224 | { 225 | my @retval = (); 226 | my @drive_list = split(" ", qx($smartctl --scan | awk '{print \$1}')); 227 | 228 | foreach my $drive (@drive_list) 229 | { 230 | my $smart_enabled = qx($smartctl -i $drive | grep "SMART support is: Enabled" | awk '{print \$4}'); 231 | chomp($smart_enabled); 232 | if ($smart_enabled eq "Enabled") 233 | { 234 | push @retval, $drive; 235 | } 236 | } 237 | 238 | return @retval; 239 | } 240 | 241 | ############################################################################### 242 | # 243 | # get_drive_info 244 | # 245 | ############################################################################### 246 | sub get_drive_info 247 | { 248 | my $drive = shift; 249 | my $smart_data = qx($smartctl -a $drive); 250 | 251 | my $drive_model = ""; 252 | my $drive_family = ""; 253 | my $drive_serial = ""; 254 | my $drive_capacity = ""; 255 | my $drive_temp = 0; 256 | my $drive_is_ssd = 0; 257 | 258 | $drive_temp = get_drive_temp($drive); 259 | 260 | # Serial number 261 | if ($smart_data =~ /^Serial Number:\s*(.*)\s/m) 262 | { 263 | $drive_serial = $1; 264 | } 265 | 266 | # Device model 267 | if ($smart_data =~ /^Device Model:\s*(.*)\s/m) 268 | { 269 | $drive_model = $1; 270 | } 271 | 272 | # Model family 273 | if ($smart_data =~ /^Model Family:\s*(.*)\s/m) 274 | { 275 | $drive_family = $1; 276 | } 277 | 278 | # User capacity 279 | if ($smart_data =~ /^User Capacity:.*\[(.*)\]\s/m) 280 | { 281 | $drive_capacity = $1; 282 | } 283 | 284 | # Determine if drive is a SSD 285 | if ($smart_data =~ /^Rotation Rate:[ ]*Solid State Device/m) 286 | { 287 | $drive_is_ssd = 1; 288 | } 289 | elsif ($smart_data =~ /^[ 0-9]{3} Unknown_SSD_Attribute/m) 290 | { 291 | $drive_is_ssd = 1; 292 | } 293 | elsif ($smart_data =~ /^[ 0-9]{3} Wear_Leveling_Count/m) 294 | { 295 | $drive_is_ssd = 1; 296 | } 297 | elsif ($smart_data =~ /^[ 0-9]{3} Media_Wearout_Indicator/m) 298 | { 299 | $drive_is_ssd = 1; 300 | } 301 | elsif ($drive_family =~ /SSD/) 302 | { 303 | # Model family indicates SSD 304 | $drive_is_ssd = 1; 305 | } 306 | 307 | return ($drive_model, $drive_family, $drive_serial, $drive_capacity, $drive_temp, $drive_is_ssd); 308 | } 309 | 310 | ############################################################################### 311 | # 312 | # get_drive_temp 313 | # 314 | ############################################################################### 315 | sub get_drive_temp 316 | { 317 | my $drive = shift; 318 | my $retval = 0; 319 | 320 | $retval = qx($smartctl -A $drive | grep "194 Temperature" | awk '{print \$10}'); 321 | 322 | if (!$retval) 323 | { 324 | $retval = qx($smartctl -A $drive | grep "190 Airflow_Temperature" | awk '{print \$10}'); 325 | } 326 | 327 | return $retval; 328 | } 329 | 330 | 331 | -------------------------------------------------------------------------------- /smart_report.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ### Parameters ### 4 | 5 | # Specify your email address here: 6 | email="" 7 | 8 | # Full path to 'smartctl' program: 9 | smartctl=/usr/local/sbin/smartctl 10 | 11 | freenashost=$(hostname -s | tr '[:lower:]' '[:upper:]') 12 | boundary="===== MIME boundary; FreeNAS server ${freenashost} =====" 13 | logfile="smart_report.tmp" 14 | subject="SMART Status Report for ${freenashost}" 15 | tempWarn=40 16 | tempCrit=45 17 | sectorsCrit=10 18 | testAgeWarn=1 19 | warnSymbol="?" 20 | critSymbol="!" 21 | Drive_count=0 22 | SATA_count=0 23 | SAS_count=0 24 | Drive_list="" 25 | SATA_list="" 26 | SAS_list="" 27 | 28 | # Get list of SMART-enabled drives 29 | get_smart_drives() 30 | { 31 | gs_drives=$("$smartctl" --scan | awk '{print $1}') 32 | for gs_drive in $gs_drives; do 33 | gs_smart_flag=$("$smartctl" -i "$gs_drive" | grep -E "SMART support is:[[:blank:]]+Enabled" | awk '{print $4}') 34 | if [ "$gs_smart_flag" = "Enabled" ]; then 35 | Drive_list="$Drive_list $gs_drive" 36 | Drive_count=$((Drive_count + 1)) 37 | fi 38 | done 39 | } 40 | 41 | # Get list of SATA disks, including older drives that only report an ATA version 42 | get_sata_drives() 43 | { 44 | for drive in $Drive_list; do 45 | lFound=0 46 | gsata_smart_flag=$("$smartctl" -i "$drive" | grep -E "SATA Version is:[[:blank:]]" | awk '{print $4}') 47 | if [ "$gsata_smart_flag" = "SATA" ]; then 48 | lFound=$((lFound + 1)) 49 | else 50 | gsata_smart_flag=$("$smartctl" -i "$drive" | grep -E "ATA Version is:[[:blank:]]" | awk '{print $1}') 51 | if [ "$gsata_smart_flag" = "ATA" ]; then 52 | lFound=$((lFound + 1)) 53 | fi 54 | fi 55 | if [ $lFound -gt 0 ]; then 56 | SATA_list="$SATA_list $drive" 57 | SATA_count=$((SATA_count + 1)) 58 | fi 59 | done 60 | } 61 | 62 | # Get list of SAS disks 63 | get_sas_drives() 64 | { 65 | for drive in $Drive_list; do 66 | gsas_smart_flag=$("$smartctl" -i "$drive" | grep -E "Transport protocol:[[:blank:]]+SAS" | awk '{print $3}') 67 | if [ "$gsas_smart_flag" = "SAS" ]; then 68 | SAS_list="$SAS_list $drive" 69 | SAS_count=$((SAS_count + 1)) 70 | fi 71 | done 72 | } 73 | 74 | ### Fetch drive lists ### 75 | get_smart_drives 76 | get_sata_drives 77 | get_sas_drives 78 | 79 | ### Set email headers ### 80 | printf "%s\n" "To: ${email} 81 | Subject: ${subject} 82 | Mime-Version: 1.0 83 | Content-Type: multipart/mixed; boundary=\"$boundary\" 84 | 85 | --${boundary} 86 | Content-Type: text/html; charset=\"US-ASCII\" 87 | Content-Transfer-Encoding: 7bit 88 | Content-Disposition: inline 89 |
" > ${logfile}
 90 | 
 91 | if [ $Drive_count -eq 0 ]; then
 92 |   echo "##### No SMART-enabled disks found on this system #####" >> "$logfile"
 93 | fi
 94 | 
 95 | ###### Summary for SATA drives ######
 96 | if [ $SATA_count -gt 0 ]; then
 97 |   (
 98 |    echo "########## SMART status report summary for all SATA drives on server ${freenashost} ##########"
 99 |    echo ""
100 |    echo "+-------+------------------------+----+------+-----+-----+-------+-------+--------+------+----------+------+-----------+----+"
101 |    echo "|Device |Serial                  |Temp| Power|Start|Spin |ReAlloc|Current|Offline |Seek  |Total     |High  |    Command|Last|"
102 |    echo "|       |Number                  |    | On   |Stop |Retry|Sectors|Pending|Uncorrec|Errors|Seeks     |Fly   |    Timeout|Test|"
103 |    echo "|       |                        |    | Hours|Count|Count|       |Sectors|Sectors |      |          |Writes|    Count  |Age |"
104 |    echo "+-------+------------------------+----+------+-----+-----+-------+-------+--------+------+----------+------+-----------+----+"
105 |   ) >> "$logfile"
106 |   
107 |   ###### Detail information for each SATA drive ######
108 |   for drive in $SATA_list; do
109 |     (
110 |     devid=$(basename "$drive")
111 |     lastTestHours=$("$smartctl" -l selftest "$drive" | grep "# 1" | awk '{print $9}')
112 |     "$smartctl" -A -i -v 7,hex48 "$drive" | \
113 |     awk -v device="$devid" -v tempWarn="$tempWarn" -v tempCrit="$tempCrit" -v sectorsCrit="$sectorsCrit" \
114 |     -v testAgeWarn="$testAgeWarn" -v warnSymbol="$warnSymbol" -v critSymbol="$critSymbol" \
115 |     -v lastTestHours="$lastTestHours" '
116 |     /Serial Number:/{serial=$3}
117 |     /190 Airflow_Temperature/{temp=$10}
118 |     /190 Temperature_Case/{temp=$10}
119 |     /194 Temperature/{temp=$10}
120 |     /Power_On_Hours/{split($10,a,"+");sub(/h/,"",a[1]);onHours=a[1];}
121 |     /Power_Cycle_Count/{startStop=$10}
122 |     /Start_Stop_Count/{startStop=$10}
123 |     /Spin_Retry_Count/{spinRetry=$10}
124 |     /Reallocated_Sector/{reAlloc=$10}
125 |     /Current_Pending_Sector/{pending=$10}
126 |     /Offline_Uncorrectable/{offlineUnc=$10}
127 |     /Seek_Error_Rate/{seekErrors=("0x" substr($10,3,4));totalSeeks=("0x" substr($10,7))}
128 |     /High_Fly_Writes/{hiFlyWr=$10}
129 |     /Command_Timeout/{cmdTimeout=$10}
130 |     END {
131 |       testAge=sprintf("%.0f", (onHours - lastTestHours) / 24);
132 |       if (temp > tempCrit || reAlloc > sectorsCrit || pending > sectorsCrit || offlineUnc > sectorsCrit)
133 |         device=device " " critSymbol;
134 |       else if (temp > tempWarn || reAlloc > 0 || pending > 0 || offlineUnc > 0 || testAge > testAgeWarn)
135 |         device=device " " warnSymbol;
136 |       seekErrors=sprintf("%d", seekErrors);
137 |       totalSeeks=sprintf("%d", totalSeeks);
138 |       if (totalSeeks == "0") {
139 |         seekErrors="N/A";
140 |         totalSeeks="N/A";
141 |       }
142 |       if (temp > tempWarn || temp > tempCrit) temp=temp"*"
143 |       if (reAlloc > 0 || reAlloc > sectorsCrit) reAlloc=reAlloc"*"
144 |       if (pending > 0 || pending > sectorsCrit) pending=pending"*"
145 |       if (offlineUnc > 0 || offlineUnc > sectorsCrit) offlineUnc=offlineUnc"*"
146 |       if (testAge > testAgeWarn) testAge=testAge"*"
147 |       if (hiFlyWr == "") hiFlyWr="N/A";
148 |       if (cmdTimeout == "") cmdTimeout="N/A";
149 |       printf "|%-7s|%-24s|%-4s|%6s|%5s|%5s|%7s|%7s|%8s|%6s|%10s|%6s|%11s|%4s|\n",
150 |         device, serial, temp, onHours, startStop, spinRetry, reAlloc, pending, offlineUnc,
151 |         seekErrors, totalSeeks, hiFlyWr, cmdTimeout, testAge;
152 |       }'
153 |     ) >> "$logfile"
154 |   done
155 |   (
156 |     echo "+-------+------------------------+----+------+-----+-----+-------+-------+--------+------+----------+------+-----------+----+"
157 |   ) >> "$logfile"
158 | fi
159 | 
160 | ###### Summary for SAS drives ######
161 | if [ $SAS_count -gt 0 ]; then
162 |   (
163 |     if [ $SATA_count -gt 0 ]; then
164 |       echo ""
165 |     fi
166 |   
167 |     echo "########## SMART status report summary for all SAS drives on server ${freenashost} ##########"
168 |     echo ""
169 |     echo "+-------+------------------------+----+------+-----+------+------+------+------+------+------+----+"
170 |     echo "|Device |Serial                  |Temp| Power|Start|Load  |Defect|Uncorr|Uncorr|Uncorr|Non   |Last|"
171 |     echo "|       |Number                  |    | On   |Stop |Unload|List  |Read  |Write |Verify|Medium|Test|"
172 |     echo "|       |                        |    | Hours|Count|Count |Elems |Errors|Errors|Errors|Errors|Age |"
173 |     echo "+-------+------------------------+----+------+-----+------+------+------+------+------+------+----+"
174 |   ) >> "$logfile"
175 |   
176 |   ###### Detail information for each SAS drive ######
177 |   for drive in $SAS_list; do
178 |     (
179 |     devid=$(basename "$drive")
180 |     lastTestHours=$("$smartctl" -l selftest "$drive" | grep "# 1" | awk '{print $7}')
181 |     "$smartctl" -x "$drive" | \
182 |     awk -v device="$devid" -v tempWarn="$tempWarn" -v tempCrit="$tempCrit" \
183 |     -v warnSymbol="$warnSymbol" -v critSymbol="$critSymbol" \
184 | 	-v lastTestHours="$lastTestHours" -v testAgeWarn="$testAgeWarn" '
185 |     /Serial number:/{serial=$3}
186 |     /Current Drive Temperature:/{temp=$4}
187 |     /start-stop cycles:/{startStop=$4}
188 |     /load-unload cycles:/{loadUnload=$4}
189 |     /grown defect list:/{defectList=$6}
190 |     /read:/{readErrors=$8}
191 |     /write:/{writeErrors=$8}
192 |     /verify:/{verifyErrors=$8}
193 |     /Non-medium error count:/{nonMediumErrors=$4}
194 |     /Accumulated power on time/{split($6,a,":");sub(/h/,"",a[1]);onHours=a[1];}
195 |     END {
196 |       testAge=sprintf("%.0f", (onHours - lastTestHours) / 24);
197 |       if (temp > tempCrit)
198 |         device=device " " critSymbol;
199 |       else if (temp > tempWarn || testAge > testAgeWarn)
200 |         device=device " " warnSymbol;
201 |       if (testAge > testAgeWarn) testAge=testAge"*"
202 | 	  if (defectList > 0) defectList=defectList"*"
203 |       printf "|%-7s|%-24s| %3s|%6s|%5s|%6s|%6s|%6s|%6s|%6s|%6s|%4s|\n",
204 |         device, serial, temp, onHours, startStop, loadUnload, defectList, \
205 |         readErrors, writeErrors, verifyErrors, nonMediumErrors,testAge;
206 |      }'
207 |     ) >> "$logfile"
208 |   done
209 |   (
210 |     echo "+-------+------------------------+----+------+-----+------+------+------+------+------+------+----+"
211 |   ) >> "$logfile"
212 | fi
213 | 
214 | if [ $SATA_count -gt 0 ] || [ $SAS_count -gt 0 ]; then
215 |  
216 |   ###### Emit SATA drive information ######
217 |   for drive in $SATA_list; do
218 |     vendor=$("$smartctl" -i "$drive" | grep "Vendor:" | awk '{print $NF}')
219 |     if [ -z "$vendor" ]; then
220 |       dfamily=$("$smartctl" -i "$drive" | grep "Model Family" | awk '{print $3, $4, $5, $6, $7}' | sed -e 's/[[:space:]]*$//')
221 |       dmodel=$("$smartctl" -i "$drive" | grep "Device Model" | awk '{print $3, $4, $5, $6, $7}' | sed -e 's/[[:space:]]*$//')
222 |       if [ -z "$dfamily" ]; then
223 |         dinfo=$dmodel
224 |       else
225 |         dinfo="$dfamily ($dmodel)"
226 |       fi
227 |     else
228 |       product=$("$smartctl" -i "$drive" | grep "Product:" | awk '{print $NF}')
229 |       revision=$("$smartctl" -i "$drive" | grep "Revision:" | awk '{print $NF}')
230 |       dinfo="$vendor $product $revision"
231 |     fi
232 |     serial=$("$smartctl" -i "$drive" | grep "Serial Number" | awk '{print $3}')
233 |     (
234 |     echo ""
235 |     echo "########## SATA drive $drive Serial: $serial"
236 |     echo "########## ${dinfo}" 
237 |     "$smartctl" -n never -H -A -l error "$drive"
238 |     "$smartctl" -n never -l selftest "$drive" | grep "# 1 \\|Num" | cut -c6-
239 |     ) >> "$logfile"
240 |   done
241 |   
242 |   ###### Emit SAS drive information ######
243 |   for drive in $SAS_list; do
244 |     devid=$(basename "$drive")
245 |     brand=$("$smartctl" -i "$drive" | grep "Product" | sed "s/^.* //")
246 |     serial=$("$smartctl" -i "$drive" | grep "Serial number" | sed "s/^.* //")
247 |     (
248 |     echo ""
249 |     echo "########## SMART status for SAS drive $drive $serial (${brand}) ##########"
250 |     "$smartctl" -n never -H -A -l error "$drive"
251 |     "$smartctl" -n never -l selftest "$drive" | grep "# 1 \\|Num" | cut -c6-
252 |     ) >> "$logfile"
253 |   done
254 | fi
255 | 
256 | sed -i '' -e '/smartctl 7.*/d' "$logfile"
257 | sed -i '' -e '/smartctl 6.*/d' "$logfile"
258 | sed -i '' -e '/smartctl 5.*/d' "$logfile"
259 | sed -i '' -e '/smartctl 4.*/d' "$logfile"
260 | sed -i '' -e '/Copyright/d' "$logfile"
261 | sed -i '' -e '/=== START OF READ/d' "$logfile"
262 | sed -i '' -e '/SMART Attributes Data/d' "$logfile"
263 | sed -i '' -e '/Vendor Specific SMART/d' "$logfile"
264 | sed -i '' -e '/SMART Error Log Version/d' "$logfile"
265 | 
266 | printf "%s\n" "
267 | --${boundary}--" >> ${logfile} 268 | 269 | ### Send report ### 270 | if [ -z "${email}" ]; then 271 | echo "No email address specified, information available in ${logfile}" 272 | else 273 | sendmail -t -oi < "$logfile" 274 | rm "$logfile" 275 | fi 276 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FreeNAS/TrueNAS Scripts 2 | Handy shell and Perl scripts for use on FreeNAS and TrueNAS servers 3 | 4 | Most of the shell scripts here are my versions of the useful scripts available at the ["Scripts to report SMART, ZPool and UPS status, HDD/CPU T°, HDD identification and backup the config"](https://forums.freenas.org/index.php?threads/scripts-to-report-smart-zpool-and-ups-status-hdd-cpu-t%C2%B0-hdd-identification-and-backup-the-config.27365/) thread on the FreeNAS forum. The original author is FreeNAS forum member BiduleOhm, with others contributing suggestions and code changes. I have modified the syntax and made minor changes in formatting and spacing of the generated reports. 5 | 6 | I used the excellent shell script static analysis tool at https://www.shellcheck.net to insure that all of the code is POSIX-compliant and free of issues. But this doesn't mean you won't find any errors. ☺️ 7 | 8 | All of the Perl code is my own contribution. 9 | *** 10 | #### Operating System Compatibility 11 | 12 | Tested under: 13 | * TrueNAS 12.0 (FreeBSD 12.2) 14 | * FreeNAS 11.3 (FreeBSD 11.3-STABLE) 15 | * FreeNAS 11.2 (FreeBSD 11.2-STABLE) 16 | 17 | Earlier versions of FreeNAS were supported, but are no longer tested. 18 | 19 | *** 20 | # smart_report.sh 21 | 22 | Generates and emails you a status report with detailed SMART information about your system's SATA and SAS drives. A hearty thanks to contributor marrobHD for help in adding SAS support. 23 | 24 | You will need to edit the script and enter your email address before using it. 25 | 26 | NOTE: Users of some HBA controllers may need to change the SMARTCTL call, adding a device specifier. (Hat tip to commenter Tuplink for pointing this out). 27 | 28 | Example: for a 3ware controller, edit the script to invoke SMARTCTL like this: 29 | 30 | ``` 31 | "${smartctl}" [options] -d 3ware,"${drive}" /dev/twa0 32 | ``` 33 | ...instead of... 34 | ``` 35 | "${smartctl}" [options] -d /dev/"${drive}" 36 | ``` 37 | Refer to the SMARTCTL man page for addtional details, including support for other controller types. 38 | *** 39 | # zpool_report.sh 40 | 41 | Generates and emails you a status report about your system's pools. 42 | 43 | You will need to edit the script and enter your email address before using it. 44 | *** 45 | # ups_report.sh 46 | Generates and emails you a status report about your UPS. 47 | 48 | You will need to edit the script and enter your email address before using it. You may also have the report include all of the available UPSC variables by setting the `senddetail` variable to a value greater than zero. 49 | *** 50 | # save_config.sh 51 | 52 | Saves your TrueNAS/FreeNAS system configuration files to a dataset you specify, by creating a tarball containing the SQLite configuration database (_freenas-v1.db_) and password secret seed encryption file (_pwenc_secret_). The tarball is suitable for use in restoring the configuration on TrueNAS/FreeNAS systems. 53 | 54 | **!!! Security Warning !!!** 55 | > The system configuration and password secret seed encryption file are sensitive information and should be stored on a dataset available only to system administrators! 56 | 57 | The backup database and tarball filenames are formed from the hostname, complete TrueNAS/FreeNAS version, date, and _tar_ or _db_ extension, in this format: _hostname-version-date.extension_. Here are examples from a recent backup on my server named _brutus_: 58 | 59 | ``` 60 | brutus-FreeNAS-11.2-U8-06e1172340-20210806114838.tar 61 | brutus-FreeNAS-11.2-U8-06e1172340-20210806114838.db 62 | ``` 63 | 64 | Edit the script and set variable `configdir` to specify the configuration directory, a dataset where you want the backup files stored. 65 | 66 | Optional features: 67 | * Specify your email address in variable `notifyemail` to receive notification messages whenever the script executes. 68 | * Specify your ESXi short hostname in variable `esxihost` to backup the ESXi server configuration file. These backup filenames are formed from the hostname and date in this format: _hostname-configBundle-date.tgz_. Here is an example from a recent backup on my ESXi server _frisco_, on which _brutus_ is a guest: 69 | 70 | ``` 71 | frisco-configBundle-20210806114840.tgz 72 | ``` 73 | Procedure: 74 | * Create backup of _/data/freenas-v1.db_ using the SQLite `.backup main` command with backup target _hostname-version-date.db_ in the configuration directory 75 | * Copy _/data/pwenc_secret_ to the configuration directory 76 | * Check integrity of the backup database with the SQLite `pragma integrity_check;` command 77 | * Copy the validated backup database to _freenas-v1.db_ in the configuration directory 78 | * Add _freenas-v1.db_ and _pwenc_secret_ to tar file _hostname-version-date.tar_ in the configuration directory 79 | * Optionally create ESXi configuration bundle in the configuration directory 80 | * Optionally send an email notification upon completion 81 | 82 | Note that each invocation of the script creates these files in the configuration directory: 83 | * _hostname-version-date.db_ : validated backup of configuration database _/data/freenas-v1.db_ 84 | * _hostname-version-date.tar_ : tar file containing the above configuration database along with the password secret seed encryption file _pwenc_secret_. 85 | * _freenas-v1.db_ : copy of the validated backup configuration database above; over-written each time the script is executed 86 | * _pwenc_secret_ : copy of _/data/pwenc_secret_, over-written each time the script is executed 87 | *** 88 | # save_config_enc.sh 89 | 90 | Saves your FreeNAS system configuration and password secret seed files to a dataset you specify, optionally sending you an email message containing these files in an encrypted tarball. 91 | 92 | **!!! Security Warning !!!** 93 | > The system configuration and password secret seed encryption file are sensitive information and should be stored on a dataset available only to system administrators! 94 | 95 | Supports the versions of FreeNAS which use an SQLite-based configuration file: these include FreeNAS 9.x-12.x, and probably earlier versions as well. 96 | 97 | The backup configuration filenames are formed from the hostname, complete FreeNAS version, and date, in this format: _hostname-freenas_version-date.db_. Here is an example from a recent backup on my server named _bandit_: 98 | 99 | ``` 100 | bandit-FreeNAS-11.0-RELEASE-a2dc21583-20170710234500.db 101 | ``` 102 | 103 | Edit this script and set variable `configdir` to specify the target dataset where you want the backup files copied. 104 | 105 | Optional feature: Specify your email address and create a passphrase file to receive an email message whenever it executes. The script will create an encrypted tarball containing the configuration file and password secret seed files, which it will include with the email message as a MIME-encoded attachment. 106 | 107 | To enable this feature you must: 108 | * Edit the script and specify your email address in variable 'mail' 109 | * Create a passphrase file. By default, the script will look for a passphrase in `/root/config_passphrase`, but you may use any file location you prefer. This is a simple text file with a single line containing the passphrase you wish to use for encrypting/decrypting the configuration tarball. This file should be owned by `root` and you should secure it by setting its permissions to 0600 (owner read/write). 110 | 111 | The attachment filename is formed from the hostname, complete FreeNAS version, and date, in this format: _hostname-freenas_version-date.tar.gz.enc_. Here is an example from a recent backup on my server named _bandit_: 112 | 113 | ``` 114 | bandit-FreeNAS-11.0-RELEASE-a2dc21583-20170710234500.tar.gz.enc 115 | ``` 116 | The script uses `tar` to store the configuration and password secret seed files in a gzipped tarball, which it encrypts by calling `openssl`, using the passphrase you specified above. For FreeNAS versions prior to 12.x, this is the command used to encrypt the tarball: 117 | 118 | `openssl enc -e -aes-256-cbc -md sha512 -salt -S "$(openssl rand -hex 4)" -pass file:[passphrase_file] -in [tarball] -out [encrypted_tarball]` 119 | 120 | To decrypt the email attachment, use this command on your FreeNAS system: 121 | 122 | `openssl enc -d -aes-256-cbc -md sha512 -pass file:[passphrase_file] -in [encrypted_file] -out [unencrypted_file]` 123 | 124 | For version 12.x of FreeNAS we add the new OpenSSL v1.1.1 options `-pbkdf2` and `-iter` thus: 125 | 126 | `openssl enc -e -aes-256-cbc -md sha512 -pbkdf2 -iter 128000 -salt -S "$(openssl rand -hex 8)" -pass file:[passphrase_file] -in [tarball] -out [encrypted_tarball]` 127 | 128 | To decrypt the email attachment, use this command on your FreeNAS system: 129 | 130 | `openssl enc -d -aes-256-cbc -md sha512 -pbkdf2 -iter 128000 -pass file:[passphrase_file] -in [encrypted_file] -out [unencrypted_file]` 131 | 132 | In the above commands: 133 | * `passphrase_file` is a file containing the same passphrase you configured on your FreeNAS server 134 | * `encrypted_file` is your locally-saved copy of the email attachment 135 | * `unencrypted_file` is the unencrypted contents of the email attachment 136 | *** 137 | # set_hdd_erc.sh 138 | 139 | Sets the Error Recovery Control (aka SCTERC or TLER) read and write values on your system's hard drives. What is this? There is a good discussion in the ["Checking for TLER, ERC, etc. support on a drive"](https://forums.freenas.org/index.php?threads/checking-for-tler-erc-etc-support-on-a-drive.27126/) thread on the FreeNAS forum, and you can find more gory details in [this FAQ](https://www.smartmontools.org/wiki/FAQ#WhatiserrorrecoverycontrolERCandwhyitisimportanttoenableitfortheSATAdisksinRAID) at the [smartmontools.org](https://www.smartmontools.org) website. This key quote from the FAQ sums up why you want to set this up on your FreeNAS servers: 140 | 141 | >"It is best for ERC to be "enabled" when in a RAID array to prevent the recovery time from a disk read or write error from exceeding the RAID implementation's timeout threshold. If a drive times out, the hard disk will need to be manually re-added to the array, requiring a re-build and re-synchronization of the hard disk. Limiting the drives recovery timeout helps for improved error handling in the hardware or software RAID environments." 142 | 143 | By default, the script sets both the read and write timeout value to 7 seconds. You can change either or both of these values to better suit your environment. 144 | 145 | Some hard drives retain these values when powered down, but some do not - including the HGST 7K4000 drives I use in one of my systems. For this reason, I configure my FreeNAS servers to run `set_hdd_src.sh` as a post-init startup script. 146 | *** 147 | # get_hdd_temp.sh 148 | 149 | Displays the current temperature of your system's CPU and drives. 150 | 151 | By default, the script uses `sysctl` to determine the number of CPU cores and report their temperatures. This reports a temperature for each core on systems equipped with modern multi-core CPUs. The optional IPMI support, if enabled, reports a single temperature for each socketed CPU. The latter result is probably more useful for monitoring CPU status. 152 | 153 | To enable IPMI support, edit the script and: 154 | * Set the `use_ipmi` variable to `1` 155 | * Specify the IPMI host's IP address or DNS-resolvable hostname in the `ipmihost` variable. 156 | * Specify the IPMI username in the `ipmiuser` variable. 157 | * Specify the IPMI password file location in the `ipmipwfile` variable. This is a simple text file containing the IPMI user's password on a single line. You should protect this file by setting its permissions to 0600. 158 | 159 | Drive output includes: the device ID, temperature (in Centigrade), capacity, serial number, and drive family/model. Here is sample output from one of my systems equipped with dual CPUs, using the IPMI feature and with serial numbers obfuscated: 160 | 161 | ``` 162 | === CPU (2) === 163 | CPU 1: [35C] 164 | CPU 2: [38C] 165 | 166 | === DRIVES === 167 | da1: 19C [8.58GB] SN9999999999999999 INTEL SSDSC2BA100G3L 168 | da2: 39C [4.00TB] SN9999999999999999 HGST Deskstar NAS (HGST HDN724040ALE640) 169 | da3: 36C [4.00TB] SN9999999999999999 HGST Deskstar NAS (HGST HDN724040ALE640) 170 | da4: 27C [240GB] SN9999999999999999 Intel 730 and DC S35x0/3610/3700 (INTEL SSDSC2BB240G4) 171 | da5: 27C [2.00TB] SN9999999999999999 Western Digital Green (WDC WD20EARX-00PASB0) 172 | da6: 28C [2.00TB] SN9999999999999999 Western Digital Red (WDC WD20EFRX-68EUZN0) 173 | da7: 19C [8.58GB] SN9999999999999999 INTEL SSDSC2BA100G3L 174 | da8: 31C [6.00TB] SN9999999999999999 Western Digital Black (WDC WD6001FZWX-00A2VA0) 175 | da9: 29C [2.00TB] SN9999999999999999 Western Digital Green (WDC WD20EARX-00PASB0) 176 | da10: 29C [2.00TB] SN9999999999999999 Western Digital Red (WDC WD20EFRX-68EUZN0) 177 | da11: 34C [4.00TB] SN9999999999999999 HGST HDN726040ALE614 178 | da12: 37C [4.00TB] SN9999999999999999 HGST HDN726040ALE614 179 | da13: 37C [4.00TB] SN9999999999999999 Western Digital Re (WDC WD4000FYYZ-01UL1B1) 180 | da14: 38C [4.00TB] SN9999999999999999 Western Digital Re (WDC WD4000FYYZ-01UL1B1) 181 | ``` 182 | (Thanks to P. Robar for his helpful suggestions with respect to `sysctl` usage and the `get_smart_drives()` function.) 183 | *** 184 | # get-system-temps.pl 185 | 186 | Displays the current temperature of your system's CPU and drives. 187 | 188 | This is a Perl version of the `get_cpu_temp.sh` script above. 189 | 190 | By default, the script uses `sysctl` to determine the number of CPU cores and report their temperatures. This reports a temperature for each core on systems equipped with modern multi-core CPUs. The optional IPMI support, if enabled, reports a single temperature for each socketed CPU. The latter result is probably more useful for monitoring CPU status. 191 | 192 | To enable IPMI support, edit the script and: 193 | * Set the `$useipmi` variable to `1` 194 | * Specify the IPMI host's IP address or DNS-resolvable hostname in the `$ipmihost` variable. 195 | * Specify the IPMI username in the `$ipmiuser` variable. 196 | * Specify the IPMI password file location in the `$ipmipwfile` variable. This is a simple text file containing the IPMI user's password on a single line. You should protect this file by setting its permissions to 0600. 197 | 198 | Drive output includes: the device ID, temperature (in Centigrade), capacity, drive type (HDD or SDD), serial number, drive model, and (when available) the model family. Here is sample output from one of my systems equipped with dual CPUs, using the IPMI feature and with serial numbers obfuscated: 199 | 200 | ``` 201 | ========== 202 | 203 | bandit.spearfoot.net (IPMI host: falcon.ipmi.spearfoot.net) 204 | 205 | === CPU (2) === 206 | CPU 1: 35C 207 | CPU 2: 39C 208 | 209 | === Drives === 210 | da1: 20C [ 8.58 GB SSD] SN999999999999999999 INTEL SSDSC2BA100G3L 211 | da2: 37C [ 4.00 TB HDD] SN999999999999999999 HGST HDN724040ALE640 (HGST Deskstar NAS) 212 | da3: 35C [ 4.00 TB HDD] SN999999999999999999 HGST HDN724040ALE640 (HGST Deskstar NAS) 213 | da4: 28C [ 240 GB SSD] SN999999999999999999 INTEL SSDSC2BB240G4 (Intel 730 and DC S35x0/3610/3700 Series SSDs) 214 | da5: 26C [ 2.00 TB HDD] SN999999999999999999 WDC WD20EARX-00PASB0 (Western Digital Green) 215 | da6: 28C [ 2.00 TB HDD] SN999999999999999999 WDC WD20EFRX-68EUZN0 (Western Digital Red) 216 | da7: 19C [ 8.58 GB SSD] SN999999999999999999 INTEL SSDSC2BA100G3L 217 | da8: 31C [ 6.00 TB HDD] SN999999999999999999 WDC WD6001FZWX-00A2VA0 (Western Digital Black) 218 | da9: 29C [ 2.00 TB HDD] SN999999999999999999 WDC WD20EARX-00PASB0 (Western Digital Green) 219 | da10: 28C [ 2.00 TB HDD] SN999999999999999999 WDC WD20EFRX-68EUZN0 (Western Digital Red) 220 | da11: 32C [ 4.00 TB HDD] SN999999999999999999 HGST HDN726040ALE614 221 | da12: 35C [ 4.00 TB HDD] SN999999999999999999 HGST HDN726040ALE614 222 | da13: 36C [ 4.00 TB HDD] SN999999999999999999 WDC WD4000FYYZ-01UL1B1 (Western Digital Re) 223 | da14: 37C [ 4.00 TB HDD] SN999999999999999999 WDC WD4000FYYZ-01UL1B1 (Western Digital Re) 224 | ``` 225 | 226 | 227 | --------------------------------------------------------------------------------