1
0
mirror of https://github.com/mail-in-a-box/mailinabox.git synced 2024-11-22 02:17:26 +00:00

show the status of backups in the control panel

This commit is contained in:
Joshua Tauberer 2014-09-01 13:06:38 +00:00
parent 4ec6692f21
commit 3853e8dd93
5 changed files with 224 additions and 84 deletions

View File

@ -9,14 +9,66 @@
# backup/secret_key.txt) to STORAGE_ROOT/backup/encrypted. # backup/secret_key.txt) to STORAGE_ROOT/backup/encrypted.
# 5) STORAGE_ROOT/backup/after-backup is executd if it exists. # 5) STORAGE_ROOT/backup/after-backup is executd if it exists.
import sys, os, os.path, shutil, glob import os, os.path, shutil, glob, re, datetime
import dateutil.parser, dateutil.relativedelta, dateutil.tz
from utils import exclusive_process, load_environment, shell from utils import exclusive_process, load_environment, shell
# settings # settings
full_backup = "--full" in sys.argv
keep_backups_for = "31D" # destroy backups older than 31 days except the most recent full backup keep_backups_for = "31D" # destroy backups older than 31 days except the most recent full backup
def backup_status(env):
# What is the current status of backups?
# Loop through all of the files in STORAGE_ROOT/backup/duplicity to
# get a list of all of the backups taken and sum up file sizes to
# see how large the storage is.
now = datetime.datetime.now(dateutil.tz.tzlocal())
def reldate(date):
rd = dateutil.relativedelta.relativedelta(now, date)
if rd.days >= 7: return "%d days" % rd.days
if rd.days > 1: return "%d days, %d hours" % (rd.days, rd.hours)
if rd.days == 1: return "%d day, %d hours" % (rd.days, rd.hours)
return "%d hours, %d minutes" % (rd.hours, rd.minutes)
backups = { }
basedir = os.path.join(env['STORAGE_ROOT'], 'backup/duplicity/')
encdir = os.path.join(env['STORAGE_ROOT'], 'backup/encrypted/')
for fn in os.listdir(basedir):
m = re.match(r"duplicity-(full|full-signatures|(inc|new-signatures)\.(?P<incbase>\d+T\d+Z)\.to)\.(?P<date>\d+T\d+Z)\.", fn)
if not m: raise ValueError(fn)
key = m.group("date")
if key not in backups:
date = dateutil.parser.parse(m.group("date"))
backups[key] = {
"date": m.group("date"),
"date_str": date.strftime("%x %X"),
"date_delta": reldate(date),
"full": m.group("incbase") is None,
"previous": m.group("incbase") is None,
"size": 0,
"encsize": 0,
}
backups[key]["size"] += os.path.getsize(os.path.join(basedir, fn))
# Also check encrypted size.
encfn = os.path.join(encdir, fn + ".enc")
if os.path.exists(encfn):
backups[key]["encsize"] += os.path.getsize(encfn)
backups = sorted(backups.values(), key = lambda b : b["date"], reverse=True)
return {
"directory": basedir,
"encpwfile": os.path.join(env['STORAGE_ROOT'], 'backup/secret_key.txt'),
"encdirectory": encdir,
"tz": now.tzname(),
"backups": backups,
}
def perform_backup(full_backup):
env = load_environment() env = load_environment()
exclusive_process("backup") exclusive_process("backup")
@ -117,3 +169,8 @@ if os.path.exists(post_script):
shell('check_call', shell('check_call',
['su', env['STORAGE_USER'], '-c', post_script], ['su', env['STORAGE_USER'], '-c', post_script],
env=env) env=env)
if __name__ == "__main__":
import sys
full_backup = "--full" in sys.argv
perform_backup(full_backup)

View File

@ -250,6 +250,12 @@ def do_updates():
"DEBIAN_FRONTEND": "noninteractive" "DEBIAN_FRONTEND": "noninteractive"
}) })
@app.route('/system/backup/status')
@authorized_personnel_only
def backup_status():
from backup import backup_status
return json_response(backup_status(env))
# APP # APP
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -85,7 +85,10 @@
<a href="#" class="dropdown-toggle" data-toggle="dropdown">System <b class="caret"></b></a> <a href="#" class="dropdown-toggle" data-toggle="dropdown">System <b class="caret"></b></a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a href="#system_status" onclick="return show_panel(this);">Status Checks</a></li> <li><a href="#system_status" onclick="return show_panel(this);">Status Checks</a></li>
<li><a href="#system_dns" onclick="return show_panel(this);">DNS (Advanced)</a></li> <li><a href="#system_backup" onclick="return show_panel(this);">Backup Status</a></li>
<li class="divider"></li>
<li class="dropdown-header">Super Advanced Options</li>
<li><a href="#system_dns" onclick="return show_panel(this);">DNS (Custom/External)</a></li>
</ul> </ul>
</li> </li>
<li class="dropdown active"> <li class="dropdown active">
@ -115,6 +118,10 @@
{% include "system-status.html" %} {% include "system-status.html" %}
</div> </div>
<div id="panel_system_backup" class="container panel">
{% include "system-backup.html" %}
</div>
<div id="panel_system_dns" class="container panel"> <div id="panel_system_dns" class="container panel">
{% include "system-dns.html" %} {% include "system-dns.html" %}
</div> </div>

View File

@ -0,0 +1,70 @@
<style>
#backup-status tr.full-backup td { font-weight: bold; }
</style>
<h2>Backup Status</h2>
<p>The box makes an incremental backup each night. The backup is stored on the machine itself. You are responsible for copying the backup files off of the machine. Many cloud providers make this easy by allowing you to take snapshots of the machine's disk.</p>
<h3>Copying Backup Files</h3>
<p>Use SFTP (FTP over SSH) to copy files from <tt id="backup-location"></tt>. These files are encrpyted, so they are safe to store anywhere. Copy the encryption password from <tt id="backup-encpassword-file"></tt> also but keep it in a safe location.</p>
<h3>Current Backups</h3>
<p>The backup directory currently contains the backups listed below. The total size on disk of the backups is <span id="backup-total-size"></span>.</p>
<table id="backup-status" class="table" style="width: auto">
<thead>
<th colspan="2">When</th>
<th>Type</th>
<th>Size</th>
</thead>
<tbody>
</tbody>
</table>
<p><small>The size column in the table indicates the size of the encrpyted backup, but the total size on disk shown above includes storage for unencrpyted intermediate files.</small></p>
<script>
function nice_size(bytes) {
var powers = ['bytes', 'KB', 'MB', 'GB', 'TB'];
while (true) {
if (powers.length == 1) break;
if (bytes < 1000) break;
bytes /= 1024;
powers.shift();
}
return (Math.round(bytes*10)/10) + " " + powers[0];
}
function show_system_backup() {
$('#backup-status tbody').html("<tr><td colspan='2' class='text-muted'>Loading...</td></tr>")
api(
"/system/backup/status",
"GET",
{ },
function(r) {
$('#backup-location').text(r.encdirectory);
$('#backup-encpassword-file').text(r.encpwfile);
$('#backup-status tbody').html("");
var total_disk_size = 0;
for (var i = 0; i < r.backups.length; i++) {
var b = r.backups[i];
var tr = $('<tr/>');
if (b.full) tr.addClass("full-backup");
tr.append( $('<td/>').text(b.date_str + " " + r.tz) );
tr.append( $('<td/>').text(b.date_delta + " ago") );
tr.append( $('<td/>').text(b.full ? "full" : "incremental") );
tr.append( $('<td style="text-align: right"/>').text( nice_size(b.encsize)) );
$('#backup-status tbody').append(tr);
total_disk_size += b.size;
total_disk_size += b.encsize;
}
$('#backup-total-size').text(nice_size(total_disk_size));
})
}
</script>

View File

@ -2,7 +2,7 @@
source setup/functions.sh source setup/functions.sh
apt_install python3-flask links duplicity libyaml-dev python3-dnspython apt_install python3-flask links duplicity libyaml-dev python3-dnspython python3-dateutil
hide_output pip3 install rtyaml hide_output pip3 install rtyaml
# Create a backup directory and a random key for encrypting backups. # Create a backup directory and a random key for encrypting backups.