#!/bin/bash # # Gondulf SQLite Database Backup Script # Compatible with both Podman and Docker (auto-detects) # # Usage: ./backup.sh [backup_dir] # # Environment Variables: # GONDULF_DATABASE_URL - Database URL (default: sqlite:////data/gondulf.db) # BACKUP_DIR - Backup directory (default: ./backups) # BACKUP_RETENTION_DAYS - Days to keep backups (default: 7) # COMPRESS_BACKUPS - Compress backups with gzip (default: true) # CONTAINER_NAME - Container name (default: gondulf) # CONTAINER_ENGINE - Force specific engine: podman or docker (default: auto-detect) # set -euo pipefail # Auto-detect container engine detect_container_engine() { if [ -n "${CONTAINER_ENGINE:-}" ]; then echo "$CONTAINER_ENGINE" elif command -v podman &> /dev/null; then echo "podman" elif command -v docker &> /dev/null; then echo "docker" else echo "ERROR: Neither podman nor docker found" >&2 exit 1 fi } ENGINE=$(detect_container_engine) CONTAINER_NAME="${CONTAINER_NAME:-gondulf}" echo "=========================================" echo "Gondulf Database Backup" echo "=========================================" echo "Container engine: $ENGINE" echo "Container name: $CONTAINER_NAME" echo "" # Configuration DATABASE_URL="${GONDULF_DATABASE_URL:-sqlite:////data/gondulf.db}" BACKUP_DIR="${1:-${BACKUP_DIR:-./backups}}" RETENTION_DAYS="${BACKUP_RETENTION_DAYS:-7}" COMPRESS="${COMPRESS_BACKUPS:-true}" # Extract database path from URL (handle both 3-slash and 4-slash formats) if [[ "$DATABASE_URL" =~ ^sqlite:////(.+)$ ]]; then # Four slashes = absolute path DB_PATH="/${BASH_REMATCH[1]}" elif [[ "$DATABASE_URL" =~ ^sqlite:///(.+)$ ]]; then # Three slashes = relative path (assume /data in container) DB_PATH="/data/${BASH_REMATCH[1]}" else echo "ERROR: Invalid DATABASE_URL format: $DATABASE_URL" >&2 exit 1 fi echo "Database path: $DB_PATH" # Verify container is running if ! $ENGINE ps | grep -q "$CONTAINER_NAME"; then echo "ERROR: Container '$CONTAINER_NAME' is not running" >&2 echo "Start the container first with: $ENGINE start $CONTAINER_NAME" >&2 exit 1 fi # Create backup directory on host if it doesn't exist mkdir -p "$BACKUP_DIR" # Generate backup filename with timestamp TIMESTAMP=$(date +%Y%m%d_%H%M%S) BACKUP_FILE_CONTAINER="/tmp/gondulf_backup_${TIMESTAMP}.db" BACKUP_FILE_HOST="$BACKUP_DIR/gondulf_backup_${TIMESTAMP}.db" echo "Starting backup..." echo " Backup file: $BACKUP_FILE_HOST" echo "" # Perform backup using SQLite VACUUM INTO (safe hot backup) # This creates a clean, optimized copy of the database echo "Creating database backup (this may take a moment)..." $ENGINE exec "$CONTAINER_NAME" sqlite3 "$DB_PATH" "VACUUM INTO '$BACKUP_FILE_CONTAINER'" || { echo "ERROR: Backup failed" >&2 exit 1 } # Copy backup out of container to host echo "Copying backup to host..." $ENGINE cp "$CONTAINER_NAME:$BACKUP_FILE_CONTAINER" "$BACKUP_FILE_HOST" || { echo "ERROR: Failed to copy backup from container" >&2 $ENGINE exec "$CONTAINER_NAME" rm -f "$BACKUP_FILE_CONTAINER" 2>/dev/null || true exit 1 } # Clean up temporary file in container $ENGINE exec "$CONTAINER_NAME" rm -f "$BACKUP_FILE_CONTAINER" # Verify backup was created on host if [ ! -f "$BACKUP_FILE_HOST" ]; then echo "ERROR: Backup file was not created on host" >&2 exit 1 fi # Verify backup integrity echo "Verifying backup integrity..." if sqlite3 "$BACKUP_FILE_HOST" "PRAGMA integrity_check;" | grep -q "ok"; then echo "✓ Backup integrity check passed" else echo "ERROR: Backup integrity check failed" >&2 rm -f "$BACKUP_FILE_HOST" exit 1 fi echo "✓ Backup created successfully" # Compress backup if enabled if [ "$COMPRESS" = "true" ]; then echo "Compressing backup..." gzip "$BACKUP_FILE_HOST" BACKUP_FILE_HOST="$BACKUP_FILE_HOST.gz" echo "✓ Backup compressed" fi # Calculate and display backup size BACKUP_SIZE=$(du -h "$BACKUP_FILE_HOST" | cut -f1) echo "Backup size: $BACKUP_SIZE" # Clean up old backups echo "" echo "Cleaning up backups older than $RETENTION_DAYS days..." DELETED_COUNT=$(find "$BACKUP_DIR" -name "gondulf_backup_*.db*" -type f -mtime +$RETENTION_DAYS -delete -print | wc -l) if [ "$DELETED_COUNT" -gt 0 ]; then echo "✓ Deleted $DELETED_COUNT old backup(s)" else echo " No old backups to delete" fi # List current backups echo "" echo "Current backups:" if ls "$BACKUP_DIR"/gondulf_backup_*.db* 1> /dev/null 2>&1; then ls -lht "$BACKUP_DIR"/gondulf_backup_*.db* | head -10 else echo " (none)" fi echo "" echo "=========================================" echo "Backup complete!" echo "=========================================" echo "Backup location: $BACKUP_FILE_HOST" echo "Container engine: $ENGINE" echo ""