feat: Revamp Plex backup system to streamline archive structure and enhance validation processes

This commit is contained in:
Peter Wood
2025-05-26 07:51:24 -04:00
parent fbd0bf5852
commit 68f7f4ef8e
7 changed files with 390 additions and 162 deletions

View File

@@ -466,7 +466,7 @@ handle_wal_files() {
"backup")
if [ -f "$wal_file" ]; then
log_info "Found WAL/SHM file: $wal_basename"
local backup_file="${backup_path}/${wal_basename}.$(date '+%Y%m%d_%H%M%S')"
local backup_file="${backup_path}/${wal_basename}"
if sudo cp "$wal_file" "$backup_file"; then
log_success "Backed up WAL/SHM file: $wal_basename"
@@ -799,20 +799,23 @@ 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
find "${BACKUP_ROOT}" -maxdepth 1 -type f -name "plex-backup-*.tar.gz" -mtime +${MAX_BACKUP_AGE_DAYS} -delete 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)
local backup_count=$(find "${BACKUP_ROOT}" -maxdepth 1 -type f -name "plex-backup-*.tar.gz" | wc -l)
if [ "$backup_count" -gt "$MAX_BACKUPS_TO_KEEP" ]; then
local excess_count=$((backup_count - MAX_BACKUPS_TO_KEEP))
log_message "Removing $excess_count old backup(s)..."
find "${BACKUP_ROOT}" -maxdepth 1 -type d -name "????????" -printf '%T@ %p\n' | \
find "${BACKUP_ROOT}" -maxdepth 1 -type f -name "plex-backup-*.tar.gz" -printf '%T@ %p\n' | \
sort -n | head -n "$excess_count" | cut -d' ' -f2- | \
xargs -r rm -rf
xargs -r rm -f
fi
# Clean up any remaining dated directories from old backup structure
find "${BACKUP_ROOT}" -maxdepth 1 -type d -name "????????" -exec rm -rf {} \; 2>/dev/null || true
log_message "Backup cleanup completed"
}
@@ -929,9 +932,9 @@ main() {
local backup_errors=0
local files_backed_up=0
local BACKUP_PATH="${BACKUP_ROOT}/$(date '+%Y%m%d')"
local BACKUP_PATH="${BACKUP_ROOT}"
# Create today's backup directory
# Ensure backup root directory exists
mkdir -p "$BACKUP_PATH"
# Handle WAL files and check database integrity before backup
@@ -989,8 +992,8 @@ main() {
if needs_backup "$file" ]; then
log_message "Backing up: $(basename "$file")"
# Create backup filename with timestamp
local backup_file="${BACKUP_PATH}/$(basename "$file").$(date '+%Y%m%d_%H%M%S')"
# Create backup filename without timestamp (use original filename)
local backup_file="${BACKUP_PATH}/$(basename "$file")"
# Copy file
if sudo cp "$file" "$backup_file"; then
@@ -1029,32 +1032,136 @@ main() {
# Create archive if files were backed up
if [ "$files_backed_up" -gt 0 ]; then
log_message "Creating compressed archive..."
local temp_archive="/tmp/plex-backup-$(date '+%Y%m%d_%H%M%S').tar.gz"
local final_archive="${BACKUP_PATH}/plex-backup-$(date '+%Y%m%d_%H%M%S').tar.gz"
# Create archive in /tmp first to avoid "file changed" issues
if tar --exclude="*.tar.gz" -czf "$temp_archive" -C "$(dirname "$BACKUP_PATH")" "$(basename "$BACKUP_PATH")"; then
# Move the completed archive to the backup directory
if mv "$temp_archive" "$final_archive"; then
log_success "Archive created: $(basename "$final_archive")"
# Remove individual backup files, keep only the archive
find "$BACKUP_PATH" -type f ! -name "*.tar.gz" -delete
else
log_error "Failed to move archive to final location"
rm -f "$temp_archive"
backup_errors=$((backup_errors + 1))
fi
else
log_error "Failed to create archive"
# Check backup root directory is writable
if [ ! -w "$BACKUP_ROOT" ]; then
log_error "Backup root directory is not writable: $BACKUP_ROOT"
backup_errors=$((backup_errors + 1))
else
local temp_archive="/tmp/plex-backup-$(date '+%Y%m%d_%H%M%S').tar.gz"
local final_archive="${BACKUP_ROOT}/plex-backup-$(date '+%Y%m%d_%H%M%S').tar.gz"
log_info "Temporary archive: $temp_archive"
log_info "Final archive: $final_archive"
# Create archive in /tmp first, containing only the backed up files
local temp_dir="/tmp/plex-backup-staging-$(date '+%Y%m%d_%H%M%S')"
if ! mkdir -p "$temp_dir"; then
log_error "Failed to create staging directory: $temp_dir"
backup_errors=$((backup_errors + 1))
else
log_info "Created staging directory: $temp_dir"
# Copy backed up files to staging directory
local files_staged=0
for nickname in "${!PLEX_FILES[@]}"; do
local file="${PLEX_FILES[$nickname]}"
local backup_file="${BACKUP_PATH}/$(basename "$file")"
if [ -f "$backup_file" ]; then
if cp "$backup_file" "$temp_dir/"; then
files_staged=$((files_staged + 1))
log_info "Staged for archive: $(basename "$backup_file")"
else
log_warning "Failed to stage file: $(basename "$backup_file")"
fi
else
log_warning "Backup file not found for staging: $(basename "$backup_file")"
fi
done
# Check if any files were staged
if [ "$files_staged" -eq 0 ]; then
log_error "No files were staged for archive creation"
rm -rf "$temp_dir"
backup_errors=$((backup_errors + 1))
else
log_info "Staged $files_staged files for archive creation"
# Check disk space in /tmp
local temp_available_kb=$(df /tmp | awk 'NR==2 {print $4}')
local temp_available_mb=$((temp_available_kb / 1024))
local staging_size_mb=$(du -sm "$temp_dir" | cut -f1)
log_info "/tmp available space: ${temp_available_mb}MB, staging directory size: ${staging_size_mb}MB"
# Check if we have enough space (require 3x staging size for compression)
local required_space_mb=$((staging_size_mb * 3))
if [ "$temp_available_mb" -lt "$required_space_mb" ]; then
log_error "Insufficient space in /tmp for archive creation. Required: ${required_space_mb}MB, Available: ${temp_available_mb}MB"
rm -rf "$temp_dir"
backup_errors=$((backup_errors + 1))
else
# Create archive with detailed error logging
log_info "Creating archive: $(basename "$temp_archive")"
local tar_output
tar_output=$(tar -czf "$temp_archive" -C "$temp_dir" . 2>&1)
local tar_exit_code=$?
if [ $tar_exit_code -eq 0 ]; then
# Verify archive was actually created and has reasonable size
if [ -f "$temp_archive" ]; then
local archive_size_mb=$(du -sm "$temp_archive" | cut -f1)
log_success "Archive created successfully: $(basename "$temp_archive") (${archive_size_mb}MB)"
# Test archive integrity before moving
if tar -tzf "$temp_archive" >/dev/null 2>&1; then
log_success "Archive integrity verified"
# Move the completed archive to the backup root
if mv "$temp_archive" "$final_archive"; then
log_success "Archive moved to final location: $(basename "$final_archive")"
# Remove individual backup files and staging directory
rm -rf "$temp_dir"
for nickname in "${!PLEX_FILES[@]}"; do
local file="${PLEX_FILES[$nickname]}"
local backup_file="${BACKUP_PATH}/$(basename "$file")"
rm -f "$backup_file" "$backup_file.md5"
done
else
log_error "Failed to move archive to final location: $final_archive"
log_error "Temporary archive remains at: $temp_archive"
rm -rf "$temp_dir"
backup_errors=$((backup_errors + 1))
fi
else
log_error "Archive integrity check failed - archive may be corrupted"
log_error "Archive size: ${archive_size_mb}MB"
rm -f "$temp_archive"
rm -rf "$temp_dir"
backup_errors=$((backup_errors + 1))
fi
else
log_error "Archive file was not created despite tar success"
rm -rf "$temp_dir"
backup_errors=$((backup_errors + 1))
fi
else
log_error "Failed to create archive (tar exit code: $tar_exit_code)"
if [ -n "$tar_output" ]; then
log_error "Tar command output: $tar_output"
fi
# Additional diagnostic information
log_error "Staging directory contents:"
ls -la "$temp_dir" 2>&1 | while IFS= read -r line; do
log_error " $line"
done
local temp_usage=$(df -h /tmp | awk 'NR==2 {print "Used: " $3 "/" $2 " (" $5 ")"}')
log_error "Temp filesystem status: $temp_usage"
rm -rf "$temp_dir"
backup_errors=$((backup_errors + 1))
fi
fi
fi
fi
fi
# Send notification
send_notification "Backup Completed" "Successfully backed up $files_backed_up files" "success"
else
log_message "No files needed backup, removing empty backup directory"
rmdir "$BACKUP_PATH" 2>/dev/null || true
log_message "No files needed backup"
fi
# Cleanup old backups