A robust bash script for performing space-efficient incremental backups using rsync and hard links.
- Features
- How It Works
- Requirements
- Installation
- Configuration
- Usage
- Directory Structure
- Examples
- Troubleshooting
- Safety Features
- Space-efficient: Only stores changed files, using hard links for unchanged files
- Safe interruption handling: Cleans up incomplete backups if interrupted (Ctrl+C)
- Automatic versioning: Each backup is timestamped and kept separately
- Easy restore: Access any backup version by browsing dated folders
- Exclude patterns: Skip unnecessary files/folders via exclude list
- First-run ready: Works correctly on initial backup without previous data
The script uses incremental backups with hard links:
- First Backup: Copies all files from source to backup destination
- Subsequent Backups:
- Unchanged files β Hard links to previous backup (no extra space!)
- Changed/new files β Fresh copies
- Deleted files β Removed (with
--deleteflag)
Space Savings Example:
- Source: 100GB of data
- Day 1 backup: 100GB
- Day 2 backup (5GB changed): Only 5GB additional space used!
- You can still browse both complete 100GB backups separately
- Linux/Unix system (or macOS, WSL on Windows)
rsyncinstalledbashshell- Sufficient disk space on backup destination
- Read permissions on source directory
- Write permissions on backup directory
- Clone or download the script:
git clone https://github.com/samslaves/bash-backup-script.git
cd bash-backup-script- Make it executable:
chmod +x backup-script.sh- Configure paths (edit the script):
readonly SOURCE_DIR="/mnt/media/" # Change to your source
readonly BACKUP_DIR="/mnt/backup/media" # Change to your backup locationOpen backup-script.sh and modify these lines:
readonly SOURCE_DIR="/path/to/your/data/"
readonly BACKUP_DIR="/path/to/backup/location"Create exclude-list.txt in the same directory to skip files/folders:
# Example exclude-list.txt
*.tmp
*.cache
.DS_Store
node_modules/
__pycache__/
*.logPattern examples:
*.tmpβ All.tmpfiles*.logβ All log filescache/β All folders named "cache"/specific/pathβ Specific path from source root
./backup-script.shRun daily at 2 AM:
# Edit crontab
crontab -e
# Add this line:
0 2 * * * /path/to/backup-script.sh >> /var/log/backup.log 2>&1To restore from a specific backup:
# List available backups
ls -la /mnt/backup/media/
# Restore from specific date
rsync -av /mnt/backup/media/2024-01-15_14-30-00/ /mnt/media/
# Or restore from latest
rsync -av /mnt/backup/media/latest/ /mnt/media/After running the script, your backup directory looks like this:
/mnt/backup/media/
βββ 2024-01-15_10-00-00/ # First backup (100GB actual)
βββ 2024-01-16_10-00-00/ # Second backup (5GB actual, 100GB apparent)
βββ 2024-01-17_10-00-00/ # Third backup (3GB actual, 100GB apparent)
βββ latest -> 2024-01-17_10-00-00/ # Symlink to most recent
Check actual vs apparent size:
# Apparent size (what you see when browsing)
du -sh /mnt/backup/media/2024-01-16_10-00-00/
# Actual disk usage
du -sh --apparent-size /mnt/backup/media/2024-01-16_10-00-00/# In backup-script.sh
readonly SOURCE_DIR="/home/user/Photos/"
readonly BACKUP_DIR="/mnt/external/photo-backups"
# Run backup
./backup-script.sh
# Output: Backup completed successfully: /mnt/external/photo-backups/2024-01-15_14-30-00# Create exclude-list.txt
echo "*.raw" > exclude-list.txt
echo "*.psd" >> exclude-list.txt
echo "Temp/" >> exclude-list.txt
# Run backup (skips RAW files, PSDs, and Temp folder)
./backup-script.sh# Compare source with latest backup
rsync -avcn --delete /mnt/media/ /mnt/backup/media/latest/
# (dry-run with -n flag, shows differences without changing anything)Solution: Run with sudo or fix permissions:
sudo chown -R $USER:$USER /mnt/backup/mediaSolution:
- Check available space:
df -h /mnt/backup - Remove old backups:
rm -rf /mnt/backup/media/2024-01-10_*
Solution: The script now includes EXIT trap - incomplete backups are automatically removed
Solution: Add compression for network transfers:
rsync -avz ... # Add 'z' flag for compressionset -euo pipefail: Script stops immediately on any error- Trap on SIGINT/SIGTERM/EXIT: Cleans up incomplete backups if interrupted
- Hard link safety: Original files are never modified
- Atomic symlink update:
latestlink always points to complete backup - Process tracking: Background rsync process is properly monitored
# Total space used by all backups
du -sh /mnt/backup/media/
# Space used per backup
du -sh /mnt/backup/media/*/
# Number of hard links (files shared between backups)
find /mnt/backup/media/ -type f -links +1 | wc -lFeel free to submit issues or pull requests to improve this script!
This script is provided as-is under the MIT License.
- Test first! Run the script with test data before using on important files
- Monitor disk space: Ensure backup destination has enough space
- Verify backups: Periodically check that backups are working correctly
- Keep multiple backups: Consider keeping backups in multiple locations
Happy Backing Up! π