snap-sync: 0.4.3 version bump (inital posix version)

- use dash as reference shell
- refine all bashism to posix calls
- convert bash arrays to posix "pseudo arrays"
  yes, posix does not support handy arrays
- introduce global/local variables
- using (hopefully) more descriptive variable names
- new function verify_snapper_structure()
  that takes care to construct target snapper file-structure
  as needed
- 0.4.3 bump version

Signed-off-by: Ralf Zerres <ralf.zerres@networkx.de>
This commit is contained in:
2017-11-09 09:46:31 +01:00
parent 2ed161c8db
commit 4b6a06f885

View File

@@ -1,4 +1,5 @@
#!/bin/bash
#!/bin/sh
# snap-sync
# https://github.com/wesbarnett/snap-sync
# Copyright (C) 2016, 2017 James W. Barnett
@@ -24,14 +25,12 @@
# snapshot created on your system since that will be used to determine the
# difference for the next incremental snapshot.
set -o errtrace
version="0.4.2"
progname="snap-sync"
progname="`basename v$0`"
version="0.4.3"
# The following line is modified by the Makefile or
# find_snapper_config script
SNAPPER_CONFIG=/etc/sysconfig/snapper
SNAPPER_CONFIG=/etc/conf.d/snapper
TMPDIR=$(mktemp -d)
PIPE=$TMPDIR/$progname.out
@@ -39,10 +38,20 @@ mkfifo $PIPE
systemd-cat -t "$progname" < $PIPE &
exec 3>$PIPE
# global variables
#disk_uuid_$i
disk_count=-1
disk_uuid_match_count=0
disk_uuid_match=''
selected_uuid='none'
selected_target='none'
selected_subvol='none'
###
# functions
###
check_prerequisites () {
# requested binaries:
@@ -60,57 +69,77 @@ check_prerequisites () {
fi
}
die() {
die () {
error "$@"
exit 1
}
error() {
error () {
printf "==> ERROR: %s\n" "$@"
notify_error 'Error' 'Check journal for more information.'
} >&2
get_disk_infos () {
if [[ "$(findmnt -n -v --target / -o FSTYPE)" == "btrfs" ]]; then
EXCLUDE_UUID=$(findmnt -n -v -t btrfs --target / -o UUID)
TARGETS=$($ssh findmnt -n -v -t btrfs -o UUID,TARGET --list | grep -v $EXCLUDE_UUID | awk '{print $2}')
UUIDS=$($ssh findmnt -n -v -t btrfs -o UUID,TARGET --list | grep -v $EXCLUDE_UUID | awk '{print $1}')
local disk_uuid
local disk_target
local fs_option
# get mounted BTRFS infos
if [ "$(findmnt --noheadings --nofsroot --target / --output FSTYPE)" = "btrfs" ]; then
exclude_uuid=$(findmnt --noheadings --nofsroot --types btrfs --target / --output UUID)
disk_uuids=$($ssh findmnt --noheadings --nofsroot --types btrfs --output UUID,TARGET --list | grep -v $exclude_uuid | awk '{print $1}')
disk_targets=$($ssh findmnt --noheadings --nofsroot --types btrfs --output UUID,TARGET --list | grep -v $exclude_uuid | awk '{print $2}')
else
TARGETS=$($ssh findmnt -n -v -t btrfs -o TARGET --list)
UUIDS=$($ssh findmnt -n -v -t btrfs -o UUID --list)
disk_uuids=$($ssh findmnt --noheadings --nofsroot --types btrfs --output UUID --list)
disk_targets=$($ssh findmnt --noheadings --nofsroot --types btrfs --output UUID,TARGET --list)
fi
declare -a TARGETS_ARRAY
declare -a UUIDS_ARRAY
# we need at least one target disk
if [ ${#disk_targets} -eq 0 ]; then die "no suitable target disk found \n"die
fi
# Posix Shells do not support Array. Therefore using ...
# Pseudo-Arrays (assumption: equal number of members)
# Pseudo-Array: disk_uuid_$i
# Pseudo-Array: disk_target_$i
# Pseudo-Array: disk_selected_$y (reference to $i element)
# List: disk_uuid_match (reference to matching preselected uuids)
# initialize our structures
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))
for disk_uuid in $disk_uuids; do
if [ "$disk_uuid" = "$uuid_cmdline" ]; then
if [ ${#disk_uuid_match} -gt 0 ]; then
disk_uuid_match="${disk_uuid_match} $i"
else
disk_uuid_match="$i"
fi
disk_uuid_match_count=$(($disk_uuid_match_count+1))
fi
eval "disk_uuid_$i='$disk_uuid'"
disk_count=$(($disk_count+1))
i=$((i+1))
done
i=0
for x in $TARGETS; do
TARGETS_ARRAY[$i]=$x
for disk_target in $disk_targets; do
eval "disk_target_$i='$disk_target'"
i=$((i+1))
done
}
notify() {
for u in $(users | sed 's/ /\n/' | sort -u); do
sudo -u $u DISPLAY=:0 \
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(sudo -u $u id -u)/bus \
notify-send -a $progname "$1" "$2" --icon="dialog-$3"
done
notify () {
if [ nonotify ]; then
printf "%s %s\n" "$progname" "$2"
else
for u in $(users | sed 's/ /\n/' | sort -u); do
sudo -u $u DISPLAY=:0 \
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(sudo -u $u id -u)/bus \
notify-send -a $progname "$1" "$2" --icon="dialog-$3"
done
fi
}
notify_info() {
notify_info () {
notify "$1" "$2" "information"
}
@@ -179,7 +208,7 @@ parse_params () {
done
# Set reasonable defaults
source $SNAPPER_CONFIG
. $SNAPPER_CONFIG
selected_configs=${selected_configs:-$SNAPPER_CONFIGS}
description=${description:-"latest incremental backup"}
@@ -202,117 +231,188 @@ parse_params () {
}
run_config () {
declare -a BACKUPDIRS_ARRAY
declare -a MYBACKUPDIR_ARRAY
declare -a OLD_NUM_ARRAY
declare -a OLD_SNAP_ARRAY
declare -a NEW_NUM_ARRAY
declare -a NEW_SNAP_ARRAY
declare -a NEW_INFO_ARRAY
declare -a BACKUPLOC_ARRAY
declare -a CONT_BACKUP_ARRAY
printf "\nInitial configuration...\n" | tee $PIPE
printf "\nVerify configuration...\n" | tee $PIPE
# Initial configuration of where backup directories are
# commandline selection takes precedence
if [ -n "$selected_config" ]; then
selected_configs=$selected_config
fi
# Pseudo Arrays $i -> store associated elements of selected_config
i=0
for x in $selected_configs; do
if [[ "$(snapper -c $x list -t single | awk '/'"$selected_uuid"'/ {cnt++} END {print cnt}')" -gt 1 ]]; then
error "More than one snapper entry found with UUID $selected_uuid for configuration $x. Skipping configuration $x."
continue
for selected_config in $selected_configs; do
count=$(eval snapper -c $selected_config list -t single | awk '/uuid='"$selected_uuid"'/, /subvolid'="$selected_subvol"'/ {cnt++} END {print cnt}')
#count=$(eval snapper -c $selected_config list -t single | grep -c -e "subvolid=$selected_subvol" -e 'uuid=$selected_uuid')
if [ -n "$count" ] && [ "$count" -gt 1 ]; then
error "More than one snapper entry found with UUID $selected_uuid for configuration '$selected_config'. Skipping configuration '$selected_config'."
selected_configs=$(echo $selected_configs | sed -e "s/\($selected_config*\)//")
if [ "$verbose" ]; then
printf "Counter=%s" "$count"
fi
continue
fi
if [[ "$(snapper -c $x list -t single | awk '/'$progname' backup in progress/ {cnt++} END {print cnt}')" -gt 0 ]]; then
printf "\nNOTE: Previous failed %s backup snapshots found for '%s'.\n" "$progname" "$x" | tee $PIPE
# active, non finished snapshot backups are marked with following string
# "$progname backup in progress" (snapper description field)
snapper_failed_ids=$(eval snapper -c $selected_config list -t single | awk '/'"$progname"' backup in progress/ {cnt++} END {print cnt}')
if [ -n "$snapper_failed_ids" ]; then
printf "\nNOTE: Previous failed %s backup snapshots found for '%s'.\n" "$progname" "$selected_config" | tee $PIPE
read -r -p "Delete failed backup snapshots [y/N]? " delete_failed
while [[ -n "$delete_failed" && "$delete_failed" != [Yy]"es" &&
"$delete_failed" != [Yy] && "$delete_failed" != [Nn]"o" &&
"$delete_failed" != [Nn] ]]; do
#get_answer_yes_no
while [ -n "$delete_failed" ] &&
[ "$delete_failed" != "Yes" ] &&
[ "$delete_failed" != "yes" ] &&
[ "$delete_failed" != "Y" ] &&
[ "$delete_failed" != "y" ] &&
[ "$delete_failed" != "No" ] &&
[ "$delete_failed" != "no" ] &&
[ "$delete_failed" != "N" ] &&
[ "$delete_failed" != "n" ]; do
read -r -p "Delete failed backup snapshots [y/N]? " delete_failed
if [[ -n "$delete_failed" && "$delete_failed" != [Yy]"es" &&
"$delete_failed" != [Yy] && "$delete_failed" != [Nn]"o" &&
"$delete_failed" != [Nn] ]]; then
printf "Select 'y' or 'N'.\n"
if [ -n "$delete_failed" ] &&
[ "$delete_failed" != "Yes" ] &&
[ "$delete_failed" != "yes" ] &&
[ "$delete_failed" != "Y" ] &&
[ "$delete_failed" != "y" ] &&
[ "$delete_failed" != "No" ] &&
[ "$delete_failed" != "no" ] &&
[ "$delete_failed" != "N" ] &&
[ "$delete_failed" != "n" ]; then
printf "Select 'y' or 'N'.\n"
fi
done
if [[ "$delete_failed" == [Yy]"es" || "$delete_failed" == [Yy] ]]; then
snapper -c $x delete $(snapper -c $x list | awk '/'$progname' backup in progress/ {print $3}')
fi
done
if [ "$delete_failed" = "Yes" ] ||
[ "$delete_failed" = "yes" ] ||
[ "$delete_failed" = "Y" ] ||
[ "$delete_failed" = "y" ]; then
snapper -c $selected_config delete $(snapper -c $selected_config list | awk '/'"$progname"' backup in progress/ {print $3}')
fi
fi
SNAP_SYNC_EXCLUDE=no
if [[ -f "/etc/snapper/configs/$x" ]]; then
source /etc/snapper/configs/$x
else
die "Selected snapper configuration $x does not exist."
if [ -f "/etc/snapper/configs/$selected_config" ]; then
. /etc/snapper/configs/$selected_config
if [ "$SUBVOLUME" = "/" ]; then
SUBVOLUME=''
fi
else
die "Selected snapper configuration $selected_config does not exist."
fi
if [[ $SNAP_SYNC_EXCLUDE == "yes" ]]; then
continue
if [ $SNAP_SYNC_EXCLUDE = "yes" ]; then
continue
fi
printf "\n"
old_num=$(snapper -c "$x" list -t single | awk '/'"$selected_uuid"'/ {print $1}')
old_snap=$SUBVOLUME/.snapshots/$old_num/snapshot
OLD_NUM_ARRAY[$i]=$old_num
OLD_SNAP_ARRAY[$i]=$old_snap
if [[ -z "$old_num" ]]; then
printf "No backups have been performed for '%s' on this disk.\n" "$x"
read -r -p "Enter progname 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"
$ssh mkdir -p -m700 "$BACKUPDIR"
else
mybackupdir=$(snapper -c "$x" list -t single | awk -F"|" '/'"$selected_uuid"'/ {print $5}' | awk -F "," '/backupdir/ {print $1}' | awk -F"=" '{print $2}')
BACKUPDIR="$selected_mnt/$mybackupdir"
$ssh test -d $BACKUPDIR || die "%s is not a directory on %s.\n" "$BACKUPDIR" "$selected_uuid"
# processed snapshot backup is marked with userdata key/value pairs
# backupdir, uuid
snapper_sync_id=$(eval snapper -c "$selected_config" list -t single | awk '/uuid='"$selected_uuid"'/ {print $1}')
if [ ${#snapper_sync_id} -gt 0 ]; then
snapper_sync_snapshot=$SUBVOLUME/.snapshots/$snapper_sync_id/snapshot
fi
BACKUPDIRS_ARRAY[$i]="$BACKUPDIR"
MYBACKUPDIR_ARRAY[$i]="$mybackupdir"
printf "Creating new snapshot for %s...\n" "$x" | tee $PIPE
new_num=$(snapper -c "$x" create --print-number -d "$progname backup in progress")
new_snap=$SUBVOLUME/.snapshots/$new_num/snapshot
new_info=$SUBVOLUME/.snapshots/$new_num/info.xml
eval "snapper_sync_id_$i='$snapper_sync_id'"
eval "snapper_sync_snapshot_$i='$snapper_sync_snapshot'"
if [ -z "$snapper_sync_id" ]; then
printf "No backups have been performed for snapper config '%s' on target disk.\n" "$selected_config"
if [ $noconfirm ]; then
# go with defaults
if [ "$verbose" ]; then
printf "using defaults\n"
fi
else
read -r -p "Enter name of directory to store backups, relative to $selected_target (to be created if not existing): " backupdir
fi
if [ -z "$backupdir" ]; then
backup_root="$selected_target"
else
backup_root="$selected_target/$backupdir"
fi
### Todo: if interactive, e.g use pv to show progress
printf "Please be pacient, the initial backup could take awhile.\n"
else
if [ "$verbose" ]; then
printf "Last syncronized Snapshot-ID for '%s': %s\n" "$selected_config" "$snapper_sync_id"
printf "Last syncronized Snapshot-Path for '%s': %s\n" "$selected_config" "$snapper_sync_snapshot"
fi
backupdir=$(snapper -c "$selected_config" list -t single | awk -F "|" '/'"$selected_uuid"'/ {print $5}' | awk -F "," '/backupdir/ {print $1}' | awk -F"=" '{print $2}')
backup_root="$selected_target/$backupdir"
$ssh test -d $backup_root || die "%s is not a directory on %s.\n" "$backup_root" "$selected_uuid"
fi
eval "backup_root_$i=$backup_root"
eval "backup_dir_$i=$backupdir"
if [ "$verbose" ]; then
if [ -n "$ssh" ];then
printf "Backup-Path on remote %s: %s/%s\n" "$remote" "$backup_root" "backupdir"
else
printf "Backup-Path: %s/%s\n" "$backup_root" "backupdir"
fi
fi
# acting on source system
printf "Creating new snapshot for snapper config '%s' ...\n" "$selected_config" | tee $PIPE
snapper_new_id=$(snapper -c "$selected_config" create --print-number -d "$progname backup in progress")
snapper_new_snapshot=$SUBVOLUME/.snapshots/$snapper_new_id/snapshot
snapper_new_info=$SUBVOLUME/.snapshots/$snapper_new_id/info.xml
sync
backup_location=$BACKUPDIR/$x/$new_num/
if [[ -z $ssh ]]; then
printf "Will backup %s to %s\n" "$new_snap" "$backup_location/snapshot" | tee $PIPE
snapper_target_snapshot=$backup_root/$selected_config/$snapper_new_id
if [ -z "$ssh" ]; then
printf "Will backup %s to %s\n" "$snapper_new_snapshot" "$snapper_target_snapshot/snapshot" | tee $PIPE
else
printf "Will backup %s to %s\n" "$new_snap" "$remote":"$backup_location/snapshot" | tee $PIPE
printf "Will backup %s to %s\n" "$snapper_new_snapshot" "$remote":"$snapper_target_snapshot/snapshot" | tee $PIPE
fi
NEW_NUM_ARRAY[$i]="$new_num"
NEW_SNAP_ARRAY[$i]="$new_snap"
NEW_INFO_ARRAY[$i]="$new_info"
BACKUPLOC_ARRAY[$i]="$backup_location"
# save in config specific infos in pseudo Arrays
eval "snapper_new_id_$i='$snapper_new_id'"
eval "snapper_new_snapshot_$i='$snapper_new_snapshot'"
eval "snapper_new_info_$i='$snapper_new_info'"
eval "snapper_config_$i='$selected_config'"
eval "snapper_target_subvol_$i='$snapper_target_subvol'"
eval "snapper_target_snapshot_$i='$snapper_target_snapshot'"
cont_backup="K"
CONT_BACKUP_ARRAY[$i]="yes"
if [[ $noconfirm == "yes" ]]; then
cont_backup="yes"
eval "snapper_activate_$i=yes"
if [ $noconfirm ]; then
cont_backup="yes"
else
while [[ -n "$cont_backup" && "$cont_backup" != [Yy]"es" &&
"$cont_backup" != [Yy] && "$cont_backup" != [Nn]"o" &&
"$cont_backup" != [Nn] ]]; do
while [ -n "$cont_backup" ] &&
[ "$cont_backup" != "Yes" ] &&
[ "$cont_backup" != "yes" ] &&
[ "$cont_backup" != "Y" ] &&
[ "$cont_backup" != "y" ] &&
[ "$cont_backup" != "No" ] &&
[ "$cont_backup" != "no" ] &&
[ "$cont_backup" != "N" ] &&
[ "$cont_backup" != "n" ]; do
read -r -p "Continue with backup [Y/n]? " cont_backup
if [[ -n "$cont_backup" && "$cont_backup" != [Yy]"es" &&
"$cont_backup" != [Yy] && "$cont_backup" != [Nn]"o" &&
"$cont_backup" != [Nn] ]]; then
printf "Select 'Y' or 'n'.\n"
if [ -n "$cont_backup" ] &&
[ "$cont_backup" != "Yes" ] &&
[ "$cont_backup" != "yes" ] &&
[ "$cont_backup" != "Y" ] &&
[ "$cont_backup" != "y" ] &&
[ "$cont_backup" != "No" ] &&
[ "$cont_backup" != "no" ] &&
[ "$cont_backup" != "N" ] &&
[ "$cont_backup" != "n" ]; then
printf "Select 'Y' or 'n'.\n"
fi
done
done
fi
if [[ "$cont_backup" != [Yy]"es" && "$cont_backup" != [Yy] && -n "$cont_backup" ]]; then
CONT_BACKUP_ARRAY[$i]="no"
printf "Aborting backup for this configuration.\n"
snapper -c $x delete $new_num
if [ "$cont_backup" != "Yes" ] &&
[ "$cont_backup" != "yes" ] &&
[ "$cont_backup" != "Y" ] &&
[ "$cont_backup" != "y" ] &&
[ -n "$cont_backup" ]; then
eval "snapper_activate_$i=no"
printf "Aborting backup for this configuration.\n"
snapper -c $selected_config delete $snapper_new_id
fi
i=$(($i+1))
@@ -323,130 +423,175 @@ run_config () {
run_backup () {
# Actual backing up
printf "\nPerforming backups...\n" | tee $PIPE
i=-1
for x in $selected_configs; do
for selected_config in $selected_configs; do
i=$(($i+1))
SNAP_SYNC_EXCLUDE=no
if [[ -f "/etc/snapper/configs/$x" ]]; then
source /etc/snapper/configs/$x
if [ -f "/etc/snapper/configs/$selected_config" ]; then
. /etc/snapper/configs/$selected_config
else
die "Selected snapper configuration $x does not exist."
die "Selected snapper configuration '$selected_config' does not exist."
fi
cont_backup=${CONT_BACKUP_ARRAY[$i]}
if [[ $cont_backup == "no" || $SNAP_SYNC_EXCLUDE == "yes" ]]; then
notify_info "Backup in progress" "NOTE: Skipping $x configuration."
cont_backup=$(eval echo \$snapper_activate_$i)
if [ "$cont_backup" = "no" ] || [ "$SNAP_SYNC_EXCLUDE" = "yes" ]; then
notify_info "Backup in progress" "NOTE: Skipping '$selected_config' configuration."
continue
fi
notify_info "Backup in progress" "Backing up $x configuration."
notify_info "Backup in progress" "Backing up data for configuration '$selected_config'."
printf "\n"
old_num="${OLD_NUM_ARRAY[$i]}"
old_snap="${OLD_SNAP_ARRAY[$i]}"
BACKUPDIR="${BACKUPDIRS_ARRAY[$i]}"
mybackupdir="${MYBACKUPDIR_ARRAY[$i]}"
new_num="${NEW_NUM_ARRAY[$i]}"
new_snap="${NEW_SNAP_ARRAY[$i]}"
new_info="${NEW_INFO_ARRAY[$i]}"
backup_location="${BACKUPLOC_ARRAY[$i]}"
# retrieve config specific infos from pseudo Arrays
snapper_config=$(eval echo \$snapper_config_$i)
backup_root=$(eval echo \$backup_root_$i)
backup_dir=$(eval echo \$backup_dir_$i)
snapper_sync_id=$(eval echo \$snapper_sync_id_$i)
snapper_new_id=$(eval echo \$snapper_new_id_$i)
snapper_sync_snapshot=$(eval echo \$snapper_sync_snapshot_$i)
snapper_new_snapshot=$(eval echo \$snapper_new_snapshot_$i)
snapper_new_info=$(eval echo \$snapper_new_info_$i)
snapper_target_snapshot=$(eval echo \$snapper_target_snapshot_$i)
$ssh mkdir -p $backup_location
if [[ -z "$old_num" ]]; then
printf "Sending first snapshot for %s...\n" "$x" | tee $PIPE
btrfs send "$new_snap" | $ssh btrfs receive "$backup_location" &>/dev/null
# verify target structure for backupdir
verify_snapper_structure $backup_root $snapper_config $snapper_new_id
if [ -z "$snapper_sync_id" ]; then
cmd="btrfs send $snapper_new_snapshot | $ssh btrfs receive $snapper_target_snapshot"
printf "Sending first snapshot for snapper config '%s'...\n" "$selected_config" | tee $PIPE
if [ "$verbose" ]; then
printf "$cmd"
fi
btrfs send "$snapper_new_snapshot" | $ssh btrfs receive "$snapper_target_snapshot" &>/dev/null
else
printf "Sending incremental snapshot for %s...\n" "$x" | tee $PIPE
printf "Sending incremental snapshot for snapper config '%s'...\n" "$selected_config" | tee $PIPE
# 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 -c "$old_snap" "$new_snap" | $ssh btrfs receive "$backup_location"
printf "Modifying data for old snapshot for %s...\n" "$x" | tee $PIPE
snapper -v -c "$x" modify -d "old snap-sync snapshot (you may remove)" -u "backupdir=,uuid=" -c "number" "$old_num"
cmd="btrfs send -c $snapper_sync_snapshot $snapper_new_snapshot | $ssh btrfs receive $snapper_target_snapshot"
$cmd &>/dev/null
if [ "$verbose" ]; then
printf "$cmd"
printf "Deleting sync snapshot for %s...\n" "$selected_config" | tee $PIPE
fi
btrfs send -c "$snapper_sync_snapshot" "$snapper_new_snapshot" | $ssh btrfs receive "$snapper_target_snapshot" &>/dev/null
snapper -c "$selected_config" delete "$snapper_sync_id"
fi
if [[ -z $ssh ]]; then
cp "$new_info" "$backup_location"
if [ -z "$ssh" ]; then
cp "$snapper_new_info" "$snapper_target_snapshot"
else
rsync -avzq "$new_info" "$remote":"$backup_location"
rsync -avzq "$snapper_new_info" "$remote":"$snapper_target_snapshot"
fi
# It's important not to change this userdata in the snapshots, since that's how
# we find the previous one.
# It's important not to change the values of the key/value pairs ($userdata)
# which is stored in snappers info.xml file of the source snapshot.
# This is how we find the parent.
userdata="backupdir=$mybackupdir, uuid=$selected_uuid"
userdata="backupdir=$backup_dir, uuid=$selected_uuid"
# Tag new snapshot as the latest
printf "Tagging new snapshot as latest backup for %s...\n" "$x" | tee $PIPE
snapper -v -c "$x" modify -d "$description" -u "$userdata" "$new_num"
printf "Backup complete for configuration %s.\n" "$x" > $PIPE
printf "Tagging new snapshot as latest backup for '%s' ...\n" "$selected_config" | tee $PIPE
snapper -v -c "$selected_config" modify -d "$description" -u "$userdata" "$snapper_new_id"
printf "Backup complete for snapper configuration '%s'.\n" "$selected_config" > $PIPE
done
printf "\nDone!\n" | tee $PIPE
exec 3>&-
}
select_target_disk () {
if [[ "${#UUIDS_ARRAY[$@]}" -eq 0 ]]; then
die "No external btrfs subvolumes found to backup to."
fi
local i=0
local disk_id=0
if [[ "$disk_count" > 1 ]]; then
printf "Multiple mount points were found with UUID %s.\n" "$uuid_cmdline"
disk="-1"
fi
#local disk_selected_ids=''
local disk_selected_count=0
if [[ "$disk" == -1 ]]; then
if [[ "$disk_count" == 0 && "$uuid_cmdline" != "none" ]]; then
error "A device with UUID $uuid_cmdline was not found to be mounted, or it is not a BTRFS device."
fi
if [[ -z $ssh ]]; then
printf "Select a mounted BTRFS device on your local machine to backup to.\n"
# print selection table
if [ -z "$ssh" ]; then
printf "Selecting a mounted BTRFS device for backups on your local machine.\n"
else
printf "Selecting a mounted BTRFS device for backups on %s.\n" "$remote"
fi
while [ "$disk_id" -eq -1 ] || [ "$disk_id" -le $disk_count ]; do
if [ "$disk_uuid_match_count" -gt 1 ]; then
# got UUID selection from commandline
disk_count=$disk_uuid_match_count
if [ "$verbose" ]; then
printf "%s mount points were found with UUID '%s'.\n" "$disk_uuid_match_count" "$uuid_cmdline"
fi
for disk_uuid in $disk_uuid_match; do
# Pseudo-Array: disk_selected_$i (reference to $disk_uuid element)
eval "disk_selected_$i='$disk_uuid'"
disk=$(eval echo \$disk_uuid_$disk_uuid)
target=$(eval echo \$disk_target_$disk_uuid)
printf "%4s) %s %s\n" "$i" "$disk" "$target"
i=$((i+1))
done
else
printf "Select a mounted BTRFS device on %s to backup to.\n" "$remote"
while [ "$disk_id" -le $disk_count ]; do
# present all mounted BTRFS filesystems
# Pseudo-Array: disk_selected_$i (reference to $disk_id element)
eval disk_selected_$i="$disk_id"
disk=$(eval echo \$disk_uuid_$disk_id)
target=$(eval echo \$disk_target_$disk_id)
printf "%4s) %s %s\n" "$disk_id" "$disk" "$target"
i=$((i+1))
disk_id=$(($disk_id+1))
done
fi
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
if ! [[ $disk == ?(-)+([0-9]) ]]; then
printf "%4s) Exit\n" "x"
read -r -p "Enter a number: " disk_selected
case $disk_selected in
x)
break
;;
[0-9][0-9]|[0-9])
if [ "$disk_selected" -gt "$disk_count" ]; then
disk_id=0
i=0
else
break
fi
;;
*)
printf "\nNo disk selected. Select a disk to continue.\n"
disk=-1
fi
done
if [[ $disk == 0 ]]; then
exit 0
fi
disk=$(($disk-1))
disk_id=0
i=0
;;
esac
done
if [ "$disk_selected" = x ]; then
exit x
fi
selected_uuid=$(eval echo \$disk_uuid_$disk_selected)
selected_target=$(eval echo \$disk_target_$disk_selected)
}
traperror() {
traperror () {
printf "Exited due to error on line %s.\n" $1
printf "exit status: %s\n" "$2"
printf "command: %s\n" "$3"
printf "bash line: %s\n" "$4"
printf "function progname: %s\n" "$5"
printf "function name: %s\n" "$5"
exit 1
}
trapkill() {
trapkill () {
die "Exited due to user intervention."
}
usage() {
# bashism
#trap 'traperror ${LINENO} $? "$BASH_COMMAND" $BASH_LINENO "${FUNCNAME[@]}"' ERR
#trap trapkill SIGTERM SIGINT
usage () {
cat <<EOF
$progname $version
Usage: $progname [options]
@@ -466,15 +611,39 @@ Options:
-v, --verbose Be more verbose on what's going on.
EOF
exit 1
}
verify_snapper_structure () {
local backup_root=$1
local snapper_config=$2
local snapper_id=$3
if [ "$verbose" ]; then
echo "Verify snapper filesystem structure on target ..."
fi
# if not accessible, create backup-path
if $ssh [ ! -d $backup_root/$snapper_config ]; then
if [ "$verbose" ]; then
echo "Create backup-path $backup_root/$snapper_config"
fi
$ssh mkdir --mode=0700 --parents $backup_root/$snapper_config
fi
if $ssh [ ! -d $backup_root/$snapper_config/$snapper_id ]; then
if [ "$verbose" ]; then
echo "Create backup-path $backup_root/$snapper_config/$snapper_id"
fi
$ssh mkdir --mode=0700 $backup_root/$snapper_config/$snapper_id
fi
}
###
# Main
###
trap 'traperror ${LINENO} $? "$BASH_COMMAND" $BASH_LINENO "${FUNCPROGNAME[@]}"' ERR
trap trapkill SIGTERM SIGINT
cwd=`pwd`
ssh=""
@@ -482,33 +651,31 @@ parse_params $@
check_prerequisites
if [[ "$uuid_cmdline" != "none" ]]; then
if [[ -z $ssh ]]; then
# read mounted BTRFS structures
get_disk_infos
if [ "$uuid_cmdline" != "none" ]; then
if [ -z $ssh ]; then
notify_info "Backup started" "Starting backups to $uuid_cmdline..."
else
notify_info "Backup started" "Starting backups to $uuid_cmdline at $remote..."
fi
else
if [[ -z $ssh ]]; then
if [ -z $ssh ]; then
notify_info "Backup started" "Starting backups. Use command line menu to select disk."
else
notify_info "Backup started" "Starting backups. Use command line menu to select disk on $remote."
fi
fi
# read mounted BTRFS structures
get_disk_infos
# select the target BTRFS subvol
select_target_disk
selected_uuid="${UUIDS_ARRAY[$((disk))]}"
selected_mnt="${TARGETS_ARRAY[$((disk))]}"
printf "\nYou selected the disk with UUID %s.\n" "$selected_uuid" | tee $PIPE
if [[ -z $ssh ]]; then
printf "The disk is mounted at %s.\n" "$selected_mnt" | tee $PIPE
if [ -z $ssh ]; then
printf "The disk is mounted at %s.\n" "$selected_target" | tee $PIPE
else
printf "The disk is mounted at %s:%s.\n" "$remote" "$selected_mnt" | tee $PIPE
printf "The disk is mounted at %s:%s.\n" "$remote" "$selected_target" | tee $PIPE
fi
# create and initialize structures for snapper configs
@@ -517,7 +684,7 @@ run_config
# run backups using btrfs-send -> btrfs-receive
run_backup
if [[ "$uuid_cmdline" != "none" ]]; then
if [ "$uuid_cmdline" != "none" ]; then
notify_info "Finished" "Backups to $uuid_cmdline complete!"
else
notify_info "Finished" "Backups complete!"