3092 lines
107 KiB
Bash
Executable File
3092 lines
107 KiB
Bash
Executable File
#! /bin/dash
|
|
|
|
# dsnap-sync
|
|
# https://github.com/rzerres/dsnap-sync
|
|
# Copyright (C) 2016, 2017 James W. Barnett
|
|
# Copyright (C) 2017, 2018 Ralf Zerres
|
|
|
|
# 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.6.3"
|
|
|
|
# The following lines are modified by the Makefile or
|
|
# find_snapper_config script
|
|
#SNAPPER_CONFIG=/etc/conf.d/snapper
|
|
SNAPPER_CONFIG=/etc/default/snapper
|
|
SNAPPER_TEMPLATE_DIR=/etc/snapper/config-templates
|
|
SNAPPER_CONFIG_DIR=/etc/snapper/configs
|
|
|
|
# Create TEMPDIR
|
|
test ! -d $XDG_RUNTIME_DIR/$progname && mkdir -p $XDG_RUNTIME_DIR/$progname
|
|
TMPDIR=$(mktemp --tmpdir=$XDG_RUNTIME_DIR/$progname -d)
|
|
|
|
# define fifo pipes
|
|
#PIPE=$TMPDIR/$progname.out
|
|
#mkfifo $PIPE
|
|
#systemd-cat --identifier="$progname" < $PIPE &
|
|
|
|
BTRFS_PIPE=$TMPDIR/btrfs.fifo
|
|
test -p $BTRFS_PIPE && mkfifo $BTRFS_PIPE
|
|
|
|
# redirect descriptors to given pipes
|
|
#exec 3>$PIPE
|
|
4>$BTRFS_PIPE
|
|
|
|
# global variables
|
|
args=
|
|
answer=no
|
|
archive_type=full
|
|
batch=0
|
|
btrfs_quota=0
|
|
#btrfs_verbose_flag=--verbose
|
|
btrfs_verbose_flag=
|
|
color=0
|
|
donotify=0
|
|
dryrun=0
|
|
#disk_count=-1
|
|
disk_uuid_match_count=0
|
|
#disk_target_match_count=0
|
|
disk_subvolid_match_count=0
|
|
disk_uuid_match=''
|
|
error_count=0
|
|
ltfs_mountpoint="/media/tape"
|
|
target_count=0
|
|
target_match_count=0
|
|
tape_match=''
|
|
interactive=1
|
|
selected_configs=0
|
|
selected_subvol='none'
|
|
selected_target='none'
|
|
selected_uuid='none'
|
|
snapper_sync_id=0
|
|
snapper_snapshots=".snapshots" # hardcoded in snapper
|
|
snapper_snapshot_name="snapshot" # hardcoded in snapper
|
|
snapper_subvolume_template="dsnap-sync"
|
|
snapper_backup_type='none'
|
|
#snapper_config_postfix="."`hostname`
|
|
snapper_config_postfix=
|
|
snap_cleanup_algorithm="timeline"
|
|
verbose=0
|
|
volume_name=
|
|
|
|
# ascii color
|
|
BLUE=
|
|
GREEN=
|
|
MAGENTA=
|
|
RED=
|
|
YELLOW=
|
|
NO_COLOR=
|
|
|
|
###
|
|
# 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 ssh >/dev/null 2>&1 || { printf "'ssh' is not installed." && exit 1; }
|
|
which scp >/dev/null 2>&1 || { printf "'scp' 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 snapper >/dev/null 2>&1 || { printf "'snapper' is not installed." && exit 1; }
|
|
|
|
# optional binaries:
|
|
which attr >/dev/null 2>&1 || { printf "'attr' is not installed." && exit 1; }
|
|
which ionice >/dev/null 2>&1 && { do_ionice_cmd=1; }
|
|
which notify-send >/dev/null 2>&1 && { donotify=1; }
|
|
which pv >/dev/null 2>&1 && { do_pv_cmd=1; }
|
|
which tape-admin >/dev/null 2>&1 && { tape_admin_cmd=1; }
|
|
|
|
if [ $(id -u) -ne 0 ] ; then printf "$progname: must be run as root\n" ; exit 1 ; fi
|
|
|
|
if [ -n "$remote" ]; then
|
|
$ssh which sh >/dev/null 2>&1 || \
|
|
{ printf "'remote shell' is not working!\n \
|
|
Please correct your public authentication and try again.\n" && exit 1; }
|
|
fi
|
|
|
|
if [ ! -r "$SNAPPER_CONFIG" ]; then
|
|
die "$progname: $SNAPPER_CONFIG does not exist."
|
|
fi
|
|
}
|
|
|
|
check_snapper_failed_ids () {
|
|
local selected_config=${1}
|
|
local batch=${2:-0}
|
|
|
|
if [ $verbose -ge 3 ]; then
|
|
printf "${MAGENTA}check_snapper_failed_ids()...${NO_COLOR}\n"
|
|
fi
|
|
# active, non finished snapshot backups are marked with a reasonable string
|
|
# default: $(snap_description_running -> "$progname backup in progress" (userdata: host=$source)
|
|
snapper_failed_ids=$(snapper --config $selected_config list --type single \
|
|
| awk '/'"$snap_description_running"'/ {cnt++} END {print cnt}')
|
|
#snapper_failed_ids="snapper --config $selected_config list --type single \
|
|
# | awk '/'"$snap_description_running"'/' \
|
|
# | awk ' /'host='"$remote"'/ {cnt++} END {print cnt}'"
|
|
|
|
if [ ${#snapper_failed_ids} -gt 0 ]; then
|
|
if [ "$batch" ]; then
|
|
answer="yes"
|
|
else
|
|
printf "${MAGENTA}Found %s previous failed sync runs for '%s'${NO_COLOR}\n" \
|
|
"${snapper_failed_ids}" "$selected_config"
|
|
answer=no
|
|
get_answer_yes_no "Delete failed backup snapshots [y/N]? " "$answer"
|
|
fi
|
|
if [ "$answer" = "yes" ]; then
|
|
failed_id_first=$(snapper --config "$selected_config" list --type single \
|
|
| awk -F '|' ' /'"$snap_description_running"'/' \
|
|
| awk ' NR==1 {print $1} ')
|
|
failed_id_last=$(snapper --config "$selected_config" list --type single \
|
|
| awk -F '|' ' /'"$snap_description_running"'/' \
|
|
| awk ' END {print $1} ')
|
|
cmd="snapper --config $selected_config delete"
|
|
if [ $failed_id_first -lt $failed_id_last ]; then
|
|
$(eval $cmd $failed_id_first-$failed_id_last)
|
|
else
|
|
$(eval $cmd $failed_id_first)
|
|
fi
|
|
fi
|
|
fi
|
|
}
|
|
|
|
create_snapshot () {
|
|
if [ $verbose -ge 3 ]; then
|
|
printf "${MAGENTA}create_snapshot()...${NO_COLOR}\n" $snapper_config
|
|
fi
|
|
|
|
# acting on source system
|
|
if [ $dryrun -eq 0 ]; then
|
|
#printf "Creating new snapshot with snapper config '%s' ...\n" "$selected_config" | tee $PIPE
|
|
if [ $verbose -ge 3 ]; then
|
|
printf "${MAGENTA}Create new snapshot using snapper config ${GREEN}'%s'${MAGENTA}...${NO_COLOR}\n" \
|
|
"$selected_config"
|
|
fi
|
|
if [ -z $remote ]; then
|
|
snapper_source_id=$(snapper --config "$selected_config" create \
|
|
--print-number \
|
|
--description "$snap_description_running" \
|
|
--userdata "host=$HOSTNAME")
|
|
else
|
|
snapper_source_id=$(snapper --config "$selected_config" create \
|
|
--print-number \
|
|
--description "$snap_description_running" \
|
|
--userdata "host=$remote")
|
|
fi
|
|
if [ $SUBVOLUME = "/" ]; then
|
|
SUBVOLUME=""
|
|
fi
|
|
snapper_source_snapshot=$SUBVOLUME/.snapshots/$snapper_source_id/$snapper_snapshot_name
|
|
snapper_source_info=$SUBVOLUME/.snapshots/$snapper_source_id/info.xml
|
|
|
|
#btrfs quota enable $snapper_source_snapshot
|
|
sync
|
|
|
|
if [ $verbose -ge 3 ]; then
|
|
printf "${MAGENTA}Snapper source snapshot ${GREEN}'%s'${MAGENTA} created${NO_COLOR}\n" "$snapper_source_id"
|
|
fi
|
|
else
|
|
printf "${MAGENTA}dryrun${NO_COLOR}: Would create source snapshot with snapper config '%s' ...\n" "$selected_config"
|
|
snapper_source_sync_id="<snapper_source_id>"
|
|
fi
|
|
}
|
|
|
|
die () {
|
|
error "$@"
|
|
exit 1
|
|
}
|
|
|
|
error () {
|
|
printf "\n==> ERROR: %s\n" "$@"
|
|
notify_error 'Error' 'Check journal for more information.'
|
|
} >&2
|
|
|
|
get_answer_yes_no () {
|
|
local message="${1:-'Do you want to proceed [y/N]? '}"
|
|
local i="none"
|
|
|
|
# hack: answer is a global variable, using it for preselection
|
|
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
|
|
;;
|
|
*)
|
|
if [ -n "$answer" ]; then
|
|
i="$answer"
|
|
else
|
|
i="none"
|
|
printf "Select 'y' or 'n'.\n"
|
|
fi
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
get_archive_last_sync_id () {
|
|
local snapper_config=${1#snapper_config=}
|
|
local archive_type=${2##archive_type=}
|
|
local remote=${3##remote=}
|
|
local run_ssh=''
|
|
|
|
snapper_sync_id=0
|
|
if [ ${#remote} -ge 1 ]; then run_ssh=$ssh; fi
|
|
|
|
if [ $verbose -ge 3 ]; then
|
|
printf "${MAGENTA}get_archive_last_sync_id()...${NO_COLOR}\n"
|
|
fi
|
|
|
|
if [ $verbose -ge 3 ]; then
|
|
if [ $remote ]; then
|
|
printf "${MAGENTA}Get sync-ID of ${GREEN}last '%s' btrfs-stream${MAGENTA} for snapper config ${GREEN}'%s'${MAGENTA} on remote ${GREEN}'%s'${NO_COLOR}\n" \
|
|
$archive_type $snapper_config $remote
|
|
else
|
|
printf "${MAGENTA}Get sync-ID of ${GREEN}last '%s' btrfs-stream${MAGENTA} for snapper config ${GREEN}'%s'${NO_COLOR}\n" \
|
|
$archive_type $snapper_config
|
|
fi
|
|
fi
|
|
|
|
# target is LTFS tape
|
|
if [ ${#volume_name} -gt 1 ]; then
|
|
case ${archive_type} in
|
|
#incremental)
|
|
# cmd="find ${selected_target}/${backupdir}/${snapper_target_config} -name *_${archive_type}.btrfs \
|
|
# | awk -F '.*/' '{ gsub(/_${archive_type}.btrfs\$/,"_"); print \$2}' \
|
|
# | awk ' \$1 == $snapper_source_sync_id {print \$1} ' "
|
|
# ;;
|
|
*)
|
|
cmd="find ${selected_target}/${backupdir}/${snapper_target_config} -name *_${archive_type}.btrfs \
|
|
| awk -F '.*/' '{ gsub(/_${archive_type}.btrfs\$/,"_"); cnt++} END {print cnt}'"
|
|
ret=$(eval $run_ssh "$cmd" 2>/dev/null)
|
|
if [ ${#ret} -ge 1 ]; then
|
|
if [ $verbose -ge 3 ]; then
|
|
printf "${MAGENTA}Found ${GREEN}'%s'${MAGENTA} previous synced archives for config ${GREEN}'%s'${NO_COLOR}\n" \
|
|
"${ret}" "$selected_config"
|
|
fi
|
|
# get last sync-id
|
|
cmd="find ${selected_target}/${backupdir}/${snapper_target_config} -name *_${archive_type}.btrfs \
|
|
| awk -F '.*/' '{ gsub(/_${archive_type}.btrfs\$/,"_") } END {print \$2}'"
|
|
else
|
|
return 1
|
|
fi
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
ret=$(eval $run_ssh "$cmd" 2>/dev/null)
|
|
if [ ${#ret} -ge 1 ]; then
|
|
# ok, matching snapshot found
|
|
snapper_sync_id=$ret
|
|
if [ $verbose -ge 3 ]; then
|
|
printf "Got archive snapshot: ${GREEN}'%s'${NO_COLOR} (id: ${GREEN}'%s'${NO_COLOR})\n" \
|
|
$snapper_target_config $snapper_sync_id
|
|
fi
|
|
return 0
|
|
else
|
|
# no snapshot found
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
get_backupdir () {
|
|
local backup_dir=$1
|
|
|
|
if [ $verbose -ge 3 ]; then
|
|
printf "${MAGENTA}get_backupdir()...${NO_COLOR}\n" $snapper_config
|
|
fi
|
|
|
|
backupdir=$backup_dir
|
|
if [ ! $batch ]; then
|
|
answer=yes
|
|
if [ -z "$backupdir" ]; then
|
|
get_answer_yes_no "Keep empty backupdir [Y/n]? " "$answer"
|
|
else
|
|
get_answer_yes_no "Keep backupdir '$backupdir' [Y/n]? " "$answer"
|
|
fi
|
|
if [ "$answer" = "no" ]; then
|
|
read -r -p "Enter name of directory to store backups, relative to $selected_target (to be created if not existing): " backupdir
|
|
fi
|
|
fi
|
|
|
|
if [ $verbose -ge 2 ] && [ -n "$backupdir" ]; then
|
|
printf "${MAGENTA}Backup-Dir is ${GREEN}'%s'${NO_COLOR}\n" \
|
|
"$backupdir"
|
|
fi
|
|
}
|
|
|
|
get_media_infos () {
|
|
# select the target LTFS tape
|
|
if [ ${#mediapool_name} -gt 1 ] || [ ${#volume_name} -gt 1 ]; then
|
|
# read mounted LTFS structures
|
|
if [ -z $remote ]; then
|
|
tape-admin --verbose=$verbose --mount ${mediapool_name} ${volume_name}
|
|
else
|
|
$ssh tape-admin --verbose=$verbose --mount ${mediapool_name} ${volume_name}
|
|
fi
|
|
if [ $? -eq 0 ]; then
|
|
target_cmdline=$ltfs_mountpoint
|
|
if [ ${#volume_name} -eq 0 ]; then
|
|
# parse out volume-name (fuse - extended attribute)
|
|
volume_name=$($ssh attr -g ltfs.volumeName $ltfs_mountpoint)
|
|
volume_name=$(echo ${volume_name##*:} | sed -e 's/\r\n//g')
|
|
fi
|
|
get_tape_infos
|
|
if [ $? -ne 0 ]; then
|
|
printf "${RED}Error: ${NO_COLOR}Can't use valid volume from MediaPool ${GREEN}'%s'${NO_COLOR}\n" \
|
|
"$mediapool_name"
|
|
die "Can't use valid tape."
|
|
fi
|
|
else
|
|
if [ ${#volume_name} -gt 0 ]; then
|
|
printf "${RED}Error: ${NO_COLOR}Can't use volume ${GREEN}'%s'${NO_COLOR} from MediaPool ${GREEN}'%s'${NO_COLOR}\n" \
|
|
"$volume_name" "$mediapool_name"
|
|
else
|
|
printf "${RED}Error: ${NO_COLOR}Can't use valid volume from MediaPool ${GREEN}'%s'${NO_COLOR}\n" \
|
|
"$mediapool_name"
|
|
fi
|
|
die "Can't use valid tape."
|
|
fi
|
|
else
|
|
# read mounted BTRFS structures
|
|
get_disk_infos
|
|
fi
|
|
}
|
|
|
|
get_snapper_backup_type () {
|
|
local snapper_config=$1
|
|
local snapper_config_type='none'
|
|
local key
|
|
local value
|
|
|
|
# Snapshot types: btrfs-snapshot, btrfs-clone, btrfs-archive
|
|
# snapshot: 1st stage: CHILD_CONFIG="false" or missing
|
|
# clone: 2nd stage: CHILD_CONFIG="true" PARENT_CONFIG="<snapper config name>"
|
|
# archive: 3nd stage: CHILD_CONFIG="true" PARENT_CONFIG="<child snapper config name>"
|
|
# parse selected_config and return with $snapper_target_config set appropriately
|
|
|
|
if [ $verbose -ge 3 ]; then
|
|
printf "${MAGENTA}get_snapper_backup_type()...${NO_COLOR}\n"
|
|
fi
|
|
|
|
if [ ${#backuptype_cmdline} -gt 1 ]; then
|
|
snapper_backup_type=${backuptype_cmdline}
|
|
else
|
|
if [ $verbose -ge 2 ]; then
|
|
printf "${MAGENTA}Get snapper backup type for config ${GREEN}'%s'${MAGENTA}...${NO_COLOR}\n" $snapper_config
|
|
fi
|
|
|
|
# WIP -> create a PR for snapper
|
|
# Patch snapper to parse for new pairs: CONFIG_TYPE="child | root"; CONFIG_PARENT="<string>"
|
|
##config_type=$(snapper --config $1 get-config | awk '/'"CONFIG_PARENT=$snap_description_running"'/ {cnt++} END {print cnt}')
|
|
# for now, we cut an parse ourself
|
|
IFS="="
|
|
while read -r key value
|
|
do
|
|
case $key in
|
|
CONFIG_TYPE)
|
|
snapper_backup_type=$(echo $value | sed -e 's/\"\(.*\)\"/\1/')
|
|
continue
|
|
;;
|
|
CHILD_CONFIG)
|
|
snapper_target_config=$(echo $value | sed -e 's/\"\(.*\)\"/\1/')
|
|
continue
|
|
;;
|
|
PARENT_CONFIG)
|
|
snapper_parent_config=$(echo $value | sed -e 's/\"\(.*\)\"/\1/')
|
|
continue
|
|
;;
|
|
*)
|
|
# value is not relevant
|
|
continue
|
|
;;
|
|
esac
|
|
done < $SNAPPER_CONFIG_DIR/$snapper_config
|
|
fi
|
|
|
|
case $snapper_backup_type in
|
|
archive|Archive)
|
|
# archive btrfs-snapshot to non btrfs-filesystem
|
|
snapper_backup_type=btrfs-archive
|
|
snapper_target_config=archive-$snapper_config
|
|
;;
|
|
child|Child)
|
|
# clone to btrfs-snapshot
|
|
snapper_backup_type=btrfs-clone
|
|
snapper_target_config=clone-$snapper_config
|
|
;;
|
|
parent|Parent)
|
|
# disk2disk btrfs-snapshot
|
|
snapper_backup_type=btrfs-snapshot
|
|
snapper_target_config=$snapper_config
|
|
;;
|
|
*)
|
|
# disk2disk btrfs-snapshot (default)
|
|
snapper_backup_type=btrfs-snapshot
|
|
snapper_target_config=$snapper_config
|
|
;;
|
|
esac
|
|
|
|
if [ -n $snapper_config_postfix ]; then
|
|
snapper_target_config=${snapper_target_config}${snapper_config_postfix}
|
|
fi
|
|
|
|
if [ $verbose -ge 2 ]; then
|
|
printf "Snapper backup type: '%s'\n" $snapper_backup_type
|
|
printf "Snapper target configuration: '%s'\n" $snapper_target_config
|
|
printf "Snapper parent configuration: '%s'\n" $snapper_parent_config
|
|
fi
|
|
}
|
|
|
|
get_snapper_config_value () {
|
|
local remote=${1##remote=}
|
|
local config_file=${2##snapper_config=}
|
|
local config_key=${3##config_key=}
|
|
local run_ssh=''
|
|
|
|
if [ $verbose -ge 3 ]; then
|
|
printf "${MAGENTA}get_snapper_config_value()...${NO_COLOR}\n"
|
|
fi
|
|
|
|
[ ${#remote} -gt 0 ] && run_ssh=$ssh
|
|
|
|
value=$(eval $run_ssh cat $config_file \
|
|
| awk '/'"$config_key"'/' \
|
|
| awk -F "=" '{ gsub("\"",""); print $2}')
|
|
if [ $verbose -ge 3 ]; then
|
|
printf "Snapper ${GREEN}config file${NO_COLOR}: '%s'\n" \
|
|
"$config_file"
|
|
printf "Snapper key ${GREEN}'%s'${NO_COLOR}: '%s'\n" \
|
|
"$config_key" "$value"
|
|
fi
|
|
}
|
|
|
|
get_snapper_target_backupdir () {
|
|
local backupdir_cmdline=$1
|
|
|
|
if [ $verbose -ge 3 ]; then
|
|
printf "${MAGENTA}get_snapper_target_backupdir()...${NO_COLOR}\n" $snapper_config
|
|
fi
|
|
|
|
if [ $snapper_target_sync_id -gt 0 ]; then
|
|
# get metadata for backupdir from sync snapshot
|
|
backupdir=$(snapper --config "$selected_config" list --type single \
|
|
| awk -F "|" '/'"$snap_description_synced"'/' \
|
|
| awk -F "|" '/subvolid='"$selected_subvol"'/, /uuid='"$selected_uuid"'/ {print $5}' \
|
|
| awk -F "," '/backupdir/ {print $1}' \
|
|
| awk -F"=" '{print $2}')
|
|
|
|
# if not found, get metadata for backupdir from finished snapshot
|
|
if [ -z "$backupdir" ]; then
|
|
backupdir=$(snapper --config "$selected_config" list --type single \
|
|
| awk -F "|" '/'"$snap_description_finished"'/' \
|
|
| awk -F "|" '/subvolid='"$selected_subvol"'/, /uuid='"$selected_uuid"'/ {print $5}' \
|
|
| awk -F "," '/backupdir/ {print $1}' \
|
|
| awk -F"=" 'END {print $2}')
|
|
fi
|
|
|
|
if [ "$backupdir_cmdline" != 'none' ] && [ "$backupdir_cmdline" != "$backupdir" ] \
|
|
&& [ "${#backupdir}" -gt 0 ] ; then
|
|
if [ $verbose -ge 2 ]; then
|
|
# WIP: we need to adapt existing target config SUBVOLUME to reflect the new backupdir
|
|
printf "${RED}TODO: ${MAGENTA}Reset backupdir ${GREEN}'%s'${MAGENTA} as requested per commandline.${NO_COLOR}\n" \
|
|
"$backupdir"
|
|
printf "${RED}TODO: ${NO_COLOR}Need to adapt ${GREEN}SUBVOLUME${NO_COLOR} for existing ${GREEN}target-config${NO_COLOR}, to reflect the new backupdir.\n"
|
|
fi
|
|
printf "${RED}Error: ${MAGENTA}Changing the backupdir for an already existing target-config is not supported yet${NO_COLOR}\n"
|
|
return 1
|
|
elif [ "$backupdir_cmdline" != 'none' ] && [ -z "$backupdir" ] ; then
|
|
backupdir=$backupdir_cmdline
|
|
fi
|
|
fi
|
|
}
|
|
|
|
get_disk_infos () {
|
|
local disk_uuid
|
|
local disk_target
|
|
local fs_option
|
|
|
|
if [ $verbose -ge 3 ]; then
|
|
printf "${BLUE}get_disk_infos()...${NO_COLOR}\n"
|
|
fi
|
|
|
|
# wakeup automounter units
|
|
if [ ${#automount_path} -gt 0 ]; then
|
|
if [ $verbose -ge 3 ]; then
|
|
printf "${MAGENTA}Mount automounter unit ${GREEN}'%s'${MAGENTA}...${NO_COLOR}\n" \
|
|
"$automount_path"
|
|
fi
|
|
if [ $remote ]; then
|
|
ret=$(systemctl --host=$remote status *.automount \
|
|
| awk -F ': ' '$0 ~ pattern { print $2 }' \
|
|
pattern="Where: $automount_path")
|
|
else
|
|
ret=$(systemctl status *.automount \
|
|
| awk -F ': ' '$0 ~ pattern { print $2 }' \
|
|
pattern="Where: $automount_path")
|
|
fi
|
|
if [ ${#ret} -gt 0 ]; then
|
|
ret=$($ssh stat --file-system --terse $ret)
|
|
fi
|
|
fi
|
|
|
|
# get mounted BTRFS infos
|
|
if [ "$($ssh findmnt --noheadings --nofsroot --mountpoint / --output FSTYPE)" = "btrfs" ]; then
|
|
# target location is mounted as root subvolume
|
|
if [ -z $remote ]; then
|
|
# local root filesystem will be excluded as a valid target location
|
|
exclude_uuid=$(findmnt --noheadings --nofsroot --mountpoint / --output UUID)
|
|
disk_uuids=$(findmnt --noheadings --nofsroot --types btrfs --output UUID,TARGET --list \
|
|
| grep -v $exclude_uuid \
|
|
| awk '{print $1}')
|
|
disk_targets=$(findmnt --noheadings --nofsroot --types btrfs --output UUID,TARGET --list \
|
|
| grep -v $exclude_uuid \
|
|
| awk '{print $2}')
|
|
fs_options=$(findmnt --noheadings --nofsroot --types btrfs --output UUID,OPTIONS --list \
|
|
| grep -v $exclude_uuid \
|
|
| awk '{print $2}')
|
|
else
|
|
# remote root filesystem will be excluded as a valid target location
|
|
sleep 0.2
|
|
exclude_uuid=$($ssh findmnt --noheadings --nofsroot --mountpoint / --output UUID)
|
|
sleep 0.2
|
|
disk_uuids=$($ssh "findmnt --noheadings --nofsroot --types btrfs --output UUID,TARGET --list \
|
|
| grep -v \"$exclude_uuid\" \
|
|
| awk '{print \$1}'")
|
|
sleep 0.2
|
|
disk_targets=$($ssh "findmnt --noheadings --nofsroot --types btrfs --output UUID,TARGET --list \
|
|
| grep -v \"$exclude_uuid\" \
|
|
| awk '{print \$2}'")
|
|
sleep 0.2
|
|
fs_options=$($ssh "findmnt --noheadings --nofsroot --types btrfs --output UUID,OPTIONS --list \
|
|
| grep -v \"$exclude_uuid\" \
|
|
| awk '{print \$2}'")
|
|
fi
|
|
else
|
|
# target location is not mounted as root subvolume
|
|
sleep 0.2
|
|
disk_uuids=$($ssh findmnt --noheadings --nofsroot --types btrfs --output UUID --list)
|
|
sleep 0.2
|
|
disk_targets=$($ssh findmnt --noheadings --nofsroot --types btrfs --output TARGET --list)
|
|
sleep 0.2
|
|
fs_options=$($ssh findmnt --noheadings --nofsroot --types btrfs --output UUID,OPTIONS --list | awk '{print $2}')
|
|
sleep 0.2
|
|
fi
|
|
|
|
# we need at least one target disk
|
|
if [ ${#disk_targets} -eq 0 ]; then
|
|
printf "${RED}Error: ${MAGENTA}No suitable target disk found${NO_COLOR}"
|
|
return 1
|
|
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: fs_type_$i
|
|
# Pseudo-Array: disk_selected_$i (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
|
|
eval "fs_type_$i='btrfs'"
|
|
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'"
|
|
eval "fs_type_$i='btrfs'"
|
|
target_count=$(($target_count+1))
|
|
i=$((i+1))
|
|
done
|
|
i=0
|
|
for disk_target in $disk_targets; do
|
|
if [ "$disk_target" = "$target_cmdline" ]; then
|
|
target_match="$i"
|
|
target_match_count=$(($target_match_count+1))
|
|
fi
|
|
eval "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_tape_infos () {
|
|
local tape_id
|
|
local tape_target
|
|
local tape_fs_option
|
|
|
|
if [ $verbose -ge 3 ]; then
|
|
printf "${BLUE}get_tape_infos()...${NO_COLOR}\n"
|
|
fi
|
|
|
|
# get infos from mounted LTFS tapes
|
|
if [ -z $remote ]; then
|
|
# on localhost
|
|
if [ "$(findmnt --noheadings --nofsroot --types fuse --output SOURCE | awk -F ':' '{print $1}')" = "ltfs" ]; then
|
|
tape_ids=$(findmnt --noheadings --nofsroot --types fuse --output SOURCE --list \
|
|
| awk -F ':' '{print $2}')
|
|
tape_targets=$(findmnt --noheadings --nofsroot --types fuse --output TARGET --list \
|
|
| awk '{print $1}')
|
|
fs_options=$(findmnt --noheadings --nofsroot --types fuse --output SOURCE,OPTIONS --list \
|
|
| awk '{print $2}')
|
|
fi
|
|
else
|
|
# on remote host
|
|
if [ "$($ssh findmnt --noheadings --nofsroot --types fuse --output SOURCE --list | awk -F ':' '{print $1}')" = "ltfs" ]; then
|
|
tape_ids=$($ssh findmnt --noheadings --nofsroot --types fuse --output SOURCE --list \
|
|
| awk -F ':' '{print $2}')
|
|
tape_targets=$($ssh findmnt --noheadings --nofsroot --types fuse --output TARGET --list \
|
|
| awk '{print $1}')
|
|
fs_options=$($ssh findmnt --noheadings --nofsroot --types fuse --output SOURCE,OPTIONS --list \
|
|
| awk '{print $2}')
|
|
fi
|
|
fi
|
|
|
|
# we need at least one target disk
|
|
if [ ${#tape_targets} -eq 0 ]; then
|
|
printf "${RED}Error: ${MAGENTA}No suitable LTFS tape mounted yet${NO_COLOR}\n"
|
|
return 1
|
|
fi
|
|
|
|
# Posix Shells do not support Array. Therefore using ...
|
|
# Pseudo-Arrays (assumption: equal number of members)
|
|
# Pseudo-Array: tape_id_$i
|
|
# Pseudo-Array: tape_target_$i
|
|
# Pseudo-Array: fs_options_$i
|
|
# Pseudo-Array: fs_type_$i
|
|
# Pseudo-Array: tape_selected_$i (reference to $i element)
|
|
# List: tape_id_match (reference to matching preselected uuids)
|
|
# List: tape_target_match (reference to matching preselected targets)
|
|
|
|
# initialize our structures
|
|
#i=0
|
|
y=$(($target_count+1))
|
|
i=$y
|
|
for tape_id in $tape_ids; do
|
|
if [ "$tape_id" = "$tapeid_cmdline" ]; then
|
|
if [ ${#tape_id_match} -gt 0 ]; then
|
|
tape_id_match="${tape_id_match} $i"
|
|
else
|
|
tape_id_match="$i"
|
|
fi
|
|
tape_id_match_count=$(($tape_id_match_count+1))
|
|
fi
|
|
eval "tape_id_$i='$tape_id'"
|
|
eval "fs_type_$i='ltfs'"
|
|
target_count=$(($target_count+1))
|
|
i=$((i+1))
|
|
done
|
|
i=$y
|
|
for tape_target in $tape_targets; do
|
|
if [ "$tape_target" = "$target_cmdline" ]; then
|
|
target_match="$i"
|
|
target_match_count=$(($target_match_count+1))
|
|
fi
|
|
eval "target_$i='$tape_target'"
|
|
i=$((i+1))
|
|
done
|
|
#i=0
|
|
i=$y
|
|
for fs_option in $fs_options; do
|
|
eval "fs_options_$i='$fs_option'"
|
|
i=$((i+1))
|
|
done
|
|
}
|
|
|
|
get_snapper_last_sync_id () {
|
|
local snapper_config=${1#snapper_config=}
|
|
local snapper_description=${2##snapper_description=}
|
|
local snapper_uuid=${3##snapper_uuid=}
|
|
local snapper_subvolid=${4##snapper_subvolid=}
|
|
local snapper_tapeid=${5##snapper_tapeid=}
|
|
local remote=${6##remote=}
|
|
local run_ssh=''
|
|
|
|
snapper_sync_id=0
|
|
if [ ${#remote} -ge 1 ];then run_ssh=$ssh; fi
|
|
|
|
if [ $verbose -ge 3 ]; then
|
|
printf "${MAGENTA}get_snapper_last_sync_id()...${NO_COLOR}\n"
|
|
#printf "${MAGENTA}snapper_description: ${GREEN}'%s'${NO_COLOR}\n" \
|
|
# "${snapper_description}"
|
|
fi
|
|
|
|
# only process, if config does exist
|
|
cmd="$run_ssh stat --format %i $SNAPPER_CONFIG_DIR/$snapper_config 2>/dev/null"
|
|
if [ -z $(eval $cmd) ]; then
|
|
if [ $verbose -ge 3 ]; then
|
|
if [ $remote ]; then
|
|
printf "${MAGENTA}snapper config ${GREEN}'%s'${MAGENTA} on remote ${GREEN}'%s'${MAGENTA} does not exist yet.${NO_COLOR}\n" \
|
|
$snapper_config $remote
|
|
else
|
|
printf "${MAGENTA}snapper config ${GREEN}'%s'${MAGENTA} does not exist yet.${NO_COLOR}\n" \
|
|
$snapper_config
|
|
fi
|
|
fi
|
|
return 1
|
|
fi
|
|
|
|
if [ $verbose -ge 3 ]; then
|
|
if [ $remote ]; then
|
|
printf "${MAGENTA}Get last sync-ID for snapper config ${GREEN}'%s'${MAGENTA} on remote ${GREEN}'%s'${MAGENTA}...${NO_COLOR}\n" \
|
|
$snapper_config $remote
|
|
else
|
|
printf "${MAGENTA}Get last sync-ID for snapper config ${GREEN}'%s'${MAGENTA}...${NO_COLOR}\n" \
|
|
$snapper_config
|
|
fi
|
|
fi
|
|
if [ ${#snapper_subvolid} -ge 1 -a ${#snapper_uuid} -ge 1 ]; then
|
|
cmd="snapper --config $snapper_config list --type single \
|
|
| awk '/$snapper_description/' \
|
|
| awk '/subvolid="$snapper_subvolid", uuid="$snapper_uuid"/' \
|
|
| awk 'END {print \$1}'"
|
|
elif [ ${#snapper_tapeid} -ge 1 ]; then
|
|
# | awk '/"$snapper_description"' '/"$snap_description_finished/"' \
|
|
cmd="snapper --config $snapper_config list --type single \
|
|
| awk '/$snapper_description/' \
|
|
| awk '/tapeid="$snapper_tapeid"/' \
|
|
| awk 'END {print \$1}'"
|
|
else
|
|
cmd="snapper --config $snapper_config list --type single \
|
|
| awk '/$snapper_description/' \
|
|
| awk 'END {print \$1}'"
|
|
fi
|
|
snapper_sync_id=$(eval $run_ssh "$cmd")
|
|
|
|
if [ ${#snapper_sync_id} -ge 1 ]; then
|
|
# ok, matching snapshot found
|
|
snapper_sync_snapshot=$SUBVOLUME/.snapshots/$snapper_sync_id/$snapper_snapshot_name
|
|
else
|
|
# no snapshot found, try grap latest successfull sync
|
|
if [ ${#snapper_subvolid} -ge 1 -a ${#snapper_uuid} -ge 1 ]; then
|
|
cmd="snapper --config $snapper_config list --type single \
|
|
| awk '/$snap_description_finished/' \
|
|
| awk '/subvolid="$selected_subvol", uuid="$selected_uuid"/' \
|
|
| awk 'END {print \$1}'"
|
|
elif [ ${#snapper_tapeid} -ge 1 ]; then
|
|
cmd="snapper --config $snapper_config list --type single \
|
|
| awk '/$snap_description_finished/' \
|
|
| awk '/tapeid="$snapper_tapeid"/' \
|
|
| awk 'END {print \$1}'"
|
|
else
|
|
cmd="snapper --config $snapper_config list --type single \
|
|
| awk '/$snap_description_finished/' \
|
|
| awk 'END {print \$1}'"
|
|
fi
|
|
snapper_sync_id=$(eval $run_ssh "$cmd")
|
|
|
|
if [ ${#snapper_sync_id} -ge 1 ]; then
|
|
# ok, matching snapshot found
|
|
snapper_sync_snapshot=$SUBVOLUME/.snapshots/$snapper_sync_id/$snapper_snapshot_name
|
|
else
|
|
# no snapshot available
|
|
snapper_sync_id=0
|
|
fi
|
|
fi
|
|
}
|
|
|
|
get_snapper_sync_id () {
|
|
local snapper_config=${1#snapper_config=}
|
|
local remote=${2##remote=}
|
|
local run_ssh=''
|
|
local ret=
|
|
|
|
if [ $verbose -ge 3 ]; then
|
|
printf "${MAGENTA}get_snapper_sync_id()...${NO_COLOR}\n"
|
|
fi
|
|
if [ $verbose -ge 3 ]; then
|
|
if [ $remote ]; then
|
|
printf "${MAGENTA}Get sync-ID ${GREEN}'%s'${MAGENTA} for snapper config ${GREEN}'%s'${MAGENTA} on remote ${GREEN}'%s'${MAGENTA}...${NO_COLOR}\n" \
|
|
$snapper_sync_id $snapper_config $remote
|
|
else
|
|
printf "${MAGENTA}Get sync-ID ${GREEN}'%s'${MAGENTA} for snapper config ${GREEN}'%s'${MAGENTA}...${NO_COLOR}\n" \
|
|
$snapper_sync_id $snapper_config
|
|
fi
|
|
fi
|
|
|
|
[ ${#remote} -gt 0 ] && run_ssh=$ssh
|
|
|
|
cmd="snapper --config "$snapper_config" list --type single \
|
|
| awk -F '|' ' \$1 == $snapper_sync_id { gsub(/ /,_); print \$1} '"
|
|
|
|
ret=$(eval $run_ssh "$cmd")
|
|
if [ ${#ret} -ge 1 ]; then
|
|
# ok, matching snapshot found
|
|
snapper_sync_id=$ret
|
|
if [ $verbose -ge 3 ]; then
|
|
printf "Got source snapshot: ${GREEN}'%s'${NO_COLOR} (id: ${GREEN}'%s'${NO_COLOR})\n" \
|
|
$snapper_config $snapper_sync_id
|
|
fi
|
|
return 0
|
|
else
|
|
# no snapshot found
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
notify () {
|
|
# estimation: batch calls should just log
|
|
if [ $donotify -gt 0 ]; 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\n" "$2"
|
|
fi
|
|
}
|
|
|
|
notify_error () {
|
|
notify "$1" "$2" "error"
|
|
}
|
|
|
|
notify_info () {
|
|
notify "$1" "$2" "information"
|
|
}
|
|
|
|
parse_params () {
|
|
#printf "\n${BLUE}Parse arguments...${NO_COLOR}\n"
|
|
|
|
# Evaluate given call parameters
|
|
i=0
|
|
while [ $# -gt 0 ]; do
|
|
key="$1"
|
|
case $key in
|
|
-h | --help | \-\? | --usage)
|
|
# Call usage() function.
|
|
usage
|
|
;;
|
|
-a|--automount)
|
|
automount_path="$2"
|
|
shift 2
|
|
;;
|
|
-b|--backupdir)
|
|
backupdir_cmdline="$2"
|
|
shift 2
|
|
;;
|
|
--backuptype)
|
|
backuptype_cmdline="$2"
|
|
shift 2
|
|
;;
|
|
-c|--config)
|
|
# Pseudo-Array: selected_config_$i
|
|
eval "selected_config_$i='${2}'"
|
|
i=$(($i+1))
|
|
selected_configs=$i
|
|
shift 2
|
|
;;
|
|
--color)
|
|
color=1
|
|
shift 1
|
|
;;
|
|
--config-postfix)
|
|
snapper_config_postfix="$2"
|
|
shift 2
|
|
;;
|
|
--description-finished)
|
|
shift
|
|
snap_description_finished="${*}"
|
|
snap_description_finished="${snap_description_finished%% -*}"
|
|
params=$*
|
|
set -- $snap_description_finished
|
|
count=$#
|
|
set -- $params
|
|
shift $count
|
|
;;
|
|
--description-running)
|
|
shift
|
|
snap_description_running=${*}
|
|
snap_description_running="${snap_description_running%% -*}"
|
|
params=$*
|
|
set -- $snap_description_running
|
|
count=$#
|
|
set -- $params
|
|
shift $count
|
|
;;
|
|
-d|--description|--description-synced)
|
|
shift
|
|
snap_description_synced="${*}"
|
|
snap_description_synced="${snap_description_synced%% -*}"
|
|
params=$*
|
|
set -- $snap_description_synced
|
|
count=$#
|
|
set -- $params
|
|
shift $count
|
|
;;
|
|
--dry-run|--dryrun)
|
|
dryrun=1
|
|
shift 1
|
|
;;
|
|
-i|--interactive)
|
|
interactive=1
|
|
donotify=1
|
|
shift
|
|
;;
|
|
-n|--noconfirm|--batch)
|
|
batch=1
|
|
interactive=0
|
|
do_pv_cmd=0
|
|
donotify=0
|
|
shift
|
|
;;
|
|
--mediapool)
|
|
mediapool_name="$2"
|
|
shift 2
|
|
;;
|
|
--mode)
|
|
backup_mode="$2"
|
|
shift 2
|
|
;;
|
|
--nonotify)
|
|
donotify=0
|
|
shift 1
|
|
;;
|
|
--nopv)
|
|
do_pv_cmd=0
|
|
shift 1
|
|
;;
|
|
--noionice)
|
|
do_ionice_cmd=0
|
|
shift 1
|
|
;;
|
|
-p|--port)
|
|
port=$2
|
|
shift 2
|
|
;;
|
|
--remote)
|
|
remote=$2
|
|
shift 2
|
|
;;
|
|
-s|--subvolid|--SUBVOLID)
|
|
subvolid_cmdline="$2"
|
|
shift 2
|
|
;;
|
|
-t|--target|--TARGET)
|
|
target_cmdline="$2"
|
|
shift 2
|
|
;;
|
|
--volumename)
|
|
volume_name="$2"
|
|
shift 2
|
|
;;
|
|
-u|--uuid|--UUID)
|
|
uuid_cmdline="$2"
|
|
shift 2
|
|
;;
|
|
--use-btrfs-quota)
|
|
btrfs_quota=1
|
|
shift 1
|
|
;;
|
|
-v|--verbose)
|
|
verbose=$(($verbose + 1))
|
|
shift 1
|
|
;;
|
|
--version)
|
|
printf "%s v%s\n" "$progname" "$version"
|
|
exit 0
|
|
;;
|
|
--automount=*)
|
|
automount_path=${1#*=}
|
|
shift
|
|
;;
|
|
--backupdir=*)
|
|
backupdir_cmdline=${1#*=}
|
|
shift
|
|
;;
|
|
--backuptype=*)
|
|
backuptype_cmdline=${1#*=}
|
|
shift
|
|
;;
|
|
--config=*)
|
|
# Pseudo-Array: selected_config_$i
|
|
eval "selected_config_$i='${1#*=}'"
|
|
i=$(($i+1))
|
|
selected_configs=$i
|
|
shift
|
|
;;
|
|
--config-postfix=*)
|
|
snapper_config_postfix="${1#*=}"
|
|
shift
|
|
;;
|
|
--color=*)
|
|
case ${1#*=} in
|
|
yes | Yes | True | true)
|
|
color=1;
|
|
;;
|
|
*)
|
|
;;
|
|
esac
|
|
shift
|
|
;;
|
|
--description-finished=*)
|
|
snap_description_finished="${*#*=}"
|
|
snap_description_finished="${snap_description_finished%% -*}"
|
|
params_new=${*#*=}
|
|
params_new=${params_new##${snap_description_finished}}
|
|
if [ ${#params_new} -gt 0 ]; then
|
|
set -- $snap_description_finished
|
|
count=$#
|
|
set -- $params_new
|
|
fi
|
|
;;
|
|
--description-running=*)
|
|
snap_description_running="${*#*=}"
|
|
snap_description_running="${snap_description_running%% -*}"
|
|
params_new=${*#*=}
|
|
params_new=${params_new##${snap_description_running}}
|
|
params=$#
|
|
if [ ${#params_new} -gt 0 ]; then
|
|
set -- $snap_description_running
|
|
count=$#
|
|
set -- $params_new
|
|
fi
|
|
;;
|
|
-d=*|--description=*|--description-synced=*)
|
|
snap_description_synced="${*#*=}"
|
|
snap_description_synced="${snap_description_synced%% -*}"
|
|
params_new=${*#*=}
|
|
params_new=${params_new##${snap_description_synced}}
|
|
if [ ${#params_new} -gt 0 ]; then
|
|
set -- $snap_description_synced
|
|
count=$#
|
|
set -- $params_new
|
|
else
|
|
break
|
|
fi
|
|
;;
|
|
--mediapool=*)
|
|
mediapool_name=${1#*=}
|
|
shift
|
|
;;
|
|
--mode=*)
|
|
backup_mode=${1#*=}
|
|
shift
|
|
;;
|
|
--port=*)
|
|
port=${1#*=}
|
|
shift
|
|
;;
|
|
--remote=*)
|
|
remote=${1#*=}
|
|
shift
|
|
;;
|
|
--subvolid=*|--SUBVOLID=*)
|
|
subvolid_cmdline=${1#*=}
|
|
shift
|
|
;;
|
|
--target=*|--TARGET=*)
|
|
target_cmdline=${1#*=}
|
|
shift
|
|
;;
|
|
--uuid=*|--UUID=*)
|
|
uuid_cmdline=${1#*=}
|
|
shift
|
|
;;
|
|
--v=* | --verbose=*)
|
|
verbose=${1#*=}
|
|
shift
|
|
;;
|
|
--volumename=*)
|
|
volume_name=${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
|
|
if [ $selected_configs -eq 0 ]; then
|
|
. $SNAPPER_CONFIG
|
|
i=0
|
|
for selected_config in $SNAPPER_CONFIGS; do
|
|
# Pseudo-Array: target_selected_$i (reference to $disk_uuid element)
|
|
eval "selected_config_$i='$selected_config'"
|
|
i=$(($i+1))
|
|
selected_configs=$i
|
|
done
|
|
fi
|
|
|
|
# message-text used for snapper fields
|
|
snap_description_finished=${snap_description_finished:-"dsnap-sync backup"}
|
|
snap_description_running=${snap_description_running:-"dsnap-sync in progress"}
|
|
snap_description_synced=${snap_description_synced:-"dsnap-sync last incremental"}
|
|
|
|
uuid_cmdline=${uuid_cmdline:-"none"}
|
|
target_cmdline=${target_cmdline:-"none"}
|
|
subvolid_cmdline=${subvolid_cmdline:-"none"}
|
|
backupdir_cmdline=${backupdir_cmdline:-"none"}
|
|
|
|
if [ -n "$remote" ]; then
|
|
ssh="ssh $remote"
|
|
scp="scp"
|
|
if [ ! -z "$port" ]; then
|
|
ssh="$ssh -p $port"
|
|
scp="$scp -P $port"
|
|
fi
|
|
fi
|
|
|
|
if [ "$color" ]; then
|
|
# ascii color
|
|
BLUE='\033[0;34m'
|
|
GREEN='\033[0;32m'
|
|
MAGENTA='\033[0;35m'
|
|
RED='\033[0;31m'
|
|
YELLOW='\033[0;33m'
|
|
NO_COLOR='\033[0m'
|
|
fi
|
|
|
|
if [ $verbose -ge 2 ]; then
|
|
printf "${BLUE}$progname (runtime arguments)...${NO_COLOR}\n"
|
|
printf "for backup-source:\n"
|
|
#printf " selected configs: '%s'\n" "$selected_configs"
|
|
i=0
|
|
while [ $i -lt $selected_configs ]; do
|
|
printf " selected config: '%s'\n" "$(eval echo \$selected_config_$i)"
|
|
i=$(($i+1))
|
|
done
|
|
printf "for backup-target:\n"
|
|
printf " disk UUID: '%s'\n" "$uuid_cmdline"
|
|
printf " disk SUBVOLID: '%s'\n" "$subvolid_cmdline"
|
|
printf " tape MediaPool: '%s'\n" "$mediapool_name"
|
|
printf " tape VolumeName: '%s'\n" "$volume_name"
|
|
printf " TARGET name: '%s'\n" "$target_cmdline"
|
|
printf " Backupdir: '%s'\n" "$backupdir_cmdline"
|
|
printf " Backup Type: '%s'\n" "$backuptype_cmdline"
|
|
printf " Backup Mode: '%s'\n" "$backup_mode"
|
|
printf " config postfix: '%s'\n" "$snapper_config_postfix"
|
|
printf " remote host: '%s'\n" "$remote"
|
|
printf " ssh options: '%s'\n" "$ssh"
|
|
|
|
printf "Snapper Descriptions\n"
|
|
printf " backup finished: '%s'\n" "$snap_description_finished"
|
|
printf " backup running: '%s'\n" "$snap_description_running"
|
|
printf " backup synced: '%s'\n" "$snap_description_synced"
|
|
|
|
if [ $verbose -ge 2 ]; then snap_sync_options="verbose_level=$verbose"; fi
|
|
if [ $dryrun -eq 1 ]; then snap_sync_options="${snap_sync_options} dry-run=true"; fi
|
|
if [ $donotify -eq 1 ]; then snap_sync_options="${snap_sync_options} donotify=true"; fi
|
|
if [ $color -eq 1 ]; then snap_sync_options="${snap_sync_options} color=true"; fi
|
|
if [ $btrfs_quota -eq 1 ]; then snap_sync_options="${snap_sync_options} use-btrfs-quota=true"; fi
|
|
if [ $batch -eq 1 ]; then
|
|
snap_sync_options="${snap_sync_options} batch=true do_pv_cmd=$do_pv_cmd"
|
|
else
|
|
snap_sync_options="${snap_sync_options} interactive=true do_pv_cmd=$do_pv_cmd"
|
|
fi
|
|
#if [ "$interactive" -eq 1 ]; then snap_sync_options="${snap_sync_options} interactive=true"; fi
|
|
printf "Options: '%s'\n\n" "${snap_sync_options}"
|
|
fi
|
|
}
|
|
|
|
quote_args () {
|
|
# quote command in ssh call to prevent remote side from expanding any arguments
|
|
# using dash's buildin printf
|
|
|
|
args=
|
|
if [ $# -gt 0 ]; then
|
|
# no need to make COMMAND an array - ssh will merge it anyway
|
|
COMMAND=
|
|
while [ $# -gt 0 ]; do
|
|
arg=$(printf "%s" "$1")
|
|
COMMAND="${COMMAND}${arg} "
|
|
shift
|
|
done
|
|
args="${args}${COMMAND}"
|
|
fi
|
|
}
|
|
|
|
run_config_preparation () {
|
|
if [ $verbose -ge 1 ]; then
|
|
printf "${BLUE}Prepare configuration structures...${NO_COLOR}\n"
|
|
fi
|
|
|
|
SNAP_SYNC_EXCLUDE=no
|
|
# loop though selected snapper configurations
|
|
# Pseudo Arrays $i -> store associated elements of selected_config
|
|
i=0
|
|
while [ $i -lt $selected_configs ]; do
|
|
# only process on existing configurations
|
|
selected_config=$(eval echo \$selected_config_$i)
|
|
verify_snapper_config $selected_config
|
|
|
|
# cleanup failed former runs
|
|
check_snapper_failed_ids $selected_config $batch
|
|
|
|
if [ $SNAP_SYNC_EXCLUDE = "yes" ]; then
|
|
continue
|
|
fi
|
|
|
|
# parse selected_config and set $snapper_target_config appropriately
|
|
get_snapper_backup_type $selected_config
|
|
|
|
# parse backupdir
|
|
get_backupdir $backupdir_cmdline
|
|
|
|
# get latest successfully finished snapshot
|
|
# verify source
|
|
# WIP: metadata from last snapshot!
|
|
case $snapper_backup_type in
|
|
btrfs-snapshot)
|
|
get_snapper_last_sync_id "snapper_config=${selected_config}" "snapper_description=${snap_description_synced}" \
|
|
"snapper_uuid=" "snapper_subvolid=" "snapper_tapeid=" "remote="
|
|
;;
|
|
btrfs-clone)
|
|
get_snapper_last_sync_id "snapper_config=${selected_config}" "snapper_description=${snap_description_synced}" \
|
|
"snapper_uuid=" "snapper_subvolid=" "snapper_tapeid=" "remote="
|
|
;;
|
|
btrfs-archive)
|
|
get_snapper_last_sync_id "snapper_config=${selected_config}" "snapper_description=${snap_description_synced}" \
|
|
"snapper_uuid=" "snapper_subvolid=" "snapper_tapeid=" "remote="
|
|
#"snapper_uuid=" "snapper_subvolid=" "snapper_tapeid=${volume_name}" "remote="
|
|
;;
|
|
*)
|
|
if [ $verbose -ge 3 ]; then
|
|
printf "${RED}TODO:${NO_COLOR} what is needed for config_type '%s'?\n" "$snapper_backup_type"
|
|
fi
|
|
;;
|
|
esac
|
|
snapper_source_sync_id=$snapper_sync_id
|
|
|
|
if [ $snapper_sync_id -eq 0 ]; then
|
|
if [ $verbose -ge 2 ]; then
|
|
printf "${MAGENTA}No previous snapshot available for snapper config ${GREEN}'%s'${MAGENTA}...${NO_COLOR}\n" \
|
|
"$selected_config"
|
|
fi
|
|
|
|
snapper_target_sync_id=0
|
|
snapper_source_sync_snapshot='none'
|
|
snapper_target_sync_snapshot='none'
|
|
backup_root=$selected_target/$backupdir/$snapper_target_config
|
|
else
|
|
# Set snapshot-path for source
|
|
get_snapper_config_value "remote=" \
|
|
"snapper_config=$SNAPPER_CONFIG_DIR/$selected_config" \
|
|
"config_key=SUBVOLUME"
|
|
if [ $? -eq 0 ]; then
|
|
snapper_source_sync_snapshot=$value/.snapshots/$snapper_source_sync_id/$snapper_snapshot_name
|
|
fi
|
|
if [ $verbose -ge 2 ]; then
|
|
printf "${MAGENTA}Last synced ${GREEN}source snapshot${MAGENTA} for snapper config ${GREEN}'%s'${MAGENTA} is ${GREEN}'%s'${MAGENTA} ...${NO_COLOR}\n" \
|
|
"$selected_config" "$snapper_sync_id"
|
|
fi
|
|
|
|
# verfiy target
|
|
case $snapper_backup_type in
|
|
btrfs-archive)
|
|
# set snapper_target_sync_id
|
|
get_archive_last_sync_id "snapper_config=${snapper_target_config}" \
|
|
"archive_type=full" \
|
|
"remote=${remote}"
|
|
;;
|
|
*)
|
|
get_snapper_last_sync_id "snapper_config=${snapper_target_config}" \
|
|
"snapper_description=${snap_description_synced}" \
|
|
"snapper_uuid=" "snapper_subvolid=" "snapper_tapeid=" "remote=${remote}"
|
|
;;
|
|
esac
|
|
if [ $? -eq 0 ]; then
|
|
snapper_target_sync_id=$snapper_sync_id
|
|
else
|
|
snapper_target_sync_id=0
|
|
fi
|
|
|
|
# check for corresponding source and target sync id's
|
|
snapper_common_sync_id=0
|
|
if [ $snapper_target_sync_id -ne $snapper_source_sync_id ]; then
|
|
# select commen sync id
|
|
get_snapper_sync_id "snapper_config=${selected_config}" "remote="
|
|
if [ $? -eq 0 ]; then
|
|
snapper_common_sync_snapshot=$SUBVOLUME/.snapshots/$snapper_sync_id/$snapper_snapshot_name
|
|
snapper_common_sync_id=$snapper_sync_id
|
|
if [ $verbose -ge 2 ]; then
|
|
if [ $remote ]; then
|
|
printf "${MAGENTA}Common ${GREEN}synced snapshot${MAGENTA} for snapper config ${GREEN}'%s'${MAGENTA} on '%s': ${GREEN}'%s'${NO_COLOR}\n" \
|
|
"$selected_config" "$remote" "$snapper_common_sync_id"
|
|
else
|
|
printf "${MAGENTA}Common ${GREEN}synced snapshot${MAGENTA} for snapper config ${GREEN}'%s'${MAGENTA}: ${GREEN}'%s'${NO_COLOR}\n" \
|
|
"$selected_config" "$snapper_common_sync_id"
|
|
|
|
fi
|
|
fi
|
|
else
|
|
# no commen sync id found
|
|
snapper_target_sync_id=0
|
|
fi
|
|
fi
|
|
|
|
if [ $snapper_target_sync_id -eq 0 ]; then
|
|
if [ $verbose -ge 2 ]; then
|
|
if [ $remote ]; then
|
|
printf "${MAGENTA}No synced ${GREEN}target snapshot${MAGENTA} available for snapper config ${GREEN}'%s'${MAGENTA} on '%s'...${NO_COLOR}\n" \
|
|
"$selected_config" "$remote"
|
|
else
|
|
printf "${MAGENTA}No synced ${GREEN}target snapshot${MAGENTA} available for snapper config ${GREEN}'%s'${MAGENTA}...${NO_COLOR}\n" \
|
|
"$selected_config"
|
|
|
|
fi
|
|
fi
|
|
backup_root=$selected_target/$backupdir/$snapper_target_config
|
|
else
|
|
case $selected_fstype in
|
|
btrfs)
|
|
# get backupdir from snapper target
|
|
get_snapper_target_backupdir $backupdir
|
|
|
|
# set target sync_snapshot path
|
|
get_snapper_config_value "remote=$remote" \
|
|
"snapper_config=$SNAPPER_CONFIG_DIR/$snapper_target_config" \
|
|
"config_key=SUBVOLUME"
|
|
if [ $? -eq 0 ]; then
|
|
snapper_target_sync_snapshot=$value/.snapshots/$snapper_target_sync_id/$snapper_snapshot_name
|
|
backup_root=$value
|
|
fi
|
|
;;
|
|
*)
|
|
# use snapper_target_snapshot
|
|
backup_root=$selected_target/$backupdir/$snapper_target_config
|
|
;;
|
|
esac
|
|
|
|
if [ $verbose -ge 2 ]; then
|
|
if [ $remote ]; then
|
|
printf "${MAGENTA}Last synced ${GREEN}target snapshot${MAGENTA} for snapper config ${GREEN}'%s'${MAGENTA} on remote '%s' is ${GREEN}'%s'${NO_COLOR}\n" \
|
|
"$snapper_target_config" "$remote" "$snapper_target_sync_id"
|
|
else
|
|
printf "${MAGENTA}Last synced ${GREEN}target snapshot${MAGENTA} for snapper config ${GREEN}'%s'${MAGENTA} is ${GREEN}'%s'${NO_COLOR}\n" \
|
|
"$snapper_target_config" "$snapper_target_sync_id"
|
|
fi
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# save values in config specific pseudo arrays
|
|
eval "snapper_source_config_$i='$selected_config'"
|
|
eval "snapper_target_config_$i='$snapper_target_config'"
|
|
eval "snapper_backup_type_$i='$snapper_backup_type'"
|
|
eval "snapper_source_sync_id_$i='$snapper_source_sync_id'"
|
|
eval "snapper_source_sync_snapshot_$i='$snapper_source_sync_snapshot'"
|
|
eval "snapper_target_sync_id_$i='$snapper_target_sync_id'"
|
|
eval "snapper_target_sync_snapshot_$i='$snapper_target_sync_snapshot'"
|
|
eval "snapper_common_sync_id_$i='$snapper_common_sync_id'"
|
|
eval "backupdir_$i='$backupdir'"
|
|
eval "backup_root_$i='$backup_root'"
|
|
|
|
cont_backup="K"
|
|
eval "snapper_activate_$i='yes'"
|
|
if [ $batch ]; then
|
|
cont_backup="yes"
|
|
else
|
|
answer=yes
|
|
get_answer_yes_no "Continue with backup [Y/n]? " "$answer"
|
|
if [ "$answer" = "no" ]; then
|
|
snapper_activate_$i="no"
|
|
printf "Aborting backup for this configuration.\n"
|
|
#snapper --config $selected_config delete $snapper_sync_id
|
|
fi
|
|
fi
|
|
|
|
i=$(($i+1))
|
|
done
|
|
}
|
|
|
|
run_backup () {
|
|
if [ $verbose -ge 1 ]; then
|
|
printf "${BLUE}Performing backups...${NO_COLOR}\n"
|
|
fi
|
|
|
|
i=0
|
|
selected_config=
|
|
while [ $i -lt $selected_configs ]; do
|
|
# only process on existing configurations
|
|
selected_config=$(eval echo \$selected_config_$i)
|
|
|
|
if [ $verbose -ge 1 ]; then
|
|
printf "${MAGENTA}Performing backup for config ${GREEN}'%s'${MAGENTA}...${NO_COLOR}\n" \
|
|
"${selected_config}"
|
|
fi
|
|
|
|
SNAP_SYNC_EXCLUDE=no
|
|
|
|
if [ -f "/etc/snapper/configs/$selected_config" ]; then
|
|
. /etc/snapper/configs/$selected_config
|
|
else
|
|
printf "${RED}Error: ${MAGENTA}Selected snapper configuration ${GREEN}'$selected_config'${MAGENTA} does not exist${NO_COLOR}\n"
|
|
# go for next configuration
|
|
i=$(($i+1))
|
|
continue
|
|
fi
|
|
|
|
cont_backup=$(echo \$snapper_activate_$i)
|
|
if [ "$cont_backup" = "no" ] || [ "$SNAP_SYNC_EXCLUDE" = "yes" ]; then
|
|
if [ $donotify -gt 0 ]; then
|
|
notify_info "Backup in progress" "NOTE: Skipping '$selected_config' configuration."
|
|
fi
|
|
# go for next configuration
|
|
i=$(($i+1))
|
|
continue
|
|
fi
|
|
|
|
if [ $donotify -gt 0 ]; then
|
|
notify_info "Backup in progress" "Backing up data for configuration '$selected_config'."
|
|
fi
|
|
|
|
# retrieve config specific infos from pseudo Arrays
|
|
snapper_source_config=$(eval echo \$snapper_source_config_$i)
|
|
snapper_target_config=$(eval echo \$snapper_target_config_$i)
|
|
snapper_backup_type=$(eval echo \$snapper_backup_type_$i)
|
|
snapper_source_sync_id=$(eval echo \$snapper_source_sync_id_$i)
|
|
snapper_source_sync_snapshot=$(eval echo \$snapper_source_sync_snapshot_$i)
|
|
snapper_target_sync_id=$(eval echo \$snapper_target_sync_id_$i)
|
|
snapper_common_sync_id=$(eval echo \$snapper_common_sync_id_$i)
|
|
backup_dir=$(eval echo \$backupdir_$i)
|
|
backup_root=$(eval echo \$backup_root_$i)
|
|
|
|
if [ $verbose -ge 3 ]; then
|
|
printf "${MAGENTA}snapper_source_config: ${GREEN}'%s'${NO_COLOR}\n" \
|
|
"$snapper_source_config"
|
|
printf "${MAGENTA}snapper_target_config: ${GREEN}'%s'${NO_COLOR}\n" \
|
|
"$snapper_target_config"
|
|
printf "${MAGENTA}snapper_backup_type: ${GREEN}'%s'${NO_COLOR}\n" "$snapper_backup_type"
|
|
printf "${MAGENTA}snapper_source_sync_id: ${GREEN}'%s'${NO_COLOR}\n" \
|
|
"$snapper_source_sync_id"
|
|
printf "${MAGENTA}snapper_target_sync_id: ${GREEN}'%s'${NO_COLOR}\n" \
|
|
"$snapper_target_sync_id"
|
|
printf "${MAGENTA}snapper_common_sync_id: ${GREEN}'%s'${NO_COLOR}\n" \
|
|
"$snapper_common_sync_id"
|
|
printf "${MAGENTA}backup_dir: ${GREEN}'%s'${NO_COLOR}\n" "$backup_dir"
|
|
fi
|
|
|
|
# create the needed snapper structure on the target
|
|
case $snapper_backup_type in
|
|
btrfs-snapshot)
|
|
create_snapshot
|
|
|
|
# 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_id=$snapper_source_id
|
|
snapper_target_snapshot=$backup_root/.snapshots/$snapper_target_id
|
|
|
|
# create btrfs-snapshot on target
|
|
verify_snapper_structure "backup_root=$backup_root" "snapper_target_config=$snapper_target_config" "snapper_target_id=$snapper_target_id" "remote=$remote"
|
|
;;
|
|
btrfs-clone)
|
|
# check for last common snapshot
|
|
snapper_source_id=$snapper_source_sync_id
|
|
snapper_source_snapshot=$SUBVOLUME/.snapshots/$snapper_source_sync_id/$snapper_snapshot_name
|
|
snapper_source_info=$SUBVOLUME/.snapshots/$snapper_source_sync_id/info.xml
|
|
|
|
# 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_id=$snapper_source_id
|
|
snapper_target_snapshot=$backup_root/.snapshots/$snapper_target_id
|
|
|
|
# create btrfs-snapshot on target
|
|
verify_snapper_structure "backup_root=$backup_root" "snapper_target_config=$snapper_target_config" "snapper_target_id=$snapper_target_id" "remote=$remote"
|
|
;;
|
|
btrfs-archive)
|
|
if [ $snapper_source_sync_id -eq 0 ]; then
|
|
create_snapshot
|
|
else
|
|
# check for last common snapshot
|
|
snapper_source_id=$snapper_source_sync_id
|
|
snapper_source_snapshot=$SUBVOLUME/.snapshots/$snapper_source_sync_id/$snapper_snapshot_name
|
|
snapper_source_info=$SUBVOLUME/.snapshots/$snapper_source_sync_id/info.xml
|
|
fi
|
|
|
|
# targets backup location will save the snapshots in the subdirectory (snapshot-id)
|
|
# the snapshot is either the base snapshot (flat-file), or an incremental snapshot (btrfs-send stream)
|
|
snapper_target_id=$snapper_source_id
|
|
snapper_target_snapshot=$target_cmdline/$backupdir/$snapper_target_config/$snapper_target_id
|
|
|
|
# archive btrfs-snapshot to non btrfs-filesystem on target (e.g tape, ext4)
|
|
verify_archive_structure "backup_root=$backup_root" "snapper_target_config=$snapper_target_config" "snapper_target_sync_id=$snapper_target_id" "remote=$remote"
|
|
;;
|
|
*)
|
|
if [ $verbose -ge 3 ]; then
|
|
printf "${RED}WIP:${NO_COLOR} what is needed for config_type '%s'\n" "$snapper_backup_type"
|
|
fi
|
|
;;
|
|
esac
|
|
if [ $? -gt 0 ]; then
|
|
error_count=$(($error_count+1))
|
|
# go for next configuration
|
|
i=$(($i+1))
|
|
continue
|
|
fi
|
|
|
|
if [ $dryrun -eq 0 ]; then
|
|
snapper_source_snapshot_size=0
|
|
if [ "$interactive" -eq 1 ]; then
|
|
if [ $verbose -ge 2 ]; then
|
|
printf "${MAGENTA}Get size for given source snapshot (id=${GREEN}'%s'${MAGENTA}, path=${GREEN}'%s'${MAGENTA})${NO_COLOR} ...\n" \
|
|
"$snapper_source_id" "$snapper_source_snapshot"
|
|
fi
|
|
|
|
if [ $btrfs_quota -eq 1 ]; then
|
|
# qgroup for given path, exclude ancestrals
|
|
# qgroup identifiers conform to level/id where level 0 is reserved to the qgroups associated with subvolumes
|
|
snapper_source_snapshot_size=$(btrfs qgroup show -f --raw $snapper_source_snapshot 2>/dev/null \
|
|
| awk 'FNR>2 {print $2}')
|
|
if [ $verbose -ge 3 ]; then
|
|
printf "${MAGENTA}BTRFS qgroup show result: ${GREEN}'%s'\b${NO_COLOR}'%s'\n" \
|
|
"$?" "$snapper_source_snapshot_size"
|
|
fi
|
|
#if [ $? -eq 1 ]; then
|
|
# subvolume is not configured for quota, (temporary?) enable that
|
|
#btrfs_quota_tmp=1
|
|
#btrfs quota enable $snapper_source_snapshot 2>/dev/null
|
|
#btrfs quota rescan -w $snapper_source_snapshot 2>/dev/null
|
|
#fi
|
|
|
|
# need to substitue btrfs 'x.yyGiB' suffix, since pv will need 'xG'
|
|
if [ $snapper_source_snapshot_size -ge 1048576 ]; then
|
|
snapper_source_snapshot_size=$(btrfs qgroup show -f --gbytes $snapper_source_snapshot 2>/dev/null \
|
|
| awk 'FNR>2 { gsub(/.[0-9][0-9]GiB/,"G"); print $2}')
|
|
fi
|
|
if [ $verbose -ge 2 ]; then
|
|
printf "${MAGENTA}BTRFS quota size for ${GREEN}source snapshot${MAGENTA}: id=${GREEN}'%s'${MAGENTA}, size=${GREEN}'%s'${NO_COLOR} ...\n" \
|
|
"$snapper_source_id" "$snapper_source_snapshot_size"
|
|
fi
|
|
|
|
# should we disable quota usage again?
|
|
#if [ $btrfs_quota_tmp -eq 1 ]; then btrfs quota disable $snapper_source_snapshot; fi
|
|
else
|
|
snapper_source_snapshot_size=$(du --one-file-system --summarize $snapper_source_snapshot 2>/dev/null \
|
|
| awk -F ' ' '{print $1}')
|
|
if [ $snapper_source_snapshot_size -ge 1048576 ]; then
|
|
snapper_source_snapshot_size=$(($snapper_source_snapshot_size / 1024 / 1024))G
|
|
fi
|
|
if [ $verbose -ge 2 ]; then
|
|
printf "${MAGENTA}BTRFS subvolume size for ${GREEN}source snapshot${MAGENTA}: id=${GREEN}'%s'${MAGENTA}, size=${GREEN}'%s'${NO_COLOR} ...\n" \
|
|
"$snapper_source_id" "$snapper_source_snapshot_size"
|
|
fi
|
|
fi
|
|
fi
|
|
else
|
|
if [ $verbose -ge 2 ]; then
|
|
printf "${MAGENTA}dryrun: Would calculate BTRFS subvolume size for ${GREEN}source snapshot${NO_COLOR} ...\n" \
|
|
"$snapper_source_id"
|
|
fi
|
|
fi
|
|
# setting process I/O scheduling options
|
|
if [ $do_ionice_cmd -eq 1 ]; then
|
|
# class: best-efford, priority: medium
|
|
#cmd_ionice="ionice --class 2 --classlevel 5"
|
|
# class: idle
|
|
cmd_ionice="ionice --class 3"
|
|
else
|
|
cmd_ionice=''
|
|
fi
|
|
|
|
# settings for interactive progress status
|
|
if [ $do_pv_cmd -eq 1 ]; then
|
|
pv_options="--delay-start 2 --interval 5 --format \"time elapsed [%t] | avg rate %a | rate %r | transmitted [%b] | %p | time remaining [%e]\" "
|
|
cmd_pv="pv --size $snapper_source_snapshot_size $pv_options | "
|
|
#cmd_pv="pv $pv_options --size ${snapper_source_snapshot_size} | dialog --gauge \"$progname: Progress for config '$selected_config'\" 6 85 |"
|
|
else
|
|
cmd_pv=''
|
|
fi
|
|
|
|
case $selected_fstype in
|
|
btrfs)
|
|
cmd="btrfs send $btrfs_verbose_flag $snapper_source_snapshot 2>4 \
|
|
| $cmd_pv \
|
|
$cmd_ionice $ssh btrfs receive $btrfs_verbose_flag $snapper_target_snapshot/ 1>4 2>&1"
|
|
;;
|
|
*)
|
|
# Can't use btrfs receive, since target filesystem can't support btrfs snapshot feature
|
|
snapper_target_stream=${snapper_target_id}_${archive_type}.btrfs
|
|
if [ ! -f $snapper_target_snapshot/$snapper_target_stream ]; then
|
|
if [ -z $remote ]; then
|
|
cmd="btrfs send $btrfs_verbose_flag $snapper_source_snapshot 2>/dev/null \
|
|
| $cmd_pv \
|
|
$cmd_ionice cat > $snapper_target_snapshot/$snapper_target_stream"
|
|
else
|
|
cmd="btrfs send $btrfs_verbose_flag $snapper_source_snapshot 2>/dev/null \
|
|
| $cmd_pv \
|
|
$cmd_ionice $ssh 'cat > $snapper_target_snapshot/$snapper_target_stream' 2>4"
|
|
fi
|
|
else
|
|
if [ $verbose -ge 2 ]; then
|
|
printf "${RED}BTRFS_Stream: %s${NO_COLOR} already saved.\n" \
|
|
"$snapper_target_snapshot/$snapper_target_stream"
|
|
fi
|
|
# go for next configuration
|
|
i=$(($i+1))
|
|
continue
|
|
fi
|
|
;;
|
|
esac
|
|
if [ "$dryrun" -eq 0 ]; then
|
|
if [ "$snapper_source_sync_id" -eq 0 ] \
|
|
|| [ "$snapper_target_sync_id" -eq 0 ] \
|
|
|| [ "$backup_mode" = "full" ] ; then
|
|
# send full snapshot to target
|
|
if [ $verbose -ge 2 ]; then
|
|
if [ $snapper_source_snapshot_size -gt 0 ]; then
|
|
printf "${MAGENTA}Sending ${GREEN}snapshot${NO_COLOR} for snapper config ${GREEN}'%s' ${MAGENTA}(id='%s', size='%s')${NO_COLOR} ...\n" \
|
|
"$selected_config" "$snapper_source_id" "$snapper_source_snapshot_size"
|
|
else
|
|
printf "${MAGENTA}Sending ${GREEN}snapshot${NO_COLOR} for snapper config ${GREEN}'%s' ${MAGENTA}(id='%s')${NO_COLOR} ...\n" \
|
|
"$selected_config"
|
|
fi
|
|
fi
|
|
# the actual data sync to the target
|
|
# this may take a while, depending on datasize and line-speed
|
|
if [ $verbose -ge 3 ]; then
|
|
printf "cmd: '%s'\n" "$cmd"
|
|
fi
|
|
$(eval $cmd)
|
|
if [ "$?" -gt 0 ]; then
|
|
printf "${RED}BTRFS_PIPE: %s${NO_COLOR}\n" "$(cat <4)"
|
|
error_count=$(($error_count+1))
|
|
# go for next configuration
|
|
i=$(($i+1))
|
|
continue
|
|
fi
|
|
else
|
|
# source holds synced snapshots
|
|
if [ $verbose -ge 3 ]; then
|
|
printf "New ${GREEN}source${NO_COLOR} snapshot id: ${GREEN}'%s'${NO_COLOR} (path: ${GREEN}'%s'${NO_COLOR})\n" \
|
|
"$snapper_source_id" "$snapper_source_snapshot"
|
|
printf "New ${GREEN}target${NO_COLOR} snapshot id: ${GREEN}'%s'${NO_COLOR} (path: ${GREEN}'%s'${NO_COLOR})\n" \
|
|
"$snapper_target_id" "$snapper_target_snapshot/$snapper_snapshot_name"
|
|
printf "Common synced snapshot id: ${GREEN}'%s'${NO_COLOR}\n" \
|
|
"$snapper_common_sync_id"
|
|
printf "Last synced ${GREEN}source${NO_COLOR} snapshot id: ${GREEN}'%s'${NO_COLOR} (path: ${GREEN}'%s'${NO_COLOR})\n" \
|
|
"$snapper_source_sync_id" "$snapper_source_sync_snapshot"
|
|
printf "Last synced ${GREEN}target${NO_COLOR} snapshot id: ${GREEN}'%s'${NO_COLOR} (path: ${GREEN}'%s'${NO_COLOR})\n" \
|
|
"$snapper_target_sync_id" "$snapper_target_sync_snapshot"
|
|
fi
|
|
if [ $verbose -ge 2 ]; then
|
|
printf "${MAGENTA}Prepare ${GREEN}incremental snapshot${NO_COLOR} (id: ${GREEN}'%s'${NO_COLOR}) for snapper config ${GREEN}'%s'${NO_COLOR} ...\n" \
|
|
"$snapper_target_id" "$selected_config"
|
|
fi
|
|
# verify that we have a matching source and target snapshot-id
|
|
if [ $snapper_common_sync_id -eq 0 ]; then
|
|
if [ ${snapper_source_id} -eq ${snapper_target_sync_id} ]; then
|
|
# nothing to do, snapshot already in sync
|
|
if [ $verbose -ge 3 ]; then
|
|
printf "${MAGENTA}Nothing to do! Source and target snapshot (id: ${GREEN}'%s'${MAGENTA}) are in sync.${NO_COLOR}\n" \
|
|
"$snapper_target_sync_id"
|
|
fi
|
|
# go for next configuration
|
|
i=$(($i+1))
|
|
continue
|
|
elif [ ${snapper_source_sync_id} != ${snapper_target_sync_id} ]; then
|
|
if [ $snapper_target_sync_id -lt $snapper_target_id ]; then
|
|
# try to find last target_sync_id in source_config
|
|
get_snapper_sync_id "snapper_config=${snapper_source_config}" "remote="
|
|
if [ $? -eq 0 ]; then
|
|
snapper_source_sync_id=$snapper_sync_id
|
|
snapper_source_sync_snapshot=$SUBVOLUME/.snapshots/$snapper_sync_id/$snapper_snapshot_name
|
|
else
|
|
printf "${RED}Error: ${MAGENTA}No common sync id found. Aborting backup for config ${GREEN}'%s'${NO_COLOR}\n"
|
|
error_count=$(($error_count+1))
|
|
# go for next configuration
|
|
i=$(($i+1))
|
|
continue
|
|
fi
|
|
fi
|
|
elif [ ${snapper_source_id} != ${snapper_source_sync_id} ]; then
|
|
snapper_common_sync_id=$snapper_source_sync_id
|
|
snapper_common_sync_snapshot=$snapper_source_sync_snapshot
|
|
else
|
|
# use source_id as common sync-id
|
|
snapper_common_sync_id=$snapper_source_id
|
|
snapper_common_sync_snapshot=$snapper_source_sync_snapshot
|
|
fi
|
|
else
|
|
# we have a common sync_id
|
|
if [ ${snapper_source_sync_id} != ${snapper_target_sync_id} ]; then
|
|
# btrfs send: use common sync_id as a valid parent
|
|
snapper_source_sync_id=$snapper_common_sync_id
|
|
snapper_source_sync_snapshot=$SUBVOLUME/.snapshots/$snapper_source_sync_id/$snapper_snapshot_name
|
|
fi
|
|
fi
|
|
|
|
cmd="$ssh stat --format %i $snapper_target_snapshot 2>/dev/null"
|
|
ret=$(eval $cmd)
|
|
if [ $? -eq 0 ]; then
|
|
case $selected_fstype in
|
|
btrfs)
|
|
# Sends the difference between the new snapshot and old synced snapshot.
|
|
# Using the flag -c (clone-src) will require the availibility of an identical readonly
|
|
# subvolume on the source and the receiving location (the parent-id).
|
|
# using "btrfs send -p" instead of "btrfs send -c", then no parent search would be
|
|
# needed (Andreij explained in: https://www.spinics.net/lists/linux-btrfs/msg69369.html)
|
|
cmd="btrfs send $btrfs_verbose_flag -p $snapper_common_sync_snapshot $snapper_source_snapshot 2>4 \
|
|
| $cmd_pv \
|
|
$cmd_ionice $ssh btrfs receive $btrfs_verbose_flag $snapper_target_snapshot/ 1>4 2>&1"
|
|
;;
|
|
*)
|
|
# Can't use btrfs receive, since target filesystem can't support btrfs snapshot feature
|
|
snapper_target_stream=${snapper_target_id}_incremental.btrfs
|
|
if [ -z $remote ]; then
|
|
cmd="btrfs send $btrfs_verbose_flag -p $snapper_common_sync_snapshot $snapper_source_snapshot 2>4 \
|
|
| $cmd_pv \
|
|
$cmd_ionice cat > $snapper_target_snapshot/$snapper_target_stream 2>4"
|
|
else
|
|
cmd="btrfs send $btrfs_verbose_flag -p $snapper_common_sync_snapshot $snapper_source_snapshot 2>4 \
|
|
| $cmd_pv \
|
|
$cmd_ionice $ssh 'cat > $snapper_target_snapshot/$snapper_target_stream' 2>4"
|
|
fi
|
|
;;
|
|
esac
|
|
if [ $verbose -ge 2 ]; then
|
|
printf "%b" "${GREEN}btrfs send${NO_COLOR} is using\n" \
|
|
" parent snapshot id: ${GREEN}'$snapper_common_sync_id'${NO_COLOR} (path: ${GREEN}'$snapper_common_sync_snapshot'${NO_COLOR}) and\n" \
|
|
" snapshot id: ${GREEN}'$snapper_source_id'${NO_COLOR} (path: ${GREEN}'$snapper_source_snapshot'${NO_COLOR})\n" \
|
|
" to construct an incremental data-stream ...\n"
|
|
fi
|
|
if [ $verbose -ge 3 ]; then
|
|
printf "${GREEN}btrfs command:${NO_COLOR} '%s'\n" "$cmd"
|
|
fi
|
|
$(eval $cmd)
|
|
ret=$?
|
|
case $ret in
|
|
0)
|
|
;;
|
|
1)
|
|
# empty stream, no changes
|
|
printf "${MAGENTA}btrfs pipe return-code: ${GREEN}'%s'${NO_COLOR}\n" "$ret"
|
|
printf "${MAGENTA} -> %s${NO_COLOR}\n" "$(cat <4)"
|
|
run_cleanup ${selected_config}
|
|
# go for next configuration
|
|
i=$(($i+1))
|
|
continue
|
|
;;
|
|
127)
|
|
printf "${MAGENTA}btrfs pipe return-code: ${GREEN}'%s'${NO_COLOR}\n" "$ret"
|
|
;;
|
|
*)
|
|
printf "${RED}btfs pipe ERROR: '%s'\n" "$ret"
|
|
printf "${RED}BTRFS_PIPE: %s${NO_COLOR}\n" "$(cat <4)"
|
|
run_cleanup ${selected_config}
|
|
# go for next configuration
|
|
i=$(($i+1))
|
|
continue
|
|
;;
|
|
esac
|
|
else
|
|
# is this clause possible?
|
|
if [ $verbose -ge 3 ]; then
|
|
printf "${RED}Error: ${MAGENTA}No commen sync snapshot ${GREEN}'%s'${MAGENTA} on ${GREEN}source${MAGENTA} to sync metadata ...${NO_COLOR} \n" \
|
|
"$snapper_common_sync_snapshot"
|
|
error_count=$(($error_count+1))
|
|
# go for next configuration
|
|
i=$(($i+1))
|
|
continue
|
|
|
|
fi
|
|
fi
|
|
fi
|
|
else
|
|
printf "${MAGENTA}dryrun${NO_COLOR}: Would run btrfs send / btrfs receive pipe\n"
|
|
fi
|
|
|
|
# send the snapper info metadata
|
|
if [ $dryrun -eq 0 ]; then
|
|
if [ -z "$remote" ]; then
|
|
cp "$snapper_source_info" "$snapper_target_snapshot/info.xml"
|
|
else
|
|
cmd="$scp $snapper_source_info root@$remote:$snapper_target_snapshot/info.xml"
|
|
$(eval $cmd) 1>/dev/null
|
|
fi
|
|
else
|
|
printf "${MAGENTA}dryrun${NO_COLOR}: Would copy info metadata '%s' to target.\n" \
|
|
"$snapper_source_info"
|
|
fi
|
|
|
|
# Save config specific values in pseudo arrays
|
|
eval "snapper_source_id_$i='$snapper_source_id'"
|
|
eval "snapper_source_snapshot_$i='$snapper_source_snapshot'"
|
|
eval "snapper_source_info_$i='$snapper_source_info'"
|
|
eval "snapper_target_id_$i='$snapper_source_id'"
|
|
eval "snapper_target_snapshot_$i='$snapper_target_snapshot'"
|
|
|
|
# finalize backup tasks
|
|
run_finalize ${selected_config}
|
|
run_cleanup ${selected_config}
|
|
|
|
if [ $verbose -ge 1 ]; then
|
|
printf "${MAGENTA}Backup complete:${NO_COLOR} id=${GREEN}'%s'${NO_COLOR}, config=${GREEN}'%s'${NO_COLOR}\n" \
|
|
"$snapper_source_id" "$selected_config"
|
|
fi
|
|
|
|
i=$(($i+1))
|
|
done
|
|
}
|
|
|
|
run_cleanup () {
|
|
local selected_config=${1}
|
|
local batch=${2:-$false}
|
|
|
|
if [ $verbose -ge 1 ]; then
|
|
printf "${MAGENTA}Performing cleanup for config ${GREEN}'%s'${MAGENTA}...${NO_COLOR}\n" \
|
|
${selected_config}
|
|
fi
|
|
|
|
if [ $dryrun -eq 0 ]; then
|
|
# cleanup failed runs
|
|
check_snapper_failed_ids "$selected_config" "$batch"
|
|
|
|
# cleanup target
|
|
#$ssh btrfs subvolume delete $backup_root/$snapper_snapshots/$snapper_target_sync_id/$snapper_snapshot_name
|
|
#$ssh rm -rf $backup_root/$snapper_snapshots/$snapper_target_sync_id
|
|
|
|
else
|
|
printf "${MAGENTA}dryrun${NO_COLOR}: Would cleanup failed snapshot IDs ...\n"
|
|
fi
|
|
}
|
|
|
|
run_finalize () {
|
|
local selected_config=${1}
|
|
|
|
if [ $verbose -ge 1 ]; then
|
|
printf "${MAGENTA}Finalize backups for config ${GREEN}'%s'${MAGENTA}...${NO_COLOR}\n" \
|
|
${selected_config}
|
|
fi
|
|
|
|
SNAP_SYNC_EXCLUDE=no
|
|
|
|
if [ -f "/etc/snapper/configs/$selected_config" ]; then
|
|
. /etc/snapper/configs/$selected_config
|
|
else
|
|
printf "${RED}Error: ${MAGENTA}Selected snapper configuration ${GREEN}'$selected_config'${MAGENTA} does not exist${NO_COLOR}"
|
|
return 1
|
|
fi
|
|
|
|
cont_backup=$(echo \$snapper_activate_$i)
|
|
if [ "$cont_backup" = "no" ] || [ "$SNAP_SYNC_EXCLUDE" = "yes" ]; then
|
|
if [ $donotify -gt 0 ]; then
|
|
notify_info "Finalize backup" "NOTE: Skipping '$selected_config' configuration."
|
|
fi
|
|
continue
|
|
fi
|
|
|
|
if [ $donotify -gt 0 ]; then
|
|
notify_info "Finalize backup" "Cleanup tasks for configuration '$selected_config'."
|
|
fi
|
|
|
|
# retrieve config specific infos from pseudo Arrays
|
|
snapper_source_config=$(eval echo \$snapper_source_config_$i)
|
|
backupdir=$(eval echo \$backupdir_$i)
|
|
backup_root=$(eval echo \$backup_root_$i)
|
|
snapper_backup_type=$(eval echo \$snapper_backup_type_$i)
|
|
snapper_source_sync_id=$(eval echo \$snapper_source_sync_id_$i)
|
|
snapper_target_sync_id=$(eval echo \$snapper_target_sync_id_$i)
|
|
snapper_source_sync_snapshot=$(eval echo \$snapper_source_sync_snapshot_$i)
|
|
snapper_source_id=$(eval echo \$snapper_source_id_$i)
|
|
snapper_source_snapshot=$(eval echo \$snapper_source_snapshot_$i)
|
|
snapper_source_info=$(eval echo \$snapper_source_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 snapper 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=$(cat /etc/hostname)
|
|
src_uuid=$(findmnt --noheadings --output UUID --target $SUBVOLUME)
|
|
src_subvolid=$(findmnt --noheadings --output OPTIONS --target $SUBVOLUME | sed -e 's/.*subvolid=\([0-9]*\).*/\1/')
|
|
|
|
# Tag new snapshots key/value parameter
|
|
if [ $verbose -ge 2 ]; then
|
|
printf "${MAGENTA}Tagging target ...${NO_COLOR}\n"
|
|
fi
|
|
if [ "$dryrun" -eq 0 ]; then
|
|
# target snapshot
|
|
# 1) wait for target snapshot to show up in snapper list
|
|
# find "$snapper_target_sync_id" -> marked as "$snap_description_running"
|
|
# 2) toggle metadata -> mark as "$snap_description_finished", reference "$target_userdata" (host, uuid, subvolid)
|
|
# snapper orders userdata pairs lexical ascending
|
|
|
|
# !!! ugly hack !!!:
|
|
# Problem: how to trigger that database is synced? -> a feature request is send to snapper upstream source
|
|
# Solution1: right now, it is no-deterministic, when the entry in the listing will show up
|
|
# -> wait ii_max * ii_sleep seconds ( 20*15 = 300 -> 5 Min)
|
|
# for snapper to list target snapshot in database.
|
|
local ii=1
|
|
local ii_max=20
|
|
local ii_sleep=15
|
|
|
|
# Solution2: kill running snapperd
|
|
# -> will restart and sync any unseen dependencies
|
|
snapperd_pid=$(eval $ssh pgrep snapperd)
|
|
if [ $verbose -ge 3 ]; then
|
|
printf "${MAGENTA}Kill runnint ${GREEN}snapperd${MAGENTA} on target id:${GREEN}'%s'${NO_COLOR} ...\n" \
|
|
"$snapperd_pid"
|
|
fi
|
|
$(eval $ssh killall -SIGTERM snapperd 2>/dev/null)
|
|
|
|
if [ $verbose -ge 3 ]; then
|
|
printf "${MAGENTA}Identify snapper id ${GREEN}'%s'${MAGENTA} on target for configuration ${GREEN}'%s'${NO_COLOR} ...\n" \
|
|
"$snapper_target_id" "$snapper_target_config"
|
|
fi
|
|
|
|
# construct snapper match command
|
|
case $snapper_backup_type in
|
|
btrfs-archive)
|
|
# archive btrfs-snapshot to non btrfs-filesystem on target (e.g tape, ext4)
|
|
# save target-id
|
|
if [ $snapper_source_id -gt 0 ]; then
|
|
cmd="snapper --config $selected_config modify \
|
|
--cleanup-algorithm \"dsnap-sync\" \
|
|
--userdata \"tapeid=$volume_name\" \
|
|
$snapper_source_id"
|
|
|
|
if [ $verbose -ge 3 ]; then
|
|
printf "${MAGENTA}Tagging snapper metadata${NO_COLOR} for snapper id ${GREEN}'%s'${NO_COLOR} on source for configuration ${GREEN}'%s'${NO_COLOR} ...\n" \
|
|
"$snapper_source_id" "$selected_config"
|
|
printf "calling: '%s'\n" "$(eval $cmd)"
|
|
fi
|
|
ret=$(eval "$cmd")
|
|
if [ $verbose -ge 3 ]; then
|
|
printf "return: '%s'\n" "$?"
|
|
fi
|
|
sync
|
|
|
|
# update tape attributes
|
|
if [ ${#mediapool_name} -gt 1 ] && [ ${#volume_name} -gt 1 ]; then
|
|
# read mounted LTFS structures
|
|
$ssh tape-admin --verbose=$verbose --update-lastwrite ${mediapool_name} ${volume_name}
|
|
$ssh tape-admin --verbose=$verbose --update-retensiondate ${mediapool_name} ${volume_name}
|
|
fi
|
|
fi
|
|
return 0
|
|
;;
|
|
btrfs-clone)
|
|
# no tagging needed
|
|
return 0
|
|
;;
|
|
btrfs-snapshot)
|
|
# create btrfs-snapshot on target
|
|
cmd="$ssh snapper --config \"$snapper_target_config\" list --type single \
|
|
| awk ' /'\"$snap_description_running\"'/ ' \
|
|
| awk -F '|' ' \$1 == $snapper_target_id {print \$1} ' "
|
|
;;
|
|
esac
|
|
|
|
while [ "$ii" -le "$ii_max" ]; do
|
|
if [ $verbose -ge 3 ]; then
|
|
printf "calling: '%s'\n" "$cmd"
|
|
fi
|
|
ret=$(eval "$cmd")
|
|
if [ $? -eq 0 ]; then
|
|
if [ ${#ret} -gt 1 ]; then
|
|
if [ $ret -eq $snapper_target_id ]; then
|
|
# got snapshot as $snapper_target_id
|
|
if [ $verbose -ge 3 ]; then
|
|
printf "${MAGENTA}Found${NO_COLOR} snapper id ${GREEN}'%s'${NO_COLOR} on target for configuration ${GREEN}'%s'${NO_COLOR}\n" \
|
|
"$snapper_target_id" "$snapper_target_config"
|
|
fi
|
|
if [ $verbose -ge 3 ]; then
|
|
printf "${MAGENTA}Tagging metadata${NO_COLOR} for snapper id ${GREEN}'%s'${NO_COLOR} on target for configuration ${GREEN}'%s'${NO_COLOR} ...\n" \
|
|
"$snapper_target_id" "$snapper_target_config"
|
|
#printf "calling: '%s'\n" "$($cmd)"
|
|
fi
|
|
# call command (respect needed quotes)
|
|
if [ $remote ]; then
|
|
ret=$(eval $ssh snapper --config \\\'$snapper_target_config\\\' modify \
|
|
--description \\\'$snap_description_finished\\\' \
|
|
--userdata \\\'host=$src_host, subvolid=$src_subvolid, uuid=$src_uuid\\\' \
|
|
--cleanup-algorithm \'timeline\' \
|
|
\'$snapper_target_id\')
|
|
else
|
|
ret=$(eval snapper --config $snapper_target_config modify \
|
|
--description "$snap_description_finished" \
|
|
--userdata "host=$src_host, subvolid=$src_subvolid, uuid=$src_uuid" \
|
|
--cleanup-algorithm "timeline" \
|
|
$snapper_target_id)
|
|
fi
|
|
if [ $verbose -ge 3 ]; then
|
|
printf "return: '%s'\n" "$ret"
|
|
fi
|
|
break
|
|
fi
|
|
fi
|
|
fi
|
|
if [ $verbose -ge 3 ]; then
|
|
printf "%s/%s: ${RED}Waiting another '%s' seconds${NO_COLOR} for snappers database update on target ...\n" \
|
|
"$ii" "$ii_max" "$ii_sleep"
|
|
fi
|
|
sleep $ii_sleep
|
|
ii=$(($ii + 1))
|
|
done
|
|
|
|
# source snapshot
|
|
if [ $verbose -ge 2 ]; then
|
|
printf "${MAGENTA}Tagging source ...${NO_COLOR}\n"
|
|
fi
|
|
if [ $snapper_source_id -gt 0 ]; then
|
|
cmd="snapper --config $selected_config modify \
|
|
--description \"$snap_description_synced\" \
|
|
--userdata \"backupdir=$backupdir, important=yes, host=$remote, subvolid=$selected_subvol, uuid=$selected_uuid\" \
|
|
--cleanup-algorithm \"timeline\" \
|
|
$snapper_source_id"
|
|
|
|
if [ $verbose -ge 3 ]; then
|
|
printf "${MAGENTA}Tagging snapper metadata${NO_COLOR} for snapper id ${GREEN}'%s'${NO_COLOR} on source for configuration ${GREEN}'%s'${NO_COLOR} ...\n" \
|
|
"$snapper_source_id" "$selected_config"
|
|
printf "calling: '%s'\n" "$cmd"
|
|
fi
|
|
ret=$(eval "$cmd")
|
|
if [ $verbose -ge 3 ]; then
|
|
printf "return: '%s'\n" "$?"
|
|
fi
|
|
sync
|
|
fi
|
|
if [ ${#snapper_source_sync_id} -gt 0 ]; then
|
|
# TODO: no snapper method to remove userdata pair, use awk
|
|
cmd="snapper --config $selected_config modify \
|
|
--description \"$snap_description_finished\" \
|
|
--userdata \"important=no\" \
|
|
$snapper_source_sync_id"
|
|
|
|
if [ $verbose -ge 3 ]; then
|
|
printf "${MAGENTA}Tagging snapper metadata${NO_COLOR} for snapper sync id ${GREEN}'%s'${NO_COLOR} on source for configuration ${GREEN}'%s'${NO_COLOR} ...\n" \
|
|
"$snapper_source_sync_id" "$selected_config"
|
|
printf "calling: '%s'\n" "$cmd"
|
|
fi
|
|
ret=$(eval "$cmd")
|
|
snapper_source_sync_snapshot=$SUBVOLUME/.snapshots/$snapper_source_sync_id/$snapper_snapshot_name
|
|
else
|
|
snapper_source_sync_snapshot=$SUBVOLUME/.snapshots/$snapper_source_id/$snapper_snapshot_name
|
|
fi
|
|
else
|
|
# dry-run output
|
|
cmd="$ssh snapper --verbose --config $snapper_target_config modify -description $snap_description_finished --userdata $target_userdata --cleanup-algorithm $snap_cleanup_algorithm $snapper_sync_id"
|
|
printf "${MAGENTA}dryrun${NO_COLOR}: %s\n" "$cmd"
|
|
cmd="snapper --config $selected_config modify --description $snap_description_synced --userdata $userdata $snapper_sync_id"
|
|
printf "${MAGENTA}dryrun${NO_COLOR}: %s\n" "$cmd"
|
|
cmd="snapper --config $selected_config modify --description $snap_description_finished $snapper_source_sync_id"
|
|
printf "${MAGENTA}dryrun${NO_COLOR}: %s\n" "$cmd"
|
|
fi
|
|
}
|
|
|
|
select_target () {
|
|
local i=0
|
|
local target_id=0
|
|
|
|
local target_selected_count=0
|
|
local subvolid=''
|
|
local subvol=''
|
|
|
|
if [ $verbose -ge 1 ]; then
|
|
printf "${BLUE}Select backup target...${NO_COLOR}\n"
|
|
fi
|
|
|
|
# print selection table
|
|
if [ $verbose -ge 2 ]; then
|
|
if [ -z "$remote" ]; then
|
|
printf "Selecting a mounted device for backups on your local machine.\n"
|
|
else
|
|
printf "Selecting a mounted device for backups on %s.\n" "$remote"
|
|
fi
|
|
fi
|
|
while [ "$target_id" -eq 0 ] || [ "$target_id" -le $target_count ]; do
|
|
if [ "$disk_subvolid_match_count" -eq 1 ]; then
|
|
# matching SUBVOLID selection from commandline
|
|
if [ $verbose -ge 3 ]; then
|
|
printf "%s mount points were found with SUBVOLID '%s'.\n" \
|
|
"$disk_subvolid_match_count" "$subvolid_cmdline"
|
|
fi
|
|
# Pseudo-Array: target_selected_$i (reference to $disk_uuid element)
|
|
eval "target_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/')
|
|
target_selected=$disk_subvolid_match
|
|
break
|
|
fi
|
|
if [ "$target_match_count" -eq 1 ]; then
|
|
# matching TARGET selection from commandline
|
|
if [ $verbose -ge 3 ]; then
|
|
printf "%s mount points were found with TARGET '%s'.\n" \
|
|
"$target_match_count" "$target_cmdline"
|
|
fi
|
|
# Pseudo-Array: target_selected_$i (reference to $disk_uuid element)
|
|
eval "target_selected_$i='$disk_target_match'"
|
|
disk=$(eval echo \$disk_uuid_$target_match)
|
|
#target=$(eval echo \$disk_target_$disk_target_match)
|
|
target=$(eval echo \$target_$target_match)
|
|
fs_options=$(eval echo \$fs_options_$target_match | sed -e 's/.*,\(subvolid=[0-9]*\).*,\(subvol=[0-9]*\)/\1,\2/')
|
|
target_selected=$target_match
|
|
break
|
|
fi
|
|
if [ "$disk_uuid_match_count" -ge 1 ]; then
|
|
# matching UUID selection from commandline
|
|
target_count=$disk_uuid_match_count
|
|
if [ $verbose -ge 3 ]; 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 "target_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/')
|
|
if [ $disk_uuid_match -gt 1 ]; then
|
|
printf "%4s) %s (uuid=%s,%s)\n" "$i" "$target" "$disk" "$fs_options"
|
|
i=$((i+1))
|
|
else
|
|
target_selected=$disk_uuid_match
|
|
break
|
|
fi
|
|
done
|
|
else
|
|
while [ "$target_id" -lt $target_count ]; do
|
|
# present all mounted BTRFS filesystems
|
|
if [ $verbose -ge 3 ]; then
|
|
printf "Present selection for '%s' available targets\n" \
|
|
"$target_count"
|
|
fi
|
|
# Pseudo-Array: target_selected_$i (reference to $target_id element)
|
|
eval "target_selected_$i='$target_id'"
|
|
disk=$(eval echo \$disk_uuid_$target_id)
|
|
target=$(eval echo \$target_$target_id)
|
|
fs_options=$(eval echo \$fs_options_$target_id | sed -e 's/.*,\(subvolid=[0-9]*\).*,\(subvol=[0-9]*\)/\1,\2/')
|
|
fs_type=$(eval echo \$fs_type_$target_id)
|
|
case $fs_type in
|
|
btrfs)
|
|
printf "%4s) %s (type=%s,uuid=%s,%s)\n" "$i" "$target" "$fs_type" "$disk" "$fs_options"
|
|
;;
|
|
ltfs)
|
|
printf "%4s) %s (type=%s,%s)\n" "$i" "$target" "$fs_type" "$fs_options"
|
|
;;
|
|
esac
|
|
i=$((i+1))
|
|
target_id=$(($target_id+1))
|
|
done
|
|
fi
|
|
printf "%4s) Exit\n" "x"
|
|
read -r -p "Enter a number: " target_selected
|
|
case $target_selected in
|
|
x)
|
|
break
|
|
;;
|
|
[0-9][0-9]|[0-9])
|
|
if [ "$target_selected" -gt "$target_count" ]; then
|
|
target_id=0
|
|
i=0
|
|
else
|
|
break
|
|
fi
|
|
;;
|
|
*)
|
|
printf "\nNo disk selected. Select a disk to continue.\n"
|
|
target_id=0
|
|
i=0
|
|
;;
|
|
esac
|
|
done
|
|
if [ "$target_selected" = x ]; then
|
|
exit 0
|
|
fi
|
|
|
|
selected_target=$(eval echo \$target_$target_selected)
|
|
selected_fstype=$(eval echo \$fs_type_$target_selected)
|
|
selected_uuid=$(eval echo \$disk_uuid_$target_selected)
|
|
selected_subvol=$(eval echo \$fs_options_$target_selected | sed -e 's/.*subvolid=\([0-9]*\).*/\1/')
|
|
|
|
if [ $verbose -ge 2 ]; then
|
|
case $selected_fstype in
|
|
btrfs)
|
|
printf "${MAGENTA}You selected %s target with UUID ${GREEN}'%s'${MAGENTA} (subvolid=${GREEN}'%s'${MAGENTA})${NO_COLOR}.\n" \
|
|
"$selected_fstype" "$selected_uuid" "$selected_subvol"
|
|
;;
|
|
ltfs)
|
|
printf "${MAGENTA}You selected %s target (VolumeName=${GREEN}'%s'${MAGENTA})${NO_COLOR}.\n" \
|
|
"$selected_fstype" "$volume_name"
|
|
;;
|
|
esac
|
|
if [ -z "$remote" ]; then
|
|
printf "${MAGENTA}Target is mounted at ${GREEN}'%s'${MAGENTA}.${NO_COLOR}\n" \
|
|
"$selected_target"
|
|
else
|
|
printf "${MAGENTA}Target disk is mounted on host ${GREEN}'%s'${MAGENTA} at ${GREEN}'%s'${MAGENTA}.${NO_COLOR}\n" \
|
|
"$remote" "$selected_target"
|
|
fi
|
|
fi
|
|
|
|
if [ $donotify -gt 0 ]; then
|
|
if [ "$target_cmdline" != "none" ]; then
|
|
if [ -z "$remote" ]; then
|
|
notify_info "Target selected" "Using target '$target_cmdline'..."
|
|
else
|
|
notify_info "Target selected" "Using target '$target_cmdline' at '$remote'..."
|
|
fi
|
|
elif [ "$uuid_cmdline" != "none" ]; then
|
|
if [ -z "$remote" ]; then
|
|
notify_info "Target selected" "Using targets uuid '$uuid_cmdline'..."
|
|
else
|
|
notify_info "Target selected" "Using targets uuid '$uuid_cmdline' at '$remote'..."
|
|
fi
|
|
else
|
|
if [ -z "$remote" ]; then
|
|
notify_info "Target selected" "Use command line menu to select target disk..."
|
|
else
|
|
notify_info "Target selected" "Use command line menu to select target disk on $remote..."
|
|
fi
|
|
fi
|
|
fi
|
|
}
|
|
|
|
select_target_disk () {
|
|
local i=0
|
|
local disk_id=0
|
|
|
|
local disk_selected_count=0
|
|
local subvolid=''
|
|
local subvol=''
|
|
|
|
if [ $verbose -ge 1 ]; then
|
|
printf "${BLUE}Select backup target...${NO_COLOR}\n"
|
|
fi
|
|
|
|
# print selection table
|
|
if [ $verbose -ge 2 ]; then
|
|
if [ -z "$remote" ]; 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
|
|
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 -ge 2 ]; 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
|
|
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 -ge 2 ]; then
|
|
printf "${MAGENTA}You selected the disk with UUID ${GREEN}'%s'${MAGENTA} (subvolid=${GREEN}'%s'${MAGENTA})${NO_COLOR}.\n" \
|
|
"$selected_uuid" "$selected_subvol"
|
|
if [ -z "$remote" ]; then
|
|
printf "${MAGENTA}Target disk is mounted at ${GREEN}'%s'${MAGENTA}.${NO_COLOR}\n" \
|
|
"$selected_target"
|
|
else
|
|
printf "${MAGENTA}Target disk is mounted on host ${GREEN}'%s'${MAGENTA} at ${GREEN}'%s'${MAGENTA}.${NO_COLOR}\n" \
|
|
"$remote" "$selected_target"
|
|
fi
|
|
fi
|
|
|
|
if [ $donotify -gt 0 ]; then
|
|
if [ "$target_cmdline" != "none" ]; then
|
|
if [ -z "$remote" ]; then
|
|
notify_info "Target selected" "Using target '$target_cmdline'..."
|
|
else
|
|
notify_info "Target selected" "Using target '$target_cmdline' at '$remote'..."
|
|
fi
|
|
elif [ "$uuid_cmdline" != "none" ]; then
|
|
if [ -z "$remote" ]; then
|
|
notify_info "Target selected" "Using targets uuid '$uuid_cmdline'..."
|
|
else
|
|
notify_info "Target selected" "Using targets uuid '$uuid_cmdline' at '$remote'..."
|
|
fi
|
|
else
|
|
if [ -z "$remote" ]; then
|
|
notify_info "Target selected" "Use command line menu to select target disk..."
|
|
else
|
|
notify_info "Target selected" "Use command line menu to select target disk on $remote..."
|
|
fi
|
|
fi
|
|
fi
|
|
}
|
|
|
|
select_target_tape () {
|
|
local i=0
|
|
local tape_id=0
|
|
|
|
local tape_selected_count=0
|
|
local subvolid=''
|
|
local subvol=''
|
|
|
|
if [ $verbose -ge 1 ]; then
|
|
printf "${BLUE}Select target tape...${NO_COLOR}\n"
|
|
fi
|
|
|
|
# print selection table
|
|
if [ $verbose -ge 2 ]; then
|
|
if [ -z "$remote" ]; then
|
|
printf "Selecting a mounted LTFS tape for backups on your local machine.\n"
|
|
else
|
|
printf "Selecting a mounted LTFS tape for backups on %s.\n" "$remote"
|
|
fi
|
|
fi
|
|
while [ "$tape_id" -eq -1 ] || [ "$tape_id" -le $tape_count ]; do
|
|
if [ "$tape_match_count" -eq 1 ]; then
|
|
# matching LTFS selection from commandline
|
|
# Pseudo-Array: tape_selected_$i (reference to $tape_uuid element)
|
|
eval "tape_selected_$i='$tape_id_match'"
|
|
tape=$(eval echo \$tape_id_$tape_id_match)
|
|
#subvolid=$(eval echo \$tape_id_$tape_id_match)
|
|
#fs_options=$(eval echo \$tape_fs_options_$tape_id_match | sed -e 's/.*,\(subvolid=[0-9]*\).*,\(subvol=[0-9]*\)/\1,\2/')
|
|
tape_selected=$tape_id_match
|
|
break
|
|
fi
|
|
if [ "$tape_target_match_count" -eq 1 ]; then
|
|
# matching TARGET selection from commandline
|
|
# Pseudo-Array: tape_selected_$i (reference to $tape_id element)
|
|
eval "tape_selected_$i='$tape_target_match'"
|
|
tape=$(eval echo \$tape_id_$tape_target_match)
|
|
target=$(eval echo \$tape_target_$tape_target_match)
|
|
#fs_options=$(eval echo \$fs_options_$tape_target_match | sed -e 's/.*,\(subvolid=[0-9]*\).*,\(subvol=[0-9]*\)/\1,\2/')
|
|
tape_selected=$tape_target_match
|
|
break
|
|
fi
|
|
if [ "$tape_id_match_count" -gt 1 ]; then
|
|
# got LTFS ID selection from commandline
|
|
tape_count=$tape_id_match_count
|
|
if [ $verbose -ge 2 ]; then
|
|
printf "%s mount points were found with ID '%s'.\n" "$tape_id_match_count" "$uuid_cmdline"
|
|
fi
|
|
for tape_id in $tape_id_match; do
|
|
# Pseudo-Array: tape_selected_$i (reference to $tape_uuid element)
|
|
eval "tape_selected_$i='$tape_id'"
|
|
tape=$(eval echo \$tape_id_$tape_id)
|
|
tape_fs_options=$(eval echo \$fs_options_$tape_id | sed -e 's/.*,\(subvolid=[0-9]*\).*,\(subvol=[0-9]*\)/\1,\2/')
|
|
printf "%4s) %s (id=%s,%s)\n" "$i" "$target" "$tape" "$tape_fs_options"
|
|
i=$((i+1))
|
|
done
|
|
else
|
|
while [ "$tape_id" -le $tape_count ]; do
|
|
# present all mounted BTRFS filesystems
|
|
# Pseudo-Array: tape_selected_$i (reference to $tape_id element)
|
|
eval "tape_selected_$i='$tape_id'"
|
|
tape=$(eval echo \$tape_id_$tape_id)
|
|
target=$(eval echo \$tape_target_$tape_id)
|
|
#tape_fs_options=$(eval echo \$target_fs_options_$tape_id | sed -e 's/.*,\(subvolid=[0-9]*\).*,\(subvol=[0-9]*\)/\1,\2/')
|
|
printf "%4s) %s\n" "$i" "$target"
|
|
i=$((i+1))
|
|
tape_id=$(($tape_id+1))
|
|
done
|
|
fi
|
|
printf "%4s) Exit\n" "x"
|
|
read -r -p "Enter a number: " tape_selected
|
|
case $tape_selected in
|
|
x)
|
|
break
|
|
;;
|
|
[0-9][0-9]|[0-9])
|
|
if [ "$tape_selected" -gt "$tape_count" ]; then
|
|
tape_id=0
|
|
i=0
|
|
else
|
|
break
|
|
fi
|
|
;;
|
|
*)
|
|
printf "\nNo LTFS tape selected. Select a tape to continue.\n"
|
|
tape_id=0
|
|
i=0
|
|
;;
|
|
esac
|
|
done
|
|
if [ "$tape_selected" = x ]; then
|
|
exit 0
|
|
fi
|
|
|
|
selected_tape_id=$(eval echo \$tape_id_$tape_selected)
|
|
selected_tape_target=$(eval echo \$tape_target_$tape_selected)
|
|
#selected_subvol=$(eval echo \$fs_options_$tape_selected | sed -e 's/.*subvolid=\([0-9]*\).*/\1/')
|
|
|
|
if [ $verbose -ge 2 ]; then
|
|
printf "${MAGENTA}You selected the LTFS tape with ID ${GREEN}'%s'${MAGENTA}${NO_COLOR}.\n" \
|
|
"$selected_tape_id"
|
|
if [ -z "$remote" ]; then
|
|
printf "${MAGENTA}Target tape is mounted at ${GREEN}'%s'${MAGENTA}.${NO_COLOR}\n" \
|
|
"$selected_tape_target"
|
|
else
|
|
printf "${MAGENTA}Target tape is mounted on host ${GREEN}'%s'${MAGENTA} at ${GREEN}'%s'${MAGENTA}.${NO_COLOR}\n" \
|
|
"$remote" "$selected_tape_target"
|
|
fi
|
|
fi
|
|
|
|
if [ $donotify -gt 0 ]; then
|
|
if [ "$target_cmdline" != "none" ]; then
|
|
if [ -z "$remote" ]; then
|
|
notify_info "Target selected" "Using target '$target_cmdline'..."
|
|
else
|
|
notify_info "Target selected" "Using target '$target_cmdline' at '$remote'..."
|
|
fi
|
|
elif [ "$tape_id_cmdline" != "none" ]; then
|
|
if [ -z "$remote" ]; then
|
|
notify_info "LTFS Target selected" "Using LTFS targets '$tape_id_cmdline'..."
|
|
else
|
|
notify_info "LTFS Target selected" "Using LTFS targets uuid '$tape_id_cmdline' at '$remote'..."
|
|
fi
|
|
else
|
|
if [ -z "$remote" ]; then
|
|
notify_info "Target selected" "Use command line menu to select target disk..."
|
|
else
|
|
notify_info "Target selected" "Use command line menu to select target disk on $remote..."
|
|
fi
|
|
fi
|
|
fi
|
|
}
|
|
|
|
set_config(){
|
|
local config=${1:-/etc/snapper/config-templates/"$snapper_subvolume_template"}
|
|
local config_key=${2:-SUBVOLUME}
|
|
local config_value=${3:-/var/lib/dsnap-sync}
|
|
|
|
if [ -n "$remote" ]; then
|
|
$ssh sed -i \'"s#^\($config_key\s*=\s*\).*\$#\1\"$config_value\"#"\' $config
|
|
else
|
|
sed -i "s#^\($config_key\s*=\s*\).*\$#\1\"$config_value\"#" $config
|
|
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 () {
|
|
printf "Exited due to user intervention.\n"
|
|
run_cleanup $selected_config
|
|
exit 0
|
|
}
|
|
|
|
usage () {
|
|
cat <<EOF
|
|
$progname $version
|
|
Usage: $progname [options]
|
|
|
|
Options:
|
|
-a, --automount <path> start automount for given path to get a valid target mountpoint
|
|
-b, --backupdir <prefix> backupdir is a relative path that will be appended to target backup-root
|
|
--backuptype <type> Specify backup type <clone | archive>
|
|
--batch no user interaction
|
|
-d, --description <desc> Change the snapper description. Default: "latest incremental backup"
|
|
--label-finished <desc> snapper description tagging successful jobs. Default: "dsnap-sync backup"
|
|
--label-running <desc> snapper description tagging active jobs. Default: "dsnap-sync in progress"
|
|
--label-synced <desc> snapper description tagging last synced jobs
|
|
Default: "dsnap-sync last incremental"
|
|
--color Enable colored output messages
|
|
-c, --config <config> Specify the snapper configuration to use. Otherwise will perform for each snapper
|
|
configuration. You can select multiple configurations
|
|
(e.g. -c "root" -c "home"; --config root --config home)
|
|
--config-postfix <name> Specify a postfix that will be appended to the destination snapper config name
|
|
--dry-run perform a trial run (no changes are written)
|
|
--mediapool Specify the name of the tape MediaPool
|
|
-n, --noconfirm Do not ask for confirmation for each configuration. Will still prompt for backup
|
|
--nonotify Disable graphical notification (via dbus)
|
|
--nopv Disable graphical progress output (disable pv)
|
|
--noionice Disable setting of I/O class and priority options on target
|
|
-p, --port <port> The remote port
|
|
-r, --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
|
|
-s, --subvolid <subvlid> Specify the subvolume id of the mounted BTRFS subvolume to back up to. Defaults to 5
|
|
--use-btrfs-quota use btrfs-quota to calculate snapshot size
|
|
-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 selection
|
|
-t, --target <target> Specify the mountpoint of the backup device
|
|
--volumename Specify the name of the tape volume
|
|
-v, --verbose Be verbose on what's going on (min: --verbose=1, max: --verbose=3)
|
|
--version show program version
|
|
EOF
|
|
|
|
exit 0
|
|
}
|
|
|
|
verify_archive_structure () {
|
|
local backup_root=${1##backup_root=}
|
|
local snapper_config=${2##snapper_target_config=}
|
|
local snapper_id=${3##snapper_target_sync_id=}
|
|
local remote_host=${4##remote=}
|
|
|
|
if [ $verbose -ge 3 ]; then
|
|
printf "${MAGENTA}verify-archive_structure()...${NO_COLOR}\n"
|
|
fi
|
|
|
|
if [ $verbose -ge 3 ]; then
|
|
printf "${MAGENTA}Verify archive filesystem structure${NO_COLOR} on target %s...\n" \
|
|
"$remote"
|
|
fi
|
|
|
|
# if not accessible, create backup-path
|
|
cmd="$ssh stat --format %i $backup_root 2>/dev/null"
|
|
ret=$(eval $cmd)
|
|
if [ $? -eq 1 ]; then
|
|
# strip last dir from backup_root
|
|
#base_path=${backup_root%/*}; echo $base_path
|
|
if [ $dryrun -eq 0 ]; then
|
|
#$($ssh mkdir --mode=0700 --parents $base_path)
|
|
$($ssh mkdir --mode=0700 --parents $backup_root)
|
|
if [ $verbose -ge 3 ]; then
|
|
if [ -z $remote_host ]; then
|
|
printf "${MAGENTA}Create${NO_COLOR} new backup base-path ${GREEN}'%s'${NO_COLOR}...\n" \
|
|
"$backup_root"
|
|
else
|
|
printf "${MAGENTA}Create${NO_COLOR} new backup base-path ${GREEN}'%s'${NO_COLOR} on ${MAGENTA}remote host ${GREEN}'%s'${NO_COLOR=} ...\n" \
|
|
"$backup_root" "$remote_host"
|
|
fi
|
|
fi
|
|
else
|
|
if [ -z $remote_host ]; then
|
|
printf "${MAGENTA}dryrun${NO_COLOR}: Would create backup-path %s ...\n" \
|
|
"$base_path"
|
|
else
|
|
printf "${MAGENTA}dryrun${NO_COLOR}: Would create backup-path %s on remote host %s ...\n" \
|
|
"$remote_host" "$base_path"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# archive type: full or incremental
|
|
# full snyc: save a btrfs full-stream
|
|
# incremental: save a btrfs incremental-stream. Stream will depend on parent
|
|
# restore process:
|
|
# 1) copy in last full to btrfs filesystem
|
|
# 2) loop though ordered incremental path: "cat <stream> | btrfs recieve"
|
|
|
|
# verify that target can take the new archive for given snapshot id
|
|
if [ "$dryrun" -eq 0 ]; then
|
|
if [ $verbose -ge 2 ]; then
|
|
printf "${MAGENTA}Verify existence of path ${GREEN}'%s'${NO_COLOR}\n" \
|
|
"$backup_root/$snapper_id"
|
|
fi
|
|
cmd="$ssh stat --format %i $backup_root/$snapper_id 2>/dev/null"
|
|
ret=$(eval $cmd)
|
|
if [ $? -eq 1 ]; then
|
|
# Path does not exist
|
|
if [ $verbose -ge 3 ]; then
|
|
printf "${MAGENTA}Create${NO_COLOR} path ${GREEN}'%s'${NO_COLOR} to store target snapshot.\n" \
|
|
"$backup_root/$snapper_id"
|
|
fi
|
|
ret=$(eval $ssh mkdir --mode=0700 \
|
|
$backup_root/$snapper_id)
|
|
if [ $? -ne 0 ]; then
|
|
printf "${RED}ERROR: ${MAGENTA}Cancel path snapshot creation: Can't create path ${GREEN}'%s'${MAGENTA} to store target snapshot${NO_COLOR}\n" \
|
|
"$backup_root/$snapper_id"
|
|
return 1
|
|
fi
|
|
fi
|
|
else
|
|
printf "${MAGENTA}dryrun${NO_COLOR}: Would check/create path %s to store target snapshot ...\n" \
|
|
"$backup_root/$snapper_snapshots/$snapper_id"
|
|
fi
|
|
}
|
|
|
|
verify_backupdir () {
|
|
if [ $verbose -ge 3 ]; then
|
|
printf "${MAGENTA}verify_backupdir()...${NO_COLOR}\n"
|
|
fi
|
|
|
|
# verify backupdir
|
|
if [ "$snapper_target_sync_id" -eq 0 ]; then
|
|
# first backup run
|
|
snapper_target_sync_id=0
|
|
if [ "$backupdir_cmdline" != "none" ]; then
|
|
backupdir=$backupdir_cmdline
|
|
backup_root="$selected_target/$backupdir"
|
|
else
|
|
if [ ! $batch ]; 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
|
|
fi
|
|
}
|
|
|
|
verify_snapper_config () {
|
|
if [ $verbose -ge 3 ]; then
|
|
printf "${MAGENTA}verify_snapper_config()...${NO_COLOR}\n"
|
|
fi
|
|
|
|
if [ ! -f "/etc/snapper/configs/$selected_config" ]; then
|
|
if [ $verbose -ge 2 ]; then
|
|
printf "Did you forget to create the snapper configuration for config '%s' on source?\n" \
|
|
"$selected_config"
|
|
printf "You can create it with following command:\n"
|
|
printf "${MAGENTA}snapper --config ${GREEN}%s${MAGENTA} create-config --template ${GREEN}%s${MAGENTA} <btrfs-subvolume-path>${NO_COLOR}\n" \
|
|
"$selected_config" "$snapper_subvolume_template"
|
|
fi
|
|
printf "${RED}Error: ${MAGENTA}Can't backup selected snapper configuration ${GREEN}'$selected_config'${MAGENTA}, that does not exist${NO_COLOR}\n"
|
|
return 1
|
|
else
|
|
. /etc/snapper/configs/$selected_config
|
|
if [ "$SUBVOLUME" = "/" ]; then
|
|
SUBVOLUME=''
|
|
fi
|
|
count=$(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 "${RED}Error: ${GREEN}%s${NO_COLOR} entries are ${RED}marked as ${GREEN}'%s'${NO_COLOR} for snapper config ${RED}'%s'${NO_COLOR}\n" \
|
|
"$count" "$snap_description_synced" "$selected_config"
|
|
printf "Pointing to target with UUID ${GREEN}'%s'${NO_COLOR} and SUBVOLID ${GREEN}'%s'${NO_COLOR}. Skipping configuration ${GREEN}'%s'${NO_COLOR}.\n" \
|
|
"$selected_uuid" "$selected_subvol" "$selected_config"
|
|
printf "Please cleanup for further processing.\n"
|
|
error "Skipping configuration $selected_config."
|
|
$snapper_activate_$i="no"
|
|
continue
|
|
fi
|
|
fi
|
|
}
|
|
|
|
verify_snapper_structure () {
|
|
local backup_root=${1##backup_root=}
|
|
local snapper_config=${2##snapper_target_config=}
|
|
local snapper_id=${3##snapper_target_id=}
|
|
local remote_host=${4##remote=}
|
|
|
|
if [ $verbose -ge 3 ]; then
|
|
printf "${MAGENTA}verify-snapper_structure()...${NO_COLOR}\n"
|
|
fi
|
|
|
|
if [ $verbose -ge 3 ]; then
|
|
printf "${MAGENTA}Verify snapper filesystem structure${NO_COLOR} on target ${GREEN}'%s'${NO_COLOR}...\n" \
|
|
"$remote"
|
|
fi
|
|
|
|
# if not accessible, create backup-path
|
|
cmd="$ssh stat --format %i $backup_root 2>/dev/null"
|
|
ret=$(eval $cmd)
|
|
if [ $? -eq 1 ]; then
|
|
if [ "$dryrun" -eq 0 ]; then
|
|
if [ $verbose -ge 3 ]; then
|
|
if [ -z $remote_host ]; then
|
|
printf "${MAGENTA}Create${NO_COLOR} new backup-path ${GREEN}'%s'${NO_COLOR}...\n" \
|
|
"$backup_root"
|
|
else
|
|
printf "${MAGENTA}Create${NO_COLOR} new backup-path ${GREEN}'%s'${NO_COLOR} on ${MAGENTA}remote host ${GREEN}'%s'${NO_COLOR=} ...\n" \
|
|
"$backup_root" "$remote_host"
|
|
fi
|
|
fi
|
|
# strip last dir from backup_root
|
|
base_path=${backup_root%/*}
|
|
if [ ${#base_path} -ge 1 ]; then
|
|
if [ $dryrun -eq 0 ]; then
|
|
if [ $verbose -ge 3 ]; then
|
|
if [ -z $remote_host ]; then
|
|
printf "${MAGENTA}Create${NO_COLOR} new backup-path ${GREEN}'%s'${NO_COLOR}...\n" \
|
|
"$backup_root"
|
|
else
|
|
printf "${MAGENTA}Create${NO_COLOR} new backup-path ${GREEN}'%s'${NO_COLOR} on ${MAGENTA}remote host ${GREEN}'%s'${NO_COLOR=} ...\n" \
|
|
"$backup_root" "$remote_host"
|
|
fi
|
|
fi
|
|
ret=$(eval $ssh mkdir --mode=0700 --parents $base_path)
|
|
if [ $? -eq 1 ]; then
|
|
if [ -z "$remote" ]; then
|
|
printf "${RED}Error: Can't create${NO_COLOR} new snapper capable BTRFS ${MAGENTA}subvolume ${GREEN}'%s'${NO_COLOR} ...\n" \
|
|
"$backup_root"
|
|
else
|
|
printf "${RED}Error: Can't create${NO_COLOR} new snapper capable BTRFS ${MAGENTA}subvolume ${GREEN}'%s'${NO_COLOR} on ${MAGENTA}remote host ${GREEN}'%s'${NO_COLOR} ...\n" \
|
|
"$backup_root" "$remote_host"
|
|
fi
|
|
return 1
|
|
fi
|
|
else
|
|
if [ -z $remote_host ]; then
|
|
printf "${MAGENTA}dryrun${NO_COLOR}: Would create backup-path %s ...\n" \
|
|
"$base_path"
|
|
else
|
|
printf "${MAGENTA}dryrun${NO_COLOR}: Would create backup-path %s on remote host %s ...\n" \
|
|
"$remote_host" "$base_path"
|
|
fi
|
|
fi
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# verify that we have a snapper compatible structure for target config (a btrfs subvolume)
|
|
cmd="$ssh stat --format %i $backup_root 2>/dev/null"
|
|
ret=$(eval $cmd)
|
|
if [ $? -eq 1 ]; then
|
|
# no inode for given backup_root
|
|
if [ $dryrun -eq 0 ]; then
|
|
if [ $verbose -ge 2 ]; then
|
|
if [ -z "$remote" ]; then
|
|
printf "${MAGENTA}Create${NO_COLOR} new snapper capable BTRFS ${MAGENTA}subvolume ${GREEN}'%s'${NO_COLOR} ...\n" \
|
|
"$backup_root"
|
|
else
|
|
printf "${MAGENTA}Create${NO_COLOR} new snapper capable BTRFS ${MAGENTA}subvolume ${GREEN}'%s'${NO_COLOR} on ${MAGENTA}remote host ${GREEN}'%s'${NO_COLOR} ...\n" \
|
|
"$backup_root" "$remote_host"
|
|
fi
|
|
fi
|
|
# verify that we can use the correct snapper template
|
|
cmd="$ssh stat --format %i $SNAPPER_TEMPLATE_DIR/$snapper_subvolume_template 2>/dev/null"
|
|
if [ -z $(eval $cmd) ]; then
|
|
printf "${RED}Error: ${MAGENTA}Missing a snapper template %s${MAGENTA} to configure the snapper subvolume ${GREEN}%s${MAGENTA} in ${GREEN}%s${MAGENTA} on ${GREEN}%s${NO_COLOR}\n" \
|
|
"$snapper_subvolume_template" "$snapper_config" "$SNAPPER_TEMPLATE_DIR" "$remote_host"
|
|
printf "${RED}Error: ${NO_COLOR}Did you miss to install the dsnap-sync's default snapper template on %s?\n" \
|
|
"$remote"
|
|
return 1
|
|
#die "snapper template %s to configure the snapper subvolume %s is missing in %s on %s.\n" \
|
|
# "$snapper_subvolume_template" "$snapper_config" "$SNAPPER_TEMPLATE_DIR" "$remote_host"
|
|
fi
|
|
# create the non existing remote BTRFS subvolume for given config
|
|
cmd="$ssh btrfs subvolume create $backup_root 1>/dev/null"
|
|
ret=$(eval $cmd)
|
|
if [ $ret -eq 1 ]; then
|
|
printf "${RED}Error: ${MAGENTA}Creation of BTRFS subvolume (backup-root) ${GREEN}%s:%s${MAGENTA} failed.${NO_COLOR}\n" \
|
|
"$remote_host" "$backup_root"
|
|
return 1
|
|
else
|
|
cmd="$ssh chmod 0700 $backup_root"
|
|
ret=$(eval $cmd)
|
|
if [ $ret -eq 1 ]; then
|
|
printf "${RED}Error: ${MAGENTA}Changing directory-mode for BTRFS subvolume (backup-root) ${GREEN}%s:%s${MAGENTA} failed.${NO_COLOR}\n" \
|
|
"$remote_host" "$backup_root"
|
|
return 1
|
|
fi
|
|
fi
|
|
# create the non existing remote BTRFS subvolume for given snapshot
|
|
#cmd="$ssh btrfs subvolume create $backup_root/$snapper_snapshot 1>/dev/null"
|
|
#$($cmd) || \
|
|
# die "Creation of BTRFS subvolume (snapshot): %s:%s failed.\n" \
|
|
# "$remote_host" "$backup_root" "$remote_host"
|
|
# cmd="$ssh chmod 0700 $backup_root 1>/dev/null"
|
|
# $($cmd) || \
|
|
# die "Changing the directory mode for '$backup_root' on '$remote_host'."
|
|
else
|
|
printf "${MAGENTA}dryrun${NO_COLOR}: Would create new snapper configuration from template %s ...\n" "$snapper_subvolume_template"
|
|
printf "${MAGENTA}dryrun${NO_COLOR}: Would create new snapper subvolume '%s' ...\n" "$backup_root/$snapper_snapshot"
|
|
fi
|
|
elif [ $? -eq 0 ]; then
|
|
# 256: a btrfs subvolume
|
|
if [ $ret -ne 256 ]; then
|
|
printf "${RED}Error: ${GREEN}%s ${MAGENTA}needs to be a BTRFS subvolume. But given ${GREEN}%s ${MAGENTA}is just a directory.${NO_COLOR}\n" \
|
|
"$snapper_config" "$backup_root"
|
|
return 1
|
|
fi
|
|
if [ $verbose -ge 3 ]; then
|
|
printf "${RED}TODO:${NO_COLOR} check and adapt SUBVOLUME in given config '%s', since mount path might have changed meanwhile\n" "$snapper_config"
|
|
fi
|
|
#$ssh $(. $SNAPPER_CONFIG_DIR/$snapper_config)
|
|
#get_snapper_config "$SNAPPER_CONFIG_DIR/$snapper_config" "SUBVOLUME"
|
|
#if $ssh [ "$SUBVOLUME" != \"$backup_root\" ]; then
|
|
# SUBVOLUME="$backup_root"
|
|
# set_config "$SNAPPER_CONFIG_DIR/$snapper_config" "SUBVOLUME" "$SUBVOLUME"
|
|
#fi
|
|
fi
|
|
|
|
# verify that we have a valid snapper config
|
|
if [ $dryrun -eq 0 ]; then
|
|
cmd="$ssh stat --format %i $SNAPPER_CONFIG_DIR/$snapper_config 2>/dev/null"
|
|
ret=$(eval $cmd)
|
|
if [ $? -eq 1 ]; then
|
|
# path does not exist, let snapper create the structure
|
|
# and path $backup_root/.snapshots
|
|
cmd="$ssh snapper --config $snapper_config create-config \
|
|
--template $snapper_subvolume_template \
|
|
--fstype btrfs $backup_root"
|
|
if [ $verbose -ge 2 ]; then
|
|
printf "${MAGENTA}Create new BTRFS subvolume ${GREEN}'%s'${NO_COLOR} using template ${GREEN}'%s'${NO_COLOR}\n" \
|
|
$snapper_config $snapper_subvolume_template
|
|
fi
|
|
$(eval $cmd)
|
|
if [ $? -ne 0 ]; then
|
|
if [ $remote ]; then
|
|
printf "${RED}Error: ${MAGENTA}Creation of snapper capable config ${GREEN}%s${MAGENTA} on ${GREEN}%s${MAGENTA} failed${NO_COLOR}\n" \
|
|
"$backup_root" "$remote_host"
|
|
else
|
|
printf "${RED}Error: ${MAGENTA}Creation of snapper capable config ${GREEN}%s${MAGENTA} failed${NO_COLOR}\n" \
|
|
"$backup_root"
|
|
fi
|
|
return 1
|
|
fi
|
|
else
|
|
# WIP:
|
|
# snapper_config exist, now verify if SUBVOLUME needs to be updated
|
|
cmd="$ssh snapper list-configs | awk -F '|' '/'\"^$snapper_config\"'/ {print \$1}'"
|
|
#cmd="$ssh snapper list-configs | awk '/'\"^$snapper_config\"'/' | awk -F '|' ' /'\$1 == "$snapper_config"'/ {print \$1}'"
|
|
if [ -n $(eval $cmd) ]; then
|
|
# if changed, adapt targets SUBVOLUME path
|
|
if [ $verbose -ge 3 ]; then
|
|
printf "${RED}TODO:${NO_COLOR} Check if value for key 'SUBVOLUME' needs an update in snapper config %s\n" \
|
|
"$snapper_config"
|
|
fi
|
|
# WIP: Update SUBVOLUME, if backupdir has changed
|
|
#get_config "/etc/snapper/configs/$snapper_config" "SUBVOLUME"
|
|
#if $ssh [ "$SUBVOLUME" != \"$backup_root\" ]; then
|
|
# SUBVOLUME="$backup_root"
|
|
# set_config "/etc/snapper/configs/$snapper_config" "SUBVOLUME" "$SUBVOLUME"
|
|
# cmd="btrfs subvolume create $backup_root/$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 existence of SUBVOLUME $backup_root/.snapshots
|
|
cmd="$ssh stat --format %i $backup_root/$snapper_snapshots 2>/dev/null"
|
|
ret=$(eval $cmd)
|
|
if [ -z $ret ]; then
|
|
if [ $verbose -ge 2 ]; then
|
|
printf "${MAGENTA}Create new BTRFS subvolume ${GREEN}'%s'${NO_COLOR}\n" \
|
|
$backup_root/$snapper_snapshots
|
|
fi
|
|
cmd="$ssh btrfs subvolume create $backup_root/$snapper_snapshots 2>/dev/null"
|
|
ret=$(eval $cmd)
|
|
if [ $? -ne 0 ] && [ $ret -ne 256 ]; then
|
|
printf "${RED}Error: ${MAGENTA}Creation of snapper subvolume ${GREEN}%s${MAGENTA} failed${NO_COLOR}\n" \
|
|
"$backup_root/$snapper_snapshots"
|
|
return 1
|
|
fi
|
|
else
|
|
if [ $ret -ne 256 ]; then
|
|
printf "${RED}Error: ${GREEN}%s ${MAGENTA}needs to be a BTRFS subvolume. But given ${GREEN}%s${MAGENTA} is just a directory${NO_COLOR}\n" \
|
|
"$snapper_config" "$backup_root"
|
|
return 1
|
|
fi
|
|
fi
|
|
fi
|
|
else
|
|
printf "${MAGENTA}dryrun${NO_COLOR}: Would check/create for valid snapper config %s ...\n" \
|
|
"$snapper_config"
|
|
fi
|
|
|
|
# verify that target snapshot can take the new snapshot data id
|
|
if [ $dryrun -eq 0 ]; then
|
|
if [ $verbose -ge 2 ]; then
|
|
printf "${MAGENTA}Verify existence of path ${GREEN}'%s'${NO_COLOR}...\n" \
|
|
"$backup_root/$snapper_snapshots/$snapper_id"
|
|
fi
|
|
cmd="$ssh stat --format %i $backup_root/$snapper_snapshots/$snapper_id 2>/dev/null"
|
|
ret=$(eval $cmd)
|
|
if [ $? -eq 1 ]; then
|
|
# Path does not exist
|
|
if [ $verbose -ge 3 ]; then
|
|
printf "${MAGENTA}Create${NO_COLOR} path ${GREEN}'%s'${NO_COLOR} to store target snapshot.\n" \
|
|
"$backup_root/$snapper_snapshots/$snapper_id"
|
|
fi
|
|
|
|
ret=$(eval $ssh mkdir --mode=0700 \
|
|
$backup_root/$snapper_snapshots/$snapper_id)
|
|
if [ $? -ne 0 ]; then
|
|
printf "${RED}Cancel path snapshot creation${NO_COLOR}: Can't create path '%s' to store target snapshot.\n" \
|
|
"$backup_root/$snapper_snapshots/$snapper_id"
|
|
return 1
|
|
fi
|
|
else
|
|
cmd="$ssh stat --format %i $backup_root/$snapper_snapshots/$snapper_id/$snapper_snapshot_name 2>/dev/null"
|
|
ret=$(eval $cmd)
|
|
if [ $? -eq 0 ] && [ $ret -eq 256 ]; then
|
|
# a former btrfs snapshot already exists
|
|
if [ -z "$remote" ]; then
|
|
printf "${RED}Cancel snapshot creation${NO_COLOR}: Former snapshot with id ${GREEN}'%s'${NO_COLOR} already exist in ${BLUE}'%s'${NO_COLOR}\n" \
|
|
"$snapper_id" "$backup_root/$snapper_snapshots"
|
|
else
|
|
printf "${RED}Cancel snapshot creation${NO_COLOR}: Former snapshot with id ${GREEN}'%s'${NO_COLOR} already exists on ${BLUE}'%s'${NO_COLOR} in ${BLUE}'%s'${NO_COLOR}\n" \
|
|
"$snapper_id" "$remote" "$backup_root/$snapper_snapshots"
|
|
fi
|
|
|
|
# cleanup generated snapper entry
|
|
check_snapper_failed_ids $selected_config $batch
|
|
return 1
|
|
fi
|
|
fi
|
|
else
|
|
printf "${MAGENTA}dryrun${NO_COLOR}: Would check/create path %s to store target snapshot ...\n" \
|
|
"$backup_root/$snapper_snapshots/$snapper_id"
|
|
fi
|
|
}
|
|
|
|
###
|
|
# Main
|
|
###
|
|
|
|
cwd=`pwd`
|
|
ssh=""
|
|
|
|
# can't be ported to dash (ERR is not supported)
|
|
#trap 'traperror ${LINENO} $? "$BASH_COMMAND" $BASH_LINENO "${FUNCNAME[@]}"' ERR
|
|
trap trapkill TERM INT
|
|
|
|
check_prerequisites
|
|
|
|
# validate commandline options, set resonable defaults
|
|
parse_params $@
|
|
|
|
# parse infos for backup medium
|
|
get_media_infos
|
|
|
|
# select the backup target
|
|
select_target
|
|
|
|
# create and initialize structures for snapper configs
|
|
run_config_preparation
|
|
|
|
# run backups (Snapshot types: btrfs-snapshot, btrfs-clone, btrfs-archive)
|
|
run_backup
|
|
|
|
# cleanup
|
|
if [ -d $TMPDIR ]; then
|
|
rm -rf $TMPDIR || die "Failed to cleanup temporary directory '%s'\n" "$TMPDIR"
|
|
#printf "${RED}TODO: ${NO_COLOR}Will remove $TMPDIR}"
|
|
fi
|
|
|
|
printf "${BLUE}Backup run completed!${NO_COLOR}\n"
|
|
|
|
# close the read file descriptor
|
|
#exec 3>&-
|
|
exec 4>&-
|
|
|
|
if [ $donotify -gt 0 ]; then
|
|
if [ "$target_cmdline" != "none" ]; then
|
|
if [ -z "$remote" ]; then
|
|
notify_info "Finished" "Backup run to target '$target_cmdline' completed!"
|
|
else
|
|
notify_info "Finished" "Backup run to remote '$remote' target '$target_cmdline' completed!"
|
|
fi
|
|
elif [ "$uuid_cmdline" != "none" ]; then
|
|
if [ -z "$remote" ]; then
|
|
notify_info "Finished" "Backup run to uuid '$uuid_cmdline' completed!"
|
|
else
|
|
notify_info "Finished" "Backup run to remote '$remote' uuid '$uuid_cmdline' completed!"
|
|
fi
|
|
else
|
|
if [ -z "$remote" ]; then
|
|
notify_info "Finished" "Backup run to target '$selected_target' completed!"
|
|
else
|
|
notify_info "Finished" "Backup run to remote '$remote' target '$selected_target' completed!"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
if [ $error_count -ge 1 ]; then
|
|
return 1
|
|
fi
|