mirror of
https://github.com/acedanger/shell.git
synced 2025-12-06 05:40:11 -08:00
Add enhanced backup and restoration scripts for Plex Media Server with validation and monitoring features
This commit is contained in:
355
backup-plex.sh
Executable file
355
backup-plex.sh
Executable 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 "$@"
|
||||
Reference in New Issue
Block a user