Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
4164600
setup for my test snapraid env
AndyHegemann Jul 24, 2022
da16fef
add a modify threshold to the config
AndyHegemann Jul 24, 2022
7e73eff
int verification for modify threshold
AndyHegemann Jul 24, 2022
9b422ff
added parser argument for ignore-modifythreshold
AndyHegemann Jul 24, 2022
e67a86e
add check for modifythreshold into run logic
AndyHegemann Jul 24, 2022
0247877
Update snapraid-runner.conf
AndyHegemann Jul 24, 2022
ede29dd
Update snapraid-runner.conf
AndyHegemann Jul 24, 2022
6af314a
add yagmail dependency
AndyHegemann Jul 24, 2022
2934764
add oauth settings in config
AndyHegemann Jul 24, 2022
1869188
Add oauth/yagmail to send_email
AndyHegemann Jul 24, 2022
ff2fdbd
add oauth to example config
AndyHegemann Jul 24, 2022
e300ec2
Update .gitignore
AndyHegemann Jul 24, 2022
07e9844
Update snapraid-runner.conf
AndyHegemann Jul 24, 2022
623e564
Delete snapraid-runner.conf
AndyHegemann Jul 24, 2022
fc53ed8
Delete snapraid-runner.conf
AndyHegemann Jul 24, 2022
ba30e8d
update config setup
AndyHegemann Jul 24, 2022
a63a2ad
add send log on error to config file
AndyHegemann Jul 24, 2022
7a06302
add send log on error to send_email
AndyHegemann Jul 24, 2022
bf661c2
Update .gitignore
AndyHegemann Jul 24, 2022
4da0f77
Merge branch 'gmail-oauth'
AndyHegemann Jul 24, 2022
2ba7d2c
clarify settings, changed oauth to yagmail
AndyHegemann Jul 24, 2022
ade0f7b
Update snapraid-runner.conf.example
AndyHegemann Jul 24, 2022
e0d2a72
fixed send log only on error
AndyHegemann Jul 24, 2022
31fb911
Update README.md
AndyHegemann Jul 24, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
snapraid-runner.conf
oauth2_creds.json
32 changes: 25 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Snapraid Runner Script
# Snapraid Runner Script - Yagmail Fork

This script runs snapraid and sends its output to the console, a log file and
via email. All this is configurable.
Expand All @@ -8,10 +8,17 @@ scheduler.

It supports Windows, Linux and macOS and requires at least python3.7.

**This is a fork** of the original script that supports sending from new **gmail accounts that can't enable smtp access**. Yagmail is used to authenticate via oauth2, this requires some manual setup of the sending account and of the oauth2 credentials on the first run of the script.

[This](https://blog.macuyiko.com/post/2016/how-to-send-html-mails-with-oauth2-and-gmail-in-python.html) shows how to setup your sending google account via the Google API Console. And [This](https://github.com/kootenpv/yagmail#oauth2) walks through the prompts that the console will ask about on the first run.

This fork also adds an optional check on the number of modified files to abort if it is over a configurable threshold, this is a rudimentary method of stopping some ransomware. **This is a very poor method of ransomeware detection and mitigation**, offline and or immutable backups are highly suggested.

## How to use
* 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)
* Install yagmail via pip if you are planning on using it
* Download [the latest release](https://github.com/AndyHegemann/snapraid-runner/releases)
of this script and extract it anywhere or clone this repository via git.
* Copy/rename the `snapraid-runner.conf.example` to `snapraid-runner.conf` and
edit its contents. You need to at least configure `snapraid.executable` and
Expand All @@ -20,23 +27,34 @@ It supports Windows, Linux and macOS and requires at least python3.7.
`py -3 snapraid-runner.py` on Windows.

## Features
* Runs `diff` before `sync` to see how many files were deleted and aborts if
* Runs `diff` before `sync` to see how many files were deleted and or modified and aborts if
that number exceeds a set threshold.
* Can create a size-limited rotated logfile.
* Can send notification emails after each run or only for failures.
* Can attach log after each run or only for failures
* 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
~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
and contributions, but be aware that I will not merge new features that I feel
do not fit the core purpose of this tool.
do not fit the core purpose of this tool.~

I keep the PRs for features I do not plan on merging open, so if there's a
~I keep the PRs for features I do not plan on merging open, so if there's a
feature you are missing, you can have a look
[at the open PRs](https://github.com/Chronial/snapraid-runner/pulls).
[at the open PRs](https://github.com/Chronial/snapraid-runner/pulls).~

I added features to the original that I wanted, please feel free to do the same to my fork. There is a very good chance I broke something, but if I'm not going to use it then I probably won't get around to fix it. PRs for fixes and features will probably get merged if you feel like opening one.

## Changelog

### vA0.6 (24 Jul 2022)
* Add Yagmail (oauth2) support
* Add attaching log file to email report
* Add attach log file only on error
* Add abort on too many modified files
* Add --ignore-modifythreshold

### Unreleased
* Add --ignore-deletethreshold (by exterrestris, #25)
* Add support for scrub --plan, replacing --percentage (thanks to fmoledina)
Expand Down
22 changes: 18 additions & 4 deletions snapraid-runner.conf.example
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
[snapraid]
; path to the snapraid executable (e.g. /bin/snapraid)
executable = snapraid
executable = snapraid.exe
; path to the snapraid config to be used
config = snapraid.conf
; abort operation if there are more deletes than this, set to -1 to disable
deletethreshold = 40
; abort operation if there are more modify's than this, set to -1 to disable, for rudimenary
; ransomware protection
modifythreshold = 1
; if you want touch to be ran each time
touch = false
touch = true

[logging]
; logfile to write to, leave empty to disable
Expand All @@ -15,16 +18,27 @@ file = snapraid.log
maxsize = 5000

[email]
;set true to use yagmail's oauth2 for gmail insead of smtp for sending the email
use_yagmail = true
; when to send an email, comma-separated list of [success, error]
sendon = success,error
; set to false to get full programm output via email
short = true
subject = [SnapRAID] Status Report:
from =
to =
from =
to =
; maximum email size in KiB
maxsize = 500

[yagmail]
; for use only if use_oauth = true, sets the oauth2 credentials file
; location, if no file exists then one will be created when the program is ran
oauth2_file = oauth2_creds.json
;set true to attach the log file to email
send_log = false
;set true to attach the log file to email only if there is an error
iff_error = false

[smtp]
host =
; leave empty for default port
Expand Down
77 changes: 52 additions & 25 deletions snapraid-runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,10 @@ def snapraid_command(command, args={}, *, allow_statuscodes=[]):


def send_email(success):
import smtplib
from email.mime.text import MIMEText
from email import charset

if len(config["smtp"]["host"]) == 0:
if len(config["smtp"]["host"]) == 0 and not config["email"]["use_yagmail"]:
logging.error("Failed to send email because smtp host is not set")
return

Expand Down Expand Up @@ -98,22 +97,35 @@ def send_email(success):
(" SUCCESS" if success else " ERROR")
msg["From"] = config["email"]["from"]
msg["To"] = config["email"]["to"]
smtp = {"host": config["smtp"]["host"]}
if config["smtp"]["port"]:
smtp["port"] = config["smtp"]["port"]
if config["smtp"]["ssl"]:
server = smtplib.SMTP_SSL(**smtp)

if config["email"]["use_yagmail"]:
import yagmail

attachment_path = []
if config["yagmail"]["send_log"] and not (config["yagmail"]["iff_error"] and success):
attachment_path = config["logging"]["file"]

yag = yagmail.SMTP(msg["From"], oauth2_file=config["yagmail"]["oauth2_file"])
yag.send(to=msg["To"], subject=msg["Subject"], contents=body, attachments=attachment_path)
else:
server = smtplib.SMTP(**smtp)
if config["smtp"]["tls"]:
server.starttls()
if config["smtp"]["user"]:
server.login(config["smtp"]["user"], config["smtp"]["password"])
server.sendmail(
config["email"]["from"],
[config["email"]["to"]],
msg.as_string())
server.quit()
import smtplib

smtp = {"host": config["smtp"]["host"]}
if config["smtp"]["port"]:
smtp["port"] = config["smtp"]["port"]
if config["smtp"]["ssl"]:
server = smtplib.SMTP_SSL(**smtp)
else:
server = smtplib.SMTP(**smtp)
if config["smtp"]["tls"]:
server.starttls()
if config["smtp"]["user"]:
server.login(config["smtp"]["user"], config["smtp"]["password"])
server.sendmail(
config["email"]["from"],
[config["email"]["to"]],
msg.as_string())
server.quit()


def finish(is_success):
Expand All @@ -133,27 +145,29 @@ def load_config(args):
global config
parser = configparser.RawConfigParser()
parser.read(args.conf)
sections = ["snapraid", "logging", "email", "smtp", "scrub"]
sections = ["snapraid", "logging", "email", "yagmail", "smtp", "scrub"]
config = dict((x, defaultdict(lambda: "")) for x in sections)
for section in parser.sections():
for (k, v) in parser.items(section):
config[section][k] = v.strip()

int_options = [
("snapraid", "deletethreshold"), ("logging", "maxsize"),
("scrub", "older-than"), ("email", "maxsize"),
("snapraid", "deletethreshold"), ("snapraid", "modifythreshold"),
("logging", "maxsize"), ("scrub", "older-than"), ("email", "maxsize"),
]
for section, option in int_options:
try:
config[section][option] = int(config[section][option])
except ValueError:
config[section][option] = 0

config[section][option] = 0

config["snapraid"]["touch"] = (config["snapraid"]["touch"].lower() == "true")
config["email"]["short"] = (config["email"]["short"].lower() == "true")
config["yagmail"]["send_log"] = (config["yagmail"]["send_log"].lower() == "true")
config["yagmail"]["iff_error"] = (config["yagmail"]["iff_error"].lower() == "true")
config["smtp"]["ssl"] = (config["smtp"]["ssl"].lower() == "true")
config["smtp"]["tls"] = (config["smtp"]["tls"].lower() == "true")
config["scrub"]["enabled"] = (config["scrub"]["enabled"].lower() == "true")
config["email"]["short"] = (config["email"]["short"].lower() == "true")
config["snapraid"]["touch"] = (config["snapraid"]["touch"].lower() == "true")

# Migration
if config["scrub"]["percentage"]:
Expand All @@ -165,6 +179,9 @@ def load_config(args):
if args.ignore_deletethreshold:
config["snapraid"]["deletethreshold"] = -1

if args.ignore_modifythreshold:
config["snapraid"]["modifythreshold"] = -1


def setup_logger():
log_format = logging.Formatter(
Expand Down Expand Up @@ -210,6 +227,8 @@ def main():
help="Do not scrub (overrides config)")
parser.add_argument("--ignore-deletethreshold", action='store_true',
help="Sync even if configured delete threshold is exceeded")
parser.add_argument("--ignore-modifythreshold", action='store_true',
help="Sync even if configured modify threshold is exceeded")
args = parser.parse_args()

if not os.path.exists(args.conf):
Expand Down Expand Up @@ -276,6 +295,14 @@ def run():
logging.error("Run again with --ignore-deletethreshold to sync anyways")
finish(False)

if (config["snapraid"]["modifythreshold"] >= 0 and
diff_results["update"] > config["snapraid"]["modifythreshold"]):
logging.error(
"Modified files exceed modify threshold of {}, aborting".format(
config["snapraid"]["modifythreshold"]))
logging.error("Run again with --ignore-modifythreshold to sync anyways")
finish(False)

if (diff_results["remove"] + diff_results["add"] + diff_results["move"] +
diff_results["update"] == 0):
logging.info("No changes detected, no sync required")
Expand Down