fix: prune old backup safety-copies and expired admin sessions instead of letting them pile up forever
Every backup restore was leaving a .before-restore-<timestamp> 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 <noreply@anthropic.com>
This commit is contained in:
parent
2a68b2ff40
commit
81cbc4e391
3 changed files with 22 additions and 0 deletions
13
backup.py
13
backup.py
|
|
@ -1,3 +1,4 @@
|
||||||
|
import glob
|
||||||
import io
|
import io
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
|
@ -10,12 +11,23 @@ from config import DB_PATH, BASE_DIR
|
||||||
|
|
||||||
ENV_PATH = os.path.join(BASE_DIR, ".env")
|
ENV_PATH = os.path.join(BASE_DIR, ".env")
|
||||||
MAX_RESTORE_SIZE = 200 * 1024 * 1024
|
MAX_RESTORE_SIZE = 200 * 1024 * 1024
|
||||||
|
KEEP_SAFETY_COPIES = 5
|
||||||
|
|
||||||
|
|
||||||
class RestoreError(Exception):
|
class RestoreError(Exception):
|
||||||
pass
|
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:
|
def create_backup() -> bytes:
|
||||||
buf = io.BytesIO()
|
buf = io.BytesIO()
|
||||||
db_tmp = DB_PATH + ".backup_snapshot.tmp"
|
db_tmp = DB_PATH + ".backup_snapshot.tmp"
|
||||||
|
|
@ -111,4 +123,5 @@ def restore_backup(data: bytes) -> dict:
|
||||||
os.chmod(tmp_db_path, 0o600)
|
os.chmod(tmp_db_path, 0o600)
|
||||||
os.replace(tmp_db_path, DB_PATH)
|
os.replace(tmp_db_path, DB_PATH)
|
||||||
|
|
||||||
|
_prune_old_safety_copies()
|
||||||
return {"restored_env": restored_env, "safety_copy": safety_copy}
|
return {"restored_env": restored_env, "safety_copy": safety_copy}
|
||||||
|
|
|
||||||
4
bot.py
4
bot.py
|
|
@ -367,6 +367,10 @@ async def periodic_sync():
|
||||||
await reconcile_pending_payments()
|
await reconcile_pending_payments()
|
||||||
except Exception:
|
except Exception:
|
||||||
log.exception("payment reconciliation failed")
|
log.exception("payment reconciliation failed")
|
||||||
|
try:
|
||||||
|
db.delete_expired_admin_sessions()
|
||||||
|
except Exception:
|
||||||
|
log.exception("expired admin session cleanup failed")
|
||||||
await asyncio.sleep(90)
|
await asyncio.sleep(90)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
5
db.py
5
db.py
|
|
@ -382,6 +382,11 @@ def delete_admin_session(token: str):
|
||||||
conn.execute("DELETE FROM admin_sessions WHERE token=?", (token,))
|
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):
|
def list_all_subscriptions(limit: int = 200):
|
||||||
with get_conn() as conn:
|
with get_conn() as conn:
|
||||||
rows = conn.execute(
|
rows = conn.execute(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue