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:
parent
89a46089ee
commit
5033042b8c
4 changed files with 68 additions and 36 deletions
|
@ -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)
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
42
management/email_administrator.py
Executable file
42
management/email_administrator.py
Executable 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()
|
|
@ -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.
|
||||||
|
|
Loading…
Reference in a new issue