- destinguish three states (running, synced, finished) - adapt handling where needed
1060 lines
37 KiB
Bash
Executable File
1060 lines
37 KiB
Bash
Executable File
#!/bin/sh
|
|
|
|
# snap-sync
|
|
# https://github.com/wesbarnett/snap-sync
|
|
# Copyright (C) 2016, 2017 James W. Barnett
|
|
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
|
|
# You should have received a copy of the GNU General Public License along
|
|
# with this program; if not, write to the Free Software Foundation, Inc.,
|
|
|
|
# -------------------------------------------------------------------------
|
|
|
|
# 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.
|
|
|
|
progname="${0##*/}"
|
|
version="0.4.4"
|
|
|
|
# The following lines are modified by the Makefile or
|
|
# find_snapper_config script
|
|
SNAPPER_CONFIG=/etc/conf.d/snapper
|
|
SNAPPER_TEMPLATES=/etc/snapper/config-templates
|
|
|
|
# define fifo pipes
|
|
TMPDIR_PIPE=$(mktemp -d)
|
|
PIPE=$TMPDIR_PIPE/$progname.out
|
|
mkfifo $PIPE
|
|
systemd-cat --identifier="$progname" < $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
|
|
donotify=0
|
|
answer=no
|
|
disk_count=-1
|
|
disk_uuid_match_count=0
|
|
disk_target_match_count=0
|
|
disk_subvolid_match_count=0
|
|
disk_uuid_match=''
|
|
selected_uuid='none'
|
|
selected_target='none'
|
|
selected_subvol='none'
|
|
snapper_snapshots=".snapshots" # hardcoded in snapper
|
|
|
|
###
|
|
# functions
|
|
###
|
|
|
|
check_prerequisites () {
|
|
|
|
# requested binaries:
|
|
which awk >/dev/null 2>&1 || { printf "'awk' is not installed." && exit 1; }
|
|
which sed >/dev/null 2>&1 || { printf "'sed' is not installed." && exit 1; }
|
|
which tee >/dev/null 2>&1 || { printf "'tee' is not installed." && exit 1; }
|
|
which btrfs >/dev/null 2>&1 || { printf "'btrfs' 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 || { printf "'systemd-cat' is not installed." && exit 1; }
|
|
which wc >/dev/null 2>&1 || { printf "'wc' is not installed." && exit 1; }
|
|
|
|
# optional binaries:
|
|
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 [ ! -r "$SNAPPER_CONFIG" ]; then
|
|
die "$SNAPPER_CONFIG does not exist."
|
|
fi
|
|
}
|
|
|
|
check_snapper_failed_ids () {
|
|
local noconfirm=${1:-$false}
|
|
|
|
# active, non finished snapshot backups are marked with following string
|
|
# "$progname backup in progress" (snapper description field)
|
|
snapper_failed_ids=$(eval snapper --config $selected_config list --type single | awk '/'"$snap_description_running"'/ {cnt++} END {print cnt}')
|
|
if [ ${#snapper_failed_ids} -gt 0 ]; then
|
|
#if [ -n "$snapper_failed_ids" ]; then
|
|
if [ "$noconfirm" ]; then
|
|
answer="yes"
|
|
else
|
|
printf "\nNOTE: Found %s previous failed sync runs for '%s'.\n" "${snapper_failed_ids}" "$selected_config" | tee $PIPE
|
|
get_answer_yes_no "Delete failed backup snapshots [y/N]? " "no"
|
|
fi
|
|
if [ "$answer" = "yes" ]; then
|
|
cmd2="snapper --config \"$selected_config\" list | awk '/'\"$snap_description_running\"'/ {print \$3}'"
|
|
cmd="snapper --config \"$selected_config\" delete "
|
|
$(eval $cmd $(eval $cmd2))
|
|
fi
|
|
fi
|
|
}
|
|
|
|
die () {
|
|
error "$@"
|
|
exit 1
|
|
}
|
|
|
|
error () {
|
|
printf "\n==> ERROR: %s\n" "$@"
|
|
notify_error 'Error' 'Check journal for more information.'
|
|
} >&2
|
|
|
|
get_disk_infos () {
|
|
local disk_uuid
|
|
local disk_target
|
|
local fs_option
|
|
|
|
# get mounted BTRFS infos
|
|
if [ "$(findmnt --noheadings --nofsroot --target / --output FSTYPE)" = "btrfs" ]; then
|
|
# root filesystem is never seen as valid target location
|
|
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}')
|
|
fs_options=$($ssh findmnt --noheadings --nofsroot --types btrfs --output UUID,OPTIONS --list | grep -v $exclude_uuid | awk '{print $2}')
|
|
else
|
|
disk_uuids=$($ssh findmnt --noheadings --nofsroot --types btrfs --output UUID --list)
|
|
disk_targets=$($ssh findmnt --noheadings --nofsroot --types btrfs --output TARGET --list)
|
|
fs_options=$($ssh findmnt --noheadings --nofsroot --types btrfs --output UUID,OPTIONS --list | awk '{print $2}')
|
|
fi
|
|
|
|
# we need at least one target disk
|
|
if [ ${#disk_targets} -eq 0 ]; then die "no suitable target disk found"
|
|
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: fs_options_$i
|
|
# Pseudo-Array: disk_selected_$y (reference to $i element)
|
|
# List: disk_uuid_match (reference to matching preselected uuids)
|
|
# List: disk_target_match (reference to matching preselected targets)
|
|
|
|
# initialize our structures
|
|
i=0
|
|
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 disk_target in $disk_targets; do
|
|
if [ "$disk_target" = "$target_cmdline" ]; then
|
|
disk_target_match="$i"
|
|
disk_target_match_count=$(($disk_target_match_count+1))
|
|
fi
|
|
eval "disk_target_$i='$disk_target'"
|
|
i=$((i+1))
|
|
done
|
|
i=0
|
|
for fs_option in $fs_options; do
|
|
subvolid=$(eval echo \$fs_option | sed -e 's/.*subvolid=\([0-9]*\).*/\1/')
|
|
if [ "$subvolid" = "$subvolid_cmdline" ]; then
|
|
disk_subvolid_match="$i"
|
|
disk_subvolid_match_count=$(($disk_subvolid_match_count+1))
|
|
fi
|
|
eval "fs_options_$i='$fs_option'"
|
|
i=$((i+1))
|
|
done
|
|
}
|
|
|
|
get_answer_yes_no () {
|
|
local message="${1:-'Do you want to proceed [y/N]? '}"
|
|
local i="none"
|
|
|
|
#printf "Pre-selected answer: %s\n" $answer
|
|
while [ "$i" = "none" ]; do
|
|
read -r -p "$message" i
|
|
|
|
case $i in
|
|
y|Y|yes|Yes)
|
|
answer="yes"
|
|
break
|
|
;;
|
|
n|N|no|No)
|
|
answer="no"
|
|
break
|
|
;;
|
|
*)
|
|
i="none"
|
|
printf "Select 'y' or 'n'.\n"
|
|
;;
|
|
esac
|
|
done
|
|
#printf "Selected answer: %s\n" $answer
|
|
}
|
|
|
|
notify () {
|
|
# estimation: batch calls should just log
|
|
if [ $donotify ]; then
|
|
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 "$progname: $1" "$2" --icon="dialog-$3"
|
|
done
|
|
else
|
|
printf "%s %s\n" "$progname" "$2"
|
|
fi
|
|
}
|
|
|
|
notify_info () {
|
|
notify "$1" "$2" "information"
|
|
}
|
|
|
|
notify_error() {
|
|
notify "$1" "$2" "error"
|
|
}
|
|
|
|
parse_params () {
|
|
# Evaluate given call parameters
|
|
while [ $# -gt 0 ]; do
|
|
key="$1"
|
|
case $key in
|
|
-h | --help | \-\? | --usage)
|
|
# Call usage() function.
|
|
usage
|
|
;;
|
|
-b|--backupdir)
|
|
backupdir_cmdline="$2"
|
|
shift 2
|
|
;;
|
|
-c|--config)
|
|
if [ ${#selected_config} -gt 0 ]; then
|
|
selected_configs="${selected_configs} ${2}"
|
|
else
|
|
selected_configs="$2"
|
|
fi
|
|
shift 2
|
|
;;
|
|
-d|--description|--description-finished)
|
|
description="$2"
|
|
shift 2
|
|
;;
|
|
--description-running)
|
|
description="$2"
|
|
shift 2
|
|
;;
|
|
--description-synced)
|
|
description="$2"
|
|
shift 2
|
|
;;
|
|
-n|--noconfirm)
|
|
noconfirm=1
|
|
donotify=0
|
|
shift
|
|
;;
|
|
-s|--subvolid|--SUBVOLID)
|
|
subvolid_cmdline="$2"
|
|
shift 2
|
|
;;
|
|
-t|--target|--TARGET)
|
|
target_cmdline="$2"
|
|
shift 2
|
|
;;
|
|
-u|--uuid|--UUID)
|
|
uuid_cmdline="$2"
|
|
shift 2
|
|
;;
|
|
-v|--verbose)
|
|
verbose=1
|
|
shift 1
|
|
;;
|
|
--backupdir=*)
|
|
backupdir_cmdline=${1#*=}
|
|
shift
|
|
;;
|
|
--config=*)
|
|
if [ ${#selected_config} -gt 0 ]; then
|
|
selected_config="${selected_config} ${1#*=}"
|
|
else
|
|
selected_config="${1#*=}"
|
|
fi
|
|
shift
|
|
;;
|
|
--dry-run)
|
|
dryrun=1
|
|
shift 1
|
|
;;
|
|
--remote)
|
|
remote=$2
|
|
shift 2
|
|
;;
|
|
--remote=*)
|
|
remote=${1#*=}
|
|
shift
|
|
;;
|
|
--subvolid=*|--SUBVOLID=*)
|
|
subvolid_cmdline=${1#*=}
|
|
shift
|
|
;;
|
|
--target=*|TARGET=*)
|
|
target_cmdline=${1#*=}
|
|
shift
|
|
;;
|
|
--uuid=*|UUID=*)
|
|
uuid_cmdline=${1#*=}
|
|
shift
|
|
;;
|
|
--) # End of all options
|
|
shift
|
|
break
|
|
;;
|
|
-*)
|
|
printf "WARN: Unknown option (ignored): $1" >&2
|
|
die "Unknown option"
|
|
;;
|
|
*)
|
|
printf "Unknown option: %s\nRun '%s -h' for valid options.\n" $key $progname
|
|
die "Unknown option"
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# Set reasonable defaults
|
|
. $SNAPPER_CONFIG
|
|
selected_configs=${selected_configs:-$SNAPPER_CONFIGS}
|
|
|
|
snap_description_synced=${description_lastsync:-"snap-sync last incremental"}
|
|
snap_description_finished=${description_finished:-"snap-sync backup"}
|
|
snap_description_running=${description_running:-"snap-sync in progress"}
|
|
|
|
uuid_cmdline=${uuid_cmdline:-"none"}
|
|
target_cmdline=${target_cmdline:-"none"}
|
|
subvolid_cmdline=${subvolid_cmdline:-"none"}
|
|
backupdir_cmdline=${backupdir_cmdline:-"none"}
|
|
|
|
if [ -z $remote ]; then
|
|
ssh=""
|
|
else
|
|
ssh="ssh $remote"
|
|
fi
|
|
|
|
if [ "$verbose" ]; then
|
|
printf "Snap UUID: '%s'\n" "$uuid_cmdline"
|
|
printf "Snap TARGET: '%s'\n" "$target_cmdline"
|
|
printf "Snap SUBVOLID: '%s'\n" "$subvolid_cmdline"
|
|
printf "Snap Backupdir: '%s'\n" "$backupdir_cmdline"
|
|
printf "Snap Description: '%s'\n" "$description"
|
|
printf "Snap Config: '%s'\n" "$selected_config"
|
|
printf "Snap Remote: '%s'\n" "$ssh"
|
|
|
|
printf "Snapper Descriptions"
|
|
printf " backup finished: '%s'\n" "$snap_description_finished"
|
|
printf " backup synced: '%s'\n" "$snap_description_synced"
|
|
printf " backup running: '%s'\n" "$snap_description_running"
|
|
|
|
if [ "$verbose" ]; then snap_sync_options="verbose=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
|
|
printf "Options: '%s'\n" "${snap_sync_options}"
|
|
fi
|
|
}
|
|
|
|
run_config () {
|
|
|
|
printf "\nVerify configuration...\n"
|
|
|
|
# loop though selected snapper configurations
|
|
# Pseudo Arrays $i -> store associated elements of selected_config
|
|
i=0
|
|
for selected_config in $selected_configs; do
|
|
count=$(eval snapper --config $selected_config list --type single | \
|
|
awk '/'"$snap_description_synced"'/' | \
|
|
awk '/subvolid='"$selected_subvol"'/, /uuid='"$selected_uuid"'/ {cnt++} END {print cnt}')
|
|
if [ -n "$count" ] && [ "$count" -gt 1 ]; then
|
|
printf "%s entries are marked as '%s' for snapper config '%s'\n" \
|
|
"$count" "$snap_description_synced" "$selected_config" | tee PIPE
|
|
printf "Pointing to target with UUID '%s' and SUBVOLID '%s'. Skipping configuration '%s'.\n" \
|
|
"$selected_uuid" "$selected_subvol" "$selected_config"
|
|
printf "Please cleanup for further processing.\n" | tee PIPE
|
|
error "Skipping configuration $selected_config."
|
|
selected_configs=$(echo $selected_configs | sed -e "s/\($selected_config*\)//")
|
|
continue
|
|
fi
|
|
|
|
# cleanup failed former runs
|
|
check_snapper_failed_ids $noconfirm
|
|
|
|
SNAP_SYNC_EXCLUDE=no
|
|
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
|
|
fi
|
|
|
|
printf "\n"
|
|
|
|
# get latest successfully finished snapshot
|
|
# (tagged with userdata key/value pairs)
|
|
snapper_sync_id=$(eval snapper --config "$selected_config" list --type single | \
|
|
awk '/'"$snap_description_synced"'/' | \
|
|
awk '/subvolid='"$selected_subvol"'/, /uuid='"$selected_uuid"'/ {print $1}')
|
|
if [ ${#snapper_sync_id} -gt 0 ]; then
|
|
snapper_sync_snapshot=$SUBVOLUME/.snapshots/$snapper_sync_id/snapshot
|
|
fi
|
|
|
|
eval "snapper_sync_id_$i='$snapper_sync_id'"
|
|
eval "snapper_sync_snapshot_$i='$snapper_sync_snapshot'"
|
|
|
|
# verify backupdir
|
|
if [ -z "$snapper_sync_id" ]; then
|
|
printf "No backups have been performed for snapper config '%s' on target disk.\n" "$selected_config"
|
|
if [ "$backupdir_cmdline" != "none" ]; then
|
|
backupdir=$backupdir_cmdline
|
|
backup_root="$selected_target/$backupdir"
|
|
else
|
|
if [ ! $noconfirm ]; then
|
|
read -r -p "Enter name of directory to store backups, relative to $selected_target (to be created if not existing): " backupdir
|
|
if [ -z "$backupdir" ]; then
|
|
backup_root="$selected_target"
|
|
else
|
|
backup_root="$selected_target/$backupdir"
|
|
fi
|
|
else
|
|
# use sane default
|
|
if [ -z "$backup_root" ]; then
|
|
backup_root="$selected_target"
|
|
fi
|
|
fi
|
|
fi
|
|
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 --config "$selected_config" list --type single | awk -F "|" '/subvolid='"$selected_subvol"'/, /uuid='"$selected_uuid"'/ {print $5}' | awk -F "," '/backupdir/ {print $1}' | awk -F"=" '{print $2}')
|
|
if [ -z "$backupdir" ]; then
|
|
backup_root="$selected_target"
|
|
else
|
|
backup_root="$selected_target/$backupdir"
|
|
fi
|
|
$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\n" "$remote" "$backup_root"
|
|
else
|
|
printf "Backup-Path: %s\n" "$backup_root"
|
|
fi
|
|
fi
|
|
|
|
# acting on source system
|
|
if [ ! $dryrun ]; then
|
|
#printf "Creating new snapshot with snapper config '%s' ...\n" "$selected_config" | tee $PIPE
|
|
printf "Creating new snapshot with snapper config '%s' ...\n" "$selected_config"
|
|
snapper_new_id=$(snapper --config "$selected_config" create --print-number -d "$snap_description_running")
|
|
snapper_new_snapshot=$SUBVOLUME/.snapshots/$snapper_new_id/snapshot
|
|
snapper_new_info=$SUBVOLUME/.snapshots/$snapper_new_id/info.xml
|
|
sync
|
|
else
|
|
#printf "dryrun: Creating new snapshot with snapper config '%s' ...\n" "$selected_config" | tee $PIPE
|
|
printf "dryrun: Creating new snapshot with snapper config '%s' ...\n" "$selected_config"
|
|
snapper_new_id="<new_snapper_id>"
|
|
fi
|
|
|
|
# if we want to use snapper on the target to supervise the synced snapshots
|
|
# the backup_location needs to be in a subvol ".snapshots" inside $selected_config (hardcoded in snapper)
|
|
snapper_target_config="snap-$selected_config"
|
|
snapper_target_snapshot=$backup_root/$snapper_target_config/.snapshots/$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" "$snapper_new_snapshot" "$remote":"$snapper_target_snapshot/snapshot" | tee $PIPE
|
|
fi
|
|
|
|
# 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_sync_snapshot_$i='$snapper_sync_snapshot'"
|
|
eval "snapper_config_$i='$selected_config'"
|
|
eval "snapper_target_config_$i='$snapper_target_config'"
|
|
eval "snapper_target_snapshot_$i='$snapper_target_snapshot'"
|
|
|
|
cont_backup="K"
|
|
eval "snapper_activate_$i=yes"
|
|
if [ $noconfirm ]; then
|
|
cont_backup="yes"
|
|
else
|
|
get_answer_yes_no "Continue with backup [Y/n]? " "yes"
|
|
if [ "$answer" = "no" ]; then
|
|
eval "snapper_activate_$i=no"
|
|
printf "Aborting backup for this configuration.\n"
|
|
snapper --config $selected_config delete $snapper_new_id
|
|
fi
|
|
fi
|
|
i=$(($i+1))
|
|
|
|
done
|
|
}
|
|
|
|
run_backup () {
|
|
# Actual backing up
|
|
printf "\nPerforming backups...\n" | tee $PIPE
|
|
|
|
i=-1
|
|
for selected_config in $selected_configs; do
|
|
|
|
i=$(($i+1))
|
|
|
|
SNAP_SYNC_EXCLUDE=no
|
|
|
|
if [ -f "/etc/snapper/configs/$selected_config" ]; then
|
|
. /etc/snapper/configs/$selected_config
|
|
else
|
|
die "Selected snapper configuration '$selected_config' does not exist."
|
|
fi
|
|
|
|
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 data for configuration '$selected_config'."
|
|
|
|
printf "\n"
|
|
|
|
# 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_config=$(eval echo \$snapper_target_config_$i)
|
|
snapper_target_snapshot=$(eval echo \$snapper_target_snapshot_$i)
|
|
|
|
verify_snapper_structure "backup_root=$backup_root" "snapper_target_config=$snapper_target_config" "snapper_new_id=$snapper_new_id"
|
|
|
|
snapper_target_snapshot_size=$(du --sum $snapper_new_snapshot 2>/dev/null | awk -F ' ' '{print $1}')
|
|
|
|
# settings for interactive progress status
|
|
if [ $do_pv_cmd ]; then
|
|
pv_options="--delay-start 2 --interval 5 --timer --rate --bytes --fineta --progress"
|
|
cmd_pv="pv $pv_options --size $snapper_target_snapshot_size |"
|
|
#cmd_pv="pv $pv_options --size $snapper_target_snapshot_size | dialog --gauge \"$progname: Progress for config '$selected_config'\" 6 85 |"
|
|
else
|
|
cmd_pv=''
|
|
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
|
|
printf "dryrun: %s\n" "$cmd"
|
|
fi
|
|
else
|
|
if [ "$verbose" ]; then verbose_flag="-v"; fi
|
|
printf "Sending incremental snapshot for snapper config '%s' ...\n" "$selected_config"
|
|
# checking if parent snapshot-id (as saved on source) is also available on target
|
|
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.
|
|
if [ ! "$dryrun" ]; then
|
|
if [ "$verbose" ]; then
|
|
printf "btrfs-send will use targets snapshot '%s' to sync metadata for %s ...\n" "$snapper_sync_snapshot" "$snapper_new_snapshot"
|
|
fi
|
|
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"
|
|
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
|
|
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
|
|
fi
|
|
fi
|
|
|
|
# finally: send the snapper info metadata
|
|
if [ -z "$ssh" ]; then
|
|
if [ ! "$dryrun" ]; then
|
|
cp "$snapper_new_info" "$snapper_target_snapshot"
|
|
else
|
|
cmd="cp $snapper_new_info $snapper_target_snapshot"
|
|
printf "dryrun: %s\n" "$cmd"
|
|
fi
|
|
else
|
|
if [ ! "$dryrun" ]; then
|
|
rsync -avzq "$snapper_new_info" "$remote":"$snapper_target_snapshot"
|
|
else
|
|
cmd="rsync -avzq $snapper_new_info $remote:$snapper_target_snapshot"
|
|
printf "dryrun: %s\n" "$cmd"
|
|
fi
|
|
fi
|
|
done
|
|
}
|
|
|
|
run_finalize () {
|
|
# Actual backing up
|
|
printf "\nFinalize backups...\n" | tee $PIPE
|
|
|
|
i=-1
|
|
for selected_config in $selected_configs; do
|
|
|
|
i=$(($i+1))
|
|
|
|
SNAP_SYNC_EXCLUDE=no
|
|
|
|
if [ -f "/etc/snapper/configs/$selected_config" ]; then
|
|
. /etc/snapper/configs/$selected_config
|
|
else
|
|
die "Selected snapper configuration '$selected_config' does not exist."
|
|
fi
|
|
|
|
cont_backup=$(eval echo \$snapper_activate_$i)
|
|
if [ "$cont_backup" = "no" ] || [ "$SNAP_SYNC_EXCLUDE" = "yes" ]; then
|
|
notify_info "Finalize backup" "NOTE: Skipping '$selected_config' configuration."
|
|
continue
|
|
fi
|
|
|
|
notify_info "Finalize backup" "Cleanup tasks for configuration '$selected_config'."
|
|
|
|
printf "\n"
|
|
|
|
# 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_config=$(eval echo \$snapper_target_config_$i)
|
|
snapper_target_snapshot=$(eval echo \$snapper_target_snapshot_$i)
|
|
|
|
# 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.
|
|
|
|
src_host=$(eval cat /etc/hostname)
|
|
src_uuid=$(eval findmnt --noheadings --output UUID $SUBVOLUME)
|
|
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_userdata="subvolid=$src_subvolid, uuid=$src_uuid, host=$src_host"
|
|
|
|
# Tag new snapshots key/value parameter
|
|
if [ ! "$dryrun" ]; then
|
|
# source snapshot
|
|
if [ "$verbose" ]; then
|
|
printf "Tagging snapper metadata on source for configuration '%s' ...\n" "$selected_config" | tee $PIPE
|
|
fi
|
|
cmd="snapper --verbose --config \"$selected_config\" modify --description \"$snap_description_synced\" --userdata \"$userdata\" \"$snapper_new_id\""
|
|
ret=$(eval $cmd)
|
|
#printf "return: '%s'\n" "$ret"
|
|
sync
|
|
|
|
# target snapshot
|
|
if [ "$verbose" ]; then
|
|
printf "Tagging snapper metadata on target for configuration '%s' ...\n" "$selected_config" | tee $PIPE
|
|
fi
|
|
i=1
|
|
cmd="snapper --verbose --config \"$snapper_target_config\" list --type single | awk '/'\"$snap_description_running\"'/' | awk -F '|' '\$1 == "$snapper_new_id" {print \$1}'"
|
|
# !!! ugly hack !!!: wait for snapper to list target snapshot in database. how to trigger database resync?
|
|
# it is not deterministic, when the entry in the listing will show up .... for now, wait max 10 min ...
|
|
while [ "$i" -le 20 ]; do
|
|
if [ -n "$ssh" ]; then
|
|
ret=$($ssh $cmd)
|
|
else
|
|
ret=$(eval $cmd)
|
|
fi
|
|
#printf "return: '%s'\n" "$ret"
|
|
if [ -n "$ret" ]; then
|
|
if [ "$ret" -eq "$snapper_new_id" ]; then
|
|
cmd="snapper --verbose --config \"$snapper_target_config\" modify --description \"$snap_description_finished\" --userdata \"$target_userdata\" \"$snapper_new_id\""
|
|
if [ -n "$ssh" ]; then
|
|
ret=$($ssh "$cmd")
|
|
else
|
|
ret=$(eval $cmd)
|
|
fi
|
|
#printf "return: '%s'\n" "$ret"
|
|
break
|
|
fi
|
|
fi
|
|
if [ "$verbose" ]; then
|
|
printf "Waiting for snappers database update on target ...\n"
|
|
fi
|
|
sleep 30
|
|
i=$(($i + 1))
|
|
done
|
|
else
|
|
cmd="snapper --verbose --config $selected_config modify --description $snap_description_synced --userdata $userdata $snapper_new_id"
|
|
printf "dryrun: %s\n" "$cmd"
|
|
cmd="$ssh snapper --verbose --config $snapper_target_config modify -description $snap_description_finished --userdata $target_userdata $snapper_new_id"
|
|
printf "dryrun: %s\n" "$cmd"
|
|
fi
|
|
|
|
# Cleanup synced source snapshots
|
|
if [ -n "$snapper_sync_id" ]; then
|
|
cmd="snapper --verbose --config \"$snapper_config\" modify --description \"$snap_description_finished\" --cleanup timeline \"$snapper_sync_id\""
|
|
ret=$(eval $cmd)
|
|
#printf "return: '%s'\n" "$ret"
|
|
sync
|
|
fi
|
|
|
|
printf "Backup complete for snapper configuration '%s'.\n" "$selected_config" > $PIPE
|
|
|
|
done
|
|
}
|
|
|
|
select_target_disk () {
|
|
local i=0
|
|
local disk_id=0
|
|
|
|
#local disk_selected_ids=''
|
|
local disk_selected_count=0
|
|
local subvolid=''
|
|
local subvol=''
|
|
|
|
# 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_subvolid_match_count" -eq 1 ]; then
|
|
# matching SUBVOLID selection from commandline
|
|
# Pseudo-Array: disk_selected_$i (reference to $disk_uuid element)
|
|
eval "disk_selected_$i='$disk_subvolid_match'"
|
|
disk=$(eval echo \$disk_uuid_$disk_subvolid_match)
|
|
subvolid=$(eval echo \$disk_subvolid_$disk_subvolid_match)
|
|
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
|
|
break
|
|
fi
|
|
if [ "$disk_target_match_count" -eq 1 ]; then
|
|
# matching TARGET selection from commandline
|
|
# Pseudo-Array: disk_selected_$i (reference to $disk_uuid element)
|
|
eval "disk_selected_$i='$disk_target_match'"
|
|
disk=$(eval echo \$disk_uuid_$disk_target_match)
|
|
target=$(eval echo \$disk_target_$disk_target_match)
|
|
fs_options=$(eval echo \$fs_options_$disk_target_match | sed -e 's/.*,\(subvolid=[0-9]*\).*,\(subvol=[0-9]*\)/\1,\2/')
|
|
disk_selected=$disk_target_match
|
|
break
|
|
fi
|
|
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)
|
|
fs_options=$(eval echo \$fs_options_$disk_uuid | sed -e 's/.*,\(subvolid=[0-9]*\).*,\(subvol=[0-9]*\)/\1,\2/')
|
|
printf "%4s) %s (uuid=%s,%s)\n" "$i" "$target" "$disk" "$fs_options"
|
|
i=$((i+1))
|
|
done
|
|
else
|
|
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)
|
|
fs_options=$(eval echo \$fs_options_$disk_id | sed -e 's/.*,\(subvolid=[0-9]*\).*,\(subvol=[0-9]*\)/\1,\2/')
|
|
printf "%4s) %s (uuid=%s,%s)\n" "$i" "$target" "$disk" "$fs_options"
|
|
i=$((i+1))
|
|
disk_id=$(($disk_id+1))
|
|
done
|
|
fi
|
|
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_id=0
|
|
i=0
|
|
;;
|
|
esac
|
|
done
|
|
if [ "$disk_selected" = x ]; then
|
|
exit 0
|
|
fi
|
|
|
|
selected_uuid=$(eval echo \$disk_uuid_$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/')
|
|
if [ "$verbose" ]; then
|
|
printf "Selected Subvol-ID=%s: %s on %s\n" "$selected_subvol" "$selected_target" "$selected_uuid"
|
|
fi
|
|
|
|
}
|
|
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 name: %s\n" "$5"
|
|
exit 1
|
|
}
|
|
|
|
trapkill () {
|
|
die "Exited due to user intervention."
|
|
}
|
|
|
|
usage () {
|
|
cat <<EOF
|
|
$progname $version
|
|
Usage: $progname [options]
|
|
|
|
Options:
|
|
-d, --description <desc> Change the snapper description. Default: "latest incremental backup"
|
|
-c, --config <config> Specify the snapper configuration to use. Otherwise will perform for each snapper
|
|
configuration. Can list multiple configurations within quotes, space-separated
|
|
(e.g. -c "root home").
|
|
-n, --noconfirm Do not ask for confirmation for each configuration. Will still prompt for 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."
|
|
If multiple mount points are found with the same UUID, will prompt user."
|
|
-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
|
|
should specify the remote machine's hostname or ip address. The 'root' user must be
|
|
permitted to login on the remote machine.
|
|
--dry-run perform a trial run where no changes are made.
|
|
-v, --verbose Be more verbose on what's going on.
|
|
|
|
EOF
|
|
|
|
exit 1
|
|
}
|
|
|
|
verify_snapper_structure () {
|
|
local backup_root=${1##backup_root=}
|
|
local snapper_config=${2##snapper_target_config=}
|
|
local snapper_id=${3##snapper_new_id=}
|
|
|
|
if [ "$verbose" ]; then
|
|
printf "Verify snapper filesystem structure on target ...\n"
|
|
fi
|
|
|
|
# if not accessible, create backup-path
|
|
if $ssh [ ! -d $backup_root ]; then
|
|
if [ ! "$dryrun" ]; then
|
|
if [ "$verbose" ]; then
|
|
printf "Create backup-path %s ...\n" "$backup_root"
|
|
fi
|
|
$ssh mkdir --mode=0700 --parents $backup_root
|
|
else
|
|
printf "dryrun: Would create backup-path $backup_root %s ...\n" "$backup_root"
|
|
fi
|
|
fi
|
|
|
|
# verify that we have a snapper compatible structure for selected config on target
|
|
if $ssh [ ! -d $backup_root/$snapper_config ]; then
|
|
if $ssh [ ! -f $SNAPPER_TEMPLATES/snap-sync ]; then
|
|
printf "A snapper template %s to configure the snapper subvolume %s is missing in %s.\n" "snap-sync" "$snapper_config" "$SNAPPER_TEMPLATES"
|
|
printf "Did you miss to install the snap-sync's default snapper template?\n"
|
|
die "snapper template %s to configure the snapper subvolume %s is missing in %s.\n" "snap-sync" "$snapper_config" "$SNAPPER_TEMPLATES"
|
|
fi
|
|
if [ ! "$dryrun" ]; then
|
|
if [ "$verbose" ]; then
|
|
printf "Create new snapper capable subvolume in '%s' ...\n" "$backup_root/$snapper_config"
|
|
fi
|
|
# TODO: test if there is any old snapper config
|
|
create_config="btrfs subvolume create $backup_root/$snapper_config"
|
|
$ssh $create_config || die "Snapper structure for config %s to hold target snapshots could not be created in directory on %s.\n" "$snapper_config" "$backup_root"
|
|
# snapper-logic will create $backup_root/$snapper_config/.snapshots
|
|
$ssh snapper --config $snapper_config create-config --template snap-sync $backup_root/$snapper_config
|
|
$ssh chmod 0700 $backup_root/$snapper_config
|
|
sync
|
|
else
|
|
printf "dryrun: Would create new snapper structure in '%s' ...\n" "$backup_root/$snapper_config"
|
|
fi
|
|
create_subvol="btrfs subvolume create $backup_root/$snapper_subvol"
|
|
$ssh $create_subvol || die "BTRFS subvolume %s to hold snapshots for config %s could not be created in directory on %s.\n" "$snapper_subvol" "$snapper_config" "$backup_root"
|
|
$ssh snapper --config $snapper_subvol create-config --template snap-sync $backup_root/$snapper_subvol
|
|
$ssh chmod 0700 $backup_root/$snapper_subvol
|
|
else
|
|
cmd="$ssh stat --format=%i $backup_root/$snapper_config"
|
|
if [ $(eval $cmd) -ne 256 ]; then
|
|
die "%s needs to be a BTRFS subvolume. But given %s is just a directory.\n" "$snapper_config" "$backup_root/$snapper_config"
|
|
fi
|
|
# test if there is any restover/old snapper config
|
|
if $ssh [ ! -d $backup_root/$snapper_config/$snapper_snapshots ]; then
|
|
cmd="btrfs subvolume create $backup_root/$snapper_config/$snapper_snapshots"
|
|
$ssh $cmd || die "Can't create subvolume %s in %s to hold target snapshots.\n" "$snapper_snapshots" "$backup_root/$snapper_config"
|
|
fi
|
|
fi
|
|
# verify that target snapshot id can take the new snapshot data
|
|
if [ ! "$dryrun" ]; then
|
|
if $ssh [ ! -d $backup_root/$snapper_config/$snapper_snapshots/$snapper_id ]; then
|
|
if [ "$verbose" ]; then
|
|
printf "Create path %s to store target snapshot.\n" "$backup_root/$snapper_config/$snapper_snapshots/$snapper_id"
|
|
fi
|
|
$ssh mkdir --mode=0700 $backup_root/$snapper_config/$snapper_snapshots/$snapper_id
|
|
else
|
|
if [ "$verbose" ]; then
|
|
printf "Snapshot %s already in use on target %s.\n" "$snapper_id" "$backup_root/$snapper_config/$snapper_snapshots"
|
|
fi
|
|
printf "Cancel Snapshot creation: Former snapshot with id '%s' already exist in '%s'\n" "\
|
|
$snapper_id" "$backup_root/$snapper_config/$snapper_snapshots"
|
|
# cleanup generated snapper entry
|
|
check_snapper_failed_ids
|
|
die "Can't create new snapshot with given snapshot-id!"
|
|
return=1
|
|
fi
|
|
else
|
|
printf "dryrun: Would check/create path %s to store target snapshot ...\n" \
|
|
"$backup_root/$snapper_config/$snapper_snapshots/$snapper_id"
|
|
fi
|
|
}
|
|
|
|
###
|
|
# Main
|
|
###
|
|
|
|
cwd=`pwd`
|
|
ssh=""
|
|
|
|
# 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} $?' ERR
|
|
trap trapkill TERM INT
|
|
|
|
parse_params $@
|
|
|
|
check_prerequisites
|
|
|
|
# read mounted BTRFS structures
|
|
get_disk_infos
|
|
|
|
if [ "$target_cmdline" != "none" ]; then
|
|
if [ -z "$ssh" ]; then
|
|
notify_info "Backup started" "Starting backups to '$target_cmdline' ..."
|
|
else
|
|
notify_info "Backup started" "Starting backups to '$target_cmdline' at $remote ..."
|
|
fi
|
|
elif [ "$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
|
|
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
|
|
|
|
# select the target BTRFS subvol
|
|
select_target_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_target" | tee $PIPE
|
|
else
|
|
printf "The disk is mounted at %s:%s.\n" "$remote" "$selected_target" | tee $PIPE
|
|
fi
|
|
|
|
# create and initialize structures for snapper configs
|
|
run_config
|
|
|
|
# run backups using btrfs-send -> btrfs-receive
|
|
run_backup
|
|
|
|
# finalize backup tasks
|
|
run_finalize
|
|
|
|
printf "\nDone!\n" | tee $PIPE
|
|
exec 3>&-
|
|
|
|
if [ "$uuid_cmdline" != "none" ]; then
|
|
notify_info "Finished" "Backups to $uuid_cmdline complete!"
|
|
else
|
|
notify_info "Finished" "Backups complete!"
|
|
fi
|