#!/usr/bin/env python3 """ Art4Hotel Hub — Backup diario Crea snapshot del DB + uploads, conserva últimos N días. Corre cada noche via cron. """ import sqlite3, shutil, datetime, tarfile, sys from pathlib import Path BASE = Path("/mnt/iclaude/art4hotel-hub") DB = BASE / "art4hotel.db" UPLOADS = BASE / "uploads" BACKUPS = BASE / "backups" KEEP_DAYS = 30 def log(msg): ts = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") print(f"[{ts}] {msg}", flush=True) def backup_db(out): """Use SQLite online backup (safe with WAL, doesn't lock writers).""" src = sqlite3.connect(str(DB)) dst = sqlite3.connect(str(out)) with dst: src.backup(dst) src.close() dst.close() def backup_uploads(out_tar): if not UPLOADS.exists(): return 0 n = 0 with tarfile.open(str(out_tar), "w:gz") as tar: for p in UPLOADS.rglob("*"): if p.is_file(): tar.add(str(p), arcname=str(p.relative_to(UPLOADS.parent))) n += 1 return n def prune_old(): cutoff = datetime.datetime.now() - datetime.timedelta(days=KEEP_DAYS) removed = 0 for d in BACKUPS.iterdir(): if not d.is_dir(): continue try: date_part = d.name.split("_")[0] d_date = datetime.datetime.strptime(date_part, "%Y-%m-%d") if d_date < cutoff: shutil.rmtree(d) removed += 1 except Exception: pass return removed def main(): BACKUPS.mkdir(exist_ok=True) stamp = datetime.datetime.now().strftime("%Y-%m-%d_%H%M") out_dir = BACKUPS / stamp out_dir.mkdir(exist_ok=True) log(f"Backup → {out_dir}") try: backup_db(out_dir / "art4hotel.db") log(f" DB OK ({(out_dir/'art4hotel.db').stat().st_size//1024} KB)") except Exception as e: log(f" DB ERROR: {e}") sys.exit(1) try: n = backup_uploads(out_dir / "uploads.tar.gz") if (out_dir / "uploads.tar.gz").exists(): log(f" uploads OK ({n} archivos, {(out_dir/'uploads.tar.gz').stat().st_size//1024} KB)") except Exception as e: log(f" uploads ERROR: {e}") removed = prune_old() if removed: log(f" Eliminados {removed} backup(s) > {KEEP_DAYS} días") log("Listo.") if __name__ == "__main__": main()