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