* Before, doing `$ PREFIX=/usr/local make install` would install files to`/usr/local/usr/local..` which is wrong * With this PR, files will be installed to the expected location e.g. `/usr/local/etc/restic` * `Makefile` almost completely rewritten * As e.g. `default.env` would source `_global.env`, `default.env` must be edited to find the right location of `_global.env` depending on what `$PREFIX` was set to. * see documented build stages in the `Makefile` itself. * Made sure that the rules are correct so that only modifed files are installed, not all at once unnecessarily like before. * A sub-goal was that the [PKGBUILD](https://aur.archlinux.org/cgit/aur.git/tree/PKGBUILD?h=restic-systemd-automatic-backup#n20) for Arch should not need to do any custom install configuration, to keep everything easier to maintain. `$ make install` should work out of the box for Arch. * Additionally added the `-b` flag to `install(1)` that makes a backup of existing `etc/restic/*` files before installing a newer version. Fixes #49
104 lines
4.4 KiB
Bash
104 lines
4.4 KiB
Bash
#!/usr/bin/env bash
|
|
# Make a backup with restic to Backblaze B2.
|
|
#
|
|
# This script is typically run (as root user) either like:
|
|
# - from restic service/timer: $PREFIX/etc/systemd/system/restic-backup.{service,timer}
|
|
# - from a cronjob: $PREFIX/etc/cron.d/restic
|
|
# - manually by a user. For it to work, the environment variables must be set in the shell where this script is executed
|
|
# $ source $PREFIX/etc/default.env
|
|
# $ restic_backup.sh
|
|
|
|
# Exit on error, unset var, pipe failure
|
|
set -euo pipefail
|
|
|
|
# Clean up lock if we are killed.
|
|
# If killed by systemd, like $(systemctl stop restic), then it kills the whole cgroup and all it's subprocesses.
|
|
# However if we kill this script ourselves, we need this trap that kills all subprocesses manually.
|
|
exit_hook() {
|
|
echo "In exit_hook(), being killed" >&2
|
|
jobs -p | xargs kill
|
|
restic unlock
|
|
}
|
|
trap exit_hook INT TERM
|
|
|
|
|
|
# Assert that all needed environment variables are set.
|
|
# TODO in future if this grows, move this to a restic_lib.sh
|
|
assert_envvars() {
|
|
local varnames=("$@")
|
|
for varname in "${varnames[@]}"; do
|
|
if [ -z ${!varname+x} ]; then
|
|
printf "%s must be set for this script to work.\n\nDid you forget to source a /etc/restic/*.env profile in the current shell before executing this script?\n" "$varname" >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
}
|
|
assert_envvars \
|
|
B2_ACCOUNT_ID B2_ACCOUNT_KEY B2_CONNECTIONS \
|
|
RESTIC_BACKUP_PATHS RESTIC_BACKUP_TAG \
|
|
RESTIC_BACKUP_EXCLUDE_FILE RESTIC_BACKUP_EXTRA_ARGS RESTIC_PASSWORD_FILE RESTIC_REPOSITORY RESTIC_VERBOSITY_LEVEL \
|
|
RESTIC_RETENTION_DAYS RESTIC_RETENTION_MONTHS RESTIC_RETENTION_WEEKS RESTIC_RETENTION_YEARS
|
|
|
|
|
|
# Convert to arrays, as arrays should be used to build command lines. See https://github.com/koalaman/shellcheck/wiki/SC2086
|
|
IFS=':' read -ra backup_paths <<< "$RESTIC_BACKUP_PATHS"
|
|
IFS=' ' read -ra extra_args <<< "$RESTIC_BACKUP_EXTRA_ARGS"
|
|
|
|
|
|
# Set up exclude files: global + path-specific ones
|
|
# NOTE that restic will fail the backup if not all listed --exclude-files exist. Thus we should only list them if they are really all available.
|
|
## Global backup configuration.
|
|
exclusion_args=(--exclude-file "$RESTIC_BACKUP_EXCLUDE_FILE")
|
|
## Self-contained backup exclusion files per backup path. E.g. having an USB disk at /mnt/media in RESTIC_BACKUP_PATHS,
|
|
# then a file /mnt/media/.backup_exclude.txt will automatically be detected and used:
|
|
for backup_path in "${backup_paths[@]}"; do
|
|
if [ -f "$backup_path/.backup_exclude.txt" ]; then
|
|
exclusion_args=("${exclusion_args[@]}" --exclude-file "$backup_path/.backup_exclude.txt")
|
|
fi
|
|
done
|
|
|
|
# NOTE start all commands in background and wait for them to finish.
|
|
# Reason: bash ignores any signals while child process is executing and thus the trap exit hook is not triggered.
|
|
# However if put in subprocesses, wait(1) waits until the process finishes OR signal is received.
|
|
# Reference: https://unix.stackexchange.com/questions/146756/forward-sigterm-to-child-in-bash
|
|
|
|
# Remove locks from other stale processes to keep the automated backup running.
|
|
restic unlock &
|
|
wait $!
|
|
|
|
# Do the backup!
|
|
# See restic-backup(1) or http://restic.readthedocs.io/en/latest/040_backup.html
|
|
# --one-file-system makes sure we only backup exactly those mounted file systems specified in $RESTIC_BACKUP_PATHS, and thus not directories like /dev, /sys etc.
|
|
# --tag lets us reference these backups later when doing restic-forget.
|
|
restic backup \
|
|
--verbose="$RESTIC_VERBOSITY_LEVEL" \
|
|
--one-file-system \
|
|
--tag "$RESTIC_BACKUP_TAG" \
|
|
--option b2.connections="$B2_CONNECTIONS" \
|
|
"${exclusion_args[@]}" \
|
|
"${extra_args[@]}" \
|
|
"${backup_paths[@]}" &
|
|
wait $!
|
|
|
|
# Dereference and delete/prune old backups.
|
|
# See restic-forget(1) or http://restic.readthedocs.io/en/latest/060_forget.html
|
|
# --group-by only the tag and path, and not by hostname. This is because I create a B2 Bucket per host, and if this hostname accidentially change some time, there would now be multiple backup sets.
|
|
restic forget \
|
|
--verbose="$RESTIC_VERBOSITY_LEVEL" \
|
|
--tag "$RESTIC_BACKUP_TAG" \
|
|
--option b2.connections="$B2_CONNECTIONS" \
|
|
--prune \
|
|
--group-by "paths,tags" \
|
|
--keep-daily "$RESTIC_RETENTION_DAYS" \
|
|
--keep-weekly "$RESTIC_RETENTION_WEEKS" \
|
|
--keep-monthly "$RESTIC_RETENTION_MONTHS" \
|
|
--keep-yearly "$RESTIC_RETENTION_YEARS" &
|
|
wait $!
|
|
|
|
# Check repository for errors.
|
|
# NOTE this takes much time (and data transfer from remote repo?), do this in a separate systemd.timer which is run less often.
|
|
#restic check &
|
|
#wait $!
|
|
|
|
echo "Backup & cleaning is done."
|