backups: email the administrator when there's a problem

Refactor by moving the email-the-admin code out of the status checks and into a new separate tool.

This is why I suppressed non-error output of the backups last commit - so it doesn't send a daily email.
This commit is contained in:
Joshua Tauberer 2016-01-02 17:00:51 -05:00
parent 89a46089ee
commit 5033042b8c
4 changed files with 68 additions and 36 deletions

View file

@ -12,6 +12,10 @@ Control Panel:
* Report free memory usage. * Report free memory usage.
System:
* The daily backup will now email the administrator if there is a problem.
v0.15 (January 1, 2016) v0.15 (January 1, 2016)
----------------------- -----------------------

View file

@ -2,7 +2,7 @@
# This script is run daily (at 3am each night). # This script is run daily (at 3am each night).
# Take a backup. # Take a backup.
management/backup.py management/backup.py | management/email_administrator.py "Backup Status"
# Run status checks and email the administrator if anything changed. # Run status checks and email the administrator if anything changed.
management/status_checks.py --show-changes --smtp management/status_checks.py --show-changes | management/email_administrator.py "Status Checks Change Notice"

View file

@ -0,0 +1,42 @@
#!/usr/bin/python3
# Reads in STDIN. If the stream is not empty, mail it to the system administrator.
import sys
import smtplib
from email.message import Message
from utils import load_environment
# Load system environment info.
env = load_environment()
# Process command line args.
subject = sys.argv[1]
# Administrator's email address.
admin_addr = "administrator@" + env['PRIMARY_HOSTNAME']
# Read in STDIN.
content = sys.stdin.read().strip()
# If there's nothing coming in, just exit.
if content == "":
sys.exit(0)
# create MIME message
msg = Message()
msg['From'] = "\"%s\" <%s>" % (env['PRIMARY_HOSTNAME'], admin_addr)
msg['To'] = admin_addr
msg['Subject'] = "[%s] %s" % (env['PRIMARY_HOSTNAME'], subject)
msg.set_payload(content, "UTF-8")
# send
smtpclient = smtplib.SMTP('localhost', 25)
smtpclient.ehlo()
smtpclient.sendmail(
admin_addr, # MAIL FROM
admin_addr, # RCPT TO
msg.as_string())
smtpclient.quit()

View file

@ -767,15 +767,11 @@ def check_miab_version(env, output):
output.print_error("A new version of Mail-in-a-Box is available. You are running version %s. The latest version is %s. For upgrade instructions, see https://mailinabox.email. " output.print_error("A new version of Mail-in-a-Box is available. You are running version %s. The latest version is %s. For upgrade instructions, see https://mailinabox.email. "
% (this_ver, latest_ver)) % (this_ver, latest_ver))
def run_and_output_changes(env, pool, send_via_email): def run_and_output_changes(env, pool):
import json import json
from difflib import SequenceMatcher from difflib import SequenceMatcher
if not send_via_email: out = ConsoleOutput()
out = ConsoleOutput()
else:
import io
out = FileOutput(io.StringIO(""), 70)
# Run status checks. # Run status checks.
cur = BufferedOutput() cur = BufferedOutput()
@ -834,28 +830,6 @@ def run_and_output_changes(env, pool, send_via_email):
out.add_heading(category) out.add_heading(category)
out.print_warning("This section was removed.") out.print_warning("This section was removed.")
if send_via_email:
# If there were changes, send off an email.
buf = out.buf.getvalue()
if len(buf) > 0:
# create MIME message
from email.message import Message
msg = Message()
msg['From'] = "\"%s\" <administrator@%s>" % (env['PRIMARY_HOSTNAME'], env['PRIMARY_HOSTNAME'])
msg['To'] = "administrator@%s" % env['PRIMARY_HOSTNAME']
msg['Subject'] = "[%s] Status Checks Change Notice" % env['PRIMARY_HOSTNAME']
msg.set_payload(buf, "UTF-8")
# send to administrator@
import smtplib
mailserver = smtplib.SMTP('localhost', 25)
mailserver.ehlo()
mailserver.sendmail(
"administrator@%s" % env['PRIMARY_HOSTNAME'], # MAIL FROM
"administrator@%s" % env['PRIMARY_HOSTNAME'], # RCPT TO
msg.as_string())
mailserver.quit()
# Store the current status checks output for next time. # Store the current status checks output for next time.
os.makedirs(os.path.dirname(cache_fn), exist_ok=True) os.makedirs(os.path.dirname(cache_fn), exist_ok=True)
with open(cache_fn, "w") as f: with open(cache_fn, "w") as f:
@ -886,7 +860,7 @@ class FileOutput:
words = re.split("(\s+)", message) words = re.split("(\s+)", message)
linelen = 0 linelen = 0
for w in words: for w in words:
if linelen + len(w) > self.width-1-len(first_line): if self.width and (linelen + len(w) > self.width-1-len(first_line)):
print(file=self.buf) print(file=self.buf)
print(" ", end="", file=self.buf) print(" ", end="", file=self.buf)
linelen = 0 linelen = 0
@ -902,10 +876,22 @@ class FileOutput:
class ConsoleOutput(FileOutput): class ConsoleOutput(FileOutput):
def __init__(self): def __init__(self):
self.buf = sys.stdout self.buf = sys.stdout
try:
self.width = int(shell('check_output', ['stty', 'size']).split()[1]) # Do nice line-wrapping according to the size of the terminal.
except: # The 'stty' program queries standard input for terminal information.
self.width = 76 if sys.stdin.isatty():
try:
self.width = int(shell('check_output', ['stty', 'size']).split()[1])
except:
self.width = 76
else:
# However if standard input is not a terminal, we would get
# "stty: standard input: Inappropriate ioctl for device". So
# we test with sys.stdin.isatty first, and if it is not a
# terminal don't do any line wrapping. When this script is
# run from cron, or if stdin has been redirected, this happens.
self.width = None
class BufferedOutput: class BufferedOutput:
# Record all of the instance method calls so we can play them back later. # Record all of the instance method calls so we can play them back later.
@ -933,7 +919,7 @@ if __name__ == "__main__":
run_checks(False, env, ConsoleOutput(), pool) run_checks(False, env, ConsoleOutput(), pool)
elif sys.argv[1] == "--show-changes": elif sys.argv[1] == "--show-changes":
run_and_output_changes(env, pool, sys.argv[-1] == "--smtp") run_and_output_changes(env, pool)
elif sys.argv[1] == "--check-primary-hostname": elif sys.argv[1] == "--check-primary-hostname":
# See if the primary hostname appears resolvable and has a signed certificate. # See if the primary hostname appears resolvable and has a signed certificate.