new backup script, see #11
This commit is contained in:
parent
51dd2ed70b
commit
89730bd643
|
@ -0,0 +1,54 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
# This script performs a backup of all user data:
|
||||||
|
# 1) System services are stopped while a copy of user data is made.
|
||||||
|
# 2) An incremental backup is made using rdiff-backup into the
|
||||||
|
# directory STORAGE_ROOT/backup/rdiff-history. This directory
|
||||||
|
# will contain the latest files plus a complete history for
|
||||||
|
# all prior backups.
|
||||||
|
# 3) The stopped services are restarted.
|
||||||
|
# 4) The backup directory is compressed into a single file using tar.
|
||||||
|
# 5) That file is encrypted with a long password stored in backup/secret_key.txt.
|
||||||
|
|
||||||
|
import os, os.path, subprocess
|
||||||
|
|
||||||
|
from utils import exclusive_process, load_environment
|
||||||
|
|
||||||
|
env = load_environment()
|
||||||
|
|
||||||
|
exclusive_process("backup")
|
||||||
|
|
||||||
|
# Ensure the backup directory exists.
|
||||||
|
backup_dir = os.path.join(env["STORAGE_ROOT"], 'backup')
|
||||||
|
rdiff_backup_dir = os.path.join(backup_dir, 'rdiff-history')
|
||||||
|
os.makedirs(backup_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# Stop services.
|
||||||
|
subprocess.check_call(["service", "dovecot", "stop"])
|
||||||
|
subprocess.check_call(["service", "postfix", "stop"])
|
||||||
|
|
||||||
|
# Update the backup directory which stores increments.
|
||||||
|
try:
|
||||||
|
subprocess.check_call([
|
||||||
|
"rdiff-backup",
|
||||||
|
"--exclude", backup_dir,
|
||||||
|
env["STORAGE_ROOT"],
|
||||||
|
rdiff_backup_dir])
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Start services.
|
||||||
|
subprocess.check_call(["service", "dovecot", "start"])
|
||||||
|
subprocess.check_call(["service", "postfix", "start"])
|
||||||
|
|
||||||
|
# Tar the rdiff-backup directory into a single file encrypted using the backup private key.
|
||||||
|
os.system(
|
||||||
|
"tar -zcC %s . | openssl enc -aes-256-cbc -a -salt -in /dev/stdin -out %s -pass file:%s"
|
||||||
|
%
|
||||||
|
( rdiff_backup_dir,
|
||||||
|
os.path.join(backup_dir, "latest.tgz.enc"),
|
||||||
|
os.path.join(backup_dir, "secret_key.txt"),
|
||||||
|
))
|
||||||
|
|
||||||
|
# The backup can be decrypted with:
|
||||||
|
# openssl enc -d -aes-256-cbc -a -in latest.tgz.enc -out /dev/stdout -pass file:secret_key.txt | tar -z
|
|
@ -5,13 +5,11 @@ import os, os.path
|
||||||
from flask import Flask, request, render_template
|
from flask import Flask, request, render_template
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
# Load settings from /etc/mailinabox.conf.
|
import utils
|
||||||
env = { }
|
|
||||||
for line in open("/etc/mailinabox.conf"): env.setdefault(*line.strip().split("=", 1))
|
|
||||||
env["CONF_DIR"] = os.path.join(os.path.dirname(__file__), "../conf")
|
|
||||||
|
|
||||||
from mailconfig import get_mail_users, add_mail_user, set_mail_password, remove_mail_user, get_mail_aliases, get_mail_domains, add_mail_alias, remove_mail_alias
|
from mailconfig import get_mail_users, add_mail_user, set_mail_password, remove_mail_user, get_mail_aliases, get_mail_domains, add_mail_alias, remove_mail_alias
|
||||||
|
|
||||||
|
env = utils.load_environment()
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
def index():
|
def index():
|
||||||
return render_template('index.html')
|
return render_template('index.html')
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
def load_environment():
|
||||||
|
# Load settings from /etc/mailinabox.conf.
|
||||||
|
import os.path
|
||||||
|
env = { }
|
||||||
|
for line in open("/etc/mailinabox.conf"): env.setdefault(*line.strip().split("=", 1))
|
||||||
|
env["CONF_DIR"] = os.path.join(os.path.dirname(__file__), "../conf")
|
||||||
|
return env
|
||||||
|
|
||||||
|
def exclusive_process(name):
|
||||||
|
# Ensure that a process named `name` does not execute multiple
|
||||||
|
# times concurrently.
|
||||||
|
import os, sys, atexit
|
||||||
|
pidfile = '/var/run/mailinabox-%s.pid' % name
|
||||||
|
mypid = os.getpid()
|
||||||
|
|
||||||
|
# Attempt to get a lock on ourself so that the concurrency check
|
||||||
|
# itself is not executed in parallel.
|
||||||
|
with open(__file__, 'r+') as flock:
|
||||||
|
# Try to get a lock. This blocks until a lock is acquired. The
|
||||||
|
# lock is held until the flock file is closed at the end of the
|
||||||
|
# with block.
|
||||||
|
os.lockf(flock.fileno(), os.F_LOCK, 0)
|
||||||
|
|
||||||
|
# While we have a lock, look at the pid file. First attempt
|
||||||
|
# to write our pid to a pidfile if no file already exists there.
|
||||||
|
try:
|
||||||
|
with open(pidfile, 'x') as f:
|
||||||
|
# Successfully opened a new file. Since the file is new
|
||||||
|
# there is no concurrent process. Write our pid.
|
||||||
|
f.write(str(mypid))
|
||||||
|
atexit.register(clear_my_pid, pidfile)
|
||||||
|
return
|
||||||
|
except FileExistsError:
|
||||||
|
# The pid file already exixts, but it may contain a stale
|
||||||
|
# pid of a terminated process.
|
||||||
|
with open(pidfile, 'r+') as f:
|
||||||
|
# Read the pid in the file.
|
||||||
|
existing_pid = None
|
||||||
|
try:
|
||||||
|
existing_pid = int(f.read().strip())
|
||||||
|
except ValueError:
|
||||||
|
pass # No valid integer in the file.
|
||||||
|
|
||||||
|
# Check if the pid in it is valid.
|
||||||
|
if existing_pid:
|
||||||
|
if is_pid_valid(existing_pid):
|
||||||
|
print("Another %s is already running (pid %d)." % (name, existing_pid), file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Write our pid.
|
||||||
|
f.seek(0)
|
||||||
|
f.write(str(mypid))
|
||||||
|
f.truncate()
|
||||||
|
atexit.register(clear_my_pid, pidfile)
|
||||||
|
|
||||||
|
|
||||||
|
def clear_my_pid(pidfile):
|
||||||
|
import os
|
||||||
|
os.unlink(pidfile)
|
||||||
|
|
||||||
|
|
||||||
|
def is_pid_valid(pid):
|
||||||
|
"""Checks whether a pid is a valid process ID of a currently running process."""
|
||||||
|
# adapted from http://stackoverflow.com/questions/568271/how-to-check-if-there-exists-a-process-with-a-given-pid
|
||||||
|
import os, errno
|
||||||
|
if pid <= 0: raise ValueError('Invalid PID.')
|
||||||
|
try:
|
||||||
|
os.kill(pid, 0)
|
||||||
|
except OSError as err:
|
||||||
|
if err.errno == errno.ESRCH: # No such process
|
||||||
|
return False
|
||||||
|
elif err.errno == errno.EPERM: # Not permitted to send signal
|
||||||
|
return True
|
||||||
|
else: # EINVAL
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
return True
|
|
@ -2,7 +2,13 @@
|
||||||
|
|
||||||
source setup/functions.sh
|
source setup/functions.sh
|
||||||
|
|
||||||
apt_install python3-flask links
|
apt_install python3-flask links rdiff-backup
|
||||||
|
|
||||||
|
# Create a backup directory and a random key for encrypting backups.
|
||||||
|
mkdir -p $STORAGE_ROOT/backup
|
||||||
|
if [ ! -f $STORAGE_ROOT/backup/secret_key.txt ]; then
|
||||||
|
openssl rand -base64 2048 > $STORAGE_ROOT/backup/secret_key.txt
|
||||||
|
fi
|
||||||
|
|
||||||
# Link the management server daemon into a well known location.
|
# Link the management server daemon into a well known location.
|
||||||
rm -f /usr/bin/mailinabox-daemon
|
rm -f /usr/bin/mailinabox-daemon
|
||||||
|
|
Loading…
Reference in New Issue