319 lines
9.4 KiB
Bash
319 lines
9.4 KiB
Bash
#!/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." |