diff --git a/.gitignore b/.gitignore index c0fde79..1724036 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,10 @@ snapraid-runner.conf +__pycache__ +.idea +*.pyc +build +dist +venv +*.egg-info* +.vscode/settings.json +snapraid.log diff --git a/README.md b/README.md index 5780e60..adff502 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Snapraid Runner Script +# Snapraid Runner Package -This script runs snapraid and sends its output to the console, a log file and +This python package runs snapraid and sends its output to the console, a log file and via email. All this is configurable. It can be run manually, but its main purpose is to be run via cronjob/windows @@ -12,14 +12,17 @@ It supports Windows, Linux and macOS and requires at least python3.7. * If you don’t already have it, download and install [the latest python version](https://www.python.org/downloads/). * Download [the latest release](https://github.com/Chronial/snapraid-runner/releases) - of this script and extract it anywhere or clone this repository via git. + of this package. +* Install the release with pip via `python3 -m pip install snapraid-runner-x.x.tar.gz` on Linux or `py -3 -m pip install snapraid-runner-x.x.tar.gz` on Windows * Copy/rename the `snapraid-runner.conf.example` to `snapraid-runner.conf` and edit its contents. You need to at least configure `snapraid.executable` and `snapraid.config`. * [The wiki](https://github.com/Chronial/snapraid-runner/wiki/How-to-use-snapraid-runner-with-gmail) has details on how to use gmail for sending mail. -* Run the script via `python3 snapraid-runner.py` on Linux or - `py -3 snapraid-runner.py` on Windows. +* Run the script via `snapraid-runner` command on Linux or Windows. + +## Uninstalling +* To uninstall simply run `python3 -m pip uninstall snapraid-runner` on Linux or `py -3 -m pip uninstall snapraid-runner` ## Features * Runs `diff` before `sync` to see how many files were deleted and aborts if @@ -29,8 +32,8 @@ It supports Windows, Linux and macOS and requires at least python3.7. * Can run `scrub` after `sync` ## Scope of this project and contributions -Snapraid-runner is supposed to be a small tool with clear focus. It should not -have any dependencies to keep installation trivial. I always welcome bugfixes +Snapraid-runner is supposed to be a small tool with clear focus. It should have +minimal dependencies to keep installation trivial. I always welcome bugfixes and contributions, but be aware that I will not merge new features that I feel do not fit the core purpose of this tool. @@ -64,3 +67,19 @@ feature you are missing, you can have a look ### v0.1 (16 Feb 2014) * Initial release + + +# Developers +To install local build to virtual environment: +* `python3 -m pip install build` +* `python3 -m build` +* `python3 -m venv venv` +* `source venv/bin/activate` +* `python3 -m pip install dist/snapraid-runner-x.x.tar.gz` + +# Installing +Install from pypi: +* `python3 -m pip install snapraid_runner`` +Install from release: +* download release from https://github.com/Chronial/snapraid-runner/releases +* install release: `python3 -m pip install snapraid-runner-x.x.tar.gz` diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..8cf3256 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" \ No newline at end of file diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..1a74efa --- /dev/null +++ b/setup.cfg @@ -0,0 +1,18 @@ +[metadata] +name = snapraid_runner +version = attr: snapraid_runner.VERSION +long_description = file: README.md, LICENSE.txt +classifiers = + Programming Language :: Python :: 3 + +[options] +zip_safe = False +include_package_data = True +packages = snapraid_runner +python_requires = >=3.7 +install_requires = + apprise + +[options.entry_points] +console_scripts = + snapraid-runner = snapraid_runner:main \ No newline at end of file diff --git a/snapraid-runner.conf.example b/snapraid-runner.conf.example index bc8fca4..f626869 100644 --- a/snapraid-runner.conf.example +++ b/snapraid-runner.conf.example @@ -42,3 +42,9 @@ enabled = false plan = 12 ; minimum block age (in days) for scrubbing. Only used with percentage plans older-than = 10 + +[notifications] +; when to send notifications, comma-separated list of [success, error] +sendon = success,error +; https://github.com/caronc/apprise for full list of supported services +services = mailto://{user}:{password}@hotmail.com,slack://TokenA/TokenB/TokenC/ \ No newline at end of file diff --git a/snapraid-runner.py b/snapraid_runner/SnapraidRunner.py similarity index 85% rename from snapraid-runner.py rename to snapraid_runner/SnapraidRunner.py index 56b8e9e..1babb45 100755 --- a/snapraid-runner.py +++ b/snapraid_runner/SnapraidRunner.py @@ -115,6 +115,47 @@ def send_email(success): msg.as_string()) server.quit() +def send_notification(success): + import apprise + from email.mime.text import MIMEText + from email import charset + + ap_asset = apprise.AppriseAsset() + apobj = apprise.Apprise(asset=ap_asset) + + for url in config["notifications"]["services"]: + if not apobj.add(url): + logging.error('\'%s\' is an invalid AppRise URL.' % (url)) + + if len(config["notifications"]["services"]) == 0: + logging.error("Failed to send email because no notification services are set") + return + + # use quoted-printable instead of the default base64 + charset.add_charset("utf-8", charset.SHORTEST, charset.QP) + if success: + body = "SnapRAID job completed successfully:\n\n\n" + else: + body = "Error during SnapRAID job:\n\n\n" + + log = email_log.getvalue() + maxsize = config['email'].get('maxsize', 500) * 1024 + if maxsize and len(log) > maxsize: + cut_lines = log.count("\n", maxsize // 2, -maxsize // 2) + log = ( + "NOTE: Log was too big for email and was shortened\n\n" + + log[:maxsize // 2] + + "[...]\n\n\n --- LOG WAS TOO BIG - {} LINES REMOVED --\n\n\n[...]".format( + cut_lines) + + log[-maxsize // 2:]) + body += log + + title = config["email"]["subject"] + \ + (" SUCCESS" if success else " ERROR") + + apobj.notify(body=body, title=title) + + def finish(is_success): if ("error", "success")[is_success] in config["email"]["sendon"]: @@ -122,6 +163,11 @@ def finish(is_success): send_email(is_success) except Exception: logging.exception("Failed to send email") + if ("error", "success")[is_success] in config["notifications"]["sendon"]: + try: + send_notification(is_success) + except Exception: + logging.exception("Failed to send notifications") if is_success: logging.info("Run finished successfully") else: @@ -133,7 +179,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", "notifications"] config = dict((x, defaultdict(lambda: "")) for x in sections) for section in parser.sections(): for (k, v) in parser.items(section): @@ -154,6 +200,7 @@ def load_config(args): config["scrub"]["enabled"] = (config["scrub"]["enabled"].lower() == "true") config["email"]["short"] = (config["email"]["short"].lower() == "true") config["snapraid"]["touch"] = (config["snapraid"]["touch"].lower() == "true") + config["notifications"]["services"] = config["notifications"]["services"].split(',') # Migration if config["scrub"]["percentage"]: @@ -311,4 +358,5 @@ def run(): finish(True) -main() +if __name__ == "__main__": + main() diff --git a/snapraid_runner/__init__.py b/snapraid_runner/__init__.py new file mode 100644 index 0000000..b019aea --- /dev/null +++ b/snapraid_runner/__init__.py @@ -0,0 +1,3 @@ +from .SnapraidRunner import main + +VERSION = '0.6' \ No newline at end of file