Project 04 — Backup Script¶
Build an incremental backup system with rsync, timestamped archives, and automatic rotation that keeps only the last N backups.
What You Will Build¶
A backup script that:
- Creates timestamped directory snapshots using rsync --link-dest (hard-link incremental backups)
- Compresses old backups into .tar.gz archives
- Rotates backups, keeping only the last N (configurable)
- Logs every operation
- Can be scheduled via cron
- Supports a --restore mode to recover files
Why rsync --link-dest?¶
Hard-link backups with --link-dest create the appearance of full backups while storing only the changed files. Each backup looks complete but shares unchanged files with previous backups via hard links — saving enormous amounts of disk space.
backup-2024-01-13/
file-a.txt (hard link to 2024-01-12 version — no new disk space)
file-b.txt (hard link to 2024-01-12 version — no new disk space)
file-c.txt (NEW version — new disk space used)
Getting Started¶
Step 1 — Simple rsync Backup¶
#!/usr/bin/env bash
set -euo pipefail
SOURCE="$1"
DEST="$2"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
rsync -av --delete "$SOURCE/" "$DEST/backup_$TIMESTAMP/"
echo "Backup complete: $DEST/backup_$TIMESTAMP/"
Step 2 — Hard-Link Incremental Backup¶
BACKUP_DIR="$DEST"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
DEST_NEW="$BACKUP_DIR/$TIMESTAMP"
DEST_LAST=$(ls -dt "$BACKUP_DIR"/[0-9]* 2>/dev/null | head -1 || echo "")
if [[ -n "$DEST_LAST" ]]; then
rsync -av --delete --link-dest="$DEST_LAST" "$SOURCE/" "$DEST_NEW/"
else
rsync -av "$SOURCE/" "$DEST_NEW/"
fi
Step 3 — Rotation¶
KEEP=7
# List backups sorted oldest first, delete if more than $KEEP exist
backup_count=$(ls -d "$BACKUP_DIR"/[0-9]* 2>/dev/null | wc -l)
if (( backup_count > KEEP )); then
to_delete=$(( backup_count - KEEP ))
ls -dt "$BACKUP_DIR"/[0-9]* | tail -"$to_delete" | while IFS= read -r old; do
echo "Removing old backup: $old"
rm -rf -- "$old"
done
fi
Step 4 — Full Script with Config¶
#!/usr/bin/env bash
set -euo pipefail
CONFIG="${HOME}/.backup.conf"
[[ -f "$CONFIG" ]] && source "$CONFIG"
SOURCE="${BACKUP_SOURCE:?Set BACKUP_SOURCE in $CONFIG}"
DEST="${BACKUP_DEST:?Set BACKUP_DEST in $CONFIG}"
KEEP="${BACKUP_KEEP:-7}"
LOGFILE="${BACKUP_LOG:-/var/log/backup.log}"
Sample ~/.backup.conf:
BACKUP_SOURCE=/home/user/documents
BACKUP_DEST=/mnt/backup/documents
BACKUP_KEEP=14
BACKUP_LOG=/var/log/user-backup.log
cron Schedule¶
Stretch Goals¶
- Add email notification on failure
- Add
--restore DATEmode that rsyncs a specific backup back toSOURCE - Add pre-backup hooks (e.g., dump a database before backing up)
- Add off-site sync: after local backup, sync to S3 or remote server via
rsyncover SSH
[[03-log-analyzer]] | [[05-git-hooks]]