Add enhanced backup and restoration scripts for Plex Media Server with validation and monitoring features

This commit is contained in:
Peter Wood
2025-05-25 19:53:23 -04:00
parent aa854588a5
commit a68a1cc4ba
4 changed files with 1012 additions and 2 deletions

105
README.md
View File

@@ -6,9 +6,109 @@ This repository contains various shell scripts for managing media-related tasks
- [Backup Media Script](docs/backup-media.md): Documentation for the `backup-media.sh` script.
- `plex.sh`: Script to manage the Plex Media Server (start, stop, restart, status).
- `backup-plex.sh`: Script to back up Plex Media Server databases and related files.
- `backup-plex.sh`: Enhanced Plex backup script with integrity verification, incremental backups, and advanced features.
- `restore-plex.sh`: Script to restore Plex data from backups with safety checks.
- `validate-plex-backups.sh`: Script to validate backup integrity and monitor backup health.
- `folder-metrics.sh`: Script to calculate disk usage and file count for a directory and its subdirectories.
## Enhanced Plex Backup System
This repository includes an enhanced backup system for Plex Media Server with multiple components:
### Scripts
- **`backup-plex.sh`**: Advanced backup script with integrity verification, incremental backups, and automatic cleanup
- **`restore-plex.sh`**: Safe restoration script with dry-run mode and current data backup
- **`validate-plex-backups.sh`**: Backup validation and health monitoring script
### Key Features
- **Incremental backups**: Only backs up files that have changed since last backup
- **File integrity verification**: Uses MD5 checksums to verify backup integrity
- **Automatic cleanup**: Configurable retention policies for old backups
- **Disk space monitoring**: Checks available space before starting backup
- **Safe restoration**: Backs up current data before restoring from backup
- **Comprehensive logging**: Detailed logs with color-coded output
- **Service management**: Safely stops/starts Plex during backup operations
### Usage Examples
#### Enhanced Backup Script
```bash
# Run the enhanced backup (recommended)
./backup-plex.sh
```
#### Backup Validation
```bash
# Validate all backups and generate report
./validate-plex-backups.sh --report
# Validate backups and attempt to fix common issues
./validate-plex-backups.sh --fix
# Quick validation check
./validate-plex-backups.sh
```
#### Restore from Backup
```bash
# List available backups
./restore-plex.sh
# Test restore without making changes (dry run)
./restore-plex.sh 20250125 --dry-run
# Actually restore from a specific backup
./restore-plex.sh 20250125
```
### Automation Examples
#### Daily Backup with Validation
```bash
# Add to crontab for daily backup at 3 AM
0 3 * * * /home/acedanger/shell/backup-plex.sh
# Add daily validation at 7 AM
0 7 * * * /home/acedanger/shell/validate-plex-backups.sh --fix
```
#### Weekly Full Validation Report
```bash
# Generate detailed weekly report (Sundays at 8 AM)
0 8 * * 0 /home/acedanger/shell/validate-plex-backups.sh --report
```
### Configuration
The enhanced backup script includes configurable parameters at the top of the file:
- `MAX_BACKUP_AGE_DAYS=30`: Remove backups older than 30 days
- `MAX_BACKUPS_TO_KEEP=10`: Keep maximum of 10 backup sets
- `BACKUP_ROOT`: Location for backup storage
- `LOG_ROOT`: Location for backup logs
### Backup Strategy
The system implements a robust 3-2-1 backup strategy:
1. **3 copies**: Original data + local backup + compressed archive
2. **2 different media**: Local disk + network storage
3. **1 offsite**: Consider syncing to remote location
For offsite backup, add to cron:
```bash
# Sync backups to remote server daily at 6 AM
0 6 * * * rsync -av /mnt/share/media/backups/plex/ user@remote-server:/backups/plex/
```
## Documentation
- [Plex Backup Script Documentation](./docs/plex-backup.md): Detailed documentation for the `backup-plex.sh` script.
@@ -72,6 +172,7 @@ Test your setup in isolated Docker containers with:
- Run comprehensive checks before committing changes
The test environment checks:
- Package availability and installation
- Core components (git, curl, wget, etc.)
- Additional packages from `setup/packages.list`
@@ -80,7 +181,7 @@ The test environment checks:
Tests will continue even when some packages fail to install, reporting all issues in a comprehensive summary.
# plex.sh
## plex.sh
This script is used to manage the Plex Media Server service on a systemd-based Linux distribution. It provides the following functionalities:

355
backup-plex.sh Executable file
View File

@@ -0,0 +1,355 @@
#!/bin/bash
set -e
# Color codes for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Configuration
MAX_BACKUP_AGE_DAYS=30
MAX_BACKUPS_TO_KEEP=10
BACKUP_ROOT="/mnt/share/media/backups/plex"
LOG_ROOT="/mnt/share/media/backups/logs"
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
JSON_LOG_FILE="${SCRIPT_DIR}/logs/plex-backup.json"
# Create necessary directories
mkdir -p "${LOG_ROOT}" "${SCRIPT_DIR}/logs"
# Date variables
CURRENT_DATE=$(date +%Y%m%d)
CURRENT_DATETIME=$(date +%Y%m%d_%H%M%S)
LOG_FILE="${LOG_ROOT}/plex_backup_${CURRENT_DATETIME}.log"
BACKUP_PATH="${BACKUP_ROOT}/${CURRENT_DATE}"
# Plex files to backup with their nicknames for easier handling
declare -A PLEX_FILES=(
["library_db"]="/var/lib/plexmediaserver/Library/Application Support/Plex Media Server/Plug-in Support/Databases/com.plexapp.plugins.library.db"
["blobs_db"]="/var/lib/plexmediaserver/Library/Application Support/Plex Media Server/Plug-in Support/Databases/com.plexapp.plugins.library.blobs.db"
["preferences"]="/var/lib/plexmediaserver/Library/Application Support/Plex Media Server/Preferences.xml"
)
# Logging functions
log_message() {
local message="$1"
local timestamp=$(date '+%H:%M:%S')
echo "${timestamp} ${message}" | tee -a "${LOG_FILE}"
}
log_error() {
local message="$1"
log_message "${RED}ERROR: ${message}${NC}"
}
log_success() {
local message="$1"
log_message "${GREEN}SUCCESS: ${message}${NC}"
}
log_warning() {
local message="$1"
log_message "${YELLOW}WARNING: ${message}${NC}"
}
# Initialize JSON log file
initialize_json_log() {
if [ ! -f "${JSON_LOG_FILE}" ] || ! jq empty "${JSON_LOG_FILE}" 2>/dev/null; then
echo "{}" > "${JSON_LOG_FILE}"
log_message "Initialized JSON log file"
fi
}
# Check if file needs backup based on modification time
needs_backup() {
local file="$1"
if [ ! -f "$file" ]; then
log_warning "File not found: $file"
return 1
fi
local current_mod_date=$(stat -c %Y "$file")
local last_backup_date=$(jq -r --arg file "$file" '.[$file] // 0' "${JSON_LOG_FILE}")
if [ "$last_backup_date" == "null" ] || [ "$last_backup_date" == "0" ]; then
log_message "File has never been backed up: $(basename "$file")"
return 0
fi
if [ "$current_mod_date" -gt "$last_backup_date" ]; then
log_message "File modified since last backup: $(basename "$file")"
return 0
fi
log_message "File unchanged since last backup: $(basename "$file")"
return 1
}
# Update JSON log with successful backup
update_json_log() {
local file="$1"
local mod_date=$(stat -c %Y "$file")
jq -c --arg file "$file" --argjson mod_date "$mod_date" '.[$file] = $mod_date' "${JSON_LOG_FILE}" > "${JSON_LOG_FILE}.tmp"
if [ $? -eq 0 ]; then
mv "${JSON_LOG_FILE}.tmp" "${JSON_LOG_FILE}"
log_message "Updated backup log for: $(basename "$file")"
else
log_error "Failed to update JSON log file"
rm -f "${JSON_LOG_FILE}.tmp"
return 1
fi
}
# Calculate MD5 checksum
calculate_checksum() {
local file="$1"
md5sum "$file" | cut -d' ' -f1
}
# Verify file integrity after copy
verify_backup() {
local src="$1"
local dest="$2"
if [ ! -f "$dest" ]; then
log_error "Backup file not found: $dest"
return 1
fi
local src_checksum=$(calculate_checksum "$src")
local dest_checksum=$(calculate_checksum "$dest")
if [ "$src_checksum" = "$dest_checksum" ]; then
log_success "Backup verified: $(basename "$dest")"
return 0
else
log_error "Backup verification failed: $(basename "$dest")"
return 1
fi
}
# Manage Plex service
manage_plex_service() {
local action="$1"
log_message "Attempting to $action Plex Media Server..."
if systemctl is-active --quiet plexmediaserver.service; then
case "$action" in
"stop")
sudo systemctl stop plexmediaserver.service
sleep 3 # Give it time to stop cleanly
log_success "Plex Media Server stopped"
;;
"start")
sudo systemctl start plexmediaserver.service
sleep 3 # Give it time to start
log_success "Plex Media Server started"
;;
esac
else
case "$action" in
"stop")
log_warning "Plex Media Server was not running"
;;
"start")
log_warning "Plex Media Server failed to start or was already stopped"
;;
esac
fi
}
# Check available disk space
check_disk_space() {
local backup_dir="$1"
local required_space_mb="$2"
local available_space_kb=$(df "$backup_dir" | awk 'NR==2 {print $4}')
local available_space_mb=$((available_space_kb / 1024))
if [ "$available_space_mb" -lt "$required_space_mb" ]; then
log_error "Insufficient disk space. Required: ${required_space_mb}MB, Available: ${available_space_mb}MB"
return 1
fi
log_message "Disk space check passed. Available: ${available_space_mb}MB"
return 0
}
# Estimate backup size
estimate_backup_size() {
local total_size=0
for nickname in "${!PLEX_FILES[@]}"; do
local file="${PLEX_FILES[$nickname]}"
if [ -f "$file" ] && needs_backup "$file"; then
local size_kb=$(du -k "$file" | cut -f1)
total_size=$((total_size + size_kb))
fi
done
echo $((total_size / 1024)) # Return size in MB
}
# Clean old backups
cleanup_old_backups() {
log_message "Cleaning up old backups..."
# Remove backups older than MAX_BACKUP_AGE_DAYS
find "${BACKUP_ROOT}" -maxdepth 1 -type d -name "????????" -mtime +${MAX_BACKUP_AGE_DAYS} -exec rm -rf {} \; 2>/dev/null || true
# Keep only MAX_BACKUPS_TO_KEEP most recent backups
local backup_count=$(find "${BACKUP_ROOT}" -maxdepth 1 -type d -name "????????" | wc -l)
if [ "$backup_count" -gt "$MAX_BACKUPS_TO_KEEP" ]; then
local excess=$((backup_count - MAX_BACKUPS_TO_KEEP))
find "${BACKUP_ROOT}" -maxdepth 1 -type d -name "????????" -printf '%T@ %p\n' | sort -n | head -n "$excess" | cut -d' ' -f2- | xargs rm -rf
log_message "Removed $excess old backup directories"
fi
# Clean old log files
find "${LOG_ROOT}" -name "plex_backup_*.log" -mtime +${MAX_BACKUP_AGE_DAYS} -delete 2>/dev/null || true
}
# Create backup with verification
backup_file() {
local nickname="$1"
local src_file="${PLEX_FILES[$nickname]}"
local filename=$(basename "$src_file")
local dest_file="${BACKUP_PATH}/${filename}"
log_message "Backing up $nickname: $filename"
# Copy with sudo if needed
if sudo cp "$src_file" "$dest_file"; then
# Verify the backup
if verify_backup "$src_file" "$dest_file"; then
update_json_log "$src_file"
local size=$(du -h "$dest_file" | cut -f1)
log_success "Successfully backed up $filename ($size)"
return 0
else
rm -f "$dest_file"
return 1
fi
else
log_error "Failed to copy $filename"
return 1
fi
}
# Send notification
send_notification() {
local files_backed_up="$1"
local message="🎬 Plex backup completed successfully! 📦 $files_backed_up files backed up on $(hostname)"
if command -v curl >/dev/null 2>&1; then
curl -s \
-H "tags:popcorn,backup,plex,$(hostname)" \
-d "$message" \
https://notify.peterwood.rocks/lab || log_warning "Failed to send notification"
else
log_warning "curl not available, skipping notification"
fi
}
# Main backup function
main() {
log_message "Starting enhanced Plex backup process at $(date)"
# Initialize
initialize_json_log
# Estimate backup size and check disk space
local estimated_size_mb=$(estimate_backup_size)
local required_space_mb=$((estimated_size_mb + 100)) # Add 100MB buffer
if ! check_disk_space "$(dirname "$BACKUP_PATH")" "$required_space_mb"; then
log_error "Backup aborted due to insufficient disk space"
exit 1
fi
# Stop Plex service
manage_plex_service stop
# Create backup directory
mkdir -p "${BACKUP_PATH}"
local files_backed_up=0
local backup_errors=0
# Backup each file
for nickname in "${!PLEX_FILES[@]}"; do
local file="${PLEX_FILES[$nickname]}"
if [ ! -f "$file" ]; then
log_warning "File not found: $file"
continue
fi
if needs_backup "$file"; then
if backup_file "$nickname"; then
files_backed_up=$((files_backed_up + 1))
else
backup_errors=$((backup_errors + 1))
fi
fi
done
# Start Plex service
manage_plex_service start
# Create compressed archive if files were backed up
if [ "$files_backed_up" -gt 0 ]; then
local archive_file="${SCRIPT_DIR}/plex_backup_${CURRENT_DATE}.tar.gz"
log_message "Creating compressed archive..."
if tar -czf "$archive_file" -C "$BACKUP_PATH" .; then
log_success "Archive created: $(basename "$archive_file")"
# Verify archive
if tar -tzf "$archive_file" >/dev/null 2>&1; then
log_success "Archive verification passed"
rm -rf "$BACKUP_PATH"
log_message "Temporary backup directory removed"
else
log_error "Archive verification failed"
backup_errors=$((backup_errors + 1))
fi
else
log_error "Failed to create archive"
backup_errors=$((backup_errors + 1))
fi
# Send notification
send_notification "$files_backed_up"
else
log_message "No files needed backup, removing empty backup directory"
rmdir "$BACKUP_PATH" 2>/dev/null || true
fi
# Cleanup old backups
cleanup_old_backups
# Final summary
log_message "Backup process completed at $(date)"
log_message "Files backed up: $files_backed_up"
log_message "Errors encountered: $backup_errors"
if [ "$backup_errors" -gt 0 ]; then
log_error "Backup completed with errors"
exit 1
else
log_success "Backup completed successfully"
fi
}
# Trap to ensure Plex is restarted on script exit
trap 'manage_plex_service start' EXIT
# Run main function
main "$@"

225
restore-plex.sh Executable file
View File

@@ -0,0 +1,225 @@
#!/bin/bash
# Plex Backup Restoration Script
# Usage: ./restore-plex.sh [backup_date] [--dry-run]
set -e
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Configuration
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
BACKUP_ROOT="/mnt/share/media/backups/plex"
PLEX_DATA_DIR="/var/lib/plexmediaserver/Library/Application Support/Plex Media Server"
# Plex file locations
declare -A RESTORE_LOCATIONS=(
["com.plexapp.plugins.library.db"]="$PLEX_DATA_DIR/Plug-in Support/Databases/"
["com.plexapp.plugins.library.blobs.db"]="$PLEX_DATA_DIR/Plug-in Support/Databases/"
["Preferences.xml"]="$PLEX_DATA_DIR/"
)
log_message() {
echo -e "$(date '+%H:%M:%S') $1"
}
log_error() {
log_message "${RED}ERROR: $1${NC}"
}
log_success() {
log_message "${GREEN}SUCCESS: $1${NC}"
}
log_warning() {
log_message "${YELLOW}WARNING: $1${NC}"
}
# List available backups
list_backups() {
log_message "Available backups:"
find "$BACKUP_ROOT" -maxdepth 1 -type d -name "????????" | sort -r | while read backup_dir; do
local backup_date=$(basename "$backup_dir")
local readable_date=$(date -d "${backup_date:0:4}-${backup_date:4:2}-${backup_date:6:2}" '+%B %d, %Y')
local file_count=$(ls -1 "$backup_dir" 2>/dev/null | wc -l)
echo " $backup_date ($readable_date) - $file_count files"
done
}
# Validate backup integrity
validate_backup() {
local backup_date="$1"
local backup_dir="$BACKUP_ROOT/$backup_date"
if [ ! -d "$backup_dir" ]; then
log_error "Backup directory not found: $backup_dir"
return 1
fi
log_message "Validating backup integrity for $backup_date..."
for file in "${!RESTORE_LOCATIONS[@]}"; do
local backup_file="$backup_dir/$file"
if [ -f "$backup_file" ]; then
log_success "Found: $file"
else
log_warning "Missing: $file"
fi
done
}
# Create backup of current Plex data
backup_current_data() {
local backup_suffix=$(date '+%Y%m%d_%H%M%S')
local current_backup_dir="$SCRIPT_DIR/plex_current_backup_$backup_suffix"
log_message "Creating backup of current Plex data..."
mkdir -p "$current_backup_dir"
for file in "${!RESTORE_LOCATIONS[@]}"; do
local src="${RESTORE_LOCATIONS[$file]}$file"
if [ -f "$src" ]; then
if sudo cp "$src" "$current_backup_dir/"; then
log_success "Backed up current: $file"
else
log_error "Failed to backup current: $file"
return 1
fi
fi
done
log_success "Current data backed up to: $current_backup_dir"
echo "$current_backup_dir"
}
# Restore files from backup
restore_files() {
local backup_date="$1"
local dry_run="$2"
local backup_dir="$BACKUP_ROOT/$backup_date"
if [ "$dry_run" = "true" ]; then
log_message "DRY RUN: Would restore the following files:"
else
log_message "Restoring files from backup $backup_date..."
fi
for file in "${!RESTORE_LOCATIONS[@]}"; do
local backup_file="$backup_dir/$file"
local restore_location="${RESTORE_LOCATIONS[$file]}$file"
if [ -f "$backup_file" ]; then
if [ "$dry_run" = "true" ]; then
echo " $backup_file -> $restore_location"
else
log_message "Restoring: $file"
if sudo cp "$backup_file" "$restore_location"; then
sudo chown plex:plex "$restore_location"
log_success "Restored: $file"
else
log_error "Failed to restore: $file"
return 1
fi
fi
else
log_warning "Backup file not found: $backup_file"
fi
done
}
# Manage Plex service
manage_plex_service() {
local action="$1"
log_message "$action Plex Media Server..."
case "$action" in
"stop")
sudo systemctl stop plexmediaserver.service
sleep 3
log_success "Plex stopped"
;;
"start")
sudo systemctl start plexmediaserver.service
sleep 3
log_success "Plex started"
;;
esac
}
# Main function
main() {
local backup_date="$1"
local dry_run=false
# Check for dry-run flag
if [ "$2" = "--dry-run" ] || [ "$1" = "--dry-run" ]; then
dry_run=true
fi
# If no backup date provided, list available backups
if [ -z "$backup_date" ] || [ "$backup_date" = "--dry-run" ]; then
list_backups
echo
echo "Usage: $0 <backup_date> [--dry-run]"
echo "Example: $0 20250125"
exit 0
fi
# Validate backup exists and is complete
if ! validate_backup "$backup_date"; then
log_error "Backup validation failed"
exit 1
fi
if [ "$dry_run" = "true" ]; then
restore_files "$backup_date" true
log_message "Dry run completed. No changes were made."
exit 0
fi
# Confirm restoration
echo
log_warning "This will restore Plex data from backup $backup_date"
log_warning "Current Plex data will be backed up before restoration"
read -p "Continue? (y/N): " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
log_message "Restoration cancelled"
exit 0
fi
# Stop Plex service
manage_plex_service stop
# Backup current data
local current_backup=$(backup_current_data)
if [ $? -ne 0 ]; then
log_error "Failed to backup current data"
manage_plex_service start
exit 1
fi
# Restore files
if restore_files "$backup_date" false; then
log_success "Restoration completed successfully"
log_message "Current data backup saved at: $current_backup"
else
log_error "Restoration failed"
manage_plex_service start
exit 1
fi
# Start Plex service
manage_plex_service start
log_success "Plex restoration completed. Please verify your server is working correctly."
}
# Trap to ensure Plex is restarted on script exit
trap 'manage_plex_service start' EXIT
main "$@"

329
validate-plex-backups.sh Executable file
View File

@@ -0,0 +1,329 @@
#!/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 "$@"