From 81cbc4e391bb4eadfd202a2a56cf989634f3da6a Mon Sep 17 00:00:00 2001 From: savsis Date: Sat, 12 Sep 2026 15:08:34 +0500 Subject: [PATCH] fix: prune old backup safety-copies and expired admin sessions instead of letting them pile up forever MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every backup restore was leaving a .before-restore- safety copy of both mbs.db and .env with no cleanup — would accumulate forever on a panel that restores regularly. Now keeps the 5 most recent and prunes the rest right after each restore. Verified: 8 fake copies pruned down to exactly the 5 newest, oldest-first. admin_sessions rows also never got deleted once expired (only ever filtered out of queries via expires_at>?) — same shape of problem. Piggybacks on the existing 90s periodic_sync heartbeat in bot.py instead of adding a new one. Co-Authored-By: Claude Sonnet 5 --- backup.py | 13 +++++++++++++ bot.py | 4 ++++ db.py | 5 +++++ 3 files changed, 22 insertions(+) diff --git a/backup.py b/backup.py index dc5c224..a40ad53 100644 --- a/backup.py +++ b/backup.py @@ -1,3 +1,4 @@ +import glob import io import json import os @@ -10,12 +11,23 @@ from config import DB_PATH, BASE_DIR ENV_PATH = os.path.join(BASE_DIR, ".env") MAX_RESTORE_SIZE = 200 * 1024 * 1024 +KEEP_SAFETY_COPIES = 5 class RestoreError(Exception): pass +def _prune_old_safety_copies(keep: int = KEEP_SAFETY_COPIES): + for base in (DB_PATH, ENV_PATH): + copies = sorted(glob.glob(f"{base}.before-restore-*")) + for path in copies[:-keep] if keep > 0 else copies: + try: + os.remove(path) + except OSError: + pass + + def create_backup() -> bytes: buf = io.BytesIO() db_tmp = DB_PATH + ".backup_snapshot.tmp" @@ -111,4 +123,5 @@ def restore_backup(data: bytes) -> dict: os.chmod(tmp_db_path, 0o600) os.replace(tmp_db_path, DB_PATH) + _prune_old_safety_copies() return {"restored_env": restored_env, "safety_copy": safety_copy} diff --git a/bot.py b/bot.py index 3e95e6e..a52ab2d 100644 --- a/bot.py +++ b/bot.py @@ -367,6 +367,10 @@ async def periodic_sync(): await reconcile_pending_payments() except Exception: log.exception("payment reconciliation failed") + try: + db.delete_expired_admin_sessions() + except Exception: + log.exception("expired admin session cleanup failed") await asyncio.sleep(90) diff --git a/db.py b/db.py index 65ba369..cb7dab6 100644 --- a/db.py +++ b/db.py @@ -382,6 +382,11 @@ def delete_admin_session(token: str): conn.execute("DELETE FROM admin_sessions WHERE token=?", (token,)) +def delete_expired_admin_sessions(): + with get_conn() as conn: + conn.execute("DELETE FROM admin_sessions WHERE expires_at<=?", (now_iso(),)) + + def list_all_subscriptions(limit: int = 200): with get_conn() as conn: rows = conn.execute(