#!/bin/bash # Immich Backup Validation Script # This script validates Immich backup files for integrity and completeness # Set up error handling set -e # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # No Color # Function to display usage usage() { echo "Usage: $0 [backup_directory]" echo "" echo "Arguments:" echo " backup_directory Directory containing backup files (default: ../immich_backups)" echo "" echo "This script validates:" echo " - Database backup file integrity" echo " - Upload archive integrity" echo " - File sizes and timestamps" echo " - Backup completeness" } # Parse command line arguments BACKUP_DIR="${1:-$(dirname "$0")/../immich_backups}" # Set up logging to central logs directory LOG_DIR="$(dirname "$0")/../logs" mkdir -p "$LOG_DIR" LOG_FILE="${LOG_DIR}/immich-validation.log" # Function to log validation results log_validation() { echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE" } # Verify backup directory exists if [ ! -d "$BACKUP_DIR" ]; then echo -e "${RED}Error: Backup directory not found: $BACKUP_DIR${NC}" log_validation "Error: Backup directory not found: $BACKUP_DIR" exit 1 fi log_validation "Starting backup validation for directory: $BACKUP_DIR" echo "=== IMMICH BACKUP VALIDATION ===" echo "Backup directory: $BACKUP_DIR" echo "" # Find backup files DB_BACKUPS=$(find "$BACKUP_DIR" -name "immich_db_backup_*.sql.gz" -type f | sort -r) UPLOAD_BACKUPS=$(find "$BACKUP_DIR" -name "immich_uploads_*.tar.gz" -type f | sort -r) TOTAL_ERRORS=0 echo "=== DATABASE BACKUPS ===" if [ -z "$DB_BACKUPS" ]; then echo -e "${YELLOW}Warning: No database backup files found${NC}" log_validation "Warning: No database backup files found" ((TOTAL_ERRORS++)) else for backup in $DB_BACKUPS; do echo "Validating: $(basename "$backup")" # Check file size SIZE=$(stat -c%s "$backup" 2>/dev/null || echo "0") if [ "$SIZE" -lt 1024 ]; then echo -e " ${RED}✗ File is too small (${SIZE} bytes)${NC}" log_validation "Error: File is too small (${SIZE} bytes) - $(basename "$backup")" ((TOTAL_ERRORS++)) else echo -e " ${GREEN}✓ File size OK ($(du -h "$backup" | cut -f1))${NC}" fi # Check if it's a valid gzip file if gzip -t "$backup" 2>/dev/null; then echo -e " ${GREEN}✓ Gzip file integrity OK${NC}" else echo -e " ${RED}✗ Gzip file corruption detected${NC}" log_validation "Error: Gzip file corruption detected - $(basename "$backup")" ((TOTAL_ERRORS++)) fi # Check if SQL content looks valid (basic check) if zcat "$backup" 2>/dev/null | head -n 10 | grep -q "PostgreSQL database dump"; then echo -e " ${GREEN}✓ SQL content appears valid${NC}" else echo -e " ${YELLOW}? Cannot verify SQL content format${NC}" fi echo "" done fi echo "=== UPLOAD BACKUPS ===" if [ -z "$UPLOAD_BACKUPS" ]; then echo -e "${YELLOW}Warning: No upload backup files found${NC}" log_validation "Warning: No upload backup files found" ((TOTAL_ERRORS++)) else for backup in $UPLOAD_BACKUPS; do echo "Validating: $(basename "$backup")" # Check file size SIZE=$(stat -c%s "$backup" 2>/dev/null || echo "0") if [ "$SIZE" -lt 1024 ]; then echo -e " ${RED}✗ File is too small (${SIZE} bytes)${NC}" log_validation "Error: File is too small (${SIZE} bytes) - $(basename "$backup")" ((TOTAL_ERRORS++)) else echo -e " ${GREEN}✓ File size OK ($(du -h "$backup" | cut -f1))${NC}" fi # Check if it's a valid tar.gz file if tar -tzf "$backup" >/dev/null 2>&1; then echo -e " ${GREEN}✓ Tar.gz file integrity OK${NC}" # Count files in archive FILE_COUNT=$(tar -tzf "$backup" 2>/dev/null | wc -l) echo -e " ${GREEN}✓ Archive contains ${FILE_COUNT} files/directories${NC}" else echo -e " ${RED}✗ Tar.gz file corruption detected${NC}" log_validation "Error: Tar.gz file corruption detected - $(basename "$backup")" ((TOTAL_ERRORS++)) fi echo "" done fi echo "=== BACKUP PAIRING VALIDATION ===" # Check if we have matching pairs of backups (same timestamp) DB_TIMESTAMPS=$(echo "$DB_BACKUPS" | sed 's/.*immich_db_backup_\([0-9_]*\)\.sql\.gz/\1/' | sort) UPLOAD_TIMESTAMPS=$(echo "$UPLOAD_BACKUPS" | sed 's/.*immich_uploads_\([0-9_]*\)\.tar\.gz/\1/' | sort) echo "Database backup timestamps: $(echo "$DB_TIMESTAMPS" | tr '\n' ' ')" echo "Upload backup timestamps: $(echo "$UPLOAD_TIMESTAMPS" | tr '\n' ' ')" # Find matching pairs MATCHED_PAIRS=0 for db_ts in $DB_TIMESTAMPS; do if echo "$UPLOAD_TIMESTAMPS" | grep -q "^${db_ts}$"; then echo -e "${GREEN}✓ Complete backup set found for timestamp: $db_ts${NC}" ((MATCHED_PAIRS++)) else echo -e "${YELLOW}? Incomplete backup set for timestamp: $db_ts (missing upload backup)${NC}" log_validation "Warning: Incomplete backup set for timestamp: $db_ts (missing upload backup)" fi done for upload_ts in $UPLOAD_TIMESTAMPS; do if ! echo "$DB_TIMESTAMPS" | grep -q "^${upload_ts}$"; then echo -e "${YELLOW}? Incomplete backup set for timestamp: $upload_ts (missing database backup)${NC}" log_validation "Warning: Incomplete backup set for timestamp: $upload_ts (missing database backup)" fi done echo "" echo "=== VALIDATION SUMMARY ===" echo "Complete backup pairs: $MATCHED_PAIRS" echo "Total validation errors: $TOTAL_ERRORS" log_validation "Validation summary: $MATCHED_PAIRS complete backup pairs, $TOTAL_ERRORS errors" if [ "$TOTAL_ERRORS" -eq 0 ]; then echo -e "${GREEN}✓ All backup validations passed${NC}" log_validation "Success: All backup validations passed" exit 0 else echo -e "${RED}✗ Backup validation failed with $TOTAL_ERRORS errors${NC}" log_validation "Error: Backup validation failed with $TOTAL_ERRORS errors" exit 1 fi