snap-sync: btrfs-send/btrfs-receive pipe handling
- snapper is using hardcoded subvolume name to store its ro snapshots variable snapper_snapshots holds it (.snapshots) - new optional progress visualiation (via pv) - more robust error handling rely on btrfs-send/btrfs-receive exit code
This commit is contained in:
119
bin/snap-sync
119
bin/snap-sync
@@ -33,11 +33,18 @@ version="0.4.4"
|
|||||||
SNAPPER_CONFIG=/etc/conf.d/snapper
|
SNAPPER_CONFIG=/etc/conf.d/snapper
|
||||||
SNAPPER_TEMPLATES=/etc/snapper/config-templates
|
SNAPPER_TEMPLATES=/etc/snapper/config-templates
|
||||||
|
|
||||||
TMPDIR=$(mktemp -d)
|
# define fifo pipes
|
||||||
PIPE=$TMPDIR/$progname.out
|
TMPDIR_PIPE=$(mktemp -d)
|
||||||
|
PIPE=$TMPDIR_PIPE/$progname.out
|
||||||
mkfifo $PIPE
|
mkfifo $PIPE
|
||||||
systemd-cat -t "$progname" < $PIPE &
|
systemd-cat --identifier="$progname" < $PIPE &
|
||||||
exec 3>$PIPE
|
|
||||||
|
BTRFS_PIPE=$TMPDIR_PIPE/btrfs.out
|
||||||
|
#mkfifo $BTRFS_PIPE
|
||||||
|
#systemd-cat --identifier="btrfs-pipe" < $BTRFS_PIPE &
|
||||||
|
|
||||||
|
# redirect descriptors to given pipes
|
||||||
|
exec 3>$PIPE 4>$BTRFS_PIPE
|
||||||
|
|
||||||
# global variables
|
# global variables
|
||||||
donotify=0
|
donotify=0
|
||||||
@@ -50,6 +57,7 @@ disk_uuid_match=''
|
|||||||
selected_uuid='none'
|
selected_uuid='none'
|
||||||
selected_target='none'
|
selected_target='none'
|
||||||
selected_subvol='none'
|
selected_subvol='none'
|
||||||
|
snapper_snapshots=".snapshots" # hardcoded in snapper
|
||||||
|
|
||||||
###
|
###
|
||||||
# functions
|
# functions
|
||||||
@@ -68,6 +76,7 @@ check_prerequisites () {
|
|||||||
|
|
||||||
# optional binaries:
|
# optional binaries:
|
||||||
which notify-send >/dev/null 2>&1 && { donotify=1; }
|
which notify-send >/dev/null 2>&1 && { donotify=1; }
|
||||||
|
which pv >/dev/null 2>&1 && { do_pv_cmd=1; }
|
||||||
|
|
||||||
if [ $(id -u) -ne 0 ] ; then printf "Script must be run as root" ; exit 1 ; fi
|
if [ $(id -u) -ne 0 ] ; then printf "Script must be run as root" ; exit 1 ; fi
|
||||||
|
|
||||||
@@ -283,9 +292,12 @@ parse_params () {
|
|||||||
;;
|
;;
|
||||||
--remote)
|
--remote)
|
||||||
remote=$2
|
remote=$2
|
||||||
ssh="ssh $remote"
|
|
||||||
shift 2
|
shift 2
|
||||||
;;
|
;;
|
||||||
|
--remote=*)
|
||||||
|
remote=${1#*=}
|
||||||
|
shift
|
||||||
|
;;
|
||||||
--subvolid=*|--SUBVOLID=*)
|
--subvolid=*|--SUBVOLID=*)
|
||||||
subvolid_cmdline=${1#*=}
|
subvolid_cmdline=${1#*=}
|
||||||
shift
|
shift
|
||||||
@@ -325,6 +337,8 @@ parse_params () {
|
|||||||
|
|
||||||
if [ -z $remote ]; then
|
if [ -z $remote ]; then
|
||||||
ssh=""
|
ssh=""
|
||||||
|
else
|
||||||
|
ssh="ssh $remote"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$verbose" ]; then
|
if [ "$verbose" ]; then
|
||||||
@@ -529,34 +543,73 @@ run_backup () {
|
|||||||
|
|
||||||
verify_snapper_structure "backup_root=$backup_root" "snapper_target_config=$snapper_target_config" "snapper_new_id=$snapper_new_id"
|
verify_snapper_structure "backup_root=$backup_root" "snapper_target_config=$snapper_target_config" "snapper_new_id=$snapper_new_id"
|
||||||
|
|
||||||
if [ -z "$snapper_sync_id" ]; then
|
snapper_target_snapshot_size=$(du --sum $snapper_new_snapshot 2>/dev/null | awk -F ' ' '{print $1}')
|
||||||
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
|
# settings for interactive progress status
|
||||||
if [ "$verbose" ]; then
|
if [ $do_pv_cmd ]; then
|
||||||
printf "btrfs send %s | %s btrfs receive %s\n" "$snapper_new_snapshot" "$ssh" "$snapper_target_snapshot"
|
pv_options="--delay-start 2 --interval 5 --timer --rate --bytes --fineta --progress"
|
||||||
cmd="btrfs send -v $snapper_new_snapshot | $ssh btrfs receive -v $snapper_target_snapshot"
|
cmd_pv="pv $pv_options --size $snapper_target_snapshot_size |"
|
||||||
fi
|
#cmd_pv="pv $pv_options --size $snapper_target_snapshot_size | dialog --gauge \"$progname: Progress for config '$selected_config'\" 6 85 |"
|
||||||
if [ ! "$dryrun" ]; then
|
else
|
||||||
btrfs send "$snapper_new_snapshot" | $ssh btrfs receive "$snapper_target_snapshot" &>/dev/null
|
cmd_pv=''
|
||||||
sync
|
fi
|
||||||
|
|
||||||
|
if [ -z "$snapper_sync_id" ]; then
|
||||||
|
# target never received any snapshot before
|
||||||
|
if [ "$verbose" ]; then
|
||||||
|
printf "Sending first snapshot for snapper config '%s' (size=%s) ...\n" "$selected_config" "$snapper_target_snapshot_size"
|
||||||
|
fi
|
||||||
|
cmd="btrfs send $verbose_flag $snapper_new_snapshot 2>$BTRFS_PIPE | $cmd_pv $ssh btrfs receive $verbose_flag $snapper_target_snapshot 2>$BTRFS_PIPE"
|
||||||
|
if [ ! "$dryrun" ]; then
|
||||||
|
# this could take a while, depending on datasize
|
||||||
|
$(eval $cmd)
|
||||||
|
if [ "$?" -gt 0 ]; then
|
||||||
|
printf "BTRFS_PIPE: %s" "cat $BTRFS_PIPE"
|
||||||
|
die "btrfs pipe error."
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
cmd="btrfs send -v $snapper_new_snapshot | $ssh btrfs receive -v $snapper_target_snapshot"
|
|
||||||
printf "dryrun: %s\n" "$cmd"
|
printf "dryrun: %s\n" "$cmd"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
printf "Sending incremental snapshot for snapper config '%s' ...\n" "$selected_config" | tee $PIPE
|
if [ "$verbose" ]; then verbose_flag="-v"; fi
|
||||||
# Sends the difference between the new snapshot and old snapshot to the
|
printf "Sending incremental snapshot for snapper config '%s' ...\n" "$selected_config"
|
||||||
# backup location. Using the -c flag instead of -p tells it that there
|
# checking if parent snapshot-id (as saved on source) is also available on target
|
||||||
# is an identical subvolume to the old snapshot at the receiving
|
if $ssh [ -d "$backup_root/$snapper_target_config/$snapper_snapshots/$snapper_sync_id" ]; then
|
||||||
|
# Sends the difference between the new snapshot and old synced snapshot to the
|
||||||
|
# backup location. Using the -c (clone-source) flag instead of -p (parent) tells it
|
||||||
|
# that there is an identical subvolume to the synced snapshot at the receiving
|
||||||
# location where it can get its data. This helps speed up the transfer.
|
# location where it can get its data. This helps speed up the transfer.
|
||||||
verbose_flag="-v"
|
|
||||||
if [ ! "$dryrun" ]; then
|
if [ ! "$dryrun" ]; then
|
||||||
btrfs send "$verbose_flag" -c "$snapper_sync_snapshot" "$snapper_new_snapshot" | $ssh btrfs receive "$verbose_flag" "$snapper_target_snapshot"
|
|
||||||
if [ "$verbose" ]; then
|
if [ "$verbose" ]; then
|
||||||
printf "Deleting sync snapshot for %s ...\n" "$selected_config" | tee $PIPE
|
printf "btrfs-send will use targets snapshot '%s' to sync metadata for %s ...\n" "$snapper_sync_snapshot" "$snapper_new_snapshot"
|
||||||
fi
|
fi
|
||||||
snapper -c "$selected_config" delete "$snapper_sync_id"
|
cmd="btrfs send $verbose_flag -c $snapper_sync_snapshot $snapper_new_snapshot 2>$BTRFS_PIPE | $cmd_pv $ssh btrfs receive $verbose_flag $snapper_target_snapshot 2>/dev/null"
|
||||||
sync
|
ret=$(eval $cmd)
|
||||||
|
if [ "$?" -gt 0 ]; then
|
||||||
|
printf "BTRFS_PIPE: %s" "cat $BTRFS_PIPE"
|
||||||
|
die "btrfs pipe error."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
printf "dryrun: btrfs send %s -c %s %s | %s btrfs receive %s %s\n" \
|
||||||
|
"$verbose_flag" "$snapper_sync_snapshot" "$snapper_new_snapshot" \
|
||||||
|
"$ssh" "$verbose_flag" "$snapper_target_snapshot"
|
||||||
|
printf "dryrun: snapper --config %s delete %s\n" "$selected_config" "$snapper_sync_id"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# need to use source snapshot to provide metadata for target
|
||||||
|
if [ ! "$dryrun" ]; then
|
||||||
|
if [ "$verbose" ]; then
|
||||||
|
printf "btrfs-send is using source snapshot '%s' to read metadata ...\n" "$snapper_sync_snapshot" | tee $PIPE
|
||||||
|
printf "Deleting old sync snapshot %s for %s ...\n" "$snapper_sync_id" "$selected_config" | tee $PIPE
|
||||||
|
fi
|
||||||
|
cmd="btrfs send $verbose_flag -p $snapper_sync_snapshot $snapper_new_snapshot 2>$BTRFS_PIPE | $cmd_pv $ssh btrfs receive $verbose_flag $snapper_target_snapshot 2>/dev/null"
|
||||||
|
$(eval $cmd)
|
||||||
|
ret=$(eval $cmd)
|
||||||
|
if [ "$?" -gt 0 ]; then
|
||||||
|
printf "BTRFS_PIPE: %s" "cat $BTRFS_PIPE"
|
||||||
|
die "btrfs pipe error."
|
||||||
|
fi
|
||||||
|
#printf "btrfs returns: '%i'\n" "$ret"
|
||||||
else
|
else
|
||||||
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" \
|
||||||
@@ -564,7 +617,9 @@ run_backup () {
|
|||||||
printf "dryrun: snapper --config %s delete %s\n" "$selected_config" "$snapper_sync_id"
|
printf "dryrun: snapper --config %s delete %s\n" "$selected_config" "$snapper_sync_id"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# finally: send the snapper info metadata
|
||||||
if [ -z "$ssh" ]; then
|
if [ -z "$ssh" ]; then
|
||||||
if [ ! "$dryrun" ]; then
|
if [ ! "$dryrun" ]; then
|
||||||
cp "$snapper_new_info" "$snapper_target_snapshot"
|
cp "$snapper_new_info" "$snapper_target_snapshot"
|
||||||
@@ -626,19 +681,20 @@ run_finalize () {
|
|||||||
# which is stored in snappers info.xml file of the source snapshot.
|
# which is stored in snappers info.xml file of the source snapshot.
|
||||||
# This is how we find the parent.
|
# This is how we find the parent.
|
||||||
|
|
||||||
userdata="backupdir=$backup_dir, subvolid=$selected_subvol, uuid=$selected_uuid"
|
|
||||||
src_host=$(eval cat /etc/hostname)
|
src_host=$(eval cat /etc/hostname)
|
||||||
src_uuid=$(eval findmnt --noheadings --output UUID $SUBVOLUME)
|
src_uuid=$(eval findmnt --noheadings --output UUID $SUBVOLUME)
|
||||||
src_subvolid=$(eval findmnt --noheadings --output OPTIONS $SUBVOLUME | sed -e 's/.*subvolid=\([0-9]*\).*/\1/')
|
src_subvolid=$(eval findmnt --noheadings --output OPTIONS $SUBVOLUME | sed -e 's/.*subvolid=\([0-9]*\).*/\1/')
|
||||||
|
userdata="backupdir=$backup_dir, subvolid=$selected_subvol, uuid=$selected_uuid, host=$remote"
|
||||||
target_description="snap-sync backup"
|
target_description="snap-sync 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 snapshots key/value parameter
|
||||||
printf "Tagging snapper metadata for configuration '%s' ...\n" "$selected_config" | tee $PIPE
|
printf "Tagging snapper metadata for configuration '%s' ...\n" "$selected_config" | tee $PIPE
|
||||||
if [ ! "$dryrun" ]; then
|
if [ ! "$dryrun" ]; then
|
||||||
eval snapper --verbose --config "$selected_config" modify --description \"$description\" --userdata \"$userdata\" "$snapper_new_id"
|
$(eval snapper --verbose --config "$selected_config" modify --description \"$description\" --userdata \"$userdata\" "$snapper_new_id")
|
||||||
sync
|
sync
|
||||||
$ssh snapper --verbose --config "$snapper_target_config" modify --description \"$target_description\" --userdata \"$target_userdata\" "$snapper_new_id"
|
#TODO: wait for btrfs-receive to be completed!
|
||||||
|
$ssh "snapper --verbose --config $snapper_target_config modify --description \"$target_description\" --userdata \"$target_userdata\" $snapper_new_id"
|
||||||
$ssh sync
|
$ssh sync
|
||||||
else
|
else
|
||||||
cmd="snapper -v -c $selected_config modify -d $description -u $userdata $snapper_new_id"
|
cmd="snapper -v -c $selected_config modify -d $description -u $userdata $snapper_new_id"
|
||||||
@@ -654,6 +710,7 @@ run_finalize () {
|
|||||||
sync
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -751,8 +808,8 @@ select_target_disk () {
|
|||||||
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
|
||||||
}
|
|
||||||
|
|
||||||
|
}
|
||||||
traperror () {
|
traperror () {
|
||||||
printf "Exited due to error on line %s.\n" $1
|
printf "Exited due to error on line %s.\n" $1
|
||||||
printf "exit status: %s\n" "$2"
|
printf "exit status: %s\n" "$2"
|
||||||
@@ -798,8 +855,6 @@ verify_snapper_structure () {
|
|||||||
local snapper_config=${2##snapper_target_config=}
|
local snapper_config=${2##snapper_target_config=}
|
||||||
local snapper_id=${3##snapper_new_id=}
|
local snapper_id=${3##snapper_new_id=}
|
||||||
|
|
||||||
local snapper_snapshots=".snapshots"
|
|
||||||
|
|
||||||
if [ "$verbose" ]; then
|
if [ "$verbose" ]; then
|
||||||
printf "Verify snapper filesystem structure on target ...\n"
|
printf "Verify snapper filesystem structure on target ...\n"
|
||||||
fi
|
fi
|
||||||
|
|||||||
Reference in New Issue
Block a user