mirror of
https://github.com/acedanger/shell.git
synced 2025-12-06 06:40:13 -08:00
356 lines
10 KiB
Bash
Executable File
356 lines
10 KiB
Bash
Executable File
#!/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 "$@"
|