snap-sync: trap handling, use prinf, snapper description handling

- substitute 'echo' calls with 'printf' calls where appropriate
- prepend $progname in notify messages
- trap handling in posix form
- update snapper description on successfull backup
- usage update: introduce subvolid
- usage update: introduce -t, --target
This commit is contained in:
2017-11-16 19:26:16 +01:00
parent fe15397f33
commit c36d92159e

View File

@@ -56,16 +56,18 @@ selected_subvol='none'
check_prerequisites () { check_prerequisites () {
# requested binaries: # requested binaries:
which awk >/dev/null 2>&1 || { echo "'awk' is not installed." && exit 1; } which awk >/dev/null 2>&1 || { printf "'awk' is not installed." && exit 1; }
which sed >/dev/null 2>&1 || { echo "'sed' is not installed." && exit 1; } which sed >/dev/null 2>&1 || { printf "'sed' is not installed." && exit 1; }
which tee >/dev/null 2>&1 || { echo "'tee' is not installed." && exit 1; } which tee >/dev/null 2>&1 || { printf "'tee' is not installed." && exit 1; }
which btrfs >/dev/null 2>&1 || { echo "'btrfs' is not installed." && exit 1; } which btrfs >/dev/null 2>&1 || { printf "'btrfs' is not installed." && exit 1; }
which findmnt >/dev/null 2>&1 || { echo "'findmnt' is not installed." && exit 1; } which findmnt >/dev/null 2>&1 || { printf "'findmnt' is not installed." && exit 1; }
which systemd-cat >/dev/null 2>&1 || { echo "'systemd-cat' is not installed." && exit 1; } which systemd-cat >/dev/null 2>&1 || { printf "'systemd-cat' is not installed." && exit 1; }
which wc >/dev/null 2>&1 || { echo "'wc' is not installed." && exit 1; } which wc >/dev/null 2>&1 || { printf "'wc' is not installed." && exit 1; }
which notify-send >/dev/null 2>&1 || { echo "'notify-send' is not installed." && exit 1; }
if [ $(id -u) -ne 0 ] ; then echo "Script must be run as root" ; exit 1 ; fi # optional binaries:
which notify-send >/dev/null 2>&1 && { donotify=1; }
if [ $(id -u) -ne 0 ] ; then printf "Script must be run as root" ; exit 1 ; fi
if [ ! -r "$SNAPPER_CONFIG" ]; then if [ ! -r "$SNAPPER_CONFIG" ]; then
die "$SNAPPER_CONFIG does not exist." die "$SNAPPER_CONFIG does not exist."
@@ -78,7 +80,7 @@ die () {
} }
error () { error () {
printf "==> ERROR: %s\n" "$@" printf "\n==> ERROR: %s\n" "$@"
notify_error 'Error' 'Check journal for more information.' notify_error 'Error' 'Check journal for more information.'
} >&2 } >&2
@@ -139,27 +141,26 @@ get_disk_infos () {
done done
i=0 i=0
for fs_option in $fs_options; do for fs_option in $fs_options; do
eval "fs_options_$i='$fs_option'" subvolid=$(eval echo \$fs_option | sed -e 's/.*subvolid=\([0-9]*\).*/\1/')
subvolid=$(eval echo \$fs_options | sed -e 's/.*subvolid=\([0-9]*\).*/\1/')
if [ "$subvolid" = "$subvolid_cmdline" ]; then if [ "$subvolid" = "$subvolid_cmdline" ]; then
disk_subvolid_match="$i" disk_subvolid_match="$i"
disk_subvolid_match_count=$(($disk_subvolid_match_count+1)) disk_subvolid_match_count=$(($disk_subvolid_match_count+1))
fi fi
eval "disk_target_$i='$disk_target'" eval "fs_options_$i='$fs_option'"
i=$((i+1)) i=$((i+1))
done done
} }
notify () { notify () {
# estimation: batch calls should just log # estimation: batch calls should just log
if [ nonotify ]; then if [ $donotify ]; then
printf "%s %s\n" "$progname" "$2"
else
for u in $(users | sed 's/ /\n/' | sort -u); do for u in $(users | sed 's/ /\n/' | sort -u); do
sudo -u $u DISPLAY=:0 \ sudo -u $u DISPLAY=:0 \
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(sudo -u $u id -u)/bus \ DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(sudo -u $u id -u)/bus \
notify-send -a $progname "$1" "$2" --icon="dialog-$3" notify-send -a $progname "$progname: $1" "$2" --icon="dialog-$3"
done done
else
printf "%s %s\n" "$progname" "$2"
fi fi
} }
@@ -190,23 +191,19 @@ parse_params () {
description="$2" description="$2"
shift 2 shift 2
;; ;;
--dry-run)
dryrun=1
shift 1
;;
-l|--TARGET)
target_cmdline="$2"
shift 2
;;
-n|--noconfirm) -n|--noconfirm)
noconfirm=1 noconfirm=1
nonotify=1 donotify=0
shift shift
;; ;;
-s|--subvolid|--SUBVOLID) -s|--subvolid|--SUBVOLID)
subvolid_cmdline="$2" subvolid_cmdline="$2"
shift 2 shift 2
;; ;;
-t|--TARGET)
target_cmdline="$2"
shift 2
;;
-u|--UUID) -u|--UUID)
uuid_cmdline="$2" uuid_cmdline="$2"
shift 2 shift 2
@@ -219,6 +216,10 @@ parse_params () {
selected_config=${1#*=} selected_config=${1#*=}
shift shift
;; ;;
--dry-run)
dryrun=1
shift 1
;;
--remote) --remote)
remote=$2 remote=$2
ssh="ssh $remote" ssh="ssh $remote"
@@ -241,7 +242,7 @@ parse_params () {
break break
;; ;;
-*) -*)
echo "WARN: Unknown option (ignored): $1" >&2 printf "WARN: Unknown option (ignored): $1" >&2
#shift #shift
exit 1 exit 1
;; ;;
@@ -258,21 +259,25 @@ parse_params () {
description=${description:-"latest incremental backup"} description=${description:-"latest incremental backup"}
uuid_cmdline=${uuid_cmdline:-"none"} uuid_cmdline=${uuid_cmdline:-"none"}
target_cmdline=${target_cmdline:-"none"} target_cmdline=${target_cmdline:-"none"}
subvolid_cmdline=${subvolid_cmdline:-"none"}
if [ -z $remote ]; then if [ -z $remote ]; then
ssh="" ssh=""
fi fi
if [ "$verbose" ]; then if [ "$verbose" ]; then
echo "Snap UUID : '$uuid_cmdline'" printf "Snap UUID: '%s'\n" "$uuid_cmdline"
echo "Snap TARGET: '$target_cmdline'" printf "Snap TARGET: '%s'\n" "$target_cmdline"
echo "Snap Description: '$description'" printf "Snap SUBVOLID: '%s'\n" "$subvolid_cmdline"
echo "Snap Config: '$selected_config'" printf "Snap Backupdir: '%s'\n" "$backupdir_cmdline"
echo "Snap Remote: '$ssh'" printf "Snap Description: '%s'\n" "$description"
printf "Snap Config: '%s'\n" "$selected_config"
printf "Snap Remote: '%s'\n" "$ssh"
if [ "$verbose" ]; then snap_sync_options="verbose=true"; fi if [ "$verbose" ]; then snap_sync_options="verbose=true"; fi
if [ "$dryrun" ]; then snap_sync_options="${snap_sync_options} dry-run=true"; fi if [ "$dryrun" ]; then snap_sync_options="${snap_sync_options} dry-run=true"; fi
if [ "$noconfirm" ]; then snap_sync_options="${snap_sync_options} noconfirm=true"; fi if [ "$noconfirm" ]; then snap_sync_options="${snap_sync_options} noconfirm=true"; fi
echo "Options: ${snap_sync_options}" printf "Options: '%s'\n" "${snap_sync_options}"
fi fi
} }
@@ -291,7 +296,7 @@ run_config () {
count=$(eval snapper -c $selected_config list -t single | awk '/subvolid='"$selected_subvol"'/, /uuid='"$selected_uuid"'/ {cnt++} END {print cnt}') count=$(eval snapper -c $selected_config list -t single | awk '/subvolid='"$selected_subvol"'/, /uuid='"$selected_uuid"'/ {cnt++} END {print cnt}')
#count=$(eval snapper -c $selected_config list -t single | grep -c -e "subvolid=$selected_subvol" -e 'uuid=$selected_uuid') #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 if [ -n "$count" ] && [ "$count" -gt 1 ]; then
error "More than one snapper entry found with UUID $selected_uuid and SUBVOL $selected_subvol for configuration '$selected_config'. Skipping configuration '$selected_config'." error "More than one snapper entry found with UUID $selected_uuid and SUBVOLID $selected_subvol for configuration '$selected_config'. Skipping configuration '$selected_config'."
selected_configs=$(echo $selected_configs | sed -e "s/\($selected_config*\)//") selected_configs=$(echo $selected_configs | sed -e "s/\($selected_config*\)//")
if [ "$verbose" ]; then if [ "$verbose" ]; then
printf "Counter=%s" "$count" printf "Counter=%s" "$count"
@@ -526,7 +531,7 @@ run_backup () {
cmd="btrfs send $snapper_new_snapshot | $ssh btrfs receive $snapper_target_snapshots" cmd="btrfs send $snapper_new_snapshot | $ssh btrfs receive $snapper_target_snapshots"
printf "Sending first snapshot for snapper config '%s' ...\n" "$selected_config" | tee $PIPE printf "Sending first snapshot for snapper config '%s' ...\n" "$selected_config" | tee $PIPE
if [ "$verbose" ]; then if [ "$verbose" ]; then
echo "btrfs send $snapper_new_snapshot | $ssh btrfs receive $snapper_target_snapshots" printf "btrfs send %s | %s btrfs receive %s\n" "$snapper_new_snapshot" "$ssh" "$snapper_target_snapshots"
cmd="btrfs send -v $snapper_new_snapshot | $ssh btrfs receive -v $snapper_target_snapshots" cmd="btrfs send -v $snapper_new_snapshot | $ssh btrfs receive -v $snapper_target_snapshots"
fi fi
if [ ! "$dryrun" ]; then if [ ! "$dryrun" ]; then
@@ -552,7 +557,7 @@ run_backup () {
printf "dryrun: btrfs send %s -c %s %s | %s btrfs receive %s %s\n" \ printf "dryrun: btrfs send %s -c %s %s | %s btrfs receive %s %s\n" \
"$verbose_flag" "$snapper_sync_snapshot" "$snapper_new_snapshot" \ "$verbose_flag" "$snapper_sync_snapshot" "$snapper_new_snapshot" \
"$ssh" "$verbose_flag" "$snapper_target_snapshots" "$ssh" "$verbose_flag" "$snapper_target_snapshots"
printf "dryrun: snapper -c %s delete %a\n" "$selected_config" "$snapper_sync_id" printf "dryrun: snapper --config %s delete %s\n" "$selected_config" "$snapper_sync_id"
fi fi
fi fi
@@ -584,7 +589,7 @@ run_backup () {
target_userdata="subvolid=$src_subvolid, uuid=$src_uuid, host=$src_host" target_userdata="subvolid=$src_subvolid, uuid=$src_uuid, host=$src_host"
# Tag new snapshot as the latest # Tag new snapshot as the latest
printf "Tagging new snapshot as latest backup for '%s' ...\n" "$selected_config" | tee $PIPE printf "Tagging snapper metadata for configuration '%s' ...\n" "$selected_config" | tee $PIPE
if [ ! "$dryrun" ]; then if [ ! "$dryrun" ]; then
snapper -v -c "$selected_config" modify -d "$description" -u "$userdata" "$snapper_new_id" snapper -v -c "$selected_config" modify -d "$description" -u "$userdata" "$snapper_new_id"
$ssh snapper -v -c "$snapper_target_subvol" modify -d \"$target_description\" -u \"$target_userdata\" "$snapper_new_id" $ssh snapper -v -c "$snapper_target_subvol" modify -d \"$target_description\" -u \"$target_userdata\" "$snapper_new_id"
@@ -594,10 +599,15 @@ run_backup () {
cmd="$ssh snapper -v -c $snapper_target_subvol modify -d $target_description -u $target_userdata $snapper_new_id" cmd="$ssh snapper -v -c $snapper_target_subvol modify -d $target_description -u $target_userdata $snapper_new_id"
printf "dryrun: %s\n" "$cmd" printf "dryrun: %s\n" "$cmd"
fi fi
# Cleanup old source snapshots
source_description="snap-sync backup"
$(eval snapper --verbose --config "$selected_config" modify --description \"$source_description\" --cleanup timeline "$snapper_sync_id")
#snapper -c "$selected_config" delete "$snapper_sync_id"
sync
printf "Backup complete for snapper configuration '%s'.\n" "$selected_config" > $PIPE printf "Backup complete for snapper configuration '%s'.\n" "$selected_config" > $PIPE
done done
exec 3>&-
} }
select_target_disk () { select_target_disk () {
@@ -622,7 +632,7 @@ select_target_disk () {
eval "disk_selected_$i='$disk_subvolid_match'" eval "disk_selected_$i='$disk_subvolid_match'"
disk=$(eval echo \$disk_uuid_$disk_subvolid_match) disk=$(eval echo \$disk_uuid_$disk_subvolid_match)
subvolid=$(eval echo \$disk_subvolid_$disk_subvolid_match) subvolid=$(eval echo \$disk_subvolid_$disk_subvolid_match)
fs_options=$(eval echo \$fs_options_$disk_target_match | sed -e 's/.*,\(subvolid=[0-9]*\).*,\(subvol=[0-9]*\)/\1,\2/') fs_options=$(eval echo \$fs_options_$disk_subvolid_match | sed -e 's/.*,\(subvolid=[0-9]*\).*,\(subvol=[0-9]*\)/\1,\2/')
disk_selected=$disk_subvolid_match disk_selected=$disk_subvolid_match
break break
fi fi
@@ -685,13 +695,12 @@ select_target_disk () {
esac esac
done done
if [ "$disk_selected" = x ]; then if [ "$disk_selected" = x ]; then
exit x exit 0
fi fi
selected_uuid=$(eval echo \$disk_uuid_$disk_selected) selected_uuid=$(eval echo \$disk_uuid_$disk_selected)
selected_target=$(eval echo \$disk_target_$disk_selected) selected_target=$(eval echo \$disk_target_$disk_selected)
selected_subvol=$(eval echo \$fs_options_$disk_selected | sed -e 's/.*subvolid=\([0-9]*\).*/\1/') selected_subvol=$(eval echo \$fs_options_$disk_selected | sed -e 's/.*subvolid=\([0-9]*\).*/\1/')
#fs_options=$(eval echo \$fs_options_$disk_selected | sed -e 's/.*,\(subvolid=[0-9]*\).*,\(subvol=[0-9]*\)/\1,\2/')
if [ "$verbose" ]; then if [ "$verbose" ]; then
printf "Selected Subvol-ID=%s: %s on %s\n" "$selected_subvol" "$selected_target" "$selected_uuid" printf "Selected Subvol-ID=%s: %s on %s\n" "$selected_subvol" "$selected_target" "$selected_uuid"
fi fi
@@ -722,9 +731,10 @@ Options:
(e.g. -c "root home"). (e.g. -c "root home").
-n, --noconfirm Do not ask for confirmation for each configuration. Will still prompt for backup -n, --noconfirm Do not ask for confirmation for each configuration. Will still prompt for backup
directory name on first backup" directory name on first backup"
-s, --subvolid <subvlid> Specify the subvolume id of the mounted BTRFS subvolume to back up to. Defaults to 5.
-u, --UUID <UUID> Specify the UUID of the mounted BTRFS subvolume to back up to. Otherwise will prompt." -u, --UUID <UUID> Specify the UUID of the mounted BTRFS subvolume to back up to. Otherwise will prompt."
If multiple mount points are found with the same UUID, will prompt user." If multiple mount points are found with the same UUID, will prompt user."
-l, --TARGET <target> Specify the mountpoint of the BTRFS subvolume to back up to. -t, --TARGET <target> Specify the mountpoint of the BTRFS subvolume to back up to.
--remote <address> Send the snapshot backup to a remote machine. The snapshot will be sent via ssh. You --remote <address> Send the snapshot backup to a remote machine. The snapshot will be sent via ssh. You
should specify the remote machine's hostname or ip address. The 'root' user must be should specify the remote machine's hostname or ip address. The 'root' user must be
permitted to login on the remote machine. permitted to login on the remote machine.
@@ -787,10 +797,10 @@ verify_snapper_structure () {
cwd=`pwd` cwd=`pwd`
ssh="" ssh=""
# this bashism has to be adapted # this bashism and can't be ported to dash (ERR is not supported)
#trap 'traperror ${LINENO} $? "$BASH_COMMAND" $BASH_LINENO "${FUNCNAME[@]}"' ERR #trap 'traperror ${LINENO} $? "$BASH_COMMAND" $BASH_LINENO "${FUNCNAME[@]}"' ERR
trap 'traperror ${LINENO} $?"' ERR #trap 'traperror ${LINENO} $?' ERR
trap trapkill SIGTERM SIGINT trap trapkill TERM INT
parse_params $@ parse_params $@
@@ -799,14 +809,14 @@ check_prerequisites
# read mounted BTRFS structures # read mounted BTRFS structures
get_disk_infos get_disk_infos
if [ "target_cmdline" != "none" ]; then if [ "$target_cmdline" != "none" ]; then
if [ -z "$ssh" ]; then if [ -z "$ssh" ]; then
notify_info "Backup started" "Starting backups to '$target_cmdline' ..." notify_info "Backup started" "Starting backups to '$target_cmdline' ..."
else else
notify_info "Backup started" "Starting backups to '$target_cmdline' at $remote ..." notify_info "Backup started" "Starting backups to '$target_cmdline' at $remote ..."
fi fi
elif [ "$uuid_cmdline" != "none" ]; then elif [ "$uuid_cmdline" != "none" ]; then
if [ -z $ssh ]; then if [ -z "$ssh" ]; then
notify_info "Backup started" "Starting backups to $uuid_cmdline..." notify_info "Backup started" "Starting backups to $uuid_cmdline..."
else else
notify_info "Backup started" "Starting backups to $uuid_cmdline at $remote..." notify_info "Backup started" "Starting backups to $uuid_cmdline at $remote..."
@@ -835,6 +845,9 @@ run_config
# run backups using btrfs-send -> btrfs-receive # run backups using btrfs-send -> btrfs-receive
run_backup run_backup
printf "\nDone!\n" | tee $PIPE
exec 3>&-
if [ "$uuid_cmdline" != "none" ]; then if [ "$uuid_cmdline" != "none" ]; then
notify_info "Finished" "Backups to $uuid_cmdline complete!" notify_info "Finished" "Backups to $uuid_cmdline complete!"
else else