diff --git a/vrobbler/apps/scrobbles/tasks.py b/vrobbler/apps/scrobbles/tasks.py index 709693e..da09a1f 100644 --- a/vrobbler/apps/scrobbles/tasks.py +++ b/vrobbler/apps/scrobbles/tasks.py @@ -308,7 +308,10 @@ def _retention_files_to_delete(remote_files, now): def _run_remote_cleanup(ssh_key, ssh_host, remote_path): - """SSH to remote host, list backup files, delete those outside retention.""" + """SSH to remote host, list backup files, delete those outside retention. + + Returns a summary string (e.g. "pruned 3 old backup(s)") or None. + """ import subprocess import re @@ -320,11 +323,11 @@ def _run_remote_cleanup(ssh_key, ssh_host, remote_path): logger.warning( "backup_database: could not list remote files (%s)", result.stderr.strip() ) - return + return None files = [line.strip() for line in result.stdout.splitlines() if line.strip()] if not files: - return + return None from datetime import datetime @@ -332,7 +335,7 @@ def _run_remote_cleanup(ssh_key, ssh_host, remote_path): to_delete = _retention_files_to_delete(files, now) if not to_delete: logger.info("backup_database: no remote files to prune") - return + return None # Delete in batches to avoid absurdly long command lines batch_size = 50 @@ -345,6 +348,7 @@ def _run_remote_cleanup(ssh_key, ssh_host, remote_path): logger.info( "backup_database: pruned %d remote backup(s)", len(to_delete) ) + return f"pruned {len(to_delete)} old remote backup(s)" @shared_task @@ -402,8 +406,12 @@ def backup_database(): "backup_database: dump complete (%.1f MB)", dump_size / 1_000_000 ) + size_mb = dump_size / 1_000_000 + locations = [str(backup_path)] + ssh_key = getattr(settings, "DB_BACKUP_SSH_KEY", "") ssh_dest = getattr(settings, "DB_BACKUP_SSH_DEST", "") + cleanup_summary = None if ssh_key and ssh_dest: logger.info("backup_database: copying to %s", ssh_dest) try: @@ -419,6 +427,7 @@ def backup_database(): ) _cleanup_failed_backup(backup_path) return + locations.append(ssh_dest) logger.info("backup_database: copied to %s", ssh_dest) # Parse user@host and path from dest @@ -427,7 +436,9 @@ def backup_database(): ssh_host = f"{m.group(1)}@{m.group(2)}" remote_path = m.group(3) logger.info("backup_database: pruning old remote backups") - _run_remote_cleanup(ssh_key, ssh_host, remote_path) + cleanup_summary = _run_remote_cleanup( + ssh_key, ssh_host, remote_path + ) else: logger.warning( "backup_database: DB_BACKUP_SSH_KEY and DB_BACKUP_SSH_DEST not set — " @@ -435,14 +446,25 @@ def backup_database(): backup_path, ) + msg = ( + f"✅ Vrobbler backup complete — {size_mb:.1f} MB\n" + f"Stored at: {', '.join(locations)}" + ) + if cleanup_summary: + msg += f"\nRemote: {cleanup_summary}" + ntfy_url = getattr( settings, "DB_BACKUP_NTFY_URL", "https://ntfy.unbl.ink/backups" ) - req.post(ntfy_url, data=b"Vrobbler backup succeeded") + req.post(ntfy_url, data=msg.encode()) logger.info("backup_database: completed %s", backup_path) except Exception as e: logger.error("backup_database failed: %s", e) _cleanup_failed_backup(backup_path) + ntfy_url = getattr( + settings, "DB_BACKUP_NTFY_URL", "https://ntfy.unbl.ink/backups" + ) + req.post(ntfy_url, data=f"❌ Vrobbler backup FAILED: {e}".encode()) @shared_task