Skip to content

buildplan/restic-backup-script

Repository files navigation

Automated Encrypted Backups with Restic

Shell Script Linting CodeQL

This script automates encrypted, deduplicated backups of local directories to a remote SFTP server (such as a Hetzner Storage Box) using restic.


Features

  • Client-Side Encryption: All data is encrypted on your server before being uploaded, ensuring zero-knowledge privacy from the storage provider.
  • Deduplication & Compression: Saves significant storage space by only storing unique data blocks and applying compression.
  • Snapshot-Based Backups: Creates point-in-time snapshots, allowing you to easily browse and restore files from any backup date.
  • Advanced Retention Policies: Sophisticated rules to automatically keep daily, weekly, monthly, and yearly snapshots.
  • Unified Configuration: All settings are managed in a single, easy-to-edit restic-backup.conf file.
  • Notification Support: Sends detailed success, warning, or failure notifications to ntfy, Discord, Slack, and Microsoft Teams.
  • Flexible File Exclusions: Exclude files and directories using either a dedicated exclusion file or by listing patterns directly in the configuration.
  • System Friendly: Uses nice and ionice to minimize CPU and I/O impact during backups.
  • Multiple Operation Modes: Supports standard backups, dry runs, integrity checks, difference summaries, and a safe, interactive restore mode.
  • Concurrency Control & Logging: Prevents multiple instances from running simultaneously and handles its own log rotation.
  • Pre-run Validation: Performs checks for required commands and repository connectivity before execution.
  • Cron Job Monitoring: Optional integration with Healthchecks.io for alerts if a backup job fails to run on schedule.

Quick Start

For those familiar with setting up backup scripts, here is a fast track to get you up and running.

  1. Download Files:

    mkdir -p /root/scripts/backup && cd /root/scripts/backup
    curl -LO https://raw.githubusercontent.com/buildplan/restic-backup-script/refs/heads/main/restic-backup.sh
    curl -LO https://raw.githubusercontent.com/buildplan/restic-backup-script/refs/heads/main/restic-backup.conf
    curl -LO https://raw.githubusercontent.com/buildplan/restic-backup-script/refs/heads/main/restic-excludes.txt
    chmod +x restic-backup.sh
  2. Edit Configuration:

    • Modify restic-backup.conf with your repository details, source paths, and password file location.
    • Set secure permissions: chmod 600 restic-backup.conf.
  3. Create Password & Initialize:

    # Create the password file (use a strong password)
    echo 'your-very-secure-password' | sudo tee /root/.restic-password
    sudo chmod 400 /root/.restic-password
    
    # Initialize the remote repository
    sudo ./restic-backup.sh --init
  4. Run First Backup & Schedule:

    # Run your first backup with verbose output
    sudo ./restic-backup.sh --verbose
    
    # Set up a recurring schedule with the interactive wizard
    sudo ./restic-backup.sh --install-scheduler

Usage

Run Modes

  • sudo ./restic-backup.sh - Run a standard backup silently (suitable for cron).
  • sudo ./restic-backup.sh --verbose - Run with live progress and detailed output.
  • sudo ./restic-backup.sh --dry-run - Preview changes without creating a new snapshot.
  • sudo ./restic-backup.sh --check - Verify repository integrity by checking a subset of data.
  • sudo ./restic-backup.sh --check-full - Run a full check verifying all repository data.
  • sudo ./restic-backup.sh --test - Validate configuration, permissions, and SSH connectivity.
  • sudo ./restic-backup.sh --fix-permissions --test - Run tests and interactively auto-correct insecure file permissions.
  • sudo ./restic-backup.sh --install-scheduler - Run the interactive wizard to set up an automated backup schedule (systemd/cron).
  • sudo ./restic-backup.sh --uninstall-scheduler - Remove a schedule created by the wizard.
  • sudo ./restic-backup.sh --restore - Start the interactive restore wizard.
  • sudo ./restic-backup.sh --background-restore <snapshot> <dest> - Restore in the background (non-blocking).
  • sudo ./restic-backup.sh --sync-restore <snapshot> <dest> - Restore in a cronjob (helpful for 3-2-1 backup strategy).
  • sudo ./restic-backup.sh --forget - Manually apply the retention policy and prune old data.
  • sudo ./restic-backup.sh --diff - Show a summary of changes between the last two snapshots.
  • sudo ./restic-backup.sh --stats - Display repository size, file counts, and stats.
  • sudo ./restic-backup.sh --unlock - Forcibly remove stale locks from the repository.
  • sudo ./restic-backup.sh --snapshots - List all available snapshots in the repository.
  • sudo ./restic-backup.sh --snapshots-delete - Permanently delete specific snapshots.
  • sudo ./restic-backup.sh --init - (One-time setup) Initialize the remote repository.
  • sudo ./restic-backup.sh --help - Displays help and all the flags.

Default log location: /var/log/restic-backup.log


Restoring Data

Script provides three distinct modes for restoring data, each designed for a different scenario.

1. Interactive Restore (--restore)

This is an interactive wizard for guided restores. It is the best option when you are at the terminal and need to find and recover specific files or directories.

  • Best for: Visually finding and restoring specific files or small directories.
  • Process: - Lists available snapshots for you to choose from. - Asks for a destination path. - Performs a "dry run" to show you what will be restored before making any changes. - Requires your confirmation before proceeding with the actual restore.

Usage:

sudo ./restic-backup.sh --restore

2. Background Restore (--background-restore)

This mode is designed for restoring large amounts of data (e.g., a full server recovery) without needing to keep your terminal session active.

  • Best for: Large, time-consuming restores or recovering data over a slow network connection.
  • How it works:
    • This command is non-interactive. You must provide the snapshot ID and destination path as arguments directly on the command line.
    • The restore job is launched in the background, immediately freeing up terminal.
    • All output is saved to a log file in /tmp/.
    • A success or failure notification (via ntfy, Discord, etc.) upon completion.

Usage:

# Restore the latest snapshot to a specific directory in the background
sudo ./restic-backup.sh --background-restore latest /mnt/disaster-recovery

# Restore a specific snapshot by its ID
sudo ./restic-backup.sh --background-restore a1b2c3d4 /mnt/disaster-recovery

3. Synchronous Restore (--sync-restore)

This mode runs the restore in the foreground and waits for it to complete before exiting. It's a reliable, non-interactive way to create a complete, consistent copy of backup data.

  • Best for: Creating a secondary copy of backup (for example, via a cron job) on another server (for a 3-2-1 strategy) or for use in any automation where subsequent steps depend on the restore being finished.
  • How it works:
    • This command is non-interactive and requires the snapshot ID and destination path as command-line arguments.
    • It runs as a synchronous (blocking) process. When a cron job executes the command, the job itself will not finish until the restore is 100% complete.
    • This guarantees the data copy is finished before any other commands are run or the cron job is marked as complete.

Usage:

# On a second server, pull a full copy of the latest backup
sudo ./restic-backup.sh --sync-restore latest /mnt/local-backup-copy

# On your secondary server, run a sync-restore every day at 5:00 AM.
0 5 * * * /path/to/your/script/restic-backup.sh --sync-restore latest /path/to/local/restore/copy >> /var/log/restic-restore.log 2>&1

# Can also be used in a script to ensure a process runs only after a restore
sudo ./restic-backup.sh --sync-restore latest /srv/app/data && systemctl restart my-app

Diagnostics & Error Codes

The script uses specific exit codes for different failures to help with debugging automated runs.

  • Exit Code 1: A fatal configuration error, such as a missing restic-backup.conf file or required variable.
  • Exit Code 5: Lock contention; another instance of the script is already running.
  • Exit Code 10: A required command (like restic or curl) is not installed.
  • Exit Code 11: The RESTIC_PASSWORD_FILE cannot be found.
  • Exit Code 12: The script cannot connect to or access the Restic repository.
  • Exit Code 13: A source directory in BACKUP_SOURCES does not exist or is not readable.
  • Exit Code 14: The EXCLUDE_FILE is not readable.
  • Exit Code 15: The LOG_FILE is not writable.
  • Exit Code 20: The restic init command failed.

File Structure

All files should be placed in a single directory (e.g., /root/scripts/backup).

/root/scripts/backup/
├── restic-backup.sh      (main script)
├── restic-backup.conf    (settings and credentials)
└── restic-excludes.txt   (patterns to exclude from backup)

Setup Instructions

Follow these steps to get the backup system running from scratch.

1. Prerequisites

First, ensure the required tools are installed.

The script relies on several command-line tools to function correctly. Most are standard utilities, but you should ensure they are all present on your system.

Installation

You can install all required packages with a single command.

On Debian or Ubuntu:

sudo apt-get update && sudo apt-get install -y restic jq gnupg curl bzip2 util-linux coreutils less

On CentOS, RHEL, or Fedora:

sudo dnf install -y restic jq gnupg curl bzip2 util-linux coreutils less

You could also download and install the latest version of restic.

Note: While restic can be installed from your system's package manager, it is often an older version. It is recommended to install it manually or allow the script's built-in auto-updater to fetch the latest official version for you.

# Find your architecture (e.g., x86_64 or aarch64)
uname -m
# Download the latest binary for your architecture from the Restic GitHub page
# Go to the Restic GitHub releases page to find the URL for the latest version:
# https://github.com/restic/restic/releases

# Download the latest binary for your architecture (replace URL with the one you found)
curl -LO <URL_of_latest_restic_linux_amd64.bz2>
# Unzip, make executable, and move to your path
bunzip2 restic_*.bz2
chmod +x restic_*
sudo mv restic_* /usr/local/bin/restic

Package Breakdown

Package Required For
restic The core backup tool used for all repository operations (backup, restore, check, forget).
jq Parsing JSON for diff function to show difference between last two snapshots.
curl Sending notifications to ntfy/Discord and fetching the latest version information from the GitHub API.
bzip2 Decompressing the restic binary when using the auto-install/update feature.
gnupg Provides gpg for verifying the PGP signature of the restic binary during auto-updates.
util-linux Provides flock for preventing concurrent script runs and ionice for setting I/O priority.
coreutils Provides essential commands used throughout the script, such as date, grep, sed, chmod, mv, and mktemp.
less Paging through the list of files during an interactive restore (--restore mode).

2. Configure Passwordless SSH Login (Recommended)

The most reliable way for the script to connect to a remote server is via an SSH config file.

  1. Generate a root SSH key if one doesn't already exist:

    sudo ssh-keygen -t ed25519

    (Press Enter through all prompts).

  2. Add your public key to the remote server's authorized keys. For a Hetzner Storage Box, you can paste the contents of sudo cat /root/.ssh/id_ed25519.pub into the control panel.

  3. Create an SSH config file to define an alias for your connection:

    # Open the file in an editor
    sudo nano /root/.ssh/config
  4. Add the following content, adjusting the details for your server:

    Host storagebox
        HostName u123456.your-storagebox.de
        User u123456-sub4
        Port 23
        IdentityFile /root/.ssh/id_ed25519
        ServerAliveInterval 60
        ServerAliveCountMax 240
  5. Set secure permissions and test the connection:

    sudo chmod 600 /root/.ssh/config
    
    # This command should connect without a password and print "/home"
    sudo ssh storagebox pwd

3. Place and Configure Files

  1. Create your script directory:

    mkdir -p /root/scripts/backup && cd /root/scripts/backup
  2. Download the script, configuration, and excludes files from the repository:

    # Download the main script
    curl -LO https://raw.githubusercontent.com/buildplan/restic-backup-script/refs/heads/main/restic-backup.sh
    
    # Download the configuration template
    curl -LO https://raw.githubusercontent.com/buildplan/restic-backup-script/refs/heads/main/restic-backup.conf
    
    # Download the excludes list
    curl -LO https://raw.githubusercontent.com/buildplan/restic-backup-script/refs/heads/main/restic-excludes.txt
  3. Make the script executable:

    chmod +x restic-backup.sh
  4. Set secure permissions for your configuration file:

    chmod 600 restic-backup.conf
  5. Edit restic-backup.conf and restic-excludes.txt to specify your repository path, source directories, notification settings, and exclusion patterns.

Configuration (restic-backup.conf)

All script behavior is controlled by the restic-backup.conf file. Below is an overview of the key settings available.

Core Settings

  • RESTIC_REPOSITORY: The connection string for your remote storage.
  • RESTIC_PASSWORD_FILE: The absolute path to the file containing your repository's encryption password.
  • BACKUP_SOURCES: A list of local directories to back up. Use Bash array syntax ("/path/one" "/path/two") to handle spaces correctly.

Retention Policy

You can define how many snapshots to keep for various timeframes. The script will automatically remove older snapshots that fall outside these rules.

  • KEEP_LAST: Number of the most recent snapshots to keep.
  • KEEP_DAILY: Number of daily snapshots to keep.
  • KEEP_WEEKLY: Number of weekly snapshots to keep.
  • KEEP_MONTHLY: Number of monthly snapshots to keep.
  • KEEP_YEARLY: Number of yearly snapshots to keep.

Notifications

The script can send detailed status notifications to multiple services. Each can be enabled or disabled individually.

  • NTFY_ENABLED: Set to true to enable ntfy notifications.
  • DISCORD_ENABLED: Set to true to enable Discord notifications.
  • SLACK_ENABLED: Set to true to enable Slack notifications.
  • TEAMS_ENABLED: Set to true to enable Microsoft Teams notifications.
  • You must also provide the corresponding _URL and _TOKEN for each service you enable.

Exclusions

You have two ways to exclude files and directories from your backups:

  1. EXCLUDE_FILE: Point this to a text file (like restic-excludes.txt) containing one exclusion pattern per line.
  2. EXCLUDE_PATTERNS: A space-separated list of patterns to exclude directly in the configuration file (e.g., *.tmp *.log).

Performance and Maintenance

  • LOW_PRIORITY: Set to true to run the backup with lower CPU (nice) and I/O (ionice) priority, minimizing impact on other services.
  • CHECK_AFTER_BACKUP: Set to true to automatically run a repository integrity check after each successful backup.
  • PRUNE_AFTER_FORGET: Set to true to automatically prune the repository after applying the retention policy, which frees up storage space.

4. Initial Repository Setup

Before the first backup, you need to create the repository password file and initialize the remote repository.

  1. Create the password file. This stores the encryption key for your repository. Guard this file carefully!

    # Replace 'your-very-secure-password' with a strong, unique password
    echo 'your-very-secure-password' | sudo tee /root/.restic-password
    
    # Set secure permissions
    sudo chmod 400 /root/.restic-password
  2. Initialize the repository. Run the script with the --init flag:

    # Navigate to your script directory
    cd /root/scripts/backup
    
    # Run the initialization
    sudo ./restic-backup.sh --init

5. Set up an Automated Schedule (Recommended)

The easiest and most reliable way to schedule your backups is to use the script's built-in interactive wizard. It will guide you through creating and enabling either a modern systemd timer (recommended) or a traditional cron job.

  1. Navigate to your script directory:

    cd /root/scripts/backup
  2. Run the scheduler installation wizard:

    sudo ./restic-backup.sh --install-scheduler

Follow the on-screen prompts to choose your preferred scheduling system and frequency. The script will handle creating all the necessary files and enabling the service for you.

Manual Cron Job Setup

If you prefer to manage the schedule manually instead of using the wizard, you can edit the root crontab directly.

To run the backup automatically, edit the root crontab.

  1. Open the crontab editor:

    sudo crontab -e
  2. Add the following lines to schedule your backups and maintenance.

    # Define a safe PATH that includes the location of restic
    PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
    
    # Run the Restic backup every day at 3:00 AM
    0 3 * * * /root/scripts/backup/restic-backup.sh > /dev/null 2>&1
    
    # Run the retention/prune job every Sunday at 4:00 AM 
    0 4 * * 0 /root/scripts/backup/restic-backup.sh --forget > /dev/null 2>&1
    
    # Cron job for a monthly full check (e.g., first Sunday of the month at 3 AM)
    0 3 * * 0 [ $(date +\%d) -le 07 ] && /root/scripts/backup/restic-backup.sh --check-full > /dev/null 2>&1
    

    For prune job in your restic-backup.conf, set PRUNE_AFTER_FORGET=true.
    For more details on how forget flag work, see the official Restic documentation on removing snapshots.
    Redirecting output to /dev/null is recommended, as the script handles its own logging and notifications.