!/bin/bash
#!/bin/bash
Enhanced Backup Script for Multiple Directories
This script backs up:
1. Everything in ~/Documents (including all of tickets/, but excluding files >100MB)
2. Everything in ~/.ssh/ including all subdirectories and files
3. Everything in ~/scripts/ including all subdirectories
4. Everything in ~/tools/ including all subdirectories
5. Everything in ~/Pictures/ (with optional size limit)
Set variables
DATE=$(date +%Y-%m-%d) DEST_DIR=”/home/user” DOCUMENTS_DIR=”/home/user/Documents” SSH_DIR=”/home/user/.ssh” SCRIPTS_DIR=”/home/user/scripts” TOOLS_DIR=”/home/user/tools” PICTURES_DIR=”/home/user/Pictures” TAR_NAME=”full_backup_${DATE}.tar” TAR_GZ=”${TAR_NAME}.gz” TAR_PATH=”${DEST_DIR}/${TAR_GZ}”
Sise limits for individual files
DOC_SIZE_LIMIT=”100M” PIC_SIZE_LIMIT=”500M” # Higher limit for pictures, adjust as needed
Temp tarball in home directory instead of /tmp
TMP_TAR=”${DEST_DIR}/.${TAR_NAME}.tmp”
Variables to track statistics
TOTAL_DIRS_BACKED_UP=0 TOTAL_LARGE_FILES_EXCLUDED=0 declare -A DIR_STATS # Associative array for directory statistics
Check disk space before starting (now checking home directory)
REQUIRED_SPACE_GB=10 # Adjust based on your needs AVAILABLE_SPACE_GB=$(df “$DEST_DIR” | tail -1 | awk ‘{print int($4/1024/1024)}’)
if [ “$AVAILABLE_SPACE_GB” -lt “$REQUIRED_SPACE_GB” ]; then echo “WARNING: Low disk space in $DEST_DIR (${AVAILABLE_SPACE_GB}GB available, ${REQUIRED_SPACE_GB}GB recommended)” echo -n “Continue anyway? (y/N): “ read -r response if [ “$response” != “y” ] && [ “$response” != “Y” ]; then echo “Backup cancelled” exit 1 fi fi
colour codes for output
RED=’\033[0;31m’ GREEN=’\033[0;32m’ YELLOW=’\033[1;33m’ BLUE=’\033[0;34m’ CYAN=’\033[0;36m’ NC=’\033[0m’ # No colour
Function to print colored messages
print_status() { echo -e “${GREEN}[INFO]${NC} $1” }
print_error() { echo -e “${RED}[ERROR]${NC} $1” }
print_warning() { echo -e “${YELLOW}[WARNING]${NC} $1” }
print_section() { echo -e “${CYAN}===> $1${NC}” }
Cleanup function
cleanup() { if [ -f “$TMP_TAR” ]; then rm -f “$TMP_TAR” print_status “Cleaned up temporary files” fi # Clean up any exclude files in home directory rm -f “${DEST_DIR}”/.tar_exclude_*.txt 2>/dev/null }
Set trap to cleanup on exit
trap cleanup EXIT
Function to check directory and count files
check_directory() { local dir=$1 local dir_name=$2
if [ ! -d "$dir" ]; then
print_warning "$dir_name directory not found: $dir"
return 1
fi
local file_count=$(find "$dir" -type f 2>/dev/null | wc -l)
local dir_count=$(find "$dir" -type d 2>/dev/null | wc -l)
local total_size=$(du -sh "$dir" 2>/dev/null | cut -f1)
DIR_STATS["${dir_name}_files"]=$file_count
DIR_STATS["${dir_name}_dirs"]=$dir_count
DIR_STATS["${dir_name}_size"]=$total_size
print_status "Found $file_count files and $dir_count directories in $dir_name ($total_size)"
return 0 }
Function to backup a directory with optional size exclusion
backup_directory() { local source_dir=$1 local dir_name=$2 local size_limit=$3 local parent_dir=$(dirname “$source_dir”) local base_name=$(basename “$source_dir”)
print_section "Backing up $dir_name"
if [ ! -d "$source_dir" ]; then
print_warning "$dir_name directory not found, skipping..."
return 1
fi
cd "$parent_dir" || return 1
# Check for large files if size limit is specified
if [ ! -z "$size_limit" ]; then
print_status "Checking for large files (>$size_limit) in $dir_name..."
local exclude_file="${DEST_DIR}/.tar_exclude_${dir_name}_${DATE}.txt"
# Find large files
local large_files=$(find "$base_name" -type f -size +$size_limit 2>/dev/null)
local large_file_count=$(echo "$large_files" | grep -c . 2>/dev/null || echo 0)
if [ "$large_file_count" -gt 0 ]; then
print_warning "Found $large_file_count large files (>$size_limit) in $dir_name that will be EXCLUDED:"
echo "$large_files" | while read -r file; do
if [ ! -z "$file" ]; then
local size=$(du -h "$source_dir/../$file" 2>/dev/null | cut -f1)
echo " - $file ($size)"
fi
done
TOTAL_LARGE_FILES_EXCLUDED=$((TOTAL_LARGE_FILES_EXCLUDED + large_file_count))
# Create exclude file
find "$base_name" -type f -size +$size_limit -printf '%P\n' > "$exclude_file" 2>/dev/null
else
print_status "No large files found in $dir_name (all files are under $size_limit)"
touch "$exclude_file"
fi
# Add to tar with exclusions
tar --append \
--file="$TMP_TAR" \
--exclude-from="$exclude_file" \
--warning=no-file-changed \
--warning=no-file-removed \
--ignore-failed-read \
"$base_name" 2>&1 | tail -5
local tar_exit=${PIPESTATUS[0]}
rm -f "$exclude_file"
else
# Add to tar without size exclusions
tar --append \
--file="$TMP_TAR" \
--preserve-permissions \
--warning=no-file-changed \
--warning=no-file-removed \
--ignore-failed-read \
"$base_name" 2>&1 | tail -5
local tar_exit=${PIPESTATUS[0]}
fi
if [ $tar_exit -eq 0 ] || [ $tar_exit -eq 1 ]; then
if [ $tar_exit -eq 1 ]; then
print_warning "Some files changed during backup of $dir_name (this is usually OK)"
fi
print_status "Successfully backed up $dir_name"
TOTAL_DIRS_BACKED_UP=$((TOTAL_DIRS_BACKED_UP + 1))
return 0
else
print_error "Failed to backup $dir_name (exit code: $tar_exit)"
return 1
fi }
Start backup process
clear echo “=========================================” echo -e “${BLUE} MULTI-DIRECTORY BACKUP UTILITY${NC}” echo “=========================================” print_status “Starting backup process for $(date)” print_status “Directories to backup: Documents, SSH, Scripts, Tools, Pictures” print_status “Using home directory for temporary storage: $DEST_DIR” echo “”
Check all directories first
print_section “Analyzing directories to backup” check_directory “$DOCUMENTS_DIR” “Documents” check_directory “$SSH_DIR” “SSH” check_directory “$SCRIPTS_DIR” “Scripts” check_directory “$TOOLS_DIR” “Tools” check_directory “$PICTURES_DIR” “Pictures” echo “”
Calculate total estimated size
TOTAL_EST_SIZE=$(du -csh “$DOCUMENTS_DIR” “$SSH_DIR” “$SCRIPTS_DIR” “$TOOLS_DIR” “$PICTURES_DIR” 2>/dev/null | tail -1 | cut -f1) print_status “Total estimated size before exclusions: $TOTAL_EST_SIZE” echo “”
Create initial empty tar file
print_status “Creating backup archive in home directory…” tar –create –file=”$TMP_TAR” –files-from=/dev/null 2>/dev/null
if [ $? -ne 0 ]; then print_error “Failed to create initial tar archive” exit 1 fi
Backup each directory
Documents - with 100MB file size limit
if [ -d “$DOCUMENTS_DIR” ]; then backup_directory “$DOCUMENTS_DIR” “Documents” “$DOC_SIZE_LIMIT” fi
SSH - no size limit (SSH keys are typically small)
if [ -d “$SSH_DIR” ]; then backup_directory “$SSH_DIR” “.ssh” “”
# List important SSH files
print_status "Key SSH files included:"
tar --list --file="$TMP_TAR" 2>/dev/null | grep -E "\.ssh/(id_rsa|id_ed25519|known_hosts|config|.*\.pem|.*\.pub)$" | head -10 fi
Scripts - no size limit (scripts are typically small)
if [ -d “$SCRIPTS_DIR” ]; then backup_directory “$SCRIPTS_DIR” “scripts” “”
# List some script files
print_status "Sample script files included:"
tar --list --file="$TMP_TAR" 2>/dev/null | grep -E "scripts/.*\.(sh|py|pl|rb|js)$" | head -10 fi
Tools - no size limit
if [ -d “$TOOLS_DIR” ]; then backup_directory “$TOOLS_DIR” “tools” “” fi
Pictures - with 500MB file size limit (configurable)
if [ -d “$PICTURES_DIR” ]; then backup_directory “$PICTURES_DIR” “Pictures” “$PIC_SIZE_LIMIT”
# Show picture formats included
print_status "Picture formats included:"
tar --list --file="$TMP_TAR" 2>/dev/null | grep -E "Pictures/.*\.(jpg|jpeg|png|gif|bmp|svg|webp)$" | \
sed 's/.*\.//' | sort | uniq -c | sort -rn | head -10 fi
echo “”
Check if we backed up anything
if [ $TOTAL_DIRS_BACKED_UP -eq 0 ]; then print_error “No directories were backed up!” exit 1 fi
Compress with maximum compression
print_section “Compressing backup” ORIGINAL_SIZE=$(du -h “$TMP_TAR” | cut -f1) print_status “Original tar size: $ORIGINAL_SIZE” print_status “Compressing with gzip -9 (maximum compression)…”
gzip -9 “$TMP_TAR”
if [ $? -ne 0 ]; then print_error “Failed to compress backup” exit 1 fi
Move compressed file to final destination
mv “${TMP_TAR}.gz” “$TAR_PATH”
if [ $? -ne 0 ]; then print_error “Failed to move backup to destination” exit 1 fi
Verify the backup
print_section “Verifying backup integrity” if tar -tzf “$TAR_PATH” >/dev/null 2>&1; then print_status “Backup verification successful” else print_error “Backup verification failed!” exit 1 fi
Set appropriate permissions
chmod 600 “$TAR_PATH” print_status “Set backup permissions to 600 (read/write for owner only)”
Final statistics
FINAL_SIZE=$(du -h “$TAR_PATH” | cut -f1) FILE_COUNT=$(tar -tzf “$TAR_PATH” 2>/dev/null | wc -l) COMPRESSION_RATIO=$(echo “scale=2; 100 - ($(du -b “$TAR_PATH” | cut -f1) * 100 / $(tar -tzf “$TAR_PATH” 2>/dev/null | wc -c))” | bc 2>/dev/null || echo “N/A”)
Create detailed backup report
REPORT_FILE=”${DEST_DIR}/backup_report_${DATE}.txt” { echo “======================================” echo “BACKUP REPORT” echo “======================================” echo “Date: $(date)” echo “Backup file: $TAR_PATH” echo “Original size: $ORIGINAL_SIZE” echo “Compressed size: $FINAL_SIZE” echo “Compression ratio: ${COMPRESSION_RATIO}%” echo “Total files: $FILE_COUNT” echo “Directories backed up: $TOTAL_DIRS_BACKED_UP” echo “” echo “DIRECTORY STATISTICS:” echo “——————–”
for dir in Documents SSH Scripts Tools Pictures; do
if [ ! -z "${DIR_STATS[${dir}_files]}" ]; then
echo "$dir:"
echo " Files: ${DIR_STATS[${dir}_files]}"
echo " Directories: ${DIR_STATS[${dir}_dirs]}"
echo " Original size: ${DIR_STATS[${dir}_size]}"
fi
done
echo ""
echo "EXCLUSIONS:"
echo "-----------"
echo "Large files excluded: $TOTAL_LARGE_FILES_EXCLUDED"
echo " - Documents: files >$DOC_SIZE_LIMIT"
echo " - Pictures: files >$PIC_SIZE_LIMIT"
echo ""
echo "BACKUP CONTENTS SUMMARY:"
echo "------------------------"
echo "Top-level directories:"
tar -tzf "$TAR_PATH" 2>/dev/null | cut -d/ -f1 | sort -u
echo ""
echo "File type distribution:"
tar -tzf "$TAR_PATH" 2>/dev/null | grep -E "\.[a-zA-Z0-9]+$" | \
sed 's/.*\.//' | sort | uniq -c | sort -rn | head -20 } > "$REPORT_FILE"
Summary output
echo “” echo “=========================================” echo -e “${GREEN}BACKUP COMPLETED SUCCESSFULLY!${NC}” echo “=========================================” echo -e “${BLUE}Backup file:${NC} $TAR_PATH” echo -e “${BLUE}Original size:${NC} $ORIGINAL_SIZE” echo -e “${BLUE}Compressed size:${NC} $FINAL_SIZE” echo -e “${BLUE}Total files:${NC} $FILE_COUNT” echo -e “${BLUE}Directories backed up:${NC} $TOTAL_DIRS_BACKED_UP” echo “” echo “Backup includes:” echo “ ✓ Documents (excluding files >$DOC_SIZE_LIMIT)” echo “ ✓ SSH keys and configuration” echo “ ✓ Scripts directory” echo “ ✓ Tools directory” echo “ ✓ Pictures (excluding files >$PIC_SIZE_LIMIT)” echo “”
if [ “$TOTAL_LARGE_FILES_EXCLUDED” -gt 0 ]; then echo -e “${YELLOW}Note:${NC} $TOTAL_LARGE_FILES_EXCLUDED large files were excluded from backup” fi
print_status “Detailed report saved to: $REPORT_FILE” print_status “Backup log available at: ${DEST_DIR}/backup_${DATE}.log”
Create simple log for quick reference
{ echo “Backup: $TAR_PATH” echo “Sise: $FINAL_SIZE” echo “Files: $FILE_COUNT” echo “Created: $(date)” } > “${DEST_DIR}/backup_${DATE}.log”
Optional: Show recent backups
echo “” print_section “Recent backups in $DEST_DIR:” ls -lh “${DEST_DIR}”/full_backup_*.tar.gz 2>/dev/null | tail -5
Optional: Keep only the last N backups (uncomment to enable)
KEEP_BACKUPS=7
print_status “Cleaning old backups (keeping last $KEEP_BACKUPS)…”
OLD_BACKUPS=$(ls -t “${DEST_DIR}”/full_backup_*.tar.gz 2>/dev/null | tail -n +$((KEEP_BACKUPS + 1)))
if [ ! -z “$OLD_BACKUPS” ]; then
echo “$OLD_BACKUPS” | xargs -r rm -f
print_status “Removed old backups”
fi
Optional: Test restore capability (uncomment to enable)
print_section “Testing restore capability”
TEST_DIR=”${DEST_DIR}/.backup_test_${DATE}”
mkdir -p “$TEST_DIR”
tar -xzf “$TAR_PATH” -C “$TEST_DIR” –occurrence=1 Documents/.bashrc 2>/dev/null || \
tar -xzf “$TAR_PATH” -C “$TEST_DIR” –occurrence=1 .ssh/config 2>/dev/null
if [ $? -eq 0 ]; then
print_status “Restore test successful”
rm -rf “$TEST_DIR”
else
print_warning “Could not test restore (this may be normal if test files don’t exist)”
fi
exit 0