mirror of
https://github.com/mail-in-a-box/mailinabox.git
synced 2024-12-26 07:57:05 +00:00
a67a57913d
Software updates: * Upgraded Nextcloud from 17.0.6 to 20.0.1 (with Contacts from 3.3.0 to 3.4.1 and Calendar from 2.0.3 to 2.1.2) * Upgraded Roundcube to version 1.4.9. Mail: * The MTA-STA max_age value was increased to the normal one week. Control Panel: * Two-factor authentication can now be enabled for logins to the control panel. However, keep in mind that many online services (including domain name registrars, cloud server providers, and TLS certificate providers) may allow an attacker to take over your account or issue a fraudulent TLS certificate with only access to your email address, and this new two-factor authentication does not protect access to your inbox. It therefore remains very important that user accounts with administrative email addresses have strong passwords. * TLS certificate expiry dates are now shown in ISO8601 format for clarity. -----BEGIN PGP SIGNATURE----- iQFDBAABCgAtFiEEX0wOcxPM10RpOyrquSBB9MEL3YEFAl+v8k4PHGp0QG9jY2Ft cy5pbmZvAAoJELkgQfTBC92BMYUIAJTD1iKzY1SoDNSp8JMPn2sWusOnJNrnvYEV vsrBM4AzwJv3DIZKSkYCitbTQW2FsTcjF6Jl5PCavEmAGe55AIKAPM/52Uq6jqDE aR8EZvI9ca1i7yR7DOHEI043QSPmp/iCFD48vvmKgN/LZy67TaHaOlGJbc3nfpk0 y7ejMpF/6RP6ik4snnRQoWTFShaOpB9WcEVnUO7CHZdWcpSCZ55c9yi6A6ExGk7e 97R5+JN1MgOdZ6rzWZuMWiz7EZ/Ew4jYLZpOwg8qJm0HNbYJ6+/xxsQBwaQzyBw3 TsTl4GmunNPfoNrmKdJeLy0sBwiVBv/rysjWjim5v8jAYBoKoUQ= =2oRU -----END PGP SIGNATURE----- gpgsig -----BEGIN PGP SIGNATURE----- iQIzBAABCAAdFiEEAKK/toPAcMkE+dinLzJ3OKPArjoFAl+xc7sACgkQLzJ3OKPA rjo6Zw//eYyTBlfQfFHIsLYKxJbwh6fDrIG6/Za6898cPhkJ/ugBeJlNEyT/EjpU MvtIgEU9xbG/tjsnQXsgAXJ6s7ZWm1QB5D+wqUIEeAFUn5IkCnXo0wPZJhSTNZhD 4InnWsicYZj/ByuSH179xHyTAx2uYDBbPT4HjUlzIsaopvWOKLvAfzY3r42AiNvZ e79MhKbtOs9kDkrB2LULRzz6WzJDKb11fJccf7UaBerwFvOarMr8hSpOysK0ocHk H0wbrGxjb8iBjczVP4OFh36satQ5l4B1W+QVIxZG9ufVAOe3dhv8HngaHqAVyUgF gWjDYTnL/anoMMew+kbn2sjeKH6m2ZA+u9g+mDyMGSECVVYhkpOpcbPjZlmlNAQN C5BHmHltIg90uicrhzEEPFDBR1JF7JrYO42EwnOWMwjhzRkH2cepVw86lDr+pbrH s3hvoWiFFt7cs5ShCpgZDL20ey1e+9wL6b72Qlo7ls7MK3vfZvLPxJLpTi+bnymD CNt82Mjpu3BrhjCIGp+px9E2JU/7wUwqyUbgWFtyqxCdJOZXA4ZXVtDs5pQFzhug G+Z1HxFmhxck17SD0uHhXJKRD8IRttnO5sBESJaLNB4Ws/KspHVPePNskB/1XSfr pFOqikZsoKOICZnpd/eTnUlciqFygqvB0WuFsJNttQN2dBpJViA= =ZMFZ -----END PGP SIGNATURE----- Merge upstream v0.51
342 lines
14 KiB
HTML
342 lines
14 KiB
HTML
<style>
|
|
#backup-status th { text-align: center; }
|
|
#backup-status tr.full-backup td { font-weight: bold; }
|
|
</style>
|
|
|
|
<h2>Backup Status</h2>
|
|
|
|
<p>The box makes an incremental backup each night. By default the backup is stored on the machine itself, but you can also have it stored on Amazon S3.</p>
|
|
|
|
<h3>Configuration</h3>
|
|
|
|
<form class="form-horizontal" role="form" onsubmit="set_custom_backup(); return false;">
|
|
<div class="form-group">
|
|
<label for="backup-target-type" class="col-sm-2 control-label">Backup to:</label>
|
|
<div class="col-sm-3">
|
|
<select class="form-control" rows="1" id="backup-target-type" onchange="toggle_form()">
|
|
<option value="off">Nowhere (Disable Backups)</option>
|
|
<option value="local">{{hostname}}</option>
|
|
<option value="rsync">rsync</option>
|
|
<option value="s3">Amazon S3</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<!-- LOCAL BACKUP -->
|
|
<div class="form-group backup-target-local">
|
|
<div class="col-sm-10 col-sm-offset-2">
|
|
<p>Backups are stored on this machine’s own hard disk. You are responsible for periodically using SFTP (FTP over SSH) to copy the backup files from <tt class="backup-location"></tt> to a safe location. These files are encrypted, so they are safe to store anywhere.</p>
|
|
<p>Separately copy the encryption password from <tt class="backup-encpassword-file"></tt> to a safe and secure location. You will need this file to decrypt backup files.</p>
|
|
</div>
|
|
</div>
|
|
<!-- RSYNC BACKUP -->
|
|
<div class="form-group backup-target-rsync">
|
|
<div class="col-sm-10 col-sm-offset-2">
|
|
|
|
<p>Backups synced to a remote machine using rsync over SSH, with local
|
|
copies in <tt class="backup-location"></tt>. These files are encrypted, so
|
|
they are safe to store anywhere.</p> <p>Separately copy the encryption
|
|
password from <tt class="backup-encpassword-file"></tt> to a safe and
|
|
secure location. You will need this file to decrypt backup files.</p>
|
|
|
|
</div>
|
|
</div>
|
|
<div class="form-group backup-target-rsync">
|
|
<label for="backup-target-rsync-host" class="col-sm-2 control-label">Hostname</label>
|
|
<div class="col-sm-8">
|
|
<input type="text" placeholder="hostname.local" class="form-control" rows="1" id="backup-target-rsync-host">
|
|
</div>
|
|
</div>
|
|
<div class="form-group backup-target-rsync">
|
|
<label for="backup-target-rsync-path" class="col-sm-2 control-label">Path</label>
|
|
<div class="col-sm-8">
|
|
<input type="text" placeholder="/backups/{{hostname}}" class="form-control" rows="1" id="backup-target-rsync-path">
|
|
</div>
|
|
</div>
|
|
<div class="form-group backup-target-rsync">
|
|
<label for="backup-target-rsync-user" class="col-sm-2 control-label">Username</label>
|
|
<div class="col-sm-8">
|
|
<input type="text" class="form-control" rows="1" id="backup-target-rsync-user">
|
|
</div>
|
|
</div>
|
|
<div class="form-group backup-target-rsync">
|
|
<label for="ssh-pub-key" class="col-sm-2 control-label">Public SSH Key</label>
|
|
<div class="col-sm-8">
|
|
<input type="text" class="form-control" rows="1" id="ssh-pub-key" readonly>
|
|
<div class="small" style="margin-top: 2px">
|
|
Copy the Public SSH Key above, and paste it within the <tt>~/.ssh/authorized_keys</tt>
|
|
of target user on the backup server specified above. That way you'll enable secure and
|
|
passwordless authentication from your mail-in-a-box server and your backup server.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- S3 BACKUP -->
|
|
<div class="form-group backup-target-s3">
|
|
<div class="col-sm-10 col-sm-offset-2">
|
|
<p>Backups are stored in an Amazon Web Services S3 bucket. You must have an AWS account already.</p>
|
|
<p>You MUST manually copy the encryption password from <tt class="backup-encpassword-file"></tt> to a safe and secure location. You will need this file to decrypt backup files. It is NOT stored in your Amazon S3 bucket.</p>
|
|
</div>
|
|
</div>
|
|
<div class="form-group backup-target-s3">
|
|
<label for="backup-target-s3-host-select" class="col-sm-2 control-label">S3 Region</label>
|
|
<div class="col-sm-8">
|
|
<select class="form-control" rows="1" id="backup-target-s3-host-select">
|
|
{% for name, host in backup_s3_hosts %}
|
|
<option value="{{host}}">{{name}}</option>
|
|
{% endfor %}
|
|
<option value="other">Other</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="form-group backup-target-s3">
|
|
<label for="backup-target-s3-host" class="col-sm-2 control-label">S3 Host / Endpoint</label>
|
|
<div class="col-sm-8">
|
|
<input type="text" placeholder="Endpoint" class="form-control" rows="1" id="backup-target-s3-host">
|
|
</div>
|
|
</div>
|
|
<div class="form-group backup-target-s3">
|
|
<label for="backup-target-s3-path" class="col-sm-2 control-label">S3 Path</label>
|
|
<div class="col-sm-8">
|
|
<input type="text" placeholder="your-bucket-name/backup-directory" class="form-control" rows="1" id="backup-target-s3-path">
|
|
</div>
|
|
</div>
|
|
<div class="form-group backup-target-s3">
|
|
<label for="backup-target-user" class="col-sm-2 control-label">S3 Access Key</label>
|
|
<div class="col-sm-8">
|
|
<input type="text" class="form-control" rows="1" id="backup-target-user">
|
|
</div>
|
|
</div>
|
|
<div class="form-group backup-target-s3">
|
|
<label for="backup-target-pass" class="col-sm-2 control-label">S3 Secret Access Key</label>
|
|
<div class="col-sm-8">
|
|
<input type="text" class="form-control" rows="1" id="backup-target-pass">
|
|
</div>
|
|
</div>
|
|
<!-- Common -->
|
|
<div class="form-group backup-target-local backup-target-rsync backup-target-s3">
|
|
<label for="min-age" class="col-sm-2 control-label">Retention Days:</label>
|
|
<div class="col-sm-8">
|
|
<input type="number" class="form-control" rows="1" id="min-age">
|
|
<div class="small" style="margin-top: 2px">This is the minimum time backup data is kept for. The box makes an incremental backup most nights, which requires that previous backups back to the most recent full backup be preserved, so backup data is often kept much longer than this setting. Full backups are made periodically when the incremental backup data size exceeds a limit.</div>
|
|
</div>
|
|
</div>
|
|
<div class="form-group">
|
|
<div class="col-sm-offset-2 col-sm-10">
|
|
<button id="set-s3-backup-button" type="submit" class="btn btn-primary">Save</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
|
|
<h3>Available backups</h3>
|
|
|
|
<p>The backup location currently contains the backups listed below. The total size of the backups is currently <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>
|
|
<th>Deleted in...</th>
|
|
</thead>
|
|
<tbody>
|
|
</tbody>
|
|
</table>
|
|
|
|
<!-- Hide these buttons until we're sure we can use them :) -->
|
|
<button id="create-full-backup-button" class="btn btn-primary" onclick="do_backup(true)" style="display: none;">Create Full Backup Now</button>
|
|
<button id="create-incremental-backup-button" class="btn btn-primary" onclick="do_backup(false)" style="display: none;">Create Incremental Backup Now</button>
|
|
|
|
<script>
|
|
|
|
function toggle_form() {
|
|
var target_type = $("#backup-target-type").val();
|
|
$(".backup-target-local, .backup-target-rsync, .backup-target-s3").hide();
|
|
$(".backup-target-" + target_type).show();
|
|
|
|
init_inputs(target_type);
|
|
}
|
|
|
|
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();
|
|
}
|
|
// round to have three significant figures but at most one decimal place
|
|
if (bytes >= 100)
|
|
bytes = Math.round(bytes)
|
|
else
|
|
bytes = Math.round(bytes*10)/10;
|
|
return bytes + " " + powers[0];
|
|
}
|
|
|
|
function show_system_backup() {
|
|
show_custom_backup()
|
|
|
|
$('#backup-status tbody').html("<tr><td colspan='2' class='text-muted'>Loading...</td></tr>")
|
|
api(
|
|
"/system/backup/status",
|
|
"GET",
|
|
{ },
|
|
function(r) {
|
|
if (r.error) {
|
|
show_modal_error("Backup Error", $("<pre/>").text(r.error));
|
|
return;
|
|
}
|
|
|
|
$('#backup-status tbody').html("");
|
|
var total_disk_size = 0;
|
|
|
|
if (typeof r.backups == "undefined") {
|
|
var tr = $('<tr><td colspan="3">Backups are turned off.</td></tr>');
|
|
$('#backup-status tbody').append(tr);
|
|
$('#create-full-backup-button').css("display","none")
|
|
$('#create-incremental-backup-button').css("display","none")
|
|
return;
|
|
} else if (r.backups.length == 0) {
|
|
var tr = $('<tr><td colspan="3">No backups have been made yet.</td></tr>');
|
|
$('#backup-status tbody').append(tr);
|
|
}
|
|
|
|
// Backups ARE enabled.
|
|
$('#create-full-backup-button').css("display","unset")
|
|
$('#create-incremental-backup-button').css("display","unset")
|
|
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) );
|
|
tr.append( $('<td/>').text(b.date_delta + " ago") );
|
|
tr.append( $('<td/>').text(b.full ? "full" : "increment") );
|
|
tr.append( $('<td style="text-align: right"/>').text( nice_size(b.size)) );
|
|
if (b.deleted_in)
|
|
tr.append( $('<td/>').text(b.deleted_in) );
|
|
else
|
|
tr.append( $('<td class="text-muted">unknown</td>') );
|
|
$('#backup-status tbody').append(tr);
|
|
|
|
total_disk_size += b.size;
|
|
}
|
|
|
|
total_disk_size += r.unmatched_file_size;
|
|
$('#backup-total-size').text(nice_size(total_disk_size));
|
|
})
|
|
}
|
|
|
|
function show_custom_backup() {
|
|
$(".backup-target-local, .backup-target-rsync, .backup-target-s3").hide();
|
|
api(
|
|
"/system/backup/config",
|
|
"GET",
|
|
{ },
|
|
function(r) {
|
|
$("#backup-target-user").val(r.target_user);
|
|
$("#backup-target-pass").val(r.target_pass);
|
|
$("#min-age").val(r.min_age_in_days);
|
|
$(".backup-location").text(r.file_target_directory);
|
|
$(".backup-encpassword-file").text(r.enc_pw_file);
|
|
$("#ssh-pub-key").val(r.ssh_pub_key);
|
|
|
|
if (r.target == "file://" + r.file_target_directory) {
|
|
$("#backup-target-type").val("local");
|
|
} else if (r.target == "off") {
|
|
$("#backup-target-type").val("off");
|
|
} else if (r.target.substring(0, 8) == "rsync://") {
|
|
$("#backup-target-type").val("rsync");
|
|
var path = r.target.substring(8).split('//');
|
|
var host_parts = path.shift().split('@');
|
|
$("#backup-target-rsync-user").val(host_parts[0]);
|
|
$("#backup-target-rsync-host").val(host_parts[1]);
|
|
$("#backup-target-rsync-path").val('/'+path[0]);
|
|
} else if (r.target.substring(0, 5) == "s3://") {
|
|
$("#backup-target-type").val("s3");
|
|
var hostpath = r.target.substring(5).split('/');
|
|
var host = hostpath.shift();
|
|
$("#backup-target-s3-host").val(host);
|
|
$("#backup-target-s3-path").val(hostpath.join('/'));
|
|
}
|
|
toggle_form()
|
|
})
|
|
}
|
|
|
|
function set_custom_backup() {
|
|
var target_type = $("#backup-target-type").val();
|
|
var target_user = $("#backup-target-user").val();
|
|
var target_pass = $("#backup-target-pass").val();
|
|
|
|
var target;
|
|
if (target_type == "local" || target_type == "off")
|
|
target = target_type;
|
|
else if (target_type == "s3")
|
|
target = "s3://" + $("#backup-target-s3-host").val() + "/" + $("#backup-target-s3-path").val();
|
|
else if (target_type == "rsync") {
|
|
target = "rsync://" + $("#backup-target-rsync-user").val() + "@" + $("#backup-target-rsync-host").val()
|
|
+ "/" + $("#backup-target-rsync-path").val();
|
|
target_user = '';
|
|
}
|
|
|
|
|
|
var min_age = $("#min-age").val();
|
|
api(
|
|
"/system/backup/config",
|
|
"POST",
|
|
{
|
|
target: target,
|
|
target_user: target_user,
|
|
target_pass: target_pass,
|
|
min_age: min_age
|
|
},
|
|
function(r) {
|
|
// use .text() --- it's a text response, not html
|
|
show_modal_error("Backup configuration", $("<p/>").text(r), function() { if (r == "OK") show_system_backup(); }); // refresh after modal on success
|
|
},
|
|
function(r) {
|
|
// use .text() --- it's a text response, not html
|
|
show_modal_error("Backup configuration", $("<p/>").text(r));
|
|
});
|
|
return false;
|
|
}
|
|
|
|
function init_inputs(target_type) {
|
|
function set_host(host) {
|
|
if(host !== 'other') {
|
|
$("#backup-target-s3-host").val(host);
|
|
} else {
|
|
$("#backup-target-s3-host").val('');
|
|
}
|
|
}
|
|
if (target_type == "s3") {
|
|
$('#backup-target-s3-host-select').off('change').on('change', function() {
|
|
set_host($('#backup-target-s3-host-select').val());
|
|
});
|
|
set_host($('#backup-target-s3-host-select').val());
|
|
}
|
|
}
|
|
|
|
function do_backup(is_full) {
|
|
let disclaimer = "The backup process will pause some services (such as PHP, Postfix and Dovecot). Depending on the size of the data this can take a while."
|
|
if (!is_full) {
|
|
disclaimer += "\nDepending on the amount of incremental backups done after the last full backup, the box may decide to do a full backup instead."
|
|
}
|
|
show_modal_confirm("Warning!", disclaimer, "Start Backup", () => {
|
|
api(
|
|
"/system/backup/new",
|
|
"POST",
|
|
{
|
|
full: is_full
|
|
},
|
|
function(r) {
|
|
// use .text() --- it's a text response, not html
|
|
show_modal_error("Backup configuration", $("<p/>").text(r), function() { if (r == "OK") show_system_backup(); }); // refresh after modal on success
|
|
},
|
|
function(r) {
|
|
// use .text() --- it's a text response, not html
|
|
show_modal_error("Backup configuration", $("<p/>").text(r));
|
|
});
|
|
return false;
|
|
})
|
|
}
|
|
|
|
</script>
|