Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 34

Automating Project Directory Backups

Recommended resources

Introduction

Hello! Welcome to the final lesson in our Bash scripting module. Over the last several lessons, you've assembled a powerful toolkit: you've learned to use variables, write functions, control logic with if statements, iterate with loops, parse command-line arguments, and, most recently, make your scripts robust by handling errors and exit codes.

Today, we will bring all of these skills together to achieve this lesson's goal: to write a practical automation script to back up a specified project directory. This isn't just a theoretical exercise; it's a foundational task for any system administrator or web developer. As you progress with your Django projects, you'll need a reliable way to create backups before deploying new code, migrating a database, or moving your project to a new server. A well-written backup script is your safety net.

We'll focus on using rsync, a powerful and efficient tool for synchronizing files. By the end of this lesson, you will have built a reusable, real-world backup script that incorporates the best practices you've learned throughout this module.


1. The Anatomy of a Professional Backup Script

A good backup script does more than just copy files. It should be smart, safe, and informative. The best way to understand these principles is to see a professional script being built and explained.

We are going to watch a comprehensive video that walks through the creation of a backup script using rsync. This script is an excellent model because it includes several features we've discussed, such as argument validation and dependency checking, and introduces some powerful rsync-specific features.

How To Write Bash Scripts In Linux - Complete Guide (Part 17 - Backup Script)

This video, "How To Write Bash Scripts In Linux - Complete Guide (Part 17 - Backup Script)" from Learn Linux TV, is a masterclass in building a practical script. It will serve as our main guide for this lesson.

Please watch the entire video (from 00:48 to the end). As you watch, focus on how the script is constructed piece by piece: Argument & Dependency Checks (02:08): Notice how if statements are used to ensure the user provides the correct number of arguments and that rsync is installed. This makes the script user-friendly and robust. Date-Stamping (04:19): See how the date command is used with command substitution to create dynamically named directories. This is key for organizing backups over time. Key rsync Options (05:15): Pay close attention to the explanation of the rsync options, especially: -a (archive), -v (verbose), --delete --backup and --backup-dir: This combination creates a powerful incremental backup system. --dry-run: This is the most important option for testing. It simulates the backup without changing any files. Logging (11:57): Understand why the script's output is redirected to a date-stamped log file for auditing purposes. Testing and Execution (14:20): Observe the complete workflow: testing with --dry-run, fixing an error, running the script for real, and then verifying the results.

This video demonstrates a key principle of system administration: automation should be predictable and verifiable. The use of --dry-run and log files isn't optional; it's essential for trusting your scripts.


2. Two Approaches to Backups: rsync vs. tar

The video focuses on rsync, which excels at synchronizing directories. It's incredibly efficient because, after the first run, it only transfers the parts of files that have changed. This makes it ideal for frequent, automated backups of a project that is actively being developed. The incremental backup strategy shown in the video, where old versions of files are moved to dated folders, is a perfect example of rsync's power.

Another common tool is tar, which stands for "tape archive." Its purpose is to bundle many files and directories into a single archive file (e.g., project.tar.gz). This is useful when you want to create a single, portable snapshot of your project, perhaps to download it or send it to someone.

For your goal of managing a live Django project, rsync is generally the more powerful and efficient tool for regular backups on your server.

If you're interested in seeing how a tar-based script is built, the "Bash Scripting Tutorial for Beginners" article provides a great step-by-step guide. It evolves a simple script into a more complex one using many of the techniques we've covered. Consider this optional further reading.

Bash Scripting Tutorial for Beginners

For an alternative perspective, this article from linuxconfig.org builds a backup script using tar. You can see how it evolves to include variables, functions, and error checking.

This is optional. Skim through the sections starting from "Simple Backup Bash Shell Script" to the end. You don't need to study it in depth, but notice how the backup.sh script is gradually improved in each section.


Do-It-Yourself: Backing Up Your Django Project

Now it's time to build your own backup script, applying the best practices from the Learn Linux TV video. We will create a script named backup_project.sh to back up a mock Django project directory.

Your Goal: Create a script that takes a source directory and a destination directory as arguments and uses rsync to create an incremental backup, complete with logging and a "dry run" mode for safety.

Instructions:

  1. Set up your environment:
    First, let's create a mock Django project to work with. Open your terminal and run these commands:

    cd ~
    
    
    
    # Create the project structure
    mkdir -p django_projects/my_site/static/css
    
    
    
    # Create some dummy files
    touch django_projects/my_site/manage.py
    touch django_projects/my_site/my_site/settings.py
    touch django_projects/my_site/my_site/urls.py
    echo "body { color: blue; }" > django_projects/my_site/static/css/style.css
    
    
    
    
    # Create a directory to store your scripts
    mkdir -p ~/automation_scripts
    cd ~/automation_scripts
    
  2. Create the Script File:
    Create a new file named backup_project.sh and make it executable.

    touch backup_project.sh
    chmod +x backup_project.sh
    
  3. Write the Script:
    Open backup_project.sh in your preferred text editor (like vim, nano, or VS Code) and build the script step-by-step. Use the code from the Learn Linux TV video and the concepts from our previous lessons as your guide.

    Your script should include:

    • The shebang line and "strict mode" (set -euo pipefail).
    • Argument validation to ensure exactly two arguments (source and destination) are provided. If not, print a usage message and exit with an error code.
    • A check to ensure rsync is installed. If not, print an error and exit.
    • A variable to hold the current date in YYYY-MM-DD format.
    • A variable to hold the rsync options. Crucially, start with --dry-run included in your options!
    • The main rsync command that:
      • Uses the options you defined.
      • Takes the source ($1) and destination ($2) from the command line.
      • Implements the incremental backup strategy (--backup --backup-dir=...).
      • Redirects all output to a log file in the destination directory, named with the current date (e.g., backup-2024-10-27.log).
  4. Test with Dry Run:
    Now, run your script to back up the mock project. We'll create a backups directory in your home folder to store the backups.

    
    
    
    # Make sure you are in ~/automation_scripts
    mkdir -p ~/backups
    ./backup_project.sh ~/django_projects/my_site ~/backups
    
    • Check the contents of the log file (~/backups/backup-YYYY-MM-DD.log). You should see a list of files that would have been copied and a "dry run" message at the end.
    • Verify that the ~/backups directory is still empty (except for the log file), because it was just a simulation.
  5. Perform the Real Backup:
    Once you're confident the script is behaving correctly, edit backup_project.sh and remove the --dry-run option. Save the file.

    Now, run the script again:

    ./backup_project.sh ~/django_projects/my_site ~/backups
    
    • Check the ~/backups directory. You should now see a current directory containing your full project.
    • Examine the log file again. It should show the files being copied, but without the "dry run" message.
  6. Test the Incremental Backup:
    Let's simulate a change to the project and run the backup again.

    
    
    
    # Modify a file in the source project
    echo "/* A new comment */" >> ~/django_projects/my_site/static/css/style.css
    
    
    
    
    # Run the backup again
    ./backup_project.sh ~/django_projects/my_site ~/backups
    
    • Check the log file. It should now only list the style.css file that changed.
    • Inspect the ~/backups directory. You will find a new date-stamped directory (e.g., 2024-10-27). Inside it will be the previous version of style.css. The ~/backups/current directory will contain the newest version.

You have successfully created and tested a robust, incremental backup script!

Click here to see a complete example of the `backup_project.sh` script.



#!/usr/bin/env bash




# Exit immediately if a command exits with a non-zero status.
# Treat unset variables as an error.
# The return value of a pipeline is the status of the last command to exit
# with a non-zero status, or zero if no command exited with a non-zero status.
set -euo pipefail




# --- Argument Validation ---
if [[ "$#" -ne 2 ]]; then
    echo "Usage: $0 <source_directory> <destination_directory>"
    echo "Example: $0 ~/django_projects/my_site ~/backups"
    exit 1
fi

SOURCE_DIR="$1"
DEST_DIR="$2"




# --- Dependency Check ---
if ! command -v rsync &> /dev/null; then
    echo "Error: rsync is not installed. Please install it to continue." >&2
    exit 2
fi




# --- Main Logic ---
CURRENT_DATE=$(date "+%Y-%m-%d")
LOG_FILE="${DEST_DIR}/backup-${CURRENT_DATE}.log"




# Use --archive for permissions, --verbose for output, --backup and --backup-dir for incrementals,
# --delete to remove files in dest that are gone from source.
# Start with --dry-run for testing!
RSYNC_OPTS=(
    --archive
    --verbose
    --backup
    --backup-dir="${DEST_DIR}/${CURRENT_DATE}"
    --delete



    # --dry-run # <--- Comment this out or remove it to perform a real backup
)

echo "Starting backup of ${SOURCE_DIR} to ${DEST_DIR} on ${CURRENT_DATE}" | tee -a "$LOG_FILE"




# Create destination directory if it doesn't exist
mkdir -p "$DEST_DIR"




# Run rsync and append its output (stdout and stderr) to the log file
# The 'tee -a' command above already prints the start message to the log
rsync "${RSYNC_OPTS[@]}" "${SOURCE_DIR}/" "${DEST_DIR}/current/" >> "$LOG_FILE" 2>&1

echo "Backup finished. Log available at ${LOG_FILE}"

Note: This example uses a Bash array RSYNC_OPTS=(...) to hold the options. This is a robust way to handle arguments with spaces. The tee -a command is used to print the starting message to both the screen and the log file.


Conclusion

Congratulations on completing the Bash scripting module! You have successfully built a practical, powerful, and robust automation script that directly supports your goals as a future Full Stack Developer. This backup script is not just an exercise; it's a real tool you can adapt and use for your projects.

Key Takeaways:

  • A great backup script is more than a copy command. It includes argument validation, dependency checks, and clear logging.
  • rsync is the industry standard for creating efficient, incremental backups.
  • The --dry-run option is your most important safety feature. Always test with a dry run before executing a command that modifies or deletes files.
  • Organizing backups with date-stamped directories for logs and changed files creates a clear and auditable history.

You have now mastered the fundamental building blocks of shell scripting. This knowledge will be invaluable as you automate deployment tasks, manage servers, and build complex applications.

In our next module, User and Group Administration, we'll shift our focus to managing who can access your system and what they are allowed to do. We'll start by learning how to create, modify, and delete user accounts. This is a critical skill for securing your development and production environments, as you'll often want to run services like your Django application or a database under dedicated, non-root user accounts.