From dc4b5e45126b27a80be72b12844a943f0c5f2f6c Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Thu, 19 Feb 2026 16:38:59 +0100 Subject: [PATCH] implemented automated SQLite and media backup system with rotation --- README.md | 28 ++++++++++ backend/api/management/commands/backup_db.py | 58 ++++++++++++++++++++ scripts/backup.sh | 37 +++++++++++++ 3 files changed, 123 insertions(+) create mode 100644 backend/api/management/commands/backup_db.py create mode 100755 scripts/backup.sh diff --git a/README.md b/README.md index 91d0027..a48134f 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,34 @@ bash scripts/install-hooks.sh - `python manage.py sync_flavors`: Syncs the latest products from the official Holy Energy API. - `python manage.py cleanup_duplicates`: Merges duplicate entries and maintains rating integrity. - `python manage.py seed_banners`: Updates procedurally generated banner configurations from JSON. +- `python manage.py backup_db`: Creates a consistent SQLite snapshot. + +--- + +## 💾 Backups & Restoration + +### Create a Backup +To create a full snapshot of your database and user-uploaded media: +```bash +bash scripts/backup.sh +``` +This will create a `.tar.gz` file in `backend/backups/`. + +**Automating with Cron:** +Add this to your `crontab -e` to backup every night at 2:00 AM: +```bash +0 2 * * * /path/to/Holy-Flavor-Rater/scripts/backup.sh +``` + +### Restore from Backup +1. Extract the archive: `tar -xzf backup_filename.tar.gz` +2. Stop the containers: `docker-compose down` +3. Replace the current files: + ```bash + cp extracted_folder/db.sqlite3 backend/db.sqlite3 + cp -r extracted_folder/media/* backend/media/ + ``` +4. Restart: `docker-compose up -d` --- diff --git a/backend/api/management/commands/backup_db.py b/backend/api/management/commands/backup_db.py new file mode 100644 index 0000000..20a8d67 --- /dev/null +++ b/backend/api/management/commands/backup_db.py @@ -0,0 +1,58 @@ +import sqlite3 +import os +import shutil +from datetime import datetime, timedelta +from django.conf import settings +from django.core.management.base import BaseCommand + +class Command(BaseCommand): + help = 'Creates a safe, consistent backup of the SQLite database and manages rotation.' + + def add_arguments(self, parser): + parser.add_argument('--output', type=str, help='Specific output path for the backup file') + parser.add_argument('--retention', type=int, default=7, help='Number of days to keep old backups') + + def handle(self, *args, **options): + db_conf = settings.DATABASES['default'] + if db_conf['ENGINE'] != 'django.db.backends.sqlite3': + self.stdout.write(self.style.ERROR("This command only supports SQLite.")) + return + + db_path = str(db_conf['NAME']) + backup_dir = os.path.join(settings.BASE_DIR, 'backups') + os.makedirs(backup_dir, exist_ok=True) + + # 1. Database Backup + if options['output']: + backup_path = options['output'] + else: + timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') + backup_path = os.path.join(backup_dir, f'db_backup_{timestamp}.sqlite3') + + self.stdout.write(f"Starting backup of {db_path}...") + + try: + # Use SQLite's online backup API for a consistent copy + src = sqlite3.connect(db_path) + dst = sqlite3.connect(backup_path) + with dst: + src.backup(dst) + dst.close() + src.close() + self.stdout.write(self.style.SUCCESS(f"Successfully backed up database to {backup_path}")) + except Exception as e: + self.stdout.write(self.style.ERROR(f"Database backup failed: {e}")) + return + + # 2. Cleanup Old Backups + retention_days = options['retention'] + cutoff = datetime.now() - timedelta(days=retention_days) + + self.stdout.write(f"Cleaning up backups older than {retention_days} days...") + for filename in os.listdir(backup_dir): + file_path = os.path.join(backup_dir, filename) + if os.path.isfile(file_path): + file_time = datetime.fromtimestamp(os.path.getmtime(file_path)) + if file_time < cutoff: + os.remove(file_path) + self.stdout.write(f" -> Deleted: {filename}") diff --git a/scripts/backup.sh b/scripts/backup.sh new file mode 100755 index 0000000..7f249c2 --- /dev/null +++ b/scripts/backup.sh @@ -0,0 +1,37 @@ +#!/bin/bash + +# This script creates a full snapshot of the database and user media. +# Best run as a daily cron job. + +ROOT_DIR=$(git rev-parse --show-toplevel) +cd "$ROOT_DIR" + +TIMESTAMP=$(date +%Y%m%d_%H%M%S) +BACKUP_NAME="full_snapshot_$TIMESTAMP" +TEMP_DIR="backend/backups/$BACKUP_NAME" +FINAL_DEST="backend/backups" + +echo "========================================" +echo " Starting Full Project Backup... " +echo "========================================" + +mkdir -p "$TEMP_DIR" + +# 1. Run the database backup command +echo "Backing up database..." +docker exec holy-backend python manage.py backup_db --output "/app/backups/$BACKUP_NAME/db.sqlite3" + +# 2. Copy the media folder +echo "Backing up media..." +cp -r backend/media "$TEMP_DIR/media" + +# 3. Create compressed archive +echo "Compressing..." +tar -czf "$FINAL_DEST/$BACKUP_NAME.tar.gz" -C "backend/backups" "$BACKUP_NAME" + +# 4. Cleanup temp folder +rm -rf "$TEMP_DIR" + +echo "" +echo "✅ Backup complete: $FINAL_DEST/$BACKUP_NAME.tar.gz" +echo "========================================"