diff --git a/backup-media.sh b/backup-media.sh index e55464a..5bb82b9 100755 --- a/backup-media.sh +++ b/backup-media.sh @@ -124,8 +124,8 @@ declare -A MEDIA_SERVICES=( ["audiobookshelf"]="/metadata/backups" ["tautulli"]="/config/backups" ["sabnzbd"]="/config/sabnzbd.ini" - ["jellyseerr_db"]="/config/db/" - ["jellyseerr_settings"]="/data/settings.json" + ["jellyseerr_db"]="/config/db/db.sqlite3" + ["jellyseerr_settings"]="/config/settings.json" ) # Service-specific backup destinations @@ -136,8 +136,8 @@ declare -A BACKUP_DESTINATIONS=( ["audiobookshelf"]="${BACKUP_ROOT}/audiobookshelf/" ["tautulli"]="${BACKUP_ROOT}/tautulli/" ["sabnzbd"]="${BACKUP_ROOT}/sabnzbd/sabnzbd_$(date +%Y%m%d).ini" - ["jellyseerr_db"]="${BACKUP_ROOT}/jellyseerr/backup_$(date +%Y%m%d)/" - ["jellyseerr_settings"]="${BACKUP_ROOT}/jellyseerr/backup_$(date +%Y%m%d)/" + ["jellyseerr_db"]="${BACKUP_ROOT}/jellyseerr/jellyseerr_db_$(date +%Y%m%d_%H%M%S).sqlite3" + ["jellyseerr_settings"]="${BACKUP_ROOT}/jellyseerr/settings_$(date +%Y%m%d_%H%M%S).json" ) # Logging functions @@ -455,6 +455,11 @@ backup_service() { log_message "Starting backup for service: $service" + # Handle Jellyseerr services with specialized backup function + if [[ "$service" == jellyseerr_* ]]; then + return $(backup_jellyseerr_service "$service") + fi + # Handle special cases for container names case "$service" in jellyseerr_db|jellyseerr_settings) @@ -471,11 +476,6 @@ backup_service() { local src_path="${MEDIA_SERVICES[$service]}" local dest_path="${BACKUP_DESTINATIONS[$service]}" - # Create destination directory for jellyseerr - if [[ "$service" == jellyseerr_* ]]; then - mkdir -p "$(dirname "$dest_path")" - fi - # Perform the backup if [ "$DRY_RUN" == true ]; then log_info "DRY RUN: Would backup $container:$src_path to $dest_path" @@ -551,19 +551,210 @@ backup_service() { fi } -# Backup service wrapper for parallel execution -backup_service_wrapper() { +# Specialized Jellyseerr backup function using SQLite CLI method +backup_jellyseerr_service() { local service="$1" - local temp_file="$2" + local container="jellyseerr" + local backup_start_time + backup_start_time=$(date +%s) - if backup_service "$service"; then - echo "SUCCESS:$service" >> "$temp_file" + log_message "Starting specialized Jellyseerr backup for: $service" + + # Check if container is running + if ! check_container_running "$container"; then + log_file_details "$service" "${container}:${MEDIA_SERVICES[$service]}" "${BACKUP_DESTINATIONS[$service]}" "FAILED - Container not running" + return 1 + fi + + local src_path="${MEDIA_SERVICES[$service]}" + local dest_path="${BACKUP_DESTINATIONS[$service]}" + + # Create destination directory + mkdir -p "$(dirname "$dest_path")" + + # Perform the backup + if [ "$DRY_RUN" == true ]; then + log_info "DRY RUN: Would backup $container:$src_path to $dest_path" + log_file_details "$service" "$container:$src_path" "$dest_path" "DRY RUN" + return 0 + fi + + if [ "$INTERACTIVE_MODE" == true ]; then + echo -n "Backup $service? (y/N): " + read -r response + if [[ ! "$response" =~ ^[Yy]$ ]]; then + log_info "Skipping $service backup (user choice)" + return 0 + fi + fi + + # Handle different backup methods based on service type + local backup_success=false + + if [[ "$service" == "jellyseerr_db" ]]; then + # Use SQLite CLI method for database backup (recommended by Jellyseerr docs) + log_info "Using SQLite CLI method for database backup" + + # Create a temporary backup file path inside the container + local temp_backup="/tmp/jellyseerr_db_$(date +%Y%m%d_%H%M%S).sqlite3" + + # Execute SQLite backup command inside the container + local sqlite_cmd="docker exec $container sqlite3 $src_path \".backup '$temp_backup'\"" + log_info "Executing: $sqlite_cmd" + + if eval "$sqlite_cmd" 2>&1 | tee -a "$LOG_FILE"; then + # Copy the backup file from container to host + local copy_cmd="docker cp $container:$temp_backup $dest_path" + log_info "Executing: $copy_cmd" + + if $copy_cmd 2>&1 | tee -a "$LOG_FILE"; then + # Clean up temporary file in container + docker exec "$container" rm -f "$temp_backup" 2>/dev/null || true + backup_success=true + else + log_error "Failed to copy database backup from container" + # Clean up temporary file in container + docker exec "$container" rm -f "$temp_backup" 2>/dev/null || true + fi + else + log_error "SQLite backup command failed" + fi + + elif [[ "$service" == "jellyseerr_settings" ]]; then + # Standard file copy for settings + local docker_cmd="docker cp $container:$src_path $dest_path" + log_info "Executing: $docker_cmd" + + if $docker_cmd 2>&1 | tee -a "$LOG_FILE"; then + backup_success=true + else + log_error "Settings backup failed" + fi else - echo "FAILED:$service" >> "$temp_file" + log_error "Unknown Jellyseerr service type: $service" + return 1 + fi + + if [ "$backup_success" == true ]; then + log_success "Backup completed for $service" + + # File-level metrics tracking (success) + if [[ "$METRICS_ENABLED" == "true" ]]; then + local file_size checksum + if [ -f "$dest_path" ]; then + file_size=$(stat -c%s "$dest_path" 2>/dev/null || echo "0") + checksum=$(md5sum "$dest_path" 2>/dev/null | cut -d' ' -f1 || echo "") + metrics_add_file "$dest_path" "success" "$file_size" "$checksum" + fi + fi + + # Verify the backup + if verify_jellyseerr_backup "$container" "$src_path" "$dest_path" "$service"; then + log_file_details "$service" "$container:$src_path" "$dest_path" "SUCCESS" + track_performance "backup_${service}" "$backup_start_time" + return 0 + else + log_file_details "$service" "$container:$src_path" "$dest_path" "VERIFICATION_FAILED" + # File-level metrics tracking (verification failed) + if [[ "$METRICS_ENABLED" == "true" ]]; then + local file_size + if [ -f "$dest_path" ]; then + file_size=$(stat -c%s "$dest_path" 2>/dev/null || echo "0") + metrics_add_file "$dest_path" "failed" "$file_size" "" "Verification failed" + fi + fi + return 1 + fi + else + log_error "Backup failed for $service" + log_file_details "$service" "$container:$src_path" "$dest_path" "FAILED" + # File-level metrics tracking (backup failed) + if [[ "$METRICS_ENABLED" == "true" ]]; then + local file_size + if [ -f "$dest_path" ]; then + file_size=$(stat -c%s "$dest_path" 2>/dev/null || echo "0") + metrics_add_file "$dest_path" "failed" "$file_size" "" "Backup failed" + fi + fi + return 1 fi } -# Clean old backups based on age and count +# Specialized verification for Jellyseerr backups +verify_jellyseerr_backup() { + local src_container="$1" + local src_path="$2" + local dest_path="$3" + local service="$4" + + if [ "$VERIFY_BACKUPS" != true ]; then + return 0 + fi + + log_info "Verifying Jellyseerr backup for $service" + + # Check if backup file exists and has reasonable size + if [ ! -f "$dest_path" ]; then + log_error "Backup file not found: $dest_path" + return 1 + fi + + local file_size + file_size=$(stat -c%s "$dest_path" 2>/dev/null || echo "0") + + if [ "$file_size" -eq 0 ]; then + log_error "Backup file is empty: $dest_path" + return 1 + fi + + # Service-specific verification + if [[ "$service" == "jellyseerr_db" ]]; then + # For SQLite database, verify it's a valid SQLite file + if command -v file >/dev/null 2>&1; then + if file "$dest_path" | grep -q "SQLite"; then + log_success "Database backup verification passed: Valid SQLite file ($file_size bytes)" + return 0 + else + log_error "Database backup verification failed: Not a valid SQLite file" + return 1 + fi + else + # Fallback: just check file size is reasonable (>1KB for a database) + if [ "$file_size" -gt 1024 ]; then + log_success "Database backup verification passed: File size check ($file_size bytes)" + return 0 + else + log_error "Database backup verification failed: File too small ($file_size bytes)" + return 1 + fi + fi + elif [[ "$service" == "jellyseerr_settings" ]]; then + # For settings JSON, verify it's valid JSON + if command -v jq >/dev/null 2>&1; then + if jq empty "$dest_path" 2>/dev/null; then + log_success "Settings backup verification passed: Valid JSON file ($file_size bytes)" + return 0 + else + log_error "Settings backup verification failed: Invalid JSON file" + return 1 + fi + else + # Fallback: check if it looks like JSON (starts with { or [) + if head -c 1 "$dest_path" | grep -q "[{\[]"; then + log_success "Settings backup verification passed: JSON format check ($file_size bytes)" + return 0 + else + log_error "Settings backup verification failed: Does not appear to be JSON" + return 1 + fi + fi + fi + + log_warning "Unable to verify backup for $service" + return 0 +} + +# Clean up old backups based on age and count cleanup_old_backups() { log_message "Cleaning up old backups..."