Download scripts/backup.py from sui234as/llm: direct link, hf CLI and curl.
- Browser
- Download file 3.22 kB
-
https://huggingface.co/spaces/sui234as/llm/resolve/main/scripts/backup.py
- Command line
-
hf download hf://spaces/sui234as/llm/scripts/backup.py
-
curl -L -o backup.py https://huggingface.co/spaces/sui234as/llm/resolve/main/scripts/backup.py
3.22 kB
| """ | |
| PA/POS Database Backup Script. | |
| Creates atomic, point-in-time backups of: | |
| • app.db (SQLite: chats, notes, profile facts, files) | |
| • embeddings/embeddings.db (SQLite: vector embeddings) | |
| Uses the SQLite Online Backup API (sqlite3.backup) to avoid database lock issues. | |
| Retains the last N backups (default: 7) and purges older archives. | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import logging | |
| import shutil | |
| import sqlite3 | |
| import tarfile | |
| from datetime import UTC, datetime | |
| from pathlib import Path | |
| logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") | |
| logger = logging.getLogger("backup") | |
| ROOT_DIR = Path(__file__).resolve().parents[1] | |
| BACKUPS_DIR = ROOT_DIR / "data" / "backups" | |
| APP_DB = ROOT_DIR / "app.db" | |
| EMBEDDINGS_DB = ROOT_DIR / "embeddings" / "embeddings.db" | |
| def backup_sqlite(source_path: Path, dest_path: Path) -> bool: | |
| """Use SQLite Online Backup API for an atomic, non-blocking backup.""" | |
| if not source_path.exists(): | |
| logger.warning("Source database '%s' does not exist yet. Skipping.", source_path) | |
| return False | |
| dest_path.parent.mkdir(parents=True, exist_ok=True) | |
| try: | |
| src = sqlite3.connect(str(source_path)) | |
| dst = sqlite3.connect(str(dest_path)) | |
| with dst: | |
| src.backup(dst, pages=100) | |
| dst.close() | |
| src.close() | |
| logger.info("Successfully backed up '%s' -> '%s'", source_path.name, dest_path) | |
| return True | |
| except Exception as exc: | |
| logger.error("Failed to backup '%s': %s", source_path, exc) | |
| return False | |
| def run_backup(retention: int = 7) -> Path: | |
| """Run full backup and rotate old archives.""" | |
| BACKUPS_DIR.mkdir(parents=True, exist_ok=True) | |
| timestamp = datetime.now(UTC).strftime("%Y%m%d_%H%M%S") | |
| temp_dir = BACKUPS_DIR / f"temp_{timestamp}" | |
| temp_dir.mkdir(parents=True, exist_ok=True) | |
| backed_up_any = False | |
| if APP_DB.exists(): | |
| backed_up_any |= backup_sqlite(APP_DB, temp_dir / "app.db") | |
| if EMBEDDINGS_DB.exists(): | |
| backed_up_any |= backup_sqlite(EMBEDDINGS_DB, temp_dir / "embeddings.db") | |
| archive_path = BACKUPS_DIR / f"pabot_backup_{timestamp}.tar.gz" | |
| with tarfile.open(archive_path, "w:gz") as tar: | |
| for file in temp_dir.glob("*.db"): | |
| tar.add(file, arcname=file.name) | |
| shutil.rmtree(temp_dir, ignore_errors=True) | |
| logger.info("Created backup archive: %s (%d bytes)", archive_path.name, archive_path.stat().st_size) | |
| # Rotate old backups | |
| all_backups = sorted(BACKUPS_DIR.glob("pabot_backup_*.tar.gz"), key=lambda p: p.stat().st_mtime) | |
| if len(all_backups) > retention: | |
| to_delete = all_backups[:-retention] | |
| for old_file in to_delete: | |
| old_file.unlink(missing_ok=True) | |
| logger.info("Purged old backup: %s", old_file.name) | |
| return archive_path | |
| if __name__ == "__main__": | |
| parser = argparse.ArgumentParser(description="Backup PA/POS SQLite databases") | |
| parser.add_argument("--retention", type=int, default=7, help="Number of backups to keep (default: 7)") | |
| args = parser.parse_args() | |
| archive = run_backup(retention=args.retention) | |
| print(f"Backup complete: {archive}") | |