#!/bin/bash # Prevent set -e from causing issues in testing set -uo pipefail SCRIPT_NAME=$(basename "$0") # Log to stderr with timestamp log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $SCRIPT_NAME: $*" >&2 } # Get current timestamp in seconds since epoch get_timestamp() { date +%s } # Set file modification time to a specific date set_file_mtime() { local file="$1" local new_date="$2" touch -d "$new_date" "$file" } # --- Testing Functions --- # Function to create test backup files with manipulated timestamps create_test_backups() { local dest_dir="${1:-/mnt/Backup/Ubuntu/Backups}" local use_test_timestamp="${2:-now}" log "Creating test backup files in $dest_dir (using timestamp: $use_test_timestamp)" # Create the directory mkdir -p "$dest_dir" local now_ts=$(get_timestamp) local now_date=$(date '+%Y-%m-%d') # Convert relative days to timestamp days_ago() { local d="$1" date -d "${d} days ago" +%s 2>/dev/null || echo "" } case "$use_test_timestamp" in "today") set_file_mtime "$dest_dir/test-today.tgz" "$now_date" log "Created test file: test-today.tgz (today)" ;; "yesterday") set_file_mtime "$dest_dir/test-yesterday.tgz" "$(days_ago 1)" log "Created test file: test-yesterday.tgz (1 day ago)" ;; "3days") set_file_mtime "$dest_dir/test-3days.tgz" "$(days_ago 3)" log "Created test file: test-3days.tgz (3 days ago)" ;; "6days") set_file_mtime "$dest_dir/test-6days.tgz" "$(days_ago 6)" log "Created test file: test-6days.tgz (6 days ago)" ;; "7days") set_file_mtime "$dest_dir/test-7days.tgz" "$(days_ago 7)" log "Created test file: test-7days.tgz (7 days ago)" ;; "14days") set_file_mtime "$dest_dir/test-14days.tgz" "$(days_ago 14)" log "Created test file: test-14days.tgz (14 days ago)" ;; "29days") set_file_mtime "$dest_dir/test-29days.tgz" "$(days_ago 29)" log "Created test file: test-29days.tgz (29 days ago)" ;; "60days") set_file_mtime "$dest_dir/test-60days.tgz" "$(days_ago 60)" log "Created test file: test-60days.tgz (60 days ago)" ;; "90days") set_file_mtime "$dest_dir/test-90days.tgz" "$(days_ago 90)" log "Created test file: test-90days.tgz (90 days ago)" ;; "monday") # Get yesterday's date, check if Monday, otherwise find next Monday local check_date=$(date -d "yesterday" '+%A') if [ "$check_date" = "Monday" ]; then set_file_mtime "$dest_dir/test-monday.tgz" "$(days_ago 1)" log "Created test file: test-monday.tgz (yesterday/Monday)" else # Find next Monday (2 days from now if today is Saturday, etc.) local days_to_monday=$(( (1 + (6 - $(date +%w))) % 7 )) set_file_mtime "$dest_dir/test-monday.tgz" "$(days_ago $days_to_monday)" log "Created test file: test-monday.tgz (previous Monday)" fi ;; "sunday") # Find the most recent Sunday local days_to_sunday=$(( (date +%w) % 7 )) set_file_mtime "$dest_dir/test-sunday.tgz" "$(days_ago $days_to_sunday)" log "Created test file: test-sunday.tgz (most recent Sunday)" ;; "1st") # Get the 1st day of current month current_month=$(date +%m) set_file_mtime "$dest_dir/test-1st.tgz" "$(date -d "${current_month}-01" '+%s')" log "Created test file: test-1st.tgz (1st of this month)" ;; *) log "Unknown timestamp option: $use_test_timestamp" ;; esac } # Create test files for multiple scenarios create_all_test_backups() { local dest_dir="${1:-/mnt/Backup/Ubuntu/Backups}" log "Creating comprehensive test backup files in $dest_dir" create_test_backups "$dest_dir" "today" create_test_backups "$dest_dir" "yesterday" create_test_backups "$dest_dir" "3days" create_test_backups "$dest_dir" "6days" create_test_backups "$dest_dir" "7days" create_test_backups "$dest_dir" "14days" create_test_backups "$dest_dir" "29days" create_test_backups "$dest_dir" "60days" create_test_backups "$dest_dir" "90days" create_test_backups "$dest_dir" "monday" create_test_backups "$dest_dir" "sunday" create_test_backups "$dest_dir" "1st" log "Test backup files created. Run ./backup.sh to test retention logic." } # --- Configuration --- # Backup 1: /mnt/Content → /mnt/Backup (rsync sync) CONTENT_SRC="/mnt/Content" CONTENT_DEST="/mnt/Backup" CONTENT_EXCLUDES="/etc/backup-excludes/content.exclude" # Backup 2: / (root filesystem) → /mnt/Backup/Ubuntu/Backups (compressed tar) UBUNTU_DEST="/mnt/Backup/Ubuntu/Backups" UBUNTU_EXCLUDES="/etc/backup-excludes/ubuntu.exclude" # === Helper Functions === # Run rsync with low CPU and I/O priority run_rsync() { local label="$1" shift log "Starting low-priority rsync ($label)" nice -n 19 ionice -c 3 rsync -avHAX "$@" } # Run a command with low CPU and I/O priority run_low_priority() { local label="$1" shift log "Starting low-priority $label..." nice -n 19 ionice -c 3 "$@" } # === Task 1: Sync /mnt/Content → /mnt/Backup === if [ -d "$CONTENT_SRC" ]; then log "Starting rsync of $CONTENT_SRC → $CONTENT_DEST (with excludes)" # Ensure destination directory exists run_low_priority "Create destination" \ mkdir -p "$CONTENT_DEST" # Use --delete to sync deletions - frees space at destination run_rsync "Content -> Backup (media)" \ --delete-before \ --exclude-from="$CONTENT_EXCLUDES" \ "$CONTENT_SRC"/ "$CONTENT_DEST"/ log "Finished $CONTENT_SRC → $CONTENT_DEST backup." else log "WARNING: $CONTENT_SRC not found or not a directory; skipping." fi # === Task 2: Full filesystem backup with compression === log "Starting tar backup of / → $UBUNTU_DEST (excluding system paths)" # Ensure destination directory exists run_low_priority "Create destination" \ mkdir -p "$UBUNTU_DEST" # Generate timestamp for backup filename timestamp=$(date +%Y-%m-%d) # Create backup filename with timestamp tar_output="$UBUNTU_DEST/backup-full-$timestamp.tgz" log "Starting compressed backup of /..." # Run tar with low priority (compression is I/O intensive) run_low_priority "Full system backup" \ tar -czf "$tar_output" \ --exclude-from="$UBUNTU_EXCLUDES" \ / log "Finished / → $UBUNTU_DEST backup." # === Retention Management === log "Managing backup retention in $UBUNTU_DEST..." # Create the directory if it doesn't exist (already done above, but for safety) [ ! -d "$UBUNTU_DEST" ] && sudo mkdir -p "$UBUNTU_DEST" # Count current totals daily_count=0 weekly_count=0 monthly_count=0 if [ -n "$(ls -A "$UBUNTU_DEST"/*.tgz 2>/dev/null)" ]; then # First pass: count existing files by type for file in "$UBUNTU_DEST"/*.tgz; do [ -f "$file" ] || continue mtime=$(stat -c %Y "$file") age=$(( ($(date +%s) - $mtime) / (3600 * 24) )) backup_date=$(date -d "@$mtime" '+%Y-%m-%d') day=$(date -d "$backup_date" +%d) weekday=$(date -d "$backup_date" +%w) # 0=Sunday if [ $age -lt 7 ]; then ((daily_count++)) fi if [ $weekday -eq 0 ]; then ((weekly_count++)) fi if [ $day -eq 01 ]; then ((monthly_count++)) fi done log "Current counts - Daily: $daily_count, Weekly: $weekly_count, Monthly: $monthly_count" fi # Process each backup file if [ -n "$(ls -A "$UBUNTU_DEST"/*.tgz 2>/dev/null)" ]; then for backup_file in $(ls -1 "$UBUNTU_DEST"/*.tgz | sort --key=1.2 --reverse); do filename=$(basename "$backup_file") mtime=$(stat -c %Y "$backup_file") age=$(( ($(date +%s) - $mtime) / (3600 * 24) )) # Calculate date of the backup backup_date=$(date -d "@$mtime" '+%Y-%m-%d') day=$(date -d "$backup_date" +%d) month=$(date -d "$backup_date" +%m) weekday=$(date -d "$backup_date" +%w) # 0=Sunday # Calculate months since backup backup_year=$(date -d "$backup_date" +%Y) backup_month_int=$((10#$month)) current_month_int=$((10#$month)) current_year_int=$((10#$backup_year)) if [ $backup_month_int -lt $current_month_int ]; then months_since=$(( (current_month_int - backup_month_int) + 12 )) else months_since=$(( current_month_int - backup_month_int )) fi if [ $backup_year_int -lt $current_year_int ]; then months_since=$(( months_since + 12 )) fi # Determine backup type is_daily=0 is_weekly=0 is_monthly=0 if [ $age -lt 7 ]; then is_daily=1 fi if [ $weekday -eq 0 ]; then is_weekly=1 fi if [ $day -eq 01 ]; then is_monthly=1 fi # Keep daily backups (less than 7 days old) if [ $is_daily -eq 1 ]; then log "Backup $filename: Keeping (daily backup, age: $age days)" ((daily_count++)) continue fi # Keep Sunday backups for the last month (31 days) if [ $is_weekly -eq 1 ] && [ $age -lt 31 ]; then log "Backup $filename: Keeping (weekly Sunday backup, age: $age days)" ((weekly_count++)) continue fi # Keep 1st-of-month backups for the last 3 months if [ $is_monthly -eq 1 ] && [ $months_since -lt 3 ]; then log "Backup $filename: Keeping (monthly 1st-of-month backup, age: $age days)" ((monthly_count++)) continue fi # Delete backup that doesn't meet retention criteria log "Deleting backup: $filename (age: $age days, days_since: $months_since, type: none)" sudo rm "$backup_file" done else log "No backup files found in $UBUNTU_DEST" fi log "All backups completed."