In this tutorial, we’ll walk through the creation of a bash script that utilizes Restic to perform backups to Backblaze B2 cloud storage and sends email notifications in case of failure. Restic is a modern backup program that offers efficient deduplication and encryption features.

Prerequisites

Before getting started, make sure you have the following:

  • A Backblaze B2 account and access keys
  • Restic installed on your system
  • Access to an SMTP server for sending emails

Script Overview

The bash script should perform the following tasks:

  1. Sets up the necessary variables, such as AWS access keys, repository name, and email settings.
  2. Checks for the existence of required tools and install any missing dependencies.
  3. Executes Restic commands for backup, retention policies, and cleanup.
  4. Sends email notifications in case of backup failure.

Step-by-Step Guide

Step 1: Setting up Variables

Before running the script, ensure you fill in the necessary variables in the script, such as AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, REPO_NAME, REPO_PWFILE, REPO_URL, BACKUP_FILE_LIST, EXCLUDE_FILE_LIST, RECIPIENT_EMAIL, SMTP_SERVER, SMTP_USERNAME, and SMTP_PASSWORD.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#!/bin/bash

AWS_ACCESS_KEY_ID=""
AWS_SECRET_ACCESS_KEY=""
REPO_NAME="bck-example"
REPO_PWFILE="/root/.restic"
REPO_URL="s3:s3.eu-central-003.backblazeb2.com/$REPO_NAME"
BACKUP_FILE_LIST="./backups.txt"
EXCLUDE_FILE_LIST="./exclude.txt"
RECIPIENT_EMAIL="[email protected]"
SMTP_SERVER="mail.jnns.de:465"
SMTP_USERNAME="[email protected]"
SMTP_PASSWORD=""

Step 2: Defining Helper Functions

The script defines several helper functions:

check_file_exists: Checks if a file exists. check_command_installed: Checks if a command is installed and installs it if not. run_restic: Executes Restic commands and handles errors. urlencode: Encodes special characters in a string for use in URLs. send_email: Sends email notifications in case of failure.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
check_file_exists() {
  [[ -f "$1" ]] || { echo "Error: File $1 not found. Exiting."; exit 1; }
}

check_command_installed() {
  local command_name="$1"
  if ! command -v "$command_name" &> /dev/null; then
    echo "$command_name is not installed. Installing..."
    apt install -y $command_name
  fi
}

run_restic() {
  local output
  output=$(restic -p "$REPO_PWFILE" -r "$REPO_URL" "$@" 2>&1) || { echo "Error: $output"; return 1; }
  echo "$output"
}

urlencode() {
  local string="$1"
  local encoded_string=""
  local char

  for ((i = 0; i < ${#string}; i++)); do
    char="${string:i:1}"
    case "$char" in
      [a-zA-Z0-9.~_-])
        encoded_string+="$char"
        ;;
      *)
        printf -v encoded_char '%%%02x' "'$char"
        encoded_string+="$encoded_char"
        ;;
    esac
  done

  printf "%s" "$encoded_string"
}

send_email() {
  local hostname=$(hostname)
  local subject="Backup Failure on \"$hostname\""
  local body="Backup process failed on $hostname:\n\n$output"

  echo -e "$body" | s-nail -s "$subject" \
    -S v15-compat \
    -S mta=smtps://"$(urlencode $SMTP_USERNAME)":"$SMTP_PASSWORD"@"$SMTP_SERVER" \
    -S from="$SMTP_USERNAME" \
    "$RECIPIENT_EMAIL"
}

Step 3: Installing Dependencies

The script checks if required commands (s-nail and restic) are installed and installs them if necessary.

1
2
check_command_installed "s-nail"
check_command_installed "restic"

Step 4: Backup Process

The script initiates the backup process using Restic. It unlocks the repository if necessary, initializes it if it’s not yet initialized, performs the backup, and applies retention policies.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
check_file_exists "$REPO_PWFILE"
check_file_exists "$BACKUP_FILE_LIST"

if [ -f "$EXCLUDE_FILE_LIST" ]; then
  echo "Using exclude file: $EXCLUDE_FILE_LIST"
  EXCLUDE_OPTION="--exclude-file $EXCLUDE_FILE_LIST"
else
  echo "No exclude file found. Proceeding without exclusions."
  EXCLUDE_OPTION=""
fi

export AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY

restic cache --quiet --cleanup
restic self-update --quiet

echo "Attempting to unlock or initialize repository..."
output=$(run_restic unlock) || { 
  echo "Unlock failed. Running init..."
  output=$(run_restic init) || { 
    echo "Error: Unable to initialize repository."
    send_email
    exit 1
  }
}

echo "Starting backup process..."
output=$(run_restic backup --files-from "$BACKUP_FILE_LIST" $EXCLUDE_OPTION) || { 
  echo "Backup process failed."
  send_email
  exit 1
}

echo "Performing cleanup and retention policies..."
output=$(run_restic forget --keep-daily 7 --keep-weekly 1 --keep-monthly 1 --prune) || { 
  echo "Retention policy execution failed."
  send_email
  exit 1
}

unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY

echo "Backup process completed successfully."

Conclusion

With this bash script, you can automate your backups using Restic and ensure that you’re notified promptly in case of any failures. Feel free to customize the script further to suit your specific backup needs and preferences.

Full source code can be found on my Github: https://github.com/Jnnshschl/SimpleResticB2Backup