From 0edb4f151f439a11b3848aff4c70fa689423c307 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Sun, 24 Sep 2017 01:25:59 -0600 Subject: [PATCH 1/4] Chronial/snapraid-runner Issue #3: Add support for `smart` command --- snapraid-runner.conf.example | 4 ++++ snapraid-runner.py | 25 +++++++++++++++++++++---- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/snapraid-runner.conf.example b/snapraid-runner.conf.example index dce416f..db59284 100644 --- a/snapraid-runner.conf.example +++ b/snapraid-runner.conf.example @@ -39,3 +39,7 @@ password = enabled = false percentage = 12 older-than = 10 + +[smart] +; set to true to run smart after sync and scrub +enabled = false diff --git a/snapraid-runner.py b/snapraid-runner.py index 5ea96b5..2ec5b0c 100644 --- a/snapraid-runner.py +++ b/snapraid-runner.py @@ -52,9 +52,14 @@ def snapraid_command(command, args={}, ignore_errors=False): stdout=subprocess.PIPE, stderr=subprocess.PIPE) out = [] - threads = [ - tee_log(p.stdout, out, logging.OUTPUT), - tee_log(p.stderr, [], logging.OUTERR)] + if command == 'smart': + threads = [ + tee_log(p.stdout, out, logging.SMART), + tee_log(p.stderr, [], logging.OUTERR)] + else: + threads = [ + tee_log(p.stdout, out, logging.OUTPUT), + tee_log(p.stderr, [], logging.OUTERR)] for t in threads: t.join() ret = p.wait() @@ -132,7 +137,7 @@ def load_config(args): global config parser = ConfigParser.RawConfigParser() parser.read(args.conf) - sections = ["snapraid", "logging", "email", "smtp", "scrub"] + sections = ["snapraid", "logging", "email", "smtp", "scrub", "smart"] config = dict((x, defaultdict(lambda: "")) for x in sections) for section in parser.sections(): for (k, v) in parser.items(section): @@ -150,6 +155,7 @@ def load_config(args): config["smtp"]["ssl"] = (config["smtp"]["ssl"].lower() == "true") config["scrub"]["enabled"] = (config["scrub"]["enabled"].lower() == "true") + config["smart"]["enabled"] = (config["smart"]["enabled"].lower() == "true") config["email"]["short"] = (config["email"]["short"].lower() == "true") config["snapraid"]["touch"] = (config["snapraid"]["touch"].lower() == "true") @@ -165,6 +171,8 @@ def setup_logger(): logging.addLevelName(logging.OUTPUT, "OUTPUT") logging.OUTERR = 25 logging.addLevelName(logging.OUTERR, "OUTERR") + logging.SMART = 26 + logging.addLevelName(logging.SMART, "SMART") root_logger.setLevel(logging.OUTPUT) console_logger = logging.StreamHandler(sys.stdout) console_logger.setFormatter(log_format) @@ -288,6 +296,15 @@ def run(): finish(False) logging.info("*" * 60) + if config["smart"]["enabled"]: + logging.info("Running SMART...") + try: + snapraid_command("smart") + except subprocess.CalledProcessError as e: + logging.error(e) + finish(False) + logging.info("*" * 60) + logging.info("All done") finish(True) From 9f565f1a1d0fff2805405a960c7b037eba406b66 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Mon, 2 Oct 2017 07:32:37 -0600 Subject: [PATCH 2/4] Fix running 'snapraid smart' from cron --- snapraid-runner.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/snapraid-runner.py b/snapraid-runner.py index 2ec5b0c..2152b66 100644 --- a/snapraid-runner.py +++ b/snapraid-runner.py @@ -298,6 +298,8 @@ def run(): if config["smart"]["enabled"]: logging.info("Running SMART...") + # Expand PATH to include /usr/sbin for running smartctl from cron + os.environ["PATH"] += os.pathsep + "/usr/sbin" try: snapraid_command("smart") except subprocess.CalledProcessError as e: From 8f44ea25b75c1b5a12643fad1b0cd5d5f8693b1e Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Wed, 3 Jan 2018 15:17:39 -0700 Subject: [PATCH 3/4] Cross-Platform SMART --- README.md | 10 ++++++++++ snapraid-runner.conf.example | 1 + snapraid-runner.py | 36 +++++++++++++++++++++++++++++++++--- 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d54e7c9..950287b 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,16 @@ scheduler. edit its contents. You need to at least configure `snapraid.exectable` and `snapraid.config`. * Run the script via `python snapraid-runner.py`. +* If you want to enable SMART monitoring on Windows or Mac OS, you must first + download and install [smartmontools](https://www.smartmontools.org/wiki/Download). + Most Linux distributions will have smartmontools installed by default. + * Windows users may install using the packaged .exe installer found at this link or + use [Chocolatey](https://chocolatey.org/) (i.e., `choco install smartmontools`). + * Mac OS users may install using the packaged .dmg installer found at this link or + use [Homebrew](https://brew.sh/) (i.e., `brew install smartmontools`). + * Linux users may check for smartmontools using `smartctl -V`, if not installed use + your distribution's package manager to install `smartmontools` + (e.g., `apt-get install smartmontools`, `yum install smartmontools`, etc.) ## Features * Runs `diff` before `sync` to see how many files were deleted and aborts if diff --git a/snapraid-runner.conf.example b/snapraid-runner.conf.example index db59284..682de18 100644 --- a/snapraid-runner.conf.example +++ b/snapraid-runner.conf.example @@ -41,5 +41,6 @@ percentage = 12 older-than = 10 [smart] +; Mac OS & Windows users must manually install smartmontools before enabling! ; set to true to run smart after sync and scrub enabled = false diff --git a/snapraid-runner.py b/snapraid-runner.py index 2152b66..579b21c 100644 --- a/snapraid-runner.py +++ b/snapraid-runner.py @@ -6,6 +6,7 @@ import logging import logging.handlers import os.path +import platform import subprocess import sys import threading @@ -171,6 +172,7 @@ def setup_logger(): logging.addLevelName(logging.OUTPUT, "OUTPUT") logging.OUTERR = 25 logging.addLevelName(logging.OUTERR, "OUTERR") + # Set separate SMART logging level to always output SMART logging when either "verbose" or "short" mode is set in the snapraid-runner email config logging.SMART = 26 logging.addLevelName(logging.SMART, "SMART") root_logger.setLevel(logging.OUTPUT) @@ -298,8 +300,36 @@ def run(): if config["smart"]["enabled"]: logging.info("Running SMART...") - # Expand PATH to include /usr/sbin for running smartctl from cron - os.environ["PATH"] += os.pathsep + "/usr/sbin" + # Expand PATH to include /usr/sbin for running smartctl from cron for Linux and Mac systems + # cron needs full path to /usr/sbin for the 'snapraid smart' command to find the 'smartctl' command correctly + # /usr/sbin is not in cron's PATH by default when editing root user's crontab using 'crontab -e' as root + if platform.system() == "Linux": + if os.path.exists("/usr/sbin/smartctl"): + os.environ["PATH"] += os.pathsep + "/usr/sbin" + else: + logging.error("Cannot locate smartctl, is smartmontools installed?") + # Without explicitly exiting, the program will execute the "snapraid smart" command even if we didn't find smartctl in a known location + # If the user's PATH includes the non-standard location, the SMART data will output, abeit with this error message in the logs + # On Mac the .pkg isntaller (from the downloaded .dmg) installs to '/usr/local/sbin' and Homebrew installs to '/usr/local/bin' + elif platform.system() == "Darwin": + if os.path.exists("/usr/local/sbin/smartctl"): + os.environ["PATH"] += os.pathsep + "/usr/local/sbin" + elif os.path.exists("/usr/local/bin/smartctl"): + os.environ["PATH"] += os.pathsep + "/usr/local/bin" + else: + logging.error("Cannot locate smartctl, is smartmontools installed?") + # Without explicitly exiting, the program will execute the "snapraid smart" command even if we didn't find smartctl in a known location + # If the user's PATH includes the non-standard location, the SMART data will output, abeit with this error message in the logs + elif platform.system() == "Windows": + if os.path.exists("C:/Program Files/smartmontools/bin/smartctl.exe") or os.path.exists("C:/Program Files (x86)/smartmontools/bin/smartctl.exe"): + # Windows machines have smartmontools added to PATH that Task Scheduler calls without additional configuration + pass + else: + logging.error("Cannot locate smartctl, is smartmontools installed?") + # Without explicitly exiting, the program will execute the "snapraid smart" command even if we didn't find smartctl in a known location + # If the user's PATH includes the non-standard location, the SMART data will output, abeit with this error message in the logs + else: + print platform.system(), "systems are not supported for SMART monitoring from snapraid-runner at this time" try: snapraid_command("smart") except subprocess.CalledProcessError as e: @@ -311,4 +341,4 @@ def run(): finish(True) -main() +main() \ No newline at end of file From 618adfc53f912ff8821ccee19dbb1dea275401f6 Mon Sep 17 00:00:00 2001 From: Chronial Date: Tue, 15 May 2018 13:27:12 +0200 Subject: [PATCH 4/4] Simplify smart support --- README.md | 20 ++++++------- snapraid-runner.conf.example | 9 ++---- snapraid-runner.py | 58 +++++++----------------------------- 3 files changed, 24 insertions(+), 63 deletions(-) diff --git a/README.md b/README.md index 950287b..2290066 100644 --- a/README.md +++ b/README.md @@ -15,16 +15,6 @@ scheduler. edit its contents. You need to at least configure `snapraid.exectable` and `snapraid.config`. * Run the script via `python snapraid-runner.py`. -* If you want to enable SMART monitoring on Windows or Mac OS, you must first - download and install [smartmontools](https://www.smartmontools.org/wiki/Download). - Most Linux distributions will have smartmontools installed by default. - * Windows users may install using the packaged .exe installer found at this link or - use [Chocolatey](https://chocolatey.org/) (i.e., `choco install smartmontools`). - * Mac OS users may install using the packaged .dmg installer found at this link or - use [Homebrew](https://brew.sh/) (i.e., `brew install smartmontools`). - * Linux users may check for smartmontools using `smartctl -V`, if not installed use - your distribution's package manager to install `smartmontools` - (e.g., `apt-get install smartmontools`, `yum install smartmontools`, etc.) ## Features * Runs `diff` before `sync` to see how many files were deleted and aborts if @@ -32,10 +22,20 @@ scheduler. * Can create a size-limited rotated logfile. * Can send notification emails after each run or only for failures. * Can run `scrub` after `sync` +* Can run `smart`. For this to work, you need [smartmontools](https://www.smartmontools.org/wiki/Download). + Most Linux distributions will have it installed installed by default. + * Windows users may install using the packaged .exe installer found at this link or + use [Chocolatey](https://chocolatey.org/) (i.e., `choco install smartmontools`). + * Mac OS users may install using the packaged .dmg installer found at this link or + use [Homebrew](https://brew.sh/) (i.e., `brew install smartmontools`). + * Linux users may check for smartmontools using `smartctl -V`, if not installed use + your distribution's package manager to install `smartmontools` + (e.g., `apt-get install smartmontools`, `yum install smartmontools`, etc.) ## Changelog ### Unreleased * Add support for running `snapraid touch` (by ShoGinn, PR-11) +* Add support for running `snapraid smart` (by k3vmcd, PR-15) ### v0.3 (20 Jul 2017) * Limit size of sent emails diff --git a/snapraid-runner.conf.example b/snapraid-runner.conf.example index 682de18..c2ba061 100644 --- a/snapraid-runner.conf.example +++ b/snapraid-runner.conf.example @@ -5,8 +5,10 @@ executable = snapraid config = snapraid.conf ; abort operation if there are more deletes than this, set to -1 to disable deletethreshold = 40 -; if you want touch to be ran each time +; set to true to run touch each time touch = false +; set to true to run smart. Make sure smartctl is in your PATH +smart = false [logging] ; logfile to write to, leave empty to disable @@ -39,8 +41,3 @@ password = enabled = false percentage = 12 older-than = 10 - -[smart] -; Mac OS & Windows users must manually install smartmontools before enabling! -; set to true to run smart after sync and scrub -enabled = false diff --git a/snapraid-runner.py b/snapraid-runner.py index 579b21c..724c6e8 100644 --- a/snapraid-runner.py +++ b/snapraid-runner.py @@ -6,7 +6,6 @@ import logging import logging.handlers import os.path -import platform import subprocess import sys import threading @@ -40,7 +39,7 @@ def tee_thread(): return t -def snapraid_command(command, args={}, ignore_errors=False): +def snapraid_command(command, args={}, ignore_errors=False, stdout_log_level=None): """ Run snapraid command Raises subprocess.CalledProcessError if errorlevel != 0 @@ -53,14 +52,9 @@ def snapraid_command(command, args={}, ignore_errors=False): stdout=subprocess.PIPE, stderr=subprocess.PIPE) out = [] - if command == 'smart': - threads = [ - tee_log(p.stdout, out, logging.SMART), - tee_log(p.stderr, [], logging.OUTERR)] - else: - threads = [ - tee_log(p.stdout, out, logging.OUTPUT), - tee_log(p.stderr, [], logging.OUTERR)] + threads = [ + tee_log(p.stdout, out, stdout_log_level or logging.OUTPUT), + tee_log(p.stderr, [], logging.OUTERR)] for t in threads: t.join() ret = p.wait() @@ -156,9 +150,9 @@ def load_config(args): config["smtp"]["ssl"] = (config["smtp"]["ssl"].lower() == "true") config["scrub"]["enabled"] = (config["scrub"]["enabled"].lower() == "true") - config["smart"]["enabled"] = (config["smart"]["enabled"].lower() == "true") config["email"]["short"] = (config["email"]["short"].lower() == "true") config["snapraid"]["touch"] = (config["snapraid"]["touch"].lower() == "true") + config["snapraid"]["smart"] = (config["snapraid"]["smart"].lower() == "true") if args.scrub is not None: config["scrub"]["enabled"] = args.scrub @@ -170,11 +164,11 @@ def setup_logger(): root_logger = logging.getLogger() logging.OUTPUT = 15 logging.addLevelName(logging.OUTPUT, "OUTPUT") + # We only run smart for its output, so we always include it + logging.SMART = 24 + logging.addLevelName(logging.SMART, "SMART") logging.OUTERR = 25 logging.addLevelName(logging.OUTERR, "OUTERR") - # Set separate SMART logging level to always output SMART logging when either "verbose" or "short" mode is set in the snapraid-runner email config - logging.SMART = 26 - logging.addLevelName(logging.SMART, "SMART") root_logger.setLevel(logging.OUTPUT) console_logger = logging.StreamHandler(sys.stdout) console_logger.setFormatter(log_format) @@ -298,40 +292,10 @@ def run(): finish(False) logging.info("*" * 60) - if config["smart"]["enabled"]: + if config["snapraid"]["smart"]: logging.info("Running SMART...") - # Expand PATH to include /usr/sbin for running smartctl from cron for Linux and Mac systems - # cron needs full path to /usr/sbin for the 'snapraid smart' command to find the 'smartctl' command correctly - # /usr/sbin is not in cron's PATH by default when editing root user's crontab using 'crontab -e' as root - if platform.system() == "Linux": - if os.path.exists("/usr/sbin/smartctl"): - os.environ["PATH"] += os.pathsep + "/usr/sbin" - else: - logging.error("Cannot locate smartctl, is smartmontools installed?") - # Without explicitly exiting, the program will execute the "snapraid smart" command even if we didn't find smartctl in a known location - # If the user's PATH includes the non-standard location, the SMART data will output, abeit with this error message in the logs - # On Mac the .pkg isntaller (from the downloaded .dmg) installs to '/usr/local/sbin' and Homebrew installs to '/usr/local/bin' - elif platform.system() == "Darwin": - if os.path.exists("/usr/local/sbin/smartctl"): - os.environ["PATH"] += os.pathsep + "/usr/local/sbin" - elif os.path.exists("/usr/local/bin/smartctl"): - os.environ["PATH"] += os.pathsep + "/usr/local/bin" - else: - logging.error("Cannot locate smartctl, is smartmontools installed?") - # Without explicitly exiting, the program will execute the "snapraid smart" command even if we didn't find smartctl in a known location - # If the user's PATH includes the non-standard location, the SMART data will output, abeit with this error message in the logs - elif platform.system() == "Windows": - if os.path.exists("C:/Program Files/smartmontools/bin/smartctl.exe") or os.path.exists("C:/Program Files (x86)/smartmontools/bin/smartctl.exe"): - # Windows machines have smartmontools added to PATH that Task Scheduler calls without additional configuration - pass - else: - logging.error("Cannot locate smartctl, is smartmontools installed?") - # Without explicitly exiting, the program will execute the "snapraid smart" command even if we didn't find smartctl in a known location - # If the user's PATH includes the non-standard location, the SMART data will output, abeit with this error message in the logs - else: - print platform.system(), "systems are not supported for SMART monitoring from snapraid-runner at this time" try: - snapraid_command("smart") + snapraid_command("smart", stdout_log_level=logging.SMART) except subprocess.CalledProcessError as e: logging.error(e) finish(False) @@ -341,4 +305,4 @@ def run(): finish(True) -main() \ No newline at end of file +main()