Building a Real Project: Automated Backup Script
Combine everything from this course into one working script that backs up a folder, compresses it, timestamps it, logs its work and is ready to schedule.
What we are building
This project combines what you have learned into one working tool. The script will:
- take a folder to back up as an argument,
- save a compressed
.tar.gzarchive with the date in its name, - write messages to a log file,
- delete backups older than 7 days,
- be ready to run from cron.
The steps are: check the input, create the archive, log the result, then delete old backups. Each step below adds one part of the script.
Step 1: settings
#!/bin/bash
# backup.sh - back up a folder to a compressed archive
SOURCE="$1"
DEST="$HOME/backups"
KEEP_DAYS=7
STAMP=$(date +%F_%H-%M-%S)
LOG="$DEST/backup.log"
$1 is the folder given on the command line. Putting settings at the top means you change them in one place. STAMP holds the current date and time, for example 2026-10-05_02-00-01.
Step 2: check the input
if [ $# -ne 1 ]; then
echo "Usage: $0 <folder-to-back-up>"
exit 1
fi
if [ ! -d "$SOURCE" ]; then
echo "Error: $SOURCE is not a folder"
exit 2
fi
mkdir -p "$DEST"
The first test checks that exactly one argument was given. The second checks that it is a directory, because tar would produce a confusing error for a missing or wrong path. The script stops early with a clear message and a different exit code for each problem. Finally mkdir -p makes sure the destination folder exists.
Step 3: a logging function
log() {
echo "[$(date '+%F %T')] $1" | tee -a "$LOG"
}
The function adds a time stamp to a message. tee -a prints it to the screen and appends it to the log file. From now on the script calls log instead of echo. Define this function before it is first used.
Step 4: create the archive
NAME=$(basename "$SOURCE")
ARCHIVE="$DEST/${NAME}_$STAMP.tar.gz"
log "Starting backup of $SOURCE"
if tar -czf "$ARCHIVE" -C "$(dirname "$SOURCE")" "$NAME"; then
SIZE=$(du -h "$ARCHIVE" | cut -f1)
log "Backup created: $ARCHIVE ($SIZE)"
else
log "ERROR: backup failed"
exit 3
fi
basenamegives the last part of the path, so/home/alex/DocumentsbecomesDocuments.dirnamegives the part before it.tar -czfmeans create an archive, compress it with gzip, and write it to the file named next.-Ctellstarto change to that folder first, so the archive holdsDocuments/...and not the full path.- The
ifrunstardirectly. Iftarsucceeds (exit status 0) the first block runs, otherwise theelseblock logs the failure and exits. du -hreports the archive size andcut -f1keeps only the size column.
Step 5: clean up old backups
log "Removing backups older than $KEEP_DAYS days"
find "$DEST" -name "${NAME}_*.tar.gz" -mtime +"$KEEP_DAYS" -delete
log "Done"
-mtime +7 matches files modified more than 7 days ago. The name pattern limits the deletion to backups of this folder, so nothing else in $DEST is touched.
The complete script
#!/bin/bash
# backup.sh - back up a folder to a compressed archive
SOURCE="$1"
DEST="$HOME/backups"
KEEP_DAYS=7
STAMP=$(date +%F_%H-%M-%S)
LOG="$DEST/backup.log"
log() {
echo "[$(date '+%F %T')] $1" | tee -a "$LOG"
}
if [ $# -ne 1 ]; then
echo "Usage: $0 <folder-to-back-up>"
exit 1
fi
if [ ! -d "$SOURCE" ]; then
echo "Error: $SOURCE is not a folder"
exit 2
fi
mkdir -p "$DEST"
NAME=$(basename "$SOURCE")
ARCHIVE="$DEST/${NAME}_$STAMP.tar.gz"
log "Starting backup of $SOURCE"
if tar -czf "$ARCHIVE" -C "$(dirname "$SOURCE")" "$NAME"; then
SIZE=$(du -h "$ARCHIVE" | cut -f1)
log "Backup created: $ARCHIVE ($SIZE)"
else
log "ERROR: backup failed"
exit 3
fi
log "Removing backups older than $KEEP_DAYS days"
find "$DEST" -name "${NAME}_*.tar.gz" -mtime +"$KEEP_DAYS" -delete
log "Done"
Run it and schedule it
chmod +x backup.sh
./backup.sh ~/Documents
ls -lh ~/backups
tar -tzf ~/backups/Documents_*.tar.gz | head
tar -tzf lists the contents of an archive without extracting it, so head shows the first few entries.
To run the backup every night at 2 AM, add this to crontab -e. Use full paths because cron has a small environment:
0 2 * * * /home/alex/backup.sh /home/alex/Documents >> /home/alex/backup-cron.log 2>&1
Test the restore
A backup you have never restored is untested. Extract one into a temporary folder and check that the files are there:
mkdir -p /tmp/restore-test
tar -xzf ~/backups/Documents_*.tar.gz -C /tmp/restore-test
ls /tmp/restore-test
-x extracts, -z handles gzip and -f names the file. -C chooses where to extract.
Ideas to extend it
- Back up several folders by looping over all arguments.
- Add a
--dry-runflag that prints what it would do. - Exclude folders with
tar --exclude=node_modules. - Copy the archive to another machine with
scporrsync. - Send yourself a message if the backup fails.
Do not add steps that delete the source folder. A backup script should never remove the data it protects.