Repository: Spearfoot/FreeNAS-scripts Branch: master Commit: 06ccffb9710b Files: 9 Total size: 57.8 KB Directory structure: gitextract_3dasrt3n/ ├── README.md ├── get-system-temps.pl ├── get_hdd_temp.sh ├── save_config.sh ├── save_config_enc.sh ├── set_hdd_erc.sh ├── smart_report.sh ├── ups_report.sh └── zpool_report.sh ================================================ FILE CONTENTS ================================================ ================================================ FILE: README.md ================================================ # FreeNAS/TrueNAS Scripts Handy shell and Perl scripts for use on FreeNAS and TrueNAS servers 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. 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. ☺️ All of the Perl code is my own contribution. *** #### Operating System Compatibility Tested under: * TrueNAS 12.0 (FreeBSD 12.2) * FreeNAS 11.3 (FreeBSD 11.3-STABLE) * FreeNAS 11.2 (FreeBSD 11.2-STABLE) Earlier versions of FreeNAS were supported, but are no longer tested. *** # smart_report.sh 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. You will need to edit the script and enter your email address before using it. 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). Example: for a 3ware controller, edit the script to invoke SMARTCTL like this: ``` "${smartctl}" [options] -d 3ware,"${drive}" /dev/twa0 ``` ...instead of... ``` "${smartctl}" [options] -d /dev/"${drive}" ``` Refer to the SMARTCTL man page for addtional details, including support for other controller types. *** # zpool_report.sh Generates and emails you a status report about your system's pools. You will need to edit the script and enter your email address before using it. *** # ups_report.sh Generates and emails you a status report about your UPS. 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. *** # save_config.sh 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. **!!! Security Warning !!!** > The system configuration and password secret seed encryption file are sensitive information and should be stored on a dataset available only to system administrators! 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_: ``` brutus-FreeNAS-11.2-U8-06e1172340-20210806114838.tar brutus-FreeNAS-11.2-U8-06e1172340-20210806114838.db ``` Edit the script and set variable `configdir` to specify the configuration directory, a dataset where you want the backup files stored. Optional features: * Specify your email address in variable `notifyemail` to receive notification messages whenever the script executes. * 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: ``` frisco-configBundle-20210806114840.tgz ``` Procedure: * Create backup of _/data/freenas-v1.db_ using the SQLite `.backup main` command with backup target _hostname-version-date.db_ in the configuration directory * Copy _/data/pwenc_secret_ to the configuration directory * Check integrity of the backup database with the SQLite `pragma integrity_check;` command * Copy the validated backup database to _freenas-v1.db_ in the configuration directory * Add _freenas-v1.db_ and _pwenc_secret_ to tar file _hostname-version-date.tar_ in the configuration directory * Optionally create ESXi configuration bundle in the configuration directory * Optionally send an email notification upon completion Note that each invocation of the script creates these files in the configuration directory: * _hostname-version-date.db_ : validated backup of configuration database _/data/freenas-v1.db_ * _hostname-version-date.tar_ : tar file containing the above configuration database along with the password secret seed encryption file _pwenc_secret_. * _freenas-v1.db_ : copy of the validated backup configuration database above; over-written each time the script is executed * _pwenc_secret_ : copy of _/data/pwenc_secret_, over-written each time the script is executed *** # save_config_enc.sh 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. **!!! Security Warning !!!** > The system configuration and password secret seed encryption file are sensitive information and should be stored on a dataset available only to system administrators! 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. 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_: ``` bandit-FreeNAS-11.0-RELEASE-a2dc21583-20170710234500.db ``` Edit this script and set variable `configdir` to specify the target dataset where you want the backup files copied. 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. To enable this feature you must: * Edit the script and specify your email address in variable 'mail' * 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). 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_: ``` bandit-FreeNAS-11.0-RELEASE-a2dc21583-20170710234500.tar.gz.enc ``` 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: `openssl enc -e -aes-256-cbc -md sha512 -salt -S "$(openssl rand -hex 4)" -pass file:[passphrase_file] -in [tarball] -out [encrypted_tarball]` To decrypt the email attachment, use this command on your FreeNAS system: `openssl enc -d -aes-256-cbc -md sha512 -pass file:[passphrase_file] -in [encrypted_file] -out [unencrypted_file]` For version 12.x of FreeNAS we add the new OpenSSL v1.1.1 options `-pbkdf2` and `-iter` thus: `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]` To decrypt the email attachment, use this command on your FreeNAS system: `openssl enc -d -aes-256-cbc -md sha512 -pbkdf2 -iter 128000 -pass file:[passphrase_file] -in [encrypted_file] -out [unencrypted_file]` In the above commands: * `passphrase_file` is a file containing the same passphrase you configured on your FreeNAS server * `encrypted_file` is your locally-saved copy of the email attachment * `unencrypted_file` is the unencrypted contents of the email attachment *** # set_hdd_erc.sh 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: >"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." 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. 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. *** # get_hdd_temp.sh Displays the current temperature of your system's CPU and drives. 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. To enable IPMI support, edit the script and: * Set the `use_ipmi` variable to `1` * Specify the IPMI host's IP address or DNS-resolvable hostname in the `ipmihost` variable. * Specify the IPMI username in the `ipmiuser` variable. * 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. 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: ``` === CPU (2) === CPU 1: [35C] CPU 2: [38C] === DRIVES === da1: 19C [8.58GB] SN9999999999999999 INTEL SSDSC2BA100G3L da2: 39C [4.00TB] SN9999999999999999 HGST Deskstar NAS (HGST HDN724040ALE640) da3: 36C [4.00TB] SN9999999999999999 HGST Deskstar NAS (HGST HDN724040ALE640) da4: 27C [240GB] SN9999999999999999 Intel 730 and DC S35x0/3610/3700 (INTEL SSDSC2BB240G4) da5: 27C [2.00TB] SN9999999999999999 Western Digital Green (WDC WD20EARX-00PASB0) da6: 28C [2.00TB] SN9999999999999999 Western Digital Red (WDC WD20EFRX-68EUZN0) da7: 19C [8.58GB] SN9999999999999999 INTEL SSDSC2BA100G3L da8: 31C [6.00TB] SN9999999999999999 Western Digital Black (WDC WD6001FZWX-00A2VA0) da9: 29C [2.00TB] SN9999999999999999 Western Digital Green (WDC WD20EARX-00PASB0) da10: 29C [2.00TB] SN9999999999999999 Western Digital Red (WDC WD20EFRX-68EUZN0) da11: 34C [4.00TB] SN9999999999999999 HGST HDN726040ALE614 da12: 37C [4.00TB] SN9999999999999999 HGST HDN726040ALE614 da13: 37C [4.00TB] SN9999999999999999 Western Digital Re (WDC WD4000FYYZ-01UL1B1) da14: 38C [4.00TB] SN9999999999999999 Western Digital Re (WDC WD4000FYYZ-01UL1B1) ``` (Thanks to P. Robar for his helpful suggestions with respect to `sysctl` usage and the `get_smart_drives()` function.) *** # get-system-temps.pl Displays the current temperature of your system's CPU and drives. This is a Perl version of the `get_cpu_temp.sh` script above. 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. To enable IPMI support, edit the script and: * Set the `$useipmi` variable to `1` * Specify the IPMI host's IP address or DNS-resolvable hostname in the `$ipmihost` variable. * Specify the IPMI username in the `$ipmiuser` variable. * 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. 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: ``` ========== bandit.spearfoot.net (IPMI host: falcon.ipmi.spearfoot.net) === CPU (2) === CPU 1: 35C CPU 2: 39C === Drives === da1: 20C [ 8.58 GB SSD] SN999999999999999999 INTEL SSDSC2BA100G3L da2: 37C [ 4.00 TB HDD] SN999999999999999999 HGST HDN724040ALE640 (HGST Deskstar NAS) da3: 35C [ 4.00 TB HDD] SN999999999999999999 HGST HDN724040ALE640 (HGST Deskstar NAS) da4: 28C [ 240 GB SSD] SN999999999999999999 INTEL SSDSC2BB240G4 (Intel 730 and DC S35x0/3610/3700 Series SSDs) da5: 26C [ 2.00 TB HDD] SN999999999999999999 WDC WD20EARX-00PASB0 (Western Digital Green) da6: 28C [ 2.00 TB HDD] SN999999999999999999 WDC WD20EFRX-68EUZN0 (Western Digital Red) da7: 19C [ 8.58 GB SSD] SN999999999999999999 INTEL SSDSC2BA100G3L da8: 31C [ 6.00 TB HDD] SN999999999999999999 WDC WD6001FZWX-00A2VA0 (Western Digital Black) da9: 29C [ 2.00 TB HDD] SN999999999999999999 WDC WD20EARX-00PASB0 (Western Digital Green) da10: 28C [ 2.00 TB HDD] SN999999999999999999 WDC WD20EFRX-68EUZN0 (Western Digital Red) da11: 32C [ 4.00 TB HDD] SN999999999999999999 HGST HDN726040ALE614 da12: 35C [ 4.00 TB HDD] SN999999999999999999 HGST HDN726040ALE614 da13: 36C [ 4.00 TB HDD] SN999999999999999999 WDC WD4000FYYZ-01UL1B1 (Western Digital Re) da14: 37C [ 4.00 TB HDD] SN999999999999999999 WDC WD4000FYYZ-01UL1B1 (Western Digital Re) ``` ================================================ FILE: get-system-temps.pl ================================================ #!/usr/local/bin/perl ############################################################################### # # get-system-temps.pl # # Displays CPU and drive temperatures # # Drive information reported includes the device ID, temperature, capacity, type # (SDD or HDD), serial number, model, and, if available, the model family. # # Optionally uses IPMI to report CPU temperatures. Otherwise, these are pulled # from sysctl. IPMI is more accurate in that it reports the temperature of each # socketed CPU in the system, even on virtualized instances, whereas the CPU # temperatures typically aren't available from sysctl in this case. # # Requires the smartmontools, available at: https://www.smartmontools.org/ # # Keith Nash, July 2017 # ############################################################################### use strict; use warnings; # Get system's hostname: my $hostname = qx(hostname); chomp($hostname); # Full path to the smartctl program: my $smartctl = "/usr/local/sbin/smartctl"; # IPMI setup: # Toggle IPMI support on or off: # 1 = on: use IPMI # 0 = off: use sysctl instead of IPMI my $useipmi = 0; # IPMI username and password file. The password file is a text file with the # IPMI user's password on the first line. Be sure to set permissions to 0600 # on the password file. # # You may not need credentials on some systems. In this case, ignore these # variables and modify the ipmitool variable below to suit your environment, # removing the '-I lanplus' and user credential options (-U and -f) as needed. my $ipmiuser = "root"; my $ipmipwfile = "/root/ipmi_password"; # The IPMI host must be either an IP address or a DNS-resolvable hostname. If you # have multiple systems, leave the variable blank and edit the conditional below # to specify the IPMI host according to the host on which you are running the script: my $ipmihost = ""; if ($useipmi && $ipmihost eq "") { if ($hostname =~ /bandit/) { $ipmihost="falcon.ipmi.spearfoot.net" } elsif ($hostname =~ /boomer/) { $ipmihost="felix.ipmi.spearfoot.net" } elsif ($hostname =~ /bacon/) { $ipmihost="fritz.ipmi.spearfoot.net" } else { die "No IPMI host specified!\n" } } # Full path to ipmitool program, including options and credentials: my $ipmitool = "/usr/local/bin/ipmitool -I lanplus -H $ipmihost -U $ipmiuser -f $ipmipwfile"; main(); ############################################################################### # # main # ############################################################################### sub main { printf("==========\n\n"); if ($useipmi) { printf("%s (IPMI host: %s)\n\n",$hostname,$ipmihost); } else { printf("%s\n\n",$hostname); } display_cpu_temps(); display_drive_info(); } ############################################################################### # # display_cpu_temps # ############################################################################### sub display_cpu_temps { my $temp; my $cpucores=0; if ($useipmi) { $cpucores = qx($ipmitool sdr | grep -c -i "cpu.*temp"); } else { $cpucores = qx(sysctl -n hw.ncpu); } printf("=== CPU (%d) ===\n",$cpucores); if ($useipmi) { if ($cpucores > 1) { for (my $core=1; $core <= $cpucores; $core++) { $temp=qx($ipmitool sdr | grep -i "CPU$core Temp" | awk '{print \$4}'); chomp($temp); printf("CPU %2u: %3sC\n",$core,$temp); } } else { $temp=qx($ipmitool sdr | grep -i "CPU Temp" | awk '{print \$4}'); chomp($temp); printf("CPU %2u: %3sC\n",1,$temp); } } else { for (my $core=0; $core < $cpucores; $core++) { $temp = qx(sysctl -n dev.cpu.$core.temperature); $temp =~ s/[^\-[:digit:]\.]//g; chomp($temp); if ($temp <= 0) { printf("CPU %2u: -N/A-\n",$core); } else { printf("CPU %2u: %3sC\n",$core,$temp); } } } } ############################################################################### # # display_drive_info # ############################################################################### sub display_drive_info { my $drive_id; my $drive_model; my $drive_family; my $drive_serial; my $drive_capacity; my $drive_temp; my $drive_is_ssd; my $drive_family_display; printf("\n=== Drives ===\n"); my @smart_drive_list = get_smart_drives(); foreach my $drive (@smart_drive_list) { ($drive_model, $drive_family, $drive_serial, $drive_capacity, $drive_temp, $drive_is_ssd) = get_drive_info($drive); if ($drive =~ /\/dev\/(.*)/) { $drive_id = $1; } else { $drive_id = $drive; } if ($drive_family eq "") { $drive_family_display = ""; } else { $drive_family_display = "(" . $drive_family . ")"; } printf("%6.6s: %3uC [%8.8s %s] %-20.20s %s %s\n", $drive_id, $drive_temp, $drive_capacity, $drive_is_ssd ? "SSD" : "HDD", $drive_serial, $drive_model, $drive_family_display); } } ############################################################################### # # get_smart_drives # ############################################################################### sub get_smart_drives { my @retval = (); my @drive_list = split(" ", qx($smartctl --scan | awk '{print \$1}')); foreach my $drive (@drive_list) { my $smart_enabled = qx($smartctl -i $drive | grep "SMART support is: Enabled" | awk '{print \$4}'); chomp($smart_enabled); if ($smart_enabled eq "Enabled") { push @retval, $drive; } } return @retval; } ############################################################################### # # get_drive_info # ############################################################################### sub get_drive_info { my $drive = shift; my $smart_data = qx($smartctl -a $drive); my $drive_model = ""; my $drive_family = ""; my $drive_serial = ""; my $drive_capacity = ""; my $drive_temp = 0; my $drive_is_ssd = 0; $drive_temp = get_drive_temp($drive); # Serial number if ($smart_data =~ /^Serial Number:\s*(.*)\s/m) { $drive_serial = $1; } # Device model if ($smart_data =~ /^Device Model:\s*(.*)\s/m) { $drive_model = $1; } # Model family if ($smart_data =~ /^Model Family:\s*(.*)\s/m) { $drive_family = $1; } # User capacity if ($smart_data =~ /^User Capacity:.*\[(.*)\]\s/m) { $drive_capacity = $1; } # Determine if drive is a SSD if ($smart_data =~ /^Rotation Rate:[ ]*Solid State Device/m) { $drive_is_ssd = 1; } elsif ($smart_data =~ /^[ 0-9]{3} Unknown_SSD_Attribute/m) { $drive_is_ssd = 1; } elsif ($smart_data =~ /^[ 0-9]{3} Wear_Leveling_Count/m) { $drive_is_ssd = 1; } elsif ($smart_data =~ /^[ 0-9]{3} Media_Wearout_Indicator/m) { $drive_is_ssd = 1; } elsif ($drive_family =~ /SSD/) { # Model family indicates SSD $drive_is_ssd = 1; } return ($drive_model, $drive_family, $drive_serial, $drive_capacity, $drive_temp, $drive_is_ssd); } ############################################################################### # # get_drive_temp # ############################################################################### sub get_drive_temp { my $drive = shift; my $retval = 0; $retval = qx($smartctl -A $drive | grep "194 Temperature" | awk '{print \$10}'); if (!$retval) { $retval = qx($smartctl -A $drive | grep "190 Airflow_Temperature" | awk '{print \$10}'); } return $retval; } ================================================ FILE: get_hdd_temp.sh ================================================ #!/bin/sh # Display current temperature of CPU(s) and all SMART-enabled drives # Optionally uses IPMI to report temperatures of the system CPU(s) # # If IPMI is disabled (see 'use_ipmi' below) then the script uses # sysctl to report the CPU temperatures. To use IPMI, you must # provide the IPMI host, user name, and user password file. # Full path to 'smartctl' program: smartctl=/usr/local/sbin/smartctl # IPMI support: set to a postive value to use IPMI for CPU temp # reporting, set to zero to disable IPMI and use 'sysctl' instead: use_ipmi=0 # IP address or DNS-resolvable hostname of IPMI server: ipmihost=192.168.1.x # IPMI username: ipmiuser=root # IPMI password file. This is a file containing the IPMI user's password # on a single line and should have 0600 permissions: ipmipwfile=/root/ipmi_password # Full path to 'ipmitool' program: ipmitool=/usr/local/bin/ipmitool # We need a list of the SMART-enabled drives on the system. Choose one of these # three methods to provide the list. Comment out the two unused sections of code. # 1. A string constant; just key in the devices you want to report on here: #drives="da1 da2 da3 da4 da5 da6 da7 da8 ada0" # 2. A systcl-based technique suggested on the FreeNAS forum: #drives=$(for drive in $(sysctl -n kern.disks); do \ #if [ "$(/usr/local/sbin/smartctl -i /dev/${drive} | grep "SMART support is: Enabled" | awk '{print $3}')" ] #then printf ${drive}" "; fi done | awk '{for (i=NF; i!=0 ; i--) print $i }') # 3. A smartctl-based function: get_smart_drives() { gs_smartdrives="" gs_drives=$("$smartctl" --scan | awk '{print $1}') for gs_drive in $gs_drives; do gs_smart_flag=$("$smartctl" -i "$gs_drive" | egrep "SMART support is:[[:blank:]]+Enabled" | awk '{print $4}') if [ "$gs_smart_flag" = "Enabled" ]; then gs_smartdrives="$gs_smartdrives $gs_drive" fi done echo "$gs_smartdrives" } drives=$(get_smart_drives) # end of method 3. ############################# # CPU temperatures: ############################# if [ "$use_ipmi" -eq 0 ]; then cpucores=$(sysctl -n hw.ncpu) printf '=== CPU (%s) ===\n' "$cpucores" cpucores=$((cpucores - 1)) for core in $(seq 0 $cpucores); do temp=$(sysctl -n dev.cpu."$core".temperature|sed 's/\..*$//g') if [ "$temp" -lt 0 ]; then temp="--n/a--" else temp="${temp}C" fi printf 'CPU %2.2s: %5s\n' "$core" "$temp" done echo "" else cpucores=$("$ipmitool" -I lanplus -H "$ipmihost" -U "$ipmiuser" -f "$ipmipwfile" sdr elist all | grep -c -i "cpu.*temp") printf '=== CPU (%s) ===\n' "$cpucores" if [ "$cpucores" -eq 1 ]; then temp=$("$ipmitool" -I lanplus -H "$ipmihost" -U "$ipmiuser" -f "$ipmipwfile" sdr elist all | grep "CPU Temp" | awk '{print $10}') if [ "$temp" -lt 0 ]; then temp="-n/a-" else temp="${temp}C" fi printf 'CPU %2s: %5s\n' "$core" "$temp" else for core in $(seq 1 "$cpucores"); do temp=$("$ipmitool" -I lanplus -H "$ipmihost" -U "$ipmiuser" -f "$ipmipwfile" sdr elist all | grep "CPU${core} Temp" | awk '{print $10}') if [ "$temp" -lt 0 ]; then temp="-n/a-" else temp="${temp}C" fi printf 'CPU %2s: [%s]\n' "$core" "$temp" done fi echo "" fi ############################# # Drive temperatures: ############################# echo "=== DRIVES ===" for drive in $drives; do serial=$("$smartctl" -i "$drive" | grep -i "serial number" | awk '{print $NF}') capacity=$("$smartctl" -i "$drive" | grep "User Capacity" | awk '{print $5 $6}') temp=$("$smartctl" -A "$drive" | grep "194 Temperature" | awk '{print $10}') if [ -z "$temp" ]; then temp=$("$smartctl" -A "$drive" | grep "190 Temperature_Case" | awk '{print $10}') fi if [ -z "$temp" ]; then temp=$("$smartctl" -A "$drive" | grep "190 Airflow_Temperature" | awk '{print $10}') fi if [ -z "$temp" ]; then temp=$("$smartctl" -A "$drive" | grep "Current Drive Temperature" | awk '{print $4}') fi if [ -z "$temp" ]; then temp="-n/a-" else temp="${temp}C" fi dfamily=$("$smartctl" -i "$drive" | grep "Model Family" | awk '{print $3, $4, $5, $6, $7}' | sed -e 's/[[:space:]]*$//') dmodel=$("$smartctl" -i "$drive" | grep "Device Model" | awk '{print $3, $4, $5, $6, $7}' | sed -e 's/[[:space:]]*$//') if [ -z "$dfamily" ]; then dinfo="$dmodel" else dinfo="$dfamily ($dmodel)" fi if [ -z "$dfamily" ]; then vendor=$("$smartctl" -i "$drive" | grep "Vendor:" | awk '{print $NF}') product=$("$smartctl" -i "$drive" | grep "Product:" | awk '{print $NF}') revision=$("$smartctl" -i "$drive" | grep "Revision:" | awk '{print $NF}') dinfo="$vendor $product $revision" fi printf '%6.6s: %5s %-8s %-20.20s %s\n' "$(basename "$drive")" "$temp" "$capacity" "$serial" "$dinfo" done ================================================ FILE: save_config.sh ================================================ #!/bin/sh ##### # Backup the TrueNAS/FreeNAS configuration database and password secret encryption files ##### # REQUIRED: Specify the dataset on your system where you want the configuration files copied. # Don't include the trailing slash. # Example: configdir="/mnt/tank/sysadmin/config" configdir="" # Remove this code once you've defined configdir above... :-) if [ -z "${configdir}" ]; then echo "Edit script and specify the target directory ('configdir') before using $0" exit 2 fi # Optional: Set non-zero 'do_tar' flag to have both files stored in a tarball as typically # needed when restoring a configuration. do_tar=1 # Optional: specify your email address here if you want to receive a notification message. notifyemail="" # Optional: specify the short name of your ESXi host if you are running FreeNAS # as a VM and you want to back up the ESXi host's configuration esxihost="" # Get the date and version of TrueNAS/FreeNAS: rundate=$(date) osvers=$(grep -i truenas /etc/version) if [ -z "${osvers}" ]; then osvers=$(grep -i freenas /etc/version) if [ -z "${osvers}" ]; then osvers="UNKNOWN" else osvers="FreeNAS" fi else osvers="TrueNAS" fi # Form a unique, timestamped filename for the backup configuration database and tarball P1=$(hostname -s) P2=$(< /etc/version sed -e 's/)//;s/(//;s/ /-/' | tr -d '\n') P3=$(date +%Y%m%d%H%M%S) fnconfigdest_base="$P1"-"$P2"-"$P3" fnconfigdestdb="${configdir}"/"${fnconfigdest_base}".db fnconfigtarball="${configdir}"/"${fnconfigdest_base}".tar # Copy the source database and password encryption secret key to the destination: echo "Backup ${osvers} configuration database file: ${fnconfigdestdb}" cp -f /data/pwenc_secret "$configdir" /usr/local/bin/sqlite3 /data/freenas-v1.db ".backup main '${fnconfigdestdb}'" l_status=$? # Validate the configuration file and create tarball: if [ $l_status -eq 0 ]; then dbstatus=$(sqlite3 "$fnconfigdestdb" "pragma integrity_check;") printf 'sqlite3 status: [%s]\n' "${dbstatus}" if [ "${dbstatus}" = "ok" ]; then l_status=0 if [ $do_tar -ne 0 ]; then # Save the config DB w/ its original name in the tarball -- makes restoring them easier: cp -f "${fnconfigdestdb}" "${configdir}"/freenas-v1.db tar -cvf "${fnconfigtarball}" -C "${configdir}" freenas-v1.db pwenc_secret l_status=$? printf 'tar status: [%s]\n' "${l_status}" fi else l_status=1 fi fi if [ $l_status -eq 0 ]; then echo "Success backing up configuration files to directory ${configdir}" else echo "Error backing up configuration files to directory ${configdir}" fi l_status=$? # Backup the VMware ESXi host configuration: if [ -n "${esxihost}" ]; then esxihostname=$(ssh root@"${esxihost}" hostname) esxiversion=$(ssh root@"${esxihost}" uname -a | sed -e "s|VMkernel ||;s|$esxihostname ||") esxiconfig_url=$(ssh root@"${esxihost}" vim-cmd hostsvc/firmware/backup_config | awk '{print $7}' | sed -e "s|*|$esxihostname|") esxiconfig_date=$(date +%Y%m%d%H%M%S) esxiconfig_file="${configdir}"/"${esxihost}"-configBundle-"${esxiconfig_date}".tgz echo "Downloading $esxiconfig_url to $esxiconfig_file" wget --no-check-certificate --output-document="${esxiconfig_file}" "${esxiconfig_url}" fi # Send email notification if indicated: if [ -n "${notifyemail}" ]; then freenashostuc=$(hostname -s | tr '[:lower:]' '[:upper:]') freenashostname=$(hostname) freenasversion=$(< /etc/version sed -e 's/)//;s/(//;s/ /-/' | tr -d '\n') boundary="===== MIME boundary; ${osvers} server ${freenashostname} =====" logfile="/tmp/save_config.tmp" if [ $l_status -eq 0 ]; then subject="${osvers} configuration saved on server ${freenashostuc}" else subject="${osvers} configuration backup failed on server ${freenashostuc}" fi printf "%s\n" "To: ${notifyemail} Subject: ${subject} Mime-Version: 1.0 Content-Type: multipart/mixed; boundary=\"$boundary\" --${boundary} Content-Type: text/html; charset=\"US-ASCII\" Content-Transfer-Encoding: 7bit Content-Disposition: inline
" > ${logfile}
(
if [ $l_status -eq 0 ]; then
echo "Configuration file saved successfully on ${rundate}"
else
echo "Configuration backup failed with status=${l_status} on ${rundate}"
fi
echo ""
echo "--- ${osvers} ---"
echo "Server: ${freenashostname}"
echo "Version: ${freenasversion}"
echo "Files:"
echo " ${fnconfigdestdb}"
echo " ${configdir}/pwenc_secret"
if [ "$do_tar" -ne 0 ]; then
echo " ${fnconfigtarball}"
fi
if [ -n "${esxihost}" ]; then
echo ""
echo "--- ESXi ---"
echo "Server: ${esxihostname}"
echo "Version: ${esxiversion}"
echo "File: ${esxiconfig_file}"
fi
) >> ${logfile}
printf "%s\n" "
--${boundary}--" >> ${logfile}
sendmail -t -oi < ${logfile}
rm ${logfile}
fi
================================================
FILE: save_config_enc.sh
================================================
#!/bin/sh
#################################################
# Backup FreeNAS configuration files
#
# Copies the FreeNAS sqlite3 configuration and password secret
# seed files to the location you specify in the 'configdir'
# variable below.
#
# OPTIONAL:
#
# By specifying your email address in the 'email' variable, you may choose to
# have the configuration file emailed to you in an encrypted tarball.
#
#################################################
rundate=$(date)
# Optional: specify your email address here if you want to the script to email
# you the configuration file in an encrypted tarball.
#
# Leave the email address blank to simply copy the configuration file to the
# destination you specify with the 'configdir' setting below.
email=""
# Specify the dataset on your system where you want the configuration files copied.
# Don't include the trailing slash.
# Example: configdir=/mnt/tank/sysadmin/config
configdir=""
# OpenSSL encryption passphrase file. Enter the passphrase on the the first line in
# the file. This file should have 0600 permissions.
enc_passphrasefile=/root/config_passphrase
# FreeNAS hostname:
freenashost=$(hostname -s)
# FreeBSD version:
fbsd_relver=$(uname -K)
# MIME boundary
mime_boundary="==>>> MIME boundary; FreeNAS server [${freenashost}] <<<=="
#################################################
# Append file attachment to current email message
#################################################
append_file()
{
l_mimetype=""
if [ -f "$1" ]; then
l_mimetype=$(file --mime-type "$1" | sed 's/.*: //')
printf '%s\n' "--${mime_boundary}
Content-Type: $l_mimetype
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename=\"$(basename "$1")\"
"
base64 "$1"
echo
fi
}
#################################################
# Backup the FreeNAS configuration file
#################################################
fnconfigdest_version=$(< /etc/version sed -e 's/)//;s/(//;s/ /-/' | tr -d '\n')
fnconfigdest_date=$(date +%Y%m%d%H%M%S)
fnconfigdest_base="$freenashost"-"$fnconfigdest_version"-"$fnconfigdest_date".db
fnconfigdest="$configdir"/"$fnconfigdest_base"
fnconfigtarball=./"$freenashost"-"$fnconfigdest_version"-"$fnconfigdest_date".tar.gz
fnconfigtarballenc=./"$freenashost"-"$fnconfigdest_version"-"$fnconfigdest_date".tar.gz.enc
echo "Backup configuration database file: $fnconfigdest"
# Copy the source database and password encryption secret seed file to the destination:
/usr/local/bin/sqlite3 /data/freenas-v1.db ".backup main '${fnconfigdest}'"
l_status=$?
cp -f /data/pwenc_secret "$configdir"
if [ -z "$email" ]; then
# No email message requested, show status and exit:
echo "Configuration file copied with status ${l_status}"
exit $l_status
fi
#########################################################
# Send email message with encrypted config files attached
#########################################################
fnconfigtarball=./"$freenashost"-"$fnconfigdest_version"-"$fnconfigdest_date".tar.gz
fnconfigtarballenc=./"$freenashost"-"$fnconfigdest_version"-"$fnconfigdest_date".tar.gz.enc
# Validate the configuration file and create tarball:
if [ $l_status -eq 0 ]; then
dbstatus=$(sqlite3 "$fnconfigdest" "pragma integrity_check;")
printf 'sqlite3 status: [%s]\n' "$dbstatus"
if [ "$dbstatus" = "ok" ]; then
tar -czvf "$fnconfigtarball" -C "$configdir" "$fnconfigdest_base" pwenc_secret
l_status=$?
printf 'tar status: [%s]\n' "$l_status"
else
l_status=1
fi
if [ $l_status -eq 0 ]; then
if [ "$fbsd_relver" -ge 1200000 ]; then
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"
else
openssl enc -e -aes-256-cbc -md sha512 -salt -S "$(openssl rand -hex 4)" -pass file:"$enc_passphrasefile" -in "$fnconfigtarball" -out "$fnconfigtarballenc"
fi
l_status=$?
printf 'openssl status: [%s]\n' "$l_status"
fi
fi
freenashostuc=$(hostname -s | tr '[:lower:]' '[:upper:]')
freenashostname=$(hostname)
freenasversion=$(cat /etc/version)
if [ $l_status -eq 0 ]; then
subject="FreeNAS configuration saved on server ${freenashostuc}"
savestatus="FreeNAS configuration file saved successfully on ${rundate}"
else
subject="FreeNAS configuration backup failed on server ${freenashostuc}"
savestatus="FreeNAS configuration backup failed with status=${l_status} on ${rundate}"
fi
logfile="/tmp/save_config_enc.tmp"
{
printf '%s\n' "From: root
To: ${email}
Subject: ${subject}
Mime-Version: 1.0
Content-Type: multipart/mixed; boundary=\"$mime_boundary\"
--${mime_boundary}
Content-Type: text/plain; charset=\"US-ASCII\"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline
${savestatus}
Server: ${freenashostname}
Version: ${freenasversion}
File: ${fnconfigdest}
"
if [ $l_status -eq 0 ]; then
append_file "$fnconfigtarballenc"
fi
# print last boundary with closing --
printf '%s\n' "--${mime_boundary}--"
} > "$logfile"
sendmail -t -oi < "$logfile"
rm "$logfile"
rm "$fnconfigtarball"
rm "$fnconfigtarballenc"
================================================
FILE: set_hdd_erc.sh
================================================
#!/bin/sh
# https://www.smartmontools.org/wiki/FAQ#WhatiserrorrecoverycontrolERCandwhyitisimportanttoenableitfortheSATAdisksinRAID
# ERC timeout values, in tenths of a second. The defaults below are 7 seconds for both reads and writes:
readsetting=70
writesetting=70
# We need a list of the SMART-enabled drives on the system. Choose one of these
# three methods to provide the list. Comment out the two unused sections of code.
# 1. A string constant; just key in the devices you want to report on here:
#drives="da1 da2 da3 da4 da5 da6 da7 da8 ada0"
# 2. A systcl-based technique suggested on the FreeNAS forum:
#drives=$(for drive in $(sysctl -n kern.disks); do \
#if [ "$(/usr/local/sbin/smartctl -i /dev/${drive} | grep "SMART support is: Enabled" | awk '{print $3}')" ]
#then printf ${drive}" "; fi done | awk '{for (i=NF; i!=0 ; i--) print $i }')
# 3. A smartctl-based function:
get_smart_drives()
{
gs_drives=$(/usr/local/sbin/smartctl --scan | grep "dev" | awk '{print $1}' | sed -e 's/\/dev\///' | tr '\n' ' ')
gs_smartdrives=""
for gs_drive in $gs_drives; do
gs_smart_flag=$(/usr/local/sbin/smartctl -i /dev/"$gs_drive" | grep "SMART support is: Enabled" | awk '{print $4}')
if [ "$gs_smart_flag" = "Enabled" ]; then
gs_smartdrives=$gs_smartdrives" "${gs_drive}
fi
done
eval "$1=\$gs_smartdrives"
}
drives=""
get_smart_drives drives
# end of method 3.
set_erc()
{
echo "Drive: /dev/$1"
/usr/local/sbin/smartctl -q silent -l scterc,"${readsetting}","${writesetting}" /dev/"$1"
/usr/local/sbin/smartctl -l scterc /dev/"$1" | grep "SCT\|Write\|Read"
}
for drive in $drives; do
set_erc "$drive"
done
================================================
FILE: smart_report.sh
================================================
#!/bin/sh
### Parameters ###
# Specify your email address here:
email=""
# Full path to 'smartctl' program:
smartctl=/usr/local/sbin/smartctl
freenashost=$(hostname -s | tr '[:lower:]' '[:upper:]')
boundary="===== MIME boundary; FreeNAS server ${freenashost} ====="
logfile="smart_report.tmp"
subject="SMART Status Report for ${freenashost}"
tempWarn=40
tempCrit=45
sectorsCrit=10
testAgeWarn=1
warnSymbol="?"
critSymbol="!"
Drive_count=0
SATA_count=0
SAS_count=0
Drive_list=""
SATA_list=""
SAS_list=""
# Get list of SMART-enabled drives
get_smart_drives()
{
gs_drives=$("$smartctl" --scan | awk '{print $1}')
for gs_drive in $gs_drives; do
gs_smart_flag=$("$smartctl" -i "$gs_drive" | grep -E "SMART support is:[[:blank:]]+Enabled" | awk '{print $4}')
if [ "$gs_smart_flag" = "Enabled" ]; then
Drive_list="$Drive_list $gs_drive"
Drive_count=$((Drive_count + 1))
fi
done
}
# Get list of SATA disks, including older drives that only report an ATA version
get_sata_drives()
{
for drive in $Drive_list; do
lFound=0
gsata_smart_flag=$("$smartctl" -i "$drive" | grep -E "SATA Version is:[[:blank:]]" | awk '{print $4}')
if [ "$gsata_smart_flag" = "SATA" ]; then
lFound=$((lFound + 1))
else
gsata_smart_flag=$("$smartctl" -i "$drive" | grep -E "ATA Version is:[[:blank:]]" | awk '{print $1}')
if [ "$gsata_smart_flag" = "ATA" ]; then
lFound=$((lFound + 1))
fi
fi
if [ $lFound -gt 0 ]; then
SATA_list="$SATA_list $drive"
SATA_count=$((SATA_count + 1))
fi
done
}
# Get list of SAS disks
get_sas_drives()
{
for drive in $Drive_list; do
gsas_smart_flag=$("$smartctl" -i "$drive" | grep -E "Transport protocol:[[:blank:]]+SAS" | awk '{print $3}')
if [ "$gsas_smart_flag" = "SAS" ]; then
SAS_list="$SAS_list $drive"
SAS_count=$((SAS_count + 1))
fi
done
}
### Fetch drive lists ###
get_smart_drives
get_sata_drives
get_sas_drives
### Set email headers ###
printf "%s\n" "To: ${email}
Subject: ${subject}
Mime-Version: 1.0
Content-Type: multipart/mixed; boundary=\"$boundary\"
--${boundary}
Content-Type: text/html; charset=\"US-ASCII\"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline
" > ${logfile}
if [ $Drive_count -eq 0 ]; then
echo "##### No SMART-enabled disks found on this system #####" >> "$logfile"
fi
###### Summary for SATA drives ######
if [ $SATA_count -gt 0 ]; then
(
echo "########## SMART status report summary for all SATA drives on server ${freenashost} ##########"
echo ""
echo "+-------+------------------------+----+------+-----+-----+-------+-------+--------+------+----------+------+-----------+----+"
echo "|Device |Serial |Temp| Power|Start|Spin |ReAlloc|Current|Offline |Seek |Total |High | Command|Last|"
echo "| |Number | | On |Stop |Retry|Sectors|Pending|Uncorrec|Errors|Seeks |Fly | Timeout|Test|"
echo "| | | | Hours|Count|Count| |Sectors|Sectors | | |Writes| Count |Age |"
echo "+-------+------------------------+----+------+-----+-----+-------+-------+--------+------+----------+------+-----------+----+"
) >> "$logfile"
###### Detail information for each SATA drive ######
for drive in $SATA_list; do
(
devid=$(basename "$drive")
lastTestHours=$("$smartctl" -l selftest "$drive" | grep "# 1" | awk '{print $9}')
"$smartctl" -A -i -v 7,hex48 "$drive" | \
awk -v device="$devid" -v tempWarn="$tempWarn" -v tempCrit="$tempCrit" -v sectorsCrit="$sectorsCrit" \
-v testAgeWarn="$testAgeWarn" -v warnSymbol="$warnSymbol" -v critSymbol="$critSymbol" \
-v lastTestHours="$lastTestHours" '
/Serial Number:/{serial=$3}
/190 Airflow_Temperature/{temp=$10}
/190 Temperature_Case/{temp=$10}
/194 Temperature/{temp=$10}
/Power_On_Hours/{split($10,a,"+");sub(/h/,"",a[1]);onHours=a[1];}
/Power_Cycle_Count/{startStop=$10}
/Start_Stop_Count/{startStop=$10}
/Spin_Retry_Count/{spinRetry=$10}
/Reallocated_Sector/{reAlloc=$10}
/Current_Pending_Sector/{pending=$10}
/Offline_Uncorrectable/{offlineUnc=$10}
/Seek_Error_Rate/{seekErrors=("0x" substr($10,3,4));totalSeeks=("0x" substr($10,7))}
/High_Fly_Writes/{hiFlyWr=$10}
/Command_Timeout/{cmdTimeout=$10}
END {
testAge=sprintf("%.0f", (onHours - lastTestHours) / 24);
if (temp > tempCrit || reAlloc > sectorsCrit || pending > sectorsCrit || offlineUnc > sectorsCrit)
device=device " " critSymbol;
else if (temp > tempWarn || reAlloc > 0 || pending > 0 || offlineUnc > 0 || testAge > testAgeWarn)
device=device " " warnSymbol;
seekErrors=sprintf("%d", seekErrors);
totalSeeks=sprintf("%d", totalSeeks);
if (totalSeeks == "0") {
seekErrors="N/A";
totalSeeks="N/A";
}
if (temp > tempWarn || temp > tempCrit) temp=temp"*"
if (reAlloc > 0 || reAlloc > sectorsCrit) reAlloc=reAlloc"*"
if (pending > 0 || pending > sectorsCrit) pending=pending"*"
if (offlineUnc > 0 || offlineUnc > sectorsCrit) offlineUnc=offlineUnc"*"
if (testAge > testAgeWarn) testAge=testAge"*"
if (hiFlyWr == "") hiFlyWr="N/A";
if (cmdTimeout == "") cmdTimeout="N/A";
printf "|%-7s|%-24s|%-4s|%6s|%5s|%5s|%7s|%7s|%8s|%6s|%10s|%6s|%11s|%4s|\n",
device, serial, temp, onHours, startStop, spinRetry, reAlloc, pending, offlineUnc,
seekErrors, totalSeeks, hiFlyWr, cmdTimeout, testAge;
}'
) >> "$logfile"
done
(
echo "+-------+------------------------+----+------+-----+-----+-------+-------+--------+------+----------+------+-----------+----+"
) >> "$logfile"
fi
###### Summary for SAS drives ######
if [ $SAS_count -gt 0 ]; then
(
if [ $SATA_count -gt 0 ]; then
echo ""
fi
echo "########## SMART status report summary for all SAS drives on server ${freenashost} ##########"
echo ""
echo "+-------+------------------------+----+------+-----+------+------+------+------+------+------+----+"
echo "|Device |Serial |Temp| Power|Start|Load |Defect|Uncorr|Uncorr|Uncorr|Non |Last|"
echo "| |Number | | On |Stop |Unload|List |Read |Write |Verify|Medium|Test|"
echo "| | | | Hours|Count|Count |Elems |Errors|Errors|Errors|Errors|Age |"
echo "+-------+------------------------+----+------+-----+------+------+------+------+------+------+----+"
) >> "$logfile"
###### Detail information for each SAS drive ######
for drive in $SAS_list; do
(
devid=$(basename "$drive")
lastTestHours=$("$smartctl" -l selftest "$drive" | grep "# 1" | awk '{print $7}')
"$smartctl" -x "$drive" | \
awk -v device="$devid" -v tempWarn="$tempWarn" -v tempCrit="$tempCrit" \
-v warnSymbol="$warnSymbol" -v critSymbol="$critSymbol" \
-v lastTestHours="$lastTestHours" -v testAgeWarn="$testAgeWarn" '
/Serial number:/{serial=$3}
/Current Drive Temperature:/{temp=$4}
/start-stop cycles:/{startStop=$4}
/load-unload cycles:/{loadUnload=$4}
/grown defect list:/{defectList=$6}
/read:/{readErrors=$8}
/write:/{writeErrors=$8}
/verify:/{verifyErrors=$8}
/Non-medium error count:/{nonMediumErrors=$4}
/Accumulated power on time/{split($6,a,":");sub(/h/,"",a[1]);onHours=a[1];}
END {
testAge=sprintf("%.0f", (onHours - lastTestHours) / 24);
if (temp > tempCrit)
device=device " " critSymbol;
else if (temp > tempWarn || testAge > testAgeWarn)
device=device " " warnSymbol;
if (testAge > testAgeWarn) testAge=testAge"*"
if (defectList > 0) defectList=defectList"*"
printf "|%-7s|%-24s| %3s|%6s|%5s|%6s|%6s|%6s|%6s|%6s|%6s|%4s|\n",
device, serial, temp, onHours, startStop, loadUnload, defectList, \
readErrors, writeErrors, verifyErrors, nonMediumErrors,testAge;
}'
) >> "$logfile"
done
(
echo "+-------+------------------------+----+------+-----+------+------+------+------+------+------+----+"
) >> "$logfile"
fi
if [ $SATA_count -gt 0 ] || [ $SAS_count -gt 0 ]; then
###### Emit SATA drive information ######
for drive in $SATA_list; do
vendor=$("$smartctl" -i "$drive" | grep "Vendor:" | awk '{print $NF}')
if [ -z "$vendor" ]; then
dfamily=$("$smartctl" -i "$drive" | grep "Model Family" | awk '{print $3, $4, $5, $6, $7}' | sed -e 's/[[:space:]]*$//')
dmodel=$("$smartctl" -i "$drive" | grep "Device Model" | awk '{print $3, $4, $5, $6, $7}' | sed -e 's/[[:space:]]*$//')
if [ -z "$dfamily" ]; then
dinfo=$dmodel
else
dinfo="$dfamily ($dmodel)"
fi
else
product=$("$smartctl" -i "$drive" | grep "Product:" | awk '{print $NF}')
revision=$("$smartctl" -i "$drive" | grep "Revision:" | awk '{print $NF}')
dinfo="$vendor $product $revision"
fi
serial=$("$smartctl" -i "$drive" | grep "Serial Number" | awk '{print $3}')
(
echo ""
echo "########## SATA drive $drive Serial: $serial"
echo "########## ${dinfo}"
"$smartctl" -n never -H -A -l error "$drive"
"$smartctl" -n never -l selftest "$drive" | grep "# 1 \\|Num" | cut -c6-
) >> "$logfile"
done
###### Emit SAS drive information ######
for drive in $SAS_list; do
devid=$(basename "$drive")
brand=$("$smartctl" -i "$drive" | grep "Product" | sed "s/^.* //")
serial=$("$smartctl" -i "$drive" | grep "Serial number" | sed "s/^.* //")
(
echo ""
echo "########## SMART status for SAS drive $drive $serial (${brand}) ##########"
"$smartctl" -n never -H -A -l error "$drive"
"$smartctl" -n never -l selftest "$drive" | grep "# 1 \\|Num" | cut -c6-
) >> "$logfile"
done
fi
sed -i '' -e '/smartctl 7.*/d' "$logfile"
sed -i '' -e '/smartctl 6.*/d' "$logfile"
sed -i '' -e '/smartctl 5.*/d' "$logfile"
sed -i '' -e '/smartctl 4.*/d' "$logfile"
sed -i '' -e '/Copyright/d' "$logfile"
sed -i '' -e '/=== START OF READ/d' "$logfile"
sed -i '' -e '/SMART Attributes Data/d' "$logfile"
sed -i '' -e '/Vendor Specific SMART/d' "$logfile"
sed -i '' -e '/SMART Error Log Version/d' "$logfile"
printf "%s\n" "
--${boundary}--" >> ${logfile}
### Send report ###
if [ -z "${email}" ]; then
echo "No email address specified, information available in ${logfile}"
else
sendmail -t -oi < "$logfile"
rm "$logfile"
fi
================================================
FILE: ups_report.sh
================================================
#!/bin/sh
# Send UPS report to designated email address
# Reference: http://networkupstools.org/docs/developer-guide.chunked/apas01.html
### Parameters ###
# Specify your email address here:
email=""
# Set to a value greater than zero to include all available UPSC
# variables in the report:
senddetail=0
freenashost=$(hostname -s)
freenashostuc=$(hostname -s | tr '[:lower:]' '[:upper:]')
boundary="===== MIME boundary; FreeNAS server ${freenashost} ====="
logfile="/tmp/ups_report.tmp"
subject="UPS Status Report for ${freenashostuc}"
### Set email headers ###
printf "%s\n" "To: ${email}
Subject: ${subject}
Mime-Version: 1.0
Content-Type: multipart/mixed; boundary=\"$boundary\"
--${boundary}
Content-Type: text/html; charset=\"US-ASCII\"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline
" >> ${logfile}
# Get a list of all ups devices installed on the system:
upslist=$(upsc -l "${freenashost}")
### Set email body ###
(
date "+Time: %Y-%m-%d %H:%M:%S"
echo ""
for ups in $upslist; do
ups_type=$(upsc "${ups}" device.type 2> /dev/null | tr '[:lower:]' '[:upper:]')
ups_mfr=$(upsc "${ups}" ups.mfr 2> /dev/null)
ups_model=$(upsc "${ups}" ups.model 2> /dev/null)
ups_serial=$(upsc "${ups}" ups.serial 2> /dev/null)
ups_status=$(upsc "${ups}" ups.status 2> /dev/null)
ups_load=$(upsc "${ups}" ups.load 2> /dev/null)
ups_realpower=$(upsc "${ups}" ups.realpower 2> /dev/null)
ups_realpowernominal=$(upsc "${ups}" ups.realpower.nominal 2> /dev/null)
ups_batterycharge=$(upsc "${ups}" battery.charge 2> /dev/null)
ups_batteryruntime=$(upsc "${ups}" battery.runtime 2> /dev/null)
ups_batteryvoltage=$(upsc "${ups}" battery.voltage 2> /dev/null)
ups_inputvoltage=$(upsc "${ups}" input.voltage 2> /dev/null)
ups_outputvoltage=$(upsc "${ups}" output.voltage 2> /dev/null)
printf "=== %s %s, model %s, serial number %s\n\n" "${ups_mfr}" "${ups_type}" "${ups_model}" "${ups_serial} ==="
echo "Name: ${ups}"
echo "Status: ${ups_status}"
echo "Output Load: ${ups_load}%"
if [ ! -z "${ups_realpower}" ]; then
echo "Real Power: ${ups_realpower}W"
fi
if [ ! -z "${ups_realpowernominal}" ]; then
echo "Real Power: ${ups_realpowernominal}W (nominal)"
fi
if [ ! -z "${ups_inputvoltage}" ]; then
echo "Input Voltage: ${ups_inputvoltage}V"
fi
if [ ! -z "${ups_outputvoltage}" ]; then
echo "Output Voltage: ${ups_outputvoltage}V"
fi
echo "Battery Runtime: ${ups_batteryruntime}s"
echo "Battery Charge: ${ups_batterycharge}%"
echo "Battery Voltage: ${ups_batteryvoltage}V"
echo ""
if [ $senddetail -gt 0 ]; then
echo "=== ALL AVAILABLE UPS VARIABLES ==="
upsc "${ups}"
echo ""
fi
done
) >> ${logfile}
printf "%s\n" "
--${boundary}--" >> ${logfile}
### Send report ###
if [ -z "${email}" ]; then
echo "No email address specified, information available in ${logfile}"
else
sendmail -t -oi < ${logfile}
rm ${logfile}
fi
================================================
FILE: zpool_report.sh
================================================
#!/bin/sh
### Parameters ###
# Specify your email address here:
email=""
# zpool output changed from FreeNAS version 11.0 to 11.1, breaking
# our parsing of the scrubErrors and scrubDate variables. Added a
# conditional to test for the FreeNAS version and parse accordingly.
# This changed again with the release of TrueNAS. Ironically, back to
# the way parsing worked with older versions of FreeNAS.
#
# We obtain the FreeBSD version using uname, as suggested by user
# Chris Moore on the FreeBSD forum.
#
# 'uname -K' gives 7-digit OS release and version, e.g.:
#
# FreeBSD 11.0 1100512
# FreeBSD 11.1 1101505
# FreeBSD 12.2 1202000
#
# If a scrub runs longer than 24 hours, we have two additional tokens to parse in the
# zpool status scan line output ("'x' days"):
#
# 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
# scan: scrub repaired 0B in 1 days 11:56:46 with 0 errors on Wed Dec 9 06:07:04 2020
#
# 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
# scan: scrub repaired 0B in 00:09:11 with 0 errors on Sun Dec 13 17:31:24 2020
fbsd_relver=$(uname -K)
freenashost=$(hostname -s | tr '[:lower:]' '[:upper:]')
boundary="===== MIME boundary; FreeNAS server ${freenashost} ====="
logfile="/tmp/zpool_report.tmp"
subject="ZPool Status Report for ${freenashost}"
pools=$(zpool list -H -o name)
usedWarn=75
usedCrit=90
scrubAgeWarn=30
warnSymbol="?"
critSymbol="!"
### Set email headers ###
printf "%s\n" "To: ${email}
Subject: ${subject}
Mime-Version: 1.0
Content-Type: multipart/mixed; boundary=\"$boundary\"
--${boundary}
Content-Type: text/html; charset=\"US-ASCII\"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline
" >> ${logfile}
###### summary ######
(
echo "########## ZPool status report summary for all pools on server ${freenashost} ##########"
echo ""
echo "+--------------+--------+------+------+------+----+----+--------+------+-----+"
echo "|Pool Name |Status |Read |Write |Cksum |Used|Frag|Scrub |Scrub |Last |"
echo "| | |Errors|Errors|Errors| | |Repaired|Errors|Scrub|"
echo "| | | | | | | |Bytes | |Age |"
echo "+--------------+--------+------+------+------+----+----+--------+------+-----+"
) >> ${logfile}
for pool in $pools; do
if [ "$fbsd_relver" -ge 1101000 ]; then
frag="$(zpool list -H -o frag "$pool")"
else
if [ "${pool}" = "freenas-boot" ] || [ "${pool}" = "boot-pool" ]; then
frag=""
else
frag="$(zpool list -H -o frag "$pool")"
fi
fi
status="$(zpool list -H -o health "$pool")"
errors="$(zpool status "$pool" | grep -E "(ONLINE|DEGRADED|FAULTED|UNAVAIL|REMOVED)[ \t]+[0-9]+")"
readErrors=0
for err in $(echo "$errors" | awk '{print $3}'); do
if echo "$err" | grep -E -q "[^0-9]+"; then
readErrors=1000
break
fi
readErrors=$((readErrors + err))
done
writeErrors=0
for err in $(echo "$errors" | awk '{print $4}'); do
if echo "$err" | grep -E -q "[^0-9]+"; then
writeErrors=1000
break
fi
writeErrors=$((writeErrors + err))
done
cksumErrors=0
for err in $(echo "$errors" | awk '{print $5}'); do
if echo "$err" | grep -E -q "[^0-9]+"; then
cksumErrors=1000
break
fi
cksumErrors=$((cksumErrors + err))
done
if [ "$readErrors" -gt 999 ]; then readErrors=">1K"; fi
if [ "$writeErrors" -gt 999 ]; then writeErrors=">1K"; fi
if [ "$cksumErrors" -gt 999 ]; then cksumErrors=">1K"; fi
used="$(zpool list -H -p -o capacity "$pool")"
scrubRepBytes="N/A"
scrubErrors="N/A"
scrubAge="N/A"
if [ "$(zpool status "$pool" | grep "scan" | awk '{print $2}')" = "scrub" ]; then
parseLong=0
if [ "$fbsd_relver" -gt 1101000 ] && [ "$fbsd_relver" -lt 1200000 ]; then
parseLong=$((parseLong+1))
fi
if [ "$(zpool status "$pool" | grep "scan" | awk '{print $7}')" = "days" ]; then
parseLong=$((parseLong+1))
fi
scrubRepBytes="$(zpool status "$pool" | grep "scan" | awk '{print $4}')"
if [ $parseLong -gt 0 ]; then
scrubErrors="$(zpool status "$pool" | grep "scan" | awk '{print $10}')"
scrubDate="$(zpool status "$pool" | grep "scan" | awk '{print $17"-"$14"-"$15"_"$16}')"
else
scrubErrors="$(zpool status "$pool" | grep "scan" | awk '{print $8}')"
scrubDate="$(zpool status "$pool" | grep "scan" | awk '{print $15"-"$12"-"$13"_"$14}')"
fi
scrubTS="$(date -j -f "%Y-%b-%e_%H:%M:%S" "$scrubDate" "+%s")"
currentTS="$(date "+%s")"
scrubAge=$((((currentTS - scrubTS) + 43200) / 86400))
fi
if [ "$status" = "FAULTED" ] || [ "$used" -gt "$usedCrit" ]; then
symbol="$critSymbol"
elif [ "$scrubErrors" != "N/A" ] && [ "$scrubErrors" != "0" ]; then
symbol="$critSymbol"
elif [ "$status" != "ONLINE" ] \
|| [ "$readErrors" != "0" ] \
|| [ "$writeErrors" != "0" ] \
|| [ "$cksumErrors" != "0" ] \
|| [ "$used" -gt "$usedWarn" ] \
|| [ "$(echo "$scrubAge" | awk '{print int($1)}')" -gt "$scrubAgeWarn" ]; then
symbol="$warnSymbol"
elif [ "$scrubRepBytes" != "0" ] && [ "$scrubRepBytes" != "0B" ] && [ "$scrubRepBytes" != "N/A" ]; then
symbol="$warnSymbol"
else
symbol=" "
fi
(
printf "|%-12s %1s|%-8s|%6s|%6s|%6s|%3s%%|%4s|%8s|%6s|%5s|\n" \
"$pool" "$symbol" "$status" "$readErrors" "$writeErrors" "$cksumErrors" \
"$used" "$frag" "$scrubRepBytes" "$scrubErrors" "$scrubAge"
) >> ${logfile}
done
(
echo "+--------------+--------+------+------+------+----+----+--------+------+-----+"
) >> ${logfile}
###### for each pool ######
for pool in $pools; do
(
echo ""
echo "########## ZPool status report for ${pool} ##########"
echo ""
zpool status -v "$pool"
) >> ${logfile}
done
printf "%s\n" "
--${boundary}--" >> ${logfile}
### Send report ###
if [ -z "${email}" ]; then
echo "No email address specified, information available in ${logfile}"
else
sendmail -t -oi < ${logfile}
rm ${logfile}
fi