#!/usr/bin/env bash # shellcheck disable=SC2317,SC2086,SC2181,SC2129,SC2002 # # restic backup using rclone as the transport # - designed to be cron/timer driven, uses repo key on disk (security warning) # # (a) set up and test the rclone remote # (b) run the restic init first-time repo prep # (c) prepare the encryption key and excludes files, chmod 0600 # (d) uses ~/.mailrc to send backup log/report via `mail` cmd # # SPDX-License-Identifier: MIT ## ## TEMPLATE - NEEDS ALL SYSTEM SPECIFIC VARIABLES CONFIGURED ## # restic exit codes: # Exit status is 0 if the command was successful # Exit status is 1 if there was a fatal error # (no snapshot created). # Exit status is 3 if some source data could not be read # (incomplete snapshot created). # restic init: # restic -r rclone:remote: init # restic excludes format examples: # Downloads/ # VirtualBox** # .config/**Cache* # .config/*/sessions # .config/restic/xyzzy.txt # .thunderbird/**/*.msf # .xsession-errors* # cloud provider expired SSL cert hack RCLONE_NO_CHECK_CERTIFICATE=true export RCLONE_NO_CHECK_CERTIFICATE # email and log settings MAILTO="user@example.com" LOGDIR="/home/user/.logs" TIMESTAMP=$(date +%Y-%m-%d_%H%M) MAILSUB="restic backup: ${TIMESTAMP}" LOGFILE="${LOGDIR}/restic_${TIMESTAMP}.log" # restic settings REPO="rclone:remote:" RSRC="/home/user" # encryption key, plaintext, chmod 0600 RKEY="/home/user/.config/restic/xyzzy.txt" # excludes, chmod 0600 EXCL="/home/user/.config/restic/excludes.txt" # snapshots to keep KEEP=10 # trap our signals function error_exit { echo "Trapped a kill signal, exiting." exit 99 } trap error_exit SIGHUP SIGINT SIGTERM # this may be needed for non-packaged binaries # (e.g. using newer restic on older LTS distro) function check_restic() { echo "Checking restic..." # get installed version _LOCAL=$(restic version 2>/dev/null | awk '{print $2}'; exit ${PIPESTATUS[0]}) if [[ $? -ne 0 ]]; then echo "Unable to get installed restic version" >> "${LOGFILE}" exit 1 fi # get latest version, strip leading "v" (v1.55.1 -> 1.55.1) _REMOTE=$(curl -s "https://api.github.com/repos/restic/restic/releases/latest" | grep -Po '"tag_name": "\K.*?(?=")') _REMOTE=${_REMOTE#v} # bash doesn't see versions as numbers, but as strings if [[ "${_LOCAL}" != "${_REMOTE}" ]]; then echo "Upgrade restic - installed ${_LOCAL}, latest ${_REMOTE}" >> "${LOGFILE}" else echo "Installed restic is the latest - ${_LOCAL}" >> "${LOGFILE}" fi } # uncomment as needed #check_restic # begin timestamp echo "== ${TIMESTAMP} ==" >> "${LOGFILE}" # remove stale locks restic -r "${REPO}" \ --password-file="${RKEY}" \ unlock >> "${LOGFILE}" _EC=$? # create a new snapshot if [[ $_EC -eq 0 ]]; then restic --quiet -r "${REPO}" \ backup --no-scan \ --password-file="${RKEY}" \ --exclude-file="${EXCL}" \ "${RSRC}" >> "${LOGFILE}" _EC=$? else echo "Non-zero exit from restic unlock: $_EC" >> "${LOGFILE}" fi # prune older snapshots if [[ $_EC -eq 0 ]]; then restic --quiet -r "${REPO}" \ forget --prune --keep-last ${KEEP} \ --password-file="${RKEY}" >> "${LOGFILE}" _EC=$? if [[ $_EC -ne 0 ]]; then echo "Non-zero exit from restic forget: $_EC" >> "${LOGFILE}" fi else echo "Non-zero exit from restic backup: $_EC" >> "${LOGFILE}" fi # check reposotory health restic --cleanup-cache -r "${REPO}" \ check --password-file="${RKEY}" >> "${LOGFILE}" _EC=$? if [[ $_EC -ne 0 ]]; then echo "Non-zero exit from restic check: $_EC" >> "${LOGFILE}" else # list available snapshots restic -r "${REPO}" \ snapshots -c --password-file="${RKEY}" >> "${LOGFILE}" # generate stats about repo restic -r "${REPO}" \ stats --mode=files-by-contents --password-file="${RKEY}" >> "${LOGFILE}" restic -r "${REPO}" \ stats --mode=raw-data --password-file="${RKEY}" >> "${LOGFILE}" fi # end timestamp _NOW=$(date +%Y-%m-%d_%H%M) echo "== ${_NOW} ==" >> "${LOGFILE}" # ~/.mailrc needs to be configured cat "${LOGFILE}" | mail -A mailprovider -s "${MAILSUB}" "${MAILTO}" # remove older logfiles find "${LOGDIR}" -type f -name restic\* -mtime +30 -delete exit 0