1
0
mirror of https://github.com/mail-in-a-box/mailinabox.git synced 2026-04-20 00:27:23 +02:00

merge upstream jelly branch

This commit is contained in:
KiekerJan
2022-06-19 22:39:36 +02:00
14 changed files with 121 additions and 66 deletions

View File

@@ -12,12 +12,7 @@ import dateutil.parser, dateutil.relativedelta, dateutil.tz
import rtyaml
from exclusiveprocess import Lock
from utils import load_environment, shell, wait_for_service, fix_boto, get_php_version
rsync_ssh_options = [
"--ssh-options= -i /root/.ssh/id_rsa_miab",
"--rsync-options= -e \"/usr/bin/ssh -oStrictHostKeyChecking=no -oBatchMode=yes -p 22 -i /root/.ssh/id_rsa_miab\"",
]
from utils import load_environment, shell, wait_for_service, get_php_version
def backup_status(env):
# If backups are disabled, return no status.
@@ -64,9 +59,9 @@ def backup_status(env):
"--archive-dir", backup_cache_dir,
"--gpg-options", "--cipher-algo=AES256",
"--log-fd", "1",
config["target"],
] + rsync_ssh_options,
get_env(env),
get_duplicity_target_url(config),
] + get_duplicity_additional_args(env),
get_duplicity_env_vars(env),
trap=True)
if code != 0:
# Command failed. This is likely due to an improperly configured remote
@@ -195,7 +190,43 @@ def get_passphrase(env):
return passphrase
def get_env(env):
def get_duplicity_target_url(config):
target = config["target"]
if get_target_type(config) == "s3":
from urllib.parse import urlsplit, urlunsplit
target = list(urlsplit(target))
# Although we store the S3 hostname in the target URL,
# duplicity no longer accepts it in the target URL. The hostname in
# the target URL must be the bucket name. The hostname is passed
# via get_duplicity_additional_args. Move the first part of the
# path (the bucket name) into the hostname URL component, and leave
# the rest for the path.
target[1], target[2] = target[2].lstrip('/').split('/', 1)
target = urlunsplit(target)
return target
def get_duplicity_additional_args(env):
config = get_backup_config(env)
if get_target_type(config) == 'rsync':
return [
"--ssh-options= -i /root/.ssh/id_rsa_miab",
"--rsync-options= -e \"/usr/bin/ssh -oStrictHostKeyChecking=no -oBatchMode=yes -p 22 -i /root/.ssh/id_rsa_miab\"",
]
elif get_target_type(config) == 's3':
# See note about hostname in get_duplicity_target_url.
from urllib.parse import urlsplit, urlunsplit
target = urlsplit(config["target"])
endpoint_url = urlunsplit(("https", target.netloc, '', '', ''))
return ["--s3-endpoint-url", endpoint_url]
return []
def get_duplicity_env_vars(env):
config = get_backup_config(env)
env = { "PASSPHRASE" : get_passphrase(env) }
@@ -274,10 +305,10 @@ def perform_backup(full_backup):
"--volsize", "250",
"--gpg-options", "--cipher-algo=AES256",
env["STORAGE_ROOT"],
config["target"],
get_duplicity_target_url(config),
"--allow-source-mismatch"
] + rsync_ssh_options,
get_env(env))
] + get_duplicity_additional_args(env),
get_duplicity_env_vars(env))
finally:
# Start services again.
service_command("dovecot", "start", quit=False)
@@ -293,9 +324,9 @@ def perform_backup(full_backup):
"--verbosity", "error",
"--archive-dir", backup_cache_dir,
"--force",
config["target"]
] + rsync_ssh_options,
get_env(env))
get_duplicity_target_url(config)
] + get_duplicity_additional_args(env),
get_duplicity_env_vars(env))
# From duplicity's manual:
# "This should only be necessary after a duplicity session fails or is
@@ -308,9 +339,9 @@ def perform_backup(full_backup):
"--verbosity", "error",
"--archive-dir", backup_cache_dir,
"--force",
config["target"]
] + rsync_ssh_options,
get_env(env))
get_duplicity_target_url(config)
] + get_duplicity_additional_args(env),
get_duplicity_env_vars(env))
# Change ownership of backups to the user-data user, so that the after-bcakup
# script can access them.
@@ -346,9 +377,9 @@ def run_duplicity_verification():
"--compare-data",
"--archive-dir", backup_cache_dir,
"--exclude", backup_root,
config["target"],
get_duplicity_target_url(config),
env["STORAGE_ROOT"],
] + rsync_ssh_options, get_env(env))
] + get_duplicity_additional_args(env), get_duplicity_env_vars(env))
def run_duplicity_restore(args):
env = load_environment()
@@ -358,9 +389,9 @@ def run_duplicity_restore(args):
"/usr/bin/duplicity",
"restore",
"--archive-dir", backup_cache_dir,
config["target"],
] + rsync_ssh_options + args,
get_env(env))
get_duplicity_target_url(config),
] + get_duplicity_additional_args(env) + args,
get_duplicity_env_vars(env))
def list_target_files(config):
import urllib.parse
@@ -417,7 +448,6 @@ def list_target_files(config):
elif target.scheme == "s3":
# match to a Region
fix_boto() # must call prior to importing boto
import boto.s3
from boto.exception import BotoServerError
custom_region = False

View File

@@ -121,7 +121,6 @@ def index():
no_users_exist = (len(get_mail_users(env)) == 0)
no_admins_exist = (len(get_admins(env)) == 0)
utils.fix_boto() # must call prior to importing boto
import boto.s3
backup_s3_hosts = [(r.name, r.endpoint) for r in boto.s3.regions()]

View File

@@ -58,36 +58,33 @@ def get_ssl_certificates(env):
# Not a valid PEM format for a PEM type we care about.
continue
# Remember where we got this object.
pem._filename = fn
# Is it a private key?
if isinstance(pem, RSAPrivateKey):
private_keys[pem.public_key().public_numbers()] = pem
private_keys[pem.public_key().public_numbers()] = { "filename": fn, "key": pem }
# Is it a certificate?
if isinstance(pem, Certificate):
certificates.append(pem)
certificates.append({ "filename": fn, "cert": pem })
# Process the certificates.
domains = { }
for cert in certificates:
# What domains is this certificate good for?
cert_domains, primary_domain = get_certificate_domains(cert)
cert._primary_domain = primary_domain
cert_domains, primary_domain = get_certificate_domains(cert["cert"])
cert["primary_domain"] = primary_domain
# Is there a private key file for this certificate?
private_key = private_keys.get(cert.public_key().public_numbers())
private_key = private_keys.get(cert["cert"].public_key().public_numbers())
if not private_key:
continue
cert._private_key = private_key
cert["private_key"] = private_key
# Add this cert to the list of certs usable for the domains.
for domain in cert_domains:
# The primary hostname can only use a certificate mapped
# to the system private key.
if domain == env['PRIMARY_HOSTNAME']:
if cert._private_key._filename != os.path.join(env['STORAGE_ROOT'], 'ssl', 'ssl_private_key.pem'):
if cert["private_key"]["filename"] != os.path.join(env['STORAGE_ROOT'], 'ssl', 'ssl_private_key.pem'):
continue
domains.setdefault(domain, []).append(cert)
@@ -100,10 +97,10 @@ def get_ssl_certificates(env):
#for c in cert_list: print(domain, c.not_valid_before, c.not_valid_after, "("+str(now)+")", c.issuer, c.subject, c._filename)
cert_list.sort(key = lambda cert : (
# must be valid NOW
cert.not_valid_before <= now <= cert.not_valid_after,
cert["cert"].not_valid_before <= now <= cert["cert"].not_valid_after,
# prefer one that is not self-signed
cert.issuer != cert.subject,
cert["cert"].issuer != cert["cert"].subject,
###########################################################
# The above lines ensure that valid certificates are chosen
@@ -113,7 +110,7 @@ def get_ssl_certificates(env):
# prefer one with the expiration furthest into the future so
# that we can easily rotate to new certs as we get them
cert.not_valid_after,
cert["cert"].not_valid_after,
###########################################################
# We always choose the certificate that is good for the
@@ -128,15 +125,15 @@ def get_ssl_certificates(env):
# in case a certificate is installed in multiple paths,
# prefer the... lexicographically last one?
cert._filename,
cert["filename"],
), reverse=True)
cert = cert_list.pop(0)
ret[domain] = {
"private-key": cert._private_key._filename,
"certificate": cert._filename,
"primary-domain": cert._primary_domain,
"certificate_object": cert,
"private-key": cert["private_key"]["filename"],
"certificate": cert["filename"],
"primary-domain": cert["primary_domain"],
"certificate_object": cert["cert"],
}
return ret

View File

@@ -255,6 +255,18 @@ def check_free_disk_space(rounded_values, env, output):
if rounded_values: disk_msg = "The disk has less than 15% free space."
output.print_error(disk_msg)
# Check that there's only one duplicity cache. If there's more than one,
# it's probably no longer in use, and we can recommend clearing the cache
# to save space. The cache directory may not exist yet, which is OK.
backup_cache_path = os.path.join(env['STORAGE_ROOT'], 'backup/cache')
try:
backup_cache_count = len(os.listdir(backup_cache_path))
except:
backup_cache_count = 0
if backup_cache_count > 1:
output.print_warning("The backup cache directory {} has more than one backup target cache. Consider clearing this directory to save disk space."
.format(backup_cache_path))
def check_free_memory(rounded_values, env, output):
# Check free memory.
percent_free = 100 - psutil.virtual_memory().percent
@@ -660,7 +672,7 @@ def check_dnssec(domain, env, output, dns_zonefiles, is_checking_primary=False):
if len(ds) > 0:
output.print_line("")
output.print_line("The DS record is currently set to:")
for rr in ds:
for rr in sorted(ds):
output.print_line("Key Tag: {0}, Algorithm: {1}, Digest Type: {2}, Digest: {3}".format(*rr))
def check_mail_domain(domain, env, output):

View File

@@ -175,13 +175,6 @@ def wait_for_service(port, public, env, timeout):
return False
time.sleep(min(timeout/4, 1))
def fix_boto():
# Google Compute Engine instances install some Python-2-only boto plugins that
# conflict with boto running under Python 3. Disable boto's default configuration
# file prior to importing boto so that GCE's plugin is not loaded:
import os
os.environ["BOTO_CONFIG"] = "/etc/boto3.cfg"
def get_php_version():
# Gets the version of PHP installed in the system.
return shell("check_output", ["/usr/bin/php", "-v"])[4:7]