Simple Restic SH Backup Script w/ Hooks

Hello! I have been a user and others have helped me along the way. I want to share with the community as a Freebsd user who likes to keep it simple. I have a simple sh script used to execute a Restic backup weekly. I have pre and post hooks for some simple diff checks between snapshots, restic --read-data and restic forget/prune. Anyone is free to use it, make it better, give suggestions if they feel like it. Here it is!

#!/bin/sh
# restic-backup.sh — Clean FreeBSD-style restic backup script with hook support

SCRIPT_NAME="restic"

: "${LOG_FACILITY:=local6}"
: "${LOG_LEVEL:=info}"

CONFIG_FILE="/usr/local/etc/restic-backup.conf"
[ -n "$1" ] && CONFIG_FILE="$1"
[ ! -f "$CONFIG_FILE" ] && { logger -t "$SCRIPT_NAME" -p "$LOG_FACILITY.error" "Missing config: $CONFIG_FILE"; exit 1; }

. "$CONFIG_FILE"

log() {
  logger -t "$SCRIPT_NAME" -p "$LOG_FACILITY.$LOG_LEVEL" "$1"
}

log_err() {
  logger -t "$SCRIPT_NAME" -p "$LOG_FACILITY.error" "$1"
}

run_hooks() {
  local priority="$1"
  shift
  for hook in "$@"; do
    [ -f "$hook" ] || { log_err "Hook not found: $hook"; continue; }
    "$hook" 2>&1 | logger -t "$SCRIPT_NAME" -p "$priority"
  done
}

command -v restic >/dev/null 2>&1 || { log_err "restic not found in PATH"; exit 1; }

export RESTIC_REPOSITORY="sftp:${REMOTE_USER}@${REMOTE_SERVER}:${REMOTE_REPO_PATH}"
export RESTIC_PASSWORD_FILE="$PASSWORD_FILE"

log "Starting restic backup"

restic cat config >/dev/null 2>&1 || {
  log_err "Restic repository inaccessible or uninitialized"
  exit 1
}

[ -n "$PRE_HOOKS" ] && run_hooks "$LOG_FACILITY.$LOG_LEVEL" $PRE_HOOKS

# POSIX-safe alternative to eval, native to FreeBSD sh
set -- $(printf '%s\n' "$BACKUP_OPTIONS $BACKUP_PATHS")

if restic backup "$@" 2>&1 | sed '/^[[:space:]]*$/d' | logger -t "$SCRIPT_NAME" -p "$LOG_FACILITY.$LOG_LEVEL"; then
  log "Backup completed successfully"
  [ -n "$POST_HOOKS_SUCCESS" ] && run_hooks "$LOG_FACILITY.$LOG_LEVEL" $POST_HOOKS_SUCCESS
else
  log_err "Backup failed"
  [ -n "$POST_HOOKS_FAILURE" ] && run_hooks "$LOG_FACILITY.error" $POST_HOOKS_FAILURE
fi

.conf file

# Remote repository settings
REMOTE_USER=""
REMOTE_SERVER=""
REMOTE_REPO_PATH=""
PASSWORD_FILE=""
# Logging settings
LOG_FACILITY=""
LOG_LEVEL=""
BACKUP_PATHS=""
# Additional restic options
BACKUP_OPTIONS="--exclude-caches --one-file-system"
# Hooks (optional)
# space-separated paths to executable scripts
PRE_HOOKS="/usr/local/bin/restic-check.sh"
POST_HOOKS_SUCCESS="/usr/local/bin/restic-diff.sh /usr/local/bin/restic-clean.sh"
POST_HOOKS_FAILURE=""
1 Like

pre_hooks:

#!/bin/sh
restic check --read-data-subset=10% 2>&1 | sed '/^[[:space:]]*$/d'

1 Like

post_hooks_success:

#!/bin/sh
# resticdiff.sh — compare the latest two restic snapshots

snapshots=$(restic snapshots --json 2>&1)
snapshot_ids=$(echo "$snapshots" | grep -o '"short_id":"[^"]*"' | cut -d'"' -f4 | tail -2)

from=$(echo "$snapshot_ids" | head -1)
to=$(echo "$snapshot_ids" | tail -1)

[ -z "$from" ] || [ -z "$to" ] && {
    echo "Not enough snapshots to compare"
    exit 0
}

restic diff "$from" "$to" 2>&1 | sed '/^[[:space:]]*$/d'

last :

#!/bin/sh
(restic forget -c --keep-last 30 --keep-within 1y && restic prune) 2>&1 | sed '/^[[:space:]]*$/d'
1 Like