diff --git a/CHANGELOG.md b/CHANGELOG.md index ecd890a..0d0c0de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [3.0.2] - 2022-02-01 +### Fixed +- Use arrays to build up command lines. When fixing shellcheck errors, quotes would disable expansion on e.g. $RESTIC_BACKUP_PATHS + ## [3.0.1] - 2022-02-01 ### Fixed - Environment variable assertion should allow empty values e.g. `RESTIC_BACKUP_EXTRA_ARGS` diff --git a/etc/restic/_global.env.template b/etc/restic/_global.env.template index 3b708f5..36b7c12 100644 --- a/etc/restic/_global.env.template +++ b/etc/restic/_global.env.template @@ -20,7 +20,8 @@ export B2_ACCOUNT_KEY="" # TODO fill with your applicationKe # How many network connections to set up to B2. Default is 5. export B2_CONNECTIONS=10 -# Extra args to restic-backup. This is empty here and profiles can override this after sourcing this file. +# Optional extra space-separated args to restic-backup. +# This is empty here and profiles can override this after sourcing this file. export RESTIC_BACKUP_EXTRA_ARGS= # Verbosity level from 0-3. 0 means no --verbose. diff --git a/etc/restic/default.env.template b/etc/restic/default.env.template index 38ee5d0..fc5c90e 100644 --- a/etc/restic/default.env.template +++ b/etc/restic/default.env.template @@ -15,10 +15,11 @@ source /etc/restic/_global.env export RESTIC_REPOSITORY="b2:" # TODO fill with your repo name -# What to backup (paths our mountpoints) e.g. "/ /boot /home /mnt/media". +# What to backup. Colon-separated paths e.g. to different mountpoints "/home:/mnt/usb_disk". # To backup only your home directory, set "/home/your-user" export RESTIC_BACKUP_PATHS="" # TODO fill conveniently with one or multiple paths + # Example below of how to dynamically add a path that is mounted e.g. external USB disk. # restic does not fail if a specified path is not mounted, but it's nicer to only add if they are available. #test -d /mnt/media && RESTIC_BACKUP_PATHS+=" /mnt/media" @@ -33,8 +34,8 @@ export RESTIC_RETENTION_WEEKS=16 export RESTIC_RETENTION_MONTHS=18 export RESTIC_RETENTION_YEARS=3 -# Optional extra arguments to restic-backup. +# Optional extra space-separated arguments to restic-backup. # Example: Add two additional exclude files to the global one in RESTIC_PASSWORD_FILE. -#RESTIC_BACKUP_EXTRA_ARGS="--exclude-file /path/to/extra/exclude/file/a /path/to/extra/exclude/file/b" +#RESTIC_BACKUP_EXTRA_ARGS="--exclude-file /path/to/extra/exclude/file/a --exclude-file /path/to/extra/exclude/file/b" # Example: exclude all directories that have a .git/ directory inside it. #RESTIC_BACKUP_EXTRA_ARGS="--exclude-if-present .git" diff --git a/usr/local/sbin/restic_backup.sh b/usr/local/sbin/restic_backup.sh index fb2c7f5..6a072ef 100644 --- a/usr/local/sbin/restic_backup.sh +++ b/usr/local/sbin/restic_backup.sh @@ -11,6 +11,17 @@ # 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() { @@ -29,25 +40,20 @@ assert_envvars \ RESTIC_RETENTION_DAYS RESTIC_RETENTION_MONTHS RESTIC_RETENTION_WEEKS RESTIC_RETENTION_YEARS -# 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 +# 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 files per backup path. E.g. having an USB disk at /mnt/media in RESTIC_BACKUP_PATHS, -# a file /mnt/media/.backup_exclude.txt will automatically be detected and used: -for backup_path in "${RESTIC_BACKUP_PATHS[@]}"; do +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+=" --exclude-file $backup_path/.backup_exclude.txt" + exclusion_args=("${exclusion_args[@]}" --exclude-file "$backup_path/.backup_exclude.txt") fi done @@ -69,9 +75,9 @@ restic backup \ --one-file-system \ --tag "$RESTIC_BACKUP_TAG" \ --option b2.connections="$B2_CONNECTIONS" \ - "$exclusion_args" \ - "$RESTIC_BACKUP_EXTRA_ARGS" \ - "$RESTIC_BACKUP_PATHS" & + "${exclusion_args[@]}" \ + "${extra_args[@]}" \ + "${backup_paths[@]}" & wait $! # Dereference and delete/prune old backups. diff --git a/usr/local/sbin/restic_check.sh b/usr/local/sbin/restic_check.sh index c44f7f5..eff68d2 100644 --- a/usr/local/sbin/restic_check.sh +++ b/usr/local/sbin/restic_check.sh @@ -5,6 +5,16 @@ # 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. assert_envvars() { local varnames=("$@") @@ -19,15 +29,6 @@ assert_envvars \ B2_ACCOUNT_ID B2_ACCOUNT_KEY B2_CONNECTIONS \ RESTIC_PASSWORD_FILE RESTIC_REPOSITORY RESTIC_VERBOSITY_LEVEL -# 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 # Remove locks from other stale processes to keep the automated backup running. # NOTE nope, don't unlock like restic_backup.sh. restic_backup.sh should take precedence over this script.