From 75295f95acdcd9390f512309ca94ec7bb944b7bb Mon Sep 17 00:00:00 2001 From: Anish Moorthy Date: Sun, 9 Nov 2025 15:06:28 -0800 Subject: [PATCH] Catch and log any errors from backup_status() calls in status_checks.py Duplicity seems to have pushed a broken release [1]. This doesn't fix that, but it unbreaks the status page (where the actual error can be read off). Side note: my editor seems to have stripped trailing whitespace on a couple of lines. Keeping it in the patch because I'm lazy [1] https://discourse.mailinabox.email/t/duplicity-error-nov-2025/16061/2 --- management/backup.py | 7 ++++++- management/status_checks.py | 22 +++++++++++++--------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/management/backup.py b/management/backup.py index 8cc2b375..5da69db2 100755 --- a/management/backup.py +++ b/management/backup.py @@ -17,6 +17,11 @@ from utils import load_environment, shell, wait_for_service import operator def backup_status(env): + """ + TODO: Document what the return value actually is. + + Will raise an exception if the call to the duplicity binary fails. + """ # If backups are disabled, return no status. config = get_backup_config(env) if config["target"] == "off": @@ -65,7 +70,7 @@ def backup_status(env): get_duplicity_target_url(config) ], get_duplicity_env_vars(env), - trap=True) + trap=True, capture_stderr=True) if code != 0: # Command failed. This is likely due to an improperly configured remote # destination for the backups or the last backup job terminated unexpectedly. diff --git a/management/status_checks.py b/management/status_checks.py index e4996542..0cc32bad 100755 --- a/management/status_checks.py +++ b/management/status_checks.py @@ -266,26 +266,30 @@ def check_free_memory(rounded_values, env, output): def check_backup(rounded_values, env, output): # Check backups backup_config = get_backup_config(env, for_ui=True) - + # Is the backup enabled? if backup_config.get("target", "off") == "off": output.print_warning("Backups are disabled. It is recommended to enable a backup for your box.") return else: output.print_ok("Backups are enabled") - + # Get the age of the most recent backup - backup_stat = backup_status(env) - + try: + backup_stat = backup_status(env) + except Exception as e: + output.print_error(f"Failed to obtain backup status: {e}") + return + backups = backup_stat.get("backups", {}) if backups and len(backups) > 0: most_recent = backups[0]["date"] - + # Calculate time between most recent backup and current time now = datetime.datetime.now(dateutil.tz.tzlocal()) bk_date = dateutil.parser.parse(most_recent).astimezone(dateutil.tz.tzlocal()) bk_age = dateutil.relativedelta.relativedelta(now, bk_date) - + if bk_age.days > 7: output.print_error("Backup is more than a week old") else: @@ -584,11 +588,11 @@ def check_dns_zone(domain, env, output, dns_zonefiles): continue # Choose the first IP if nameserver returns multiple ns_ip = ns_ips.split('; ')[0] - + # No need to check if we could not obtain the SOA record if SOARecord == '[timeout]': checkSOA = False - else: + else: checkSOA = True # Now query it to see what it says about this domain. @@ -801,7 +805,7 @@ def check_mail_domain(domain, env, output): # Stop if the domain is listed in the Spamhaus Domain Block List. # The user might have chosen a domain that was previously in use by a spammer # and will not be able to reliably send mail. - + # See https://www.spamhaus.org/news/article/807/using-our-public-mirrors-check-your-return-codes-now. for # information on spamhaus return codes dbl = query_dns(domain+'.dbl.spamhaus.org', "A", nxdomain=None)