The user can hit enter when creating a new directory (no directory is created). In doing so "backupdir" is set to nothing. Snapper does not keep that key. Upon a search of the latest incremental backup we didn't check for backupdir, we just had taken the first key (which ended up being the UUID ). We now check for the backupdir key, and if it is missing that means use the root directory on the mounted disk. Fixes #13.
204 lines
6.6 KiB
Bash
Executable File
204 lines
6.6 KiB
Bash
Executable File
#!/bin/bash
|
|
# James W. Barnett
|
|
|
|
# Takes snapshots of each snapper configuration. It then sends the snapshot to
|
|
# a location on an external drive. After the initial transfer, it does
|
|
# incremental snapshots on later calls. It's important not to delete the
|
|
# snapshot created on your system since that will be used to determine the
|
|
# difference for the next incremental snapshot.
|
|
|
|
version="0.2"
|
|
|
|
set -e
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
key="$1"
|
|
case $key in
|
|
-d|--description)
|
|
description="$2"
|
|
shift 2
|
|
;;
|
|
-c|--config)
|
|
selected_configs="$2"
|
|
shift 2
|
|
;;
|
|
-u|--UUID)
|
|
uuid_cmdline="$2"
|
|
shift 2
|
|
;;
|
|
-n|--noconfirm)
|
|
noconfirm="yes"
|
|
shift
|
|
;;
|
|
-h|--help)
|
|
printf "snap-sync $version\n\n"
|
|
printf "Usage: snap-sync [options]\n\n"
|
|
printf "Options:\n"
|
|
printf " -d, --description <desc> Change the snapper description. Default: \"latest incremental backup\"\n"
|
|
printf " -c, --config <config> Specify the snapper configuration to use. Otherwise will perform for each snapper\n"
|
|
printf " configuration. Can list multiple configurations within quotes, space-separated\n"
|
|
printf " (e.g. -c \"root home\").\n"
|
|
printf " -n, --noconfirm Do not ask for confirmation for each configuration. Will still prompt for backup\n"
|
|
printf " directory name on first backup\n"
|
|
printf " -u, --UUID <UUID> Specify the UUID of the mounted BTRFS subvolume to back up to. Otherwise will prompt.\n"
|
|
printf " If multiple mount points are found with the same UUID, will prompt user.\n"
|
|
exit 1
|
|
;;
|
|
*)
|
|
printf "Error: Unknown option: $key\n"
|
|
printf "Run 'snap-sync -h' for valid options.\n"
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
description=${description:-"latest incremental backup"}
|
|
uuid_cmdline=${uuid_cmdline:-"none"}
|
|
noconfirm=${noconfirm:-"no"}
|
|
|
|
if [[ $EUID -ne 0 ]]; then
|
|
printf "Script must be run as root.\n"
|
|
exit
|
|
fi
|
|
|
|
# It's important not to change this userdata in the snapshots, since that's how
|
|
# we find the previous one.
|
|
|
|
TARGETS="$(findmnt -n -v -t btrfs -o TARGET --list)"
|
|
UUIDS="$(findmnt -n -v -t btrfs -o UUID --list)"
|
|
|
|
declare -a TARGETS_ARRAY
|
|
declare -a UUIDS_ARRAY
|
|
|
|
i=0
|
|
disk=-1
|
|
disk_count=0
|
|
for x in $UUIDS; do
|
|
UUIDS_ARRAY[$i]=$x
|
|
if [[ "$x" == "$uuid_cmdline" ]]; then
|
|
disk=$i
|
|
disk_count=$(($disk_count+1))
|
|
fi
|
|
i=$((i+1))
|
|
done
|
|
|
|
i=0
|
|
for x in $TARGETS; do
|
|
TARGETS_ARRAY[$i]=$x
|
|
i=$((i+1))
|
|
done
|
|
|
|
if [[ "$disk_count" > 1 ]]; then
|
|
printf "Multiple mount points were found with UUID $uuid_cmdline.\n"
|
|
disk="-1"
|
|
fi
|
|
|
|
if [[ "$disk" == -1 ]]; then
|
|
if [[ "$disk_count" == 0 && "$uuid_cmdline" != "none" ]]; then
|
|
printf "A device with UUID $uuid_cmdline was not found to be mounted, or it is not a BTRFS device.\n"
|
|
fi
|
|
printf "Select a mounted BTRFS device to backup to.\n"
|
|
while [[ $disk -lt 0 || $disk -gt $i ]]; do
|
|
for x in "${!TARGETS_ARRAY[@]}"; do
|
|
printf "%4s) %s (%s)\n" "$((x+1))" "${UUIDS_ARRAY[$x]}" "${TARGETS_ARRAY[$x]}"
|
|
done
|
|
printf "%4s) Exit\n" "0"
|
|
read -r -p "Enter a number: " disk
|
|
done
|
|
if [[ $disk == 0 ]]; then
|
|
exit 0
|
|
fi
|
|
disk=$(($disk-1))
|
|
fi
|
|
|
|
selected_uuid="${UUIDS_ARRAY[$((disk))]}"
|
|
selected_mnt="${TARGETS_ARRAY[$((disk))]}"
|
|
printf "\nYou selected the disk with UUID %s.\n" "$selected_uuid"
|
|
printf "The disk is mounted at %s.\n" "$selected_mnt"
|
|
|
|
if [[ -f /etc/conf.d/snapper ]]; then
|
|
source /etc/conf.d/snapper
|
|
else
|
|
printf "ERROR: /etc/conf.d/snapper does not exist!\n"
|
|
exit 1
|
|
fi
|
|
|
|
selected_configs=${selected_configs:-$SNAPPER_CONFIGS}
|
|
|
|
for x in $selected_configs; do
|
|
|
|
printf "\n"
|
|
|
|
if [[ -f "/etc/snapper/configs/$x" ]]; then
|
|
source /etc/snapper/configs/$x
|
|
else
|
|
printf "Error: Selected snapper configuration $x does not exist.\n"
|
|
exit 1
|
|
fi
|
|
|
|
old_number=$(snapper -c "$x" list -t single | awk '/'"$selected_uuid"'/ {print $1}')
|
|
old_snapshot=$SUBVOLUME/.snapshots/$old_number/snapshot
|
|
|
|
if [[ -z "$old_number" ]]; then
|
|
printf "No backups have been performed for '%s' on this disk.\n" "$x"
|
|
read -r -p "Enter name of directory to store backups, relative to $selected_mnt (to be created if not existing): " mybackupdir
|
|
printf "This will be the initial backup for snapper configuration '%s' to this disk. This could take awhile.\n" "$x"
|
|
BACKUPDIR="$selected_mnt/$mybackupdir"
|
|
mkdir -p "$BACKUPDIR"
|
|
else
|
|
mybackupdir=$(snapper -c root list -t single | awk -F"|" '/'"$selected_uuid"'/ {print $5}' | awk -F "," '/backupdir/ {print $1}' | awk -F"=" '{print $2}')
|
|
BACKUPDIR="$selected_mnt/$mybackupdir"
|
|
if [[ ! -d $BACKUPDIR ]]; then
|
|
printf "ERROR: %s is not a directory on %s.\n" "$BACKUPDIR" "$selected_uuid"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
new_number=$(snapper -c "$x" create --print-number)
|
|
new_snapshot=$SUBVOLUME/.snapshots/$new_number/snapshot
|
|
new_info=$SUBVOLUME/.snapshots/$new_number/info.xml
|
|
sync
|
|
backup_location=$BACKUPDIR/$x/$new_number/
|
|
printf "Will backup %s to %s\n" "$new_snapshot" "$backup_location/snapshot"
|
|
|
|
if [[ $noconfirm == "yes" ]]; then
|
|
cont_backup="yes"
|
|
else
|
|
read -r -p "Continue with backup [Y/n]? " cont_backup
|
|
fi
|
|
|
|
if [[ "$cont_backup" != [Yy]"es" && "$cont_backup" != [Yy] && -n "$cont_backup" ]]; then
|
|
printf "Aborting backup for this configuration.\n"
|
|
snapper -c $x delete $new_number
|
|
continue
|
|
fi
|
|
|
|
|
|
mkdir -p "$backup_location"
|
|
|
|
|
|
if [[ -z "$old_number" ]]; then
|
|
|
|
btrfs send "$new_snapshot" | btrfs receive "$backup_location" &>/dev/null
|
|
|
|
else
|
|
|
|
# Sends the difference between the new snapshot and old snapshot to the
|
|
# backup location. Using the -c flag instead of -p tells it that there
|
|
# is an identical subvolume to the old snapshot at the receiving
|
|
# location where it can get its data. This helps speed up the transfer.
|
|
btrfs send "$new_snapshot" -c "$old_snapshot" | btrfs receive "$backup_location" &>/dev/null
|
|
snapper -c "$x" delete "$old_number"
|
|
fi
|
|
|
|
cp "$new_info" "$backup_location"
|
|
|
|
userdata="backupdir=$mybackupdir, uuid=$selected_uuid"
|
|
|
|
# Tag new snapshot as the latest
|
|
snapper -v -c "$x" modify -d "$description" -u "$userdata" "$new_number"
|
|
|
|
done
|
|
|
|
printf "\nDone!\n"
|