feat: Enhance Jellyseerr backup functionality with specialized methods and verification

This commit is contained in:
Peter Wood
2025-06-25 17:27:08 -04:00
parent e98594f178
commit 3b284c769c

View File

@@ -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..."