#!/bin/bash # Plex Backup Validation and Monitoring Script # Usage: ./validate-plex-backups.sh [--fix] [--report] set -e RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # Configuration SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" BACKUP_ROOT="/mnt/share/media/backups/plex" JSON_LOG_FILE="$SCRIPT_DIR/logs/plex-backup.json" REPORT_FILE="$SCRIPT_DIR/logs/backup-validation-$(date +%Y%m%d_%H%M%S).log" # Expected files in backup EXPECTED_FILES=( "com.plexapp.plugins.library.db" "com.plexapp.plugins.library.blobs.db" "Preferences.xml" ) log_message() { local message="$1" echo -e "$(date '+%H:%M:%S') $message" | tee -a "$REPORT_FILE" } log_error() { log_message "${RED}ERROR: $1${NC}" } log_success() { log_message "${GREEN}SUCCESS: $1${NC}" } log_warning() { log_message "${YELLOW}WARNING: $1${NC}" } log_info() { log_message "${BLUE}INFO: $1${NC}" } # Check backup directory structure validate_backup_structure() { log_info "Validating backup directory structure..." if [ ! -d "$BACKUP_ROOT" ]; then log_error "Backup root directory not found: $BACKUP_ROOT" return 1 fi local backup_count=$(find "$BACKUP_ROOT" -maxdepth 1 -type d -name "????????" | wc -l) log_info "Found $backup_count backup directories" if [ "$backup_count" -eq 0 ]; then log_warning "No backup directories found" return 1 fi return 0 } # Validate individual backup validate_backup() { local backup_dir="$1" local backup_date=$(basename "$backup_dir") local errors=0 log_info "Validating backup: $backup_date" # Check if directory exists and is readable if [ ! -d "$backup_dir" ] || [ ! -r "$backup_dir" ]; then log_error "Backup directory not accessible: $backup_dir" return 1 fi # Check for expected files for file in "${EXPECTED_FILES[@]}"; do local file_path="$backup_dir/$file" if [ -f "$file_path" ]; then # Check file size local size=$(stat -c%s "$file_path") if [ "$size" -gt 0 ]; then local human_size=$(du -h "$file_path" | cut -f1) log_success " $file ($human_size)" else log_error " $file is empty" errors=$((errors + 1)) fi else log_error " Missing file: $file" errors=$((errors + 1)) fi done # Check for unexpected files local file_count=$(ls -1 "$backup_dir" | wc -l) local expected_count=${#EXPECTED_FILES[@]} if [ "$file_count" -ne "$expected_count" ]; then log_warning " Expected $expected_count files, found $file_count" ls -la "$backup_dir" | grep -v "^total" | grep -v "^d" | while read line; do local filename=$(echo "$line" | awk '{print $9}') if [[ ! " ${EXPECTED_FILES[@]} " =~ " ${filename} " ]]; then log_warning " Unexpected file: $filename" fi done fi return $errors } # Check backup freshness check_backup_freshness() { log_info "Checking backup freshness..." local latest_backup=$(find "$BACKUP_ROOT" -maxdepth 1 -type d -name "????????" | sort | tail -1) if [ -z "$latest_backup" ]; then log_error "No backups found" return 1 fi local backup_date=$(basename "$latest_backup") local backup_timestamp=$(date -d "${backup_date:0:4}-${backup_date:4:2}-${backup_date:6:2}" +%s) local current_timestamp=$(date +%s) local age_days=$(( (current_timestamp - backup_timestamp) / 86400 )) log_info "Latest backup: $backup_date ($age_days days old)" if [ "$age_days" -gt 7 ]; then log_warning "Latest backup is older than 7 days" return 1 elif [ "$age_days" -gt 3 ]; then log_warning "Latest backup is older than 3 days" else log_success "Latest backup is recent" fi return 0 } # Validate JSON log file validate_json_log() { log_info "Validating JSON log file..." if [ ! -f "$JSON_LOG_FILE" ]; then log_error "JSON log file not found: $JSON_LOG_FILE" return 1 fi if ! jq empty "$JSON_LOG_FILE" 2>/dev/null; then log_error "JSON log file is invalid" return 1 fi local entry_count=$(jq 'length' "$JSON_LOG_FILE") log_success "JSON log file is valid ($entry_count entries)" return 0 } # Check disk space check_disk_space() { log_info "Checking disk space..." local backup_disk_usage=$(du -sh "$BACKUP_ROOT" | cut -f1) local available_space=$(df -h "$BACKUP_ROOT" | awk 'NR==2 {print $4}') local used_percentage=$(df "$BACKUP_ROOT" | awk 'NR==2 {print $5}' | sed 's/%//') log_info "Backup disk usage: $backup_disk_usage" log_info "Available space: $available_space" log_info "Disk usage: $used_percentage%" if [ "$used_percentage" -gt 90 ]; then log_error "Disk usage is above 90%" return 1 elif [ "$used_percentage" -gt 80 ]; then log_warning "Disk usage is above 80%" else log_success "Disk usage is acceptable" fi return 0 } # Generate backup report generate_report() { log_info "Generating backup report..." local total_backups=0 local valid_backups=0 local total_errors=0 # Header echo "==================================" >> "$REPORT_FILE" echo "Plex Backup Validation Report" >> "$REPORT_FILE" echo "Generated: $(date)" >> "$REPORT_FILE" echo "==================================" >> "$REPORT_FILE" # Validate each backup find "$BACKUP_ROOT" -maxdepth 1 -type d -name "????????" | sort | while read backup_dir; do total_backups=$((total_backups + 1)) validate_backup "$backup_dir" local backup_errors=$? if [ "$backup_errors" -eq 0 ]; then valid_backups=$((valid_backups + 1)) else total_errors=$((total_errors + backup_errors)) fi done # Summary echo >> "$REPORT_FILE" echo "Summary:" >> "$REPORT_FILE" echo " Total backups: $total_backups" >> "$REPORT_FILE" echo " Valid backups: $valid_backups" >> "$REPORT_FILE" echo " Total errors: $total_errors" >> "$REPORT_FILE" log_success "Report generated: $REPORT_FILE" } # Fix common issues fix_issues() { log_info "Attempting to fix common issues..." # Fix JSON log file if [ ! -f "$JSON_LOG_FILE" ] || ! jq empty "$JSON_LOG_FILE" 2>/dev/null; then log_info "Fixing JSON log file..." mkdir -p "$(dirname "$JSON_LOG_FILE")" echo "{}" > "$JSON_LOG_FILE" log_success "JSON log file created/fixed" fi # Remove empty backup directories find "$BACKUP_ROOT" -maxdepth 1 -type d -name "????????" -empty -delete 2>/dev/null || true # Fix permissions if needed if [ -d "$BACKUP_ROOT" ]; then chmod 755 "$BACKUP_ROOT" find "$BACKUP_ROOT" -type f -exec chmod 644 {} \; 2>/dev/null || true log_success "Fixed backup permissions" fi } # Main function main() { local fix_mode=false local report_mode=false # Parse arguments while [[ $# -gt 0 ]]; do case $1 in --fix) fix_mode=true shift ;; --report) report_mode=true shift ;; *) echo "Usage: $0 [--fix] [--report]" echo " --fix Attempt to fix common issues" echo " --report Generate detailed backup report" exit 1 ;; esac done log_info "Starting Plex backup validation..." # Create logs directory if needed mkdir -p "$(dirname "$REPORT_FILE")" local overall_status=0 # Fix issues if requested if [ "$fix_mode" = true ]; then fix_issues fi # Validate backup structure if ! validate_backup_structure; then overall_status=1 fi # Check backup freshness if ! check_backup_freshness; then overall_status=1 fi # Validate JSON log if ! validate_json_log; then overall_status=1 fi # Check disk space if ! check_disk_space; then overall_status=1 fi # Generate detailed report if requested if [ "$report_mode" = true ]; then generate_report fi # Final summary echo if [ "$overall_status" -eq 0 ]; then log_success "All validation checks passed" else log_error "Some validation checks failed" echo echo "Consider running with --fix to attempt automatic repairs" echo "Use --report for a detailed backup analysis" fi exit $overall_status } main "$@"