diff --git a/bin/dsnap-sync b/bin/dsnap-sync index fde7a69..8803427 100755 --- a/bin/dsnap-sync +++ b/bin/dsnap-sync @@ -39,7 +39,7 @@ # pair of systemd service and timer-units. progname="${0##*/}" -version="0.6.6" +version="0.6.7" # global variables args= @@ -95,17 +95,17 @@ check_prerequisites () { # Create TEMPDIR if [ -d "$XDG_RUNTIME_DIR" ]; then - test ! -d "$XDG_RUNTIME_DIR/$progname" && mkdir -p "$XDG_RUNTIME_DIR/$progname" - TMPDIR=$(mktemp --tmpdir="$XDG_RUNTIME_DIR/$progname" -d) + test ! -d "$XDG_RUNTIME_DIR/$progname" && mkdir -p "$XDG_RUNTIME_DIR/$progname" + TMPDIR=$(mktemp --tmpdir="$XDG_RUNTIME_DIR/$progname" -d) elif [ -d "$TEMP" ]; then - test ! -d "$TEMP/$progname" && mkdir -p "$XDG_RUNTIME_DIR/$progname" - TMPDIR=$(mktemp --tmpdir="$TEMP/$progname" -d) + test ! -d "$TEMP/$progname" && mkdir -p "$XDG_RUNTIME_DIR/$progname" + TMPDIR=$(mktemp --tmpdir="$TEMP/$progname" -d) elif [ -d /var/tmp ]; then - test ! -d "/var/tmp/$progname" && mkdir -p "/var/tmp/$progname" - TMPDIR=$(mktemp --tmpdir="/var/tmp/$progname" -d) + test ! -d "/var/tmp/$progname" && mkdir -p "/var/tmp/$progname" + TMPDIR=$(mktemp --tmpdir="/var/tmp/$progname" -d) fi if [ "$verbose" -ge 3 ]; then - printf "${MAGENTA}TEMPDIR: ${GREEN}'%s'${NO_COLOR}\n" "$TMPDIR" + printf "${MAGENTA}TEMPDIR: ${GREEN}'%s'${NO_COLOR}\n" "$TMPDIR" fi # define fifo pipe @@ -113,27 +113,27 @@ check_prerequisites () { test -p "$BTRFS_PIPE" && mkfifo "$BTRFS_PIPE" # 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; } + command -pv awk >/dev/null 2>&1 || { printf "'awk' is not installed." && exit 1; } + command -pv sed >/dev/null 2>&1 || { printf "'sed' is not installed." && exit 1; } + command -pv ssh >/dev/null 2>&1 || { printf "'ssh' is not installed." && exit 1; } + command -pv scp >/dev/null 2>&1 || { printf "'scp' is not installed." && exit 1; } + command -pv btrfs >/dev/null 2>&1 || { printf "'btrfs' is not installed." && exit 1; } + command -pv findmnt >/dev/null 2>&1 || { printf "'findmnt' is not installed." && exit 1; } + command -pv systemd-cat >/dev/null 2>&1 || { printf "'systemd-cat' is not installed." && exit 1; } + command -pv 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; } - which nc >/dev/null 2>&1 && { nc_cmd=1; } + command -pv attr >/dev/null 2>&1 || { printf "'attr' is not installed." && exit 1; } + command -pv ionice >/dev/null 2>&1 && { do_ionice_cmd=1; } + command -pv notify-send >/dev/null 2>&1 && { donotify=1; } + command -pv pv >/dev/null 2>&1 && { do_pv_cmd=1; } + command -pv tape-admin >/dev/null 2>&1 && { tape_admin_cmd=1; } + command -pv nc >/dev/null 2>&1 && { nc_cmd=1; } if [ "$(id -u)" -ne 0 ] ; then printf "%s: must be run as root\n", "$progname" ; exit 1 ; fi # if [ ! -r "$SNAPPER_CONFIG" ]; then - # die "$progname: $SNAPPER_CONFIG does not exist." + # die "$progname: $SNAPPER_CONFIG does not exist." # fi } @@ -142,7 +142,7 @@ check_target_subvol () { target_snapshot=${2##target_snapshot=} if [ "$verbose" -ge 2 ]; then - printf "${BLUE}check_target_subvol() ...${NO_COLOR}\n" + printf "${BLUE}check_target_subvol() ...${NO_COLOR}\n" fi # WIP: check if target subvolume already exists @@ -155,79 +155,79 @@ check_transfer_size () { clone_snapshot=${2##clone_snapshot=} if [ "$verbose" -ge 2 ]; then - printf "${BLUE}check_transfer_size() ...${NO_COLOR}\n" + printf "${BLUE}check_transfer_size() ...${NO_COLOR}\n" fi if [ $dryrun -eq 0 ]; then - transfer_size=0 - if [ "$interactive" -eq 1 ]; then - if [ "$verbose" -ge 2 ]; then - if [ ${#clone_snapshot} -gt 0 ]; then - printf "${MAGENTA}Calculate transfer size for incremental snapshot (clone=${GREEN}'%s'${MAGENTA}, source=${GREEN}'%s'${MAGENTA})${NO_COLOR} ...\n" \ - "$clone_snapshot" "$source_snapshot" - else - printf "${MAGENTA}Calculate transfer size for snapshot (source=${GREEN}'%s'${MAGENTA})${NO_COLOR} ...\n" \ - "$source_snapshot" - fi - 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 - transfer_size=$(btrfs qgroup show -f --raw "$source_snapshot" 2>/dev/null \ - | awk 'FNR>2 {print $2}') - if [ $? -eq 1 ]; then - # subvolume is not configured for quota, (temporary?, expensive?) enable that - if [ $btrfs_quota_tmp -eq 1 ]; then - btrfs quota enable "$source_snapshot" 2>/dev/null - btrfs quota rescan -w "$source_snapshot" 2>/dev/null - transfer_size=$(btrfs qgroup show -f --raw "$source_snapshot" 2>/dev/null \ - | awk 'FNR>2 {print $2}') - fi - fi + transfer_size=0 + if [ "$interactive" -eq 1 ]; then + if [ "$verbose" -ge 2 ]; then + if [ ${#clone_snapshot} -gt 0 ]; then + printf "${MAGENTA}Calculate transfer size for incremental snapshot (clone=${GREEN}'%s'${MAGENTA}, source=${GREEN}'%s'${MAGENTA})${NO_COLOR} ...\n" \ + "$clone_snapshot" "$source_snapshot" + else + printf "${MAGENTA}Calculate transfer size for snapshot (source=${GREEN}'%s'${MAGENTA})${NO_COLOR} ...\n" \ + "$source_snapshot" + fi + 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 + transfer_size=$(btrfs qgroup show -f --raw "$source_snapshot" 2>/dev/null \ + | awk 'FNR>2 {print $2}') + if [ $? -eq 1 ]; then + # subvolume is not configured for quota, (temporary?, expensive?) enable that + if [ $btrfs_quota_tmp -eq 1 ]; then + btrfs quota enable "$source_snapshot" 2>/dev/null + btrfs quota rescan -w "$source_snapshot" 2>/dev/null + transfer_size=$(btrfs qgroup show -f --raw "$source_snapshot" 2>/dev/null \ + | awk 'FNR>2 {print $2}') + fi + fi - if [ "$verbose" -ge 3 ]; then - printf "${MAGENTA}BTRFS qgroup show result: ${GREEN}'%s'\b${NO_COLOR}'%s'\n" \ - "$?" "$transfer_size" - fi + if [ "$verbose" -ge 3 ]; then + printf "${MAGENTA}BTRFS qgroup show result: ${GREEN}'%s'\b${NO_COLOR}'%s'\n" \ + "$?" "$transfer_size" + fi - # need to substitue btrfs 'x.yyGiB' suffix, since pv will need 'xG' - if [ "$transfer_size" -ge 1048576 ]; then - transfer_size=$(btrfs qgroup show -f --gbytes "$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}: size=${GREEN}'%s'${NO_COLOR} ...\n" \ - "$snapper_source_id" "$transfer_size" - fi + # need to substitue btrfs 'x.yyGiB' suffix, since pv will need 'xG' + if [ "$transfer_size" -ge 1048576 ]; then + transfer_size=$(btrfs qgroup show -f --gbytes "$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}: size=${GREEN}'%s'${NO_COLOR} ...\n" \ + "$snapper_source_id" "$transfer_size" + fi - # should we disable quota usage again? - if [ "$btrfs_quota_tmp" -eq 1 ]; then btrfs quota disable "$source_snapshot"; fi - else - if [ ${#clone_snapshot} -gt 0 ]; then - # WIP: dry run with btrfs send - # need to substitue btrfs 'x.yyGiB' suffix, since pv will need 'xG' - transfer_size=$(btrfs send -v -p "$clone_snapshot" "$source_snapshot" 2>"$BTRFS_PIPE" \ - | pv -f 2>&1 >/dev/null \ - | awk -F ' ' '{ gsub(/.[0-9][0-9]MiB/,"M"); gsub(/.[0-9][0-9]GiB/,"G"); print $1 }' ) - else - # filesystem size - transfer_size=$(du --one-file-system --summarize "$snapper_source_snapshot" 2>/dev/null \ - | awk -F ' ' '{print $1}') - if [ "$transfer_size" -ge 1048576 ]; then - transfer_size=$(($transfer_size / 1024 / 1024))G - fi - fi - if [ "$verbose" -ge 2 ]; then - printf "${MAGENTA}BTRFS transfer size for ${GREEN}source snapshot${MAGENTA}: size=${GREEN}'%s'${NO_COLOR} ...\n" \ - "$transfer_size" - fi - fi - fi + # should we disable quota usage again? + if [ "$btrfs_quota_tmp" -eq 1 ]; then btrfs quota disable "$source_snapshot"; fi + else + if [ ${#clone_snapshot} -gt 0 ]; then + # WIP: dry run with btrfs send + # need to substitue btrfs 'x.yyGiB' suffix, since pv will need 'xG' + transfer_size=$(btrfs send -v -p "$clone_snapshot" "$source_snapshot" 2>"$BTRFS_PIPE" \ + | pv -f 2>&1 >/dev/null \ + | awk -F ' ' '{ gsub(/.[0-9][0-9]MiB/,"M"); gsub(/.[0-9][0-9]GiB/,"G"); print $1 }' ) + else + # filesystem size + transfer_size=$(du --one-file-system --summarize "$snapper_source_snapshot" 2>/dev/null \ + | awk -F ' ' '{print $1}') + if [ "$transfer_size" -ge 1048576 ]; then + transfer_size=$(($transfer_size / 1024 / 1024))G + fi + fi + if [ "$verbose" -ge 2 ]; then + printf "${MAGENTA}BTRFS transfer size for ${GREEN}source snapshot${MAGENTA}: size=${GREEN}'%s'${NO_COLOR} ...\n" \ + "$transfer_size" + fi + fi + fi else - if [ "$verbose" -ge 2 ]; then - printf "${MAGENTA}dryrun: Would calculate transfer size for BTRFS ${GREEN}source snapshot${NO_COLOR} ...\n" \ - "$snapper_source_id" - fi + if [ "$verbose" -ge 2 ]; then + printf "${MAGENTA}dryrun: Would calculate transfer size for BTRFS ${GREEN}source snapshot${NO_COLOR} ...\n" \ + "$snapper_source_id" + fi fi } @@ -237,104 +237,104 @@ cleanup_snapper_failed_ids () { batch=${2:-0} if [ "$verbose" -ge 2 ]; then - printf "${BLUE}cleanup_snapper_failed_ids() ...${NO_COLOR}\n" + printf "${BLUE}cleanup_snapper_failed_ids() ...${NO_COLOR}\n" fi # active, non finished snapshot backups are marked beeing in progress. # 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}') + | 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}'" + # | 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 ${GREEN}%s${MAGENTA} previous failed sync runs for ${GREEN}'%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 + if [ "$batch" ]; then + answer="yes" + else + printf "${MAGENTA}Found ${GREEN}%s${MAGENTA} previous failed sync runs for ${GREEN}'%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_pv_cmd () { if [ "$verbose" -ge 2 ]; then - printf "${BLUE}create_pv_cmd() ...${NO_COLOR}\n" + printf "${BLUE}create_pv_cmd() ...${NO_COLOR}\n" fi # prepare cmdline output 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 $transfer_size $pv_options | " - #cmd_pv="pv $pv_options --size ${transfer_size} | dialog --gauge \"$progname: Progress for config '$selected_config'\" 6 85 |" + 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 $transfer_size $pv_options | " + #cmd_pv="pv $pv_options --size ${transfer_size} | dialog --gauge \"$progname: Progress for config '$selected_config'\" 6 85 |" else - cmd_pv='' + cmd_pv='' fi } create_snapshot () { if [ "$verbose" -ge 2 ]; then - printf "${BLUE}create_snapshot() ...${NO_COLOR}\n" $snapper_config + printf "${BLUE}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 - ret=$(snapper --config "$selected_config" create \ - --print-number \ - --description "$snap_description_running" \ - --userdata "host=$HOSTNAME") - else - ret=$(snapper --config "$selected_config" create \ - --print-number \ - --description "$snap_description_running" \ - --userdata "host=$remote") - fi - if [ "$ret" ]; then - snapper_source_id=$ret - else - # if snapper call fails, assign negative shapshot-id and return - printf "${RED}Creation of snapper source snapshot failed${NO_COLOR}.\n" "$snapper_source_id" - snapper_source_id=-1 - return 1 - fi + #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 + ret=$(snapper --config "$selected_config" create \ + --print-number \ + --description "$snap_description_running" \ + --userdata "host=$HOSTNAME") + else + ret=$(snapper --config "$selected_config" create \ + --print-number \ + --description "$snap_description_running" \ + --userdata "host=$remote") + fi + if [ "$ret" ]; then + snapper_source_id=$ret + else + # if snapper call fails, assign negative shapshot-id and return + printf "${RED}Creation of snapper source snapshot failed${NO_COLOR}.\n" "$snapper_source_id" + snapper_source_id=-1 + return 1 + 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 + 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 + #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 + 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 for snapper config ${GREEN}'%s'${NO_COLOR} ...\n" "$selected_config" - snapper_source_sync_id="" + printf "${MAGENTA}dryrun${NO_COLOR}: Would create source snapshot for snapper config ${GREEN}'%s'${NO_COLOR} ...\n" "$selected_config" + snapper_source_sync_id="" fi } @@ -354,26 +354,27 @@ get_answer_yes_no () { # hack: answer is a global variable, using it for preselection while [ "$i" = "none" ]; do - read -r "$message" i + printf "%s" "$message" + read -r 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 + 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 } @@ -387,84 +388,84 @@ get_archive_last_sync_id () { if [ ${#remote} -ge 1 ]; then run_ssh="$ssh"; fi if [ "$verbose" -ge 2 ]; then - printf "${BLUE}get_archive_last_sync_id()...${NO_COLOR}\n" + printf "${BLUE}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 + 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 + 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 + # 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 + # no snapshot found + return 1 fi } get_backupdir () { - backup_dir=$1 + backup_dir="$1" if [ "$verbose" -ge 2 ]; then - printf "${BLUE}get_backupdir() ...${NO_COLOR}\n" $snapper_config + printf "${BLUE}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 "Enter name of directory to store backups, relative to $selected_target (to be created if not existing): " backupdir - fi + 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 "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" + printf "${MAGENTA}Backup-Dir is ${GREEN}'%s'${NO_COLOR}\n" \ + "$backupdir" fi } @@ -474,76 +475,76 @@ get_disk_infos () { fs_option="" if [ "$verbose" -ge 2 ]; then - printf "${BLUE}get_disk_infos() ...${NO_COLOR}\n" + 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 + 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 + # 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 + # 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 + 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) @@ -559,80 +560,80 @@ get_disk_infos () { 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)) + 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)) + 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)) + 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_media_infos () { if [ "$verbose" -ge 2 ]; then - printf "${BLUE}get_media_infos() ...${NO_COLOR}\n" + printf "${BLUE}get_media_infos() ...${NO_COLOR}\n" fi # 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 --version - $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 + # read mounted LTFS structures + if [ -z "$remote" ]; then + tape-admin --verbose="$verbose" --mount "${mediapool_name}" "${volume_name}" + else + $ssh tape-admin --version + $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 + # read mounted BTRFS structures + get_disk_infos fi } @@ -649,75 +650,75 @@ get_snapper_backup_type () { # parse selected_config and return with $snapper_target_config set appropriately if [ "$verbose" -ge 2 ]; then - printf "${BLUE}get_snapper_backup_type() ...${NO_COLOR}\n" + printf "${BLUE}get_snapper_backup_type() ...${NO_COLOR}\n" fi if [ ${#backuptype_cmdline} -gt 1 ]; then - snapper_backup_type=${backuptype_cmdline} + 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 + 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="" - ##config_type=$(snapper --config $1 get-config | awk '/'"CONFIG_PARENT=$snap_description_running"'/ {cnt++} END {print cnt}') - # for now, we cut and parse ourself - IFS="=" - while read -r -p 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" + # WIP -> create a PR for snapper + # Patch snapper to parse for new pairs: CONFIG_TYPE="child | root"; CONFIG_PARENT="" + ##config_type=$(snapper --config $1 get-config | awk '/'"CONFIG_PARENT=$snap_description_running"'/ {cnt++} END {print cnt}') + # for now, we cut and parse ourself + IFS="=" + while read -r -p 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 target non btrfs-filesystem - snapper_backup_type=btrfs-archive - snapper_target_config=archive-"$snapper_config" - ;; - child|Child) - # clone to target 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" - ;; + archive|Archive) + # archive btrfs-snapshot to target non btrfs-filesystem + snapper_backup_type=btrfs-archive + snapper_target_config=archive-"$snapper_config" + ;; + child|Child) + # clone to target 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}" + 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" + 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 } @@ -728,27 +729,27 @@ get_snapper_config_value () { run_ssh='' if [ "$verbose" -ge 2 ]; then - printf "${BLUE}get_snapper_config_value() ...${NO_COLOR}\n" - printf "${MAGENTA}config_file: ${GREEN}'%s'${NO_COLOR}\n" "$config_file" + printf "${BLUE}get_snapper_config_value() ...${NO_COLOR}\n" + printf "${MAGENTA}config_file: ${GREEN}'%s'${NO_COLOR}\n" "$config_file" fi if [ ${#remote_host} -gt 1 ]; then - run_ssh="$ssh" + run_ssh="$ssh" fi if [ "$verbose" -ge 3 ]; then - if [ ${#remote_host} -gt 1 ]; then - printf "Snapper ${GREEN}remote host${NO_COLOR}: '%s'\n" \ - "$remote_host" - fi - printf "Snapper ${GREEN}config file${NO_COLOR}: '%s'\n" \ - "$config_file" - printf "Snapper key ${GREEN}'%s'${NO_COLOR}: '%s'\n" \ - "$config_key" "$value" + if [ ${#remote_host} -gt 1 ]; then + printf "Snapper ${GREEN}remote host${NO_COLOR}: '%s'\n" \ + "$remote_host" + fi + printf "Snapper ${GREEN}config file${NO_COLOR}: '%s'\n" \ + "$config_file" + printf "Snapper key ${GREEN}'%s'${NO_COLOR}: '%s'\n" \ + "$config_key" "$value" fi value=$(eval "$run_ssh" cat "$config_file" \ - | awk '/'"$config_key"'/' \ - | awk -F "=" '{ gsub("\"",""); print $2}') + | awk '/'"$config_key"'/' \ + | awk -F "=" '{ gsub("\"",""); print $2}') } get_snapper_last_sync_id () { @@ -762,135 +763,135 @@ get_snapper_last_sync_id () { #run_ssh='' if [ "$verbose" -ge 2 ]; then - printf "${BLUE}get_snapper_last_sync_id() ...${NO_COLOR}\n" + printf "${BLUE}get_snapper_last_sync_id() ...${NO_COLOR}\n" fi snapper_sync_id=0 if [ ${#remote_host} -ge 1 ]; then - run_ssh="$ssh"; + run_ssh="$ssh"; fi # only process, if config does exist cmd="$run_ssh stat --format %i $snapper_config_dir/$snapper_config 2>/dev/null" ret=$(eval "$cmd" 2>/dev/null) if [ -z "${#ret}" ]; then - if [ "$verbose" -ge 3 ]; then - if [ "${#remote_host}" -ge 1 ]; then - printf "${MAGENTA}snapper config ${GREEN}'%s'${MAGENTA} on remote ${GREEN}'%s'${MAGENTA} does not exist yet.${NO_COLOR}\n" \ - "$snapper_config" "$remote_host" - 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_host}" -ge 1 ]; then + printf "${MAGENTA}snapper config ${GREEN}'%s'${MAGENTA} on remote ${GREEN}'%s'${MAGENTA} does not exist yet.${NO_COLOR}\n" \ + "$snapper_config" "$remote_host" + else + printf "${MAGENTA}snapper config ${GREEN}'%s'${MAGENTA} does not exist yet.${NO_COLOR}\n" \ + "$snapper_config" + fi + fi + return 1 + fi if [ "${#snapper_subvolid}" -ge 1 ] && [ "${#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}'" + 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}'" + # | 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}'" elif [ "${#backup_dir}" -ge 1 ]; then - cmd="snapper --config $snapper_config list --type single \ - | awk '/$snapper_description/' \ - | awk '/backupdir=/' \ - | awk 'END {print \$1}'" + cmd="snapper --config $snapper_config list --type single \ + | awk '/$snapper_description/' \ + | awk '/backupdir=/' \ + | awk 'END {print \$1}'" else - cmd="snapper --config $snapper_config list --type single \ - | awk '/$snapper_description/' \ - | awk 'END {print \$1}'" - fi + cmd="snapper --config $snapper_config list --type single \ + | awk '/$snapper_description/' \ + | awk 'END {print \$1}'" + fi if [ "$verbose" -ge 3 ]; then - if [ "${#remote_host}" -ge 1 ]; then - printf "${MAGENTA}remote_host: ${GREEN}'%s'${NO_COLOR}\n" \ - "${remote_host}" - fi - printf "${MAGENTA}snapper_config: ${GREEN}'%s'${NO_COLOR}\n" \ - "${snapper_config}" + if [ "${#remote_host}" -ge 1 ]; then + printf "${MAGENTA}remote_host: ${GREEN}'%s'${NO_COLOR}\n" \ + "${remote_host}" + fi + printf "${MAGENTA}snapper_config: ${GREEN}'%s'${NO_COLOR}\n" \ + "${snapper_config}" fi snapper_sync_id=$(eval "$run_ssh" "$cmd") # if [ "$verbose" -ge 2 ]; then - # if [ -z "$remote_host" ]; then - # printf "${MAGENTA}Last snapper ${GREEN}sync_id${MAGENTA} found on ${GREEN}source${MAGENTA} is ${GREEN}'%s'${MAGENTA} ...${NO_COLOR}\n" \ - # "$snapper_sync_id" - # else - # printf "${MAGENTA}Last snapper ${GREEN}sync_id${MAGENTA} found on ${GREEN}'%s'${MAGENTA} is ${GREEN}'%s'${MAGENTA} ...${NO_COLOR}\n" \ - # "$remote_host" "$snapper_sync_id" - # fi + # if [ -z "$remote_host" ]; then + # printf "${MAGENTA}Last snapper ${GREEN}sync_id${MAGENTA} found on ${GREEN}source${MAGENTA} is ${GREEN}'%s'${MAGENTA} ...${NO_COLOR}\n" \ + # "$snapper_sync_id" + # else + # printf "${MAGENTA}Last snapper ${GREEN}sync_id${MAGENTA} found on ${GREEN}'%s'${MAGENTA} is ${GREEN}'%s'${MAGENTA} ...${NO_COLOR}\n" \ + # "$remote_host" "$snapper_sync_id" + # fi #fi if [ "${#snapper_sync_id}" -ge 1 ]; then - # ok, matching snapshot found - if [ "$verbose" -ge 3 ]; then - if [ "${#remote_host}" -ge 1 ]; then - printf "${MAGENTA}Get last sync_id for snapper config ${GREEN}'%s'${MAGENTA} on remote ${GREEN}'%s'${MAGENTA} ...${NO_COLOR}\n" \ - "$snapper_config" "$remote_host" - else - printf "${MAGENTA}Get last sync_id for snapper config ${GREEN}'%s'${MAGENTA} ...${NO_COLOR}\n" \ - "$snapper_config" - fi + # ok, matching snapshot found + if [ "$verbose" -ge 3 ]; then + if [ "${#remote_host}" -ge 1 ]; then + printf "${MAGENTA}Get last sync_id for snapper config ${GREEN}'%s'${MAGENTA} on remote ${GREEN}'%s'${MAGENTA} ...${NO_COLOR}\n" \ + "$snapper_config" "$remote_host" + else + printf "${MAGENTA}Get last sync_id for snapper config ${GREEN}'%s'${MAGENTA} ...${NO_COLOR}\n" \ + "$snapper_config" + fi fi - if [ "$SUBVOLUME" = "/" ]; then - SUBVOLUME="" - fi - snapper_sync_snapshot="$SUBVOLUME/.snapshots/$snapper_sync_id/$snapper_snapshot_name" - else - # no snapshot found, identify latest successfull sync saved on source - if [ "${#snapper_subvolid}" -ge 1 ] && [ "${#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 - if [ "$verbose" -ge 3 ]; then - printf "${MAGENTA}Search sync_id cmd=${GREEN}'%s'${NO_COLOR}\n" \ - "$run_ssh $cmd" - fi - snapper_sync_id=$(eval "$run_ssh" "$cmd") + if [ "$SUBVOLUME" = "/" ]; then + SUBVOLUME="" + fi + snapper_sync_snapshot="$SUBVOLUME/.snapshots/$snapper_sync_id/$snapper_snapshot_name" + else + # no snapshot found, identify latest successfull sync saved on source + if [ "${#snapper_subvolid}" -ge 1 ] && [ "${#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 + if [ "$verbose" -ge 3 ]; then + printf "${MAGENTA}Search sync_id cmd=${GREEN}'%s'${NO_COLOR}\n" \ + "$run_ssh $cmd" + fi + snapper_sync_id=$(eval "$run_ssh" "$cmd") - if [ "$verbose" -ge 2 ]; then - if [ "${#remote_host}" -ge 1 ]; then - printf "${MAGENTA}Matching snapper ${GREEN}sync_id${MAGENTA} found on ${GREEN}'%s'${MAGENTA} is ${GREEN}'%s'${MAGENTA} ...${NO_COLOR}\n" \ - "$remote_host" "$snapper_sync_id" - else - printf "${MAGENTA}Matching snapper ${GREEN}sync_id${MAGENTA} found on ${GREEN}source${MAGENTA} is ${GREEN}'%s'${MAGENTA} ...${NO_COLOR}\n" \ - "$snapper_sync_id" - fi + if [ "$verbose" -ge 2 ]; then + if [ "${#remote_host}" -ge 1 ]; then + printf "${MAGENTA}Matching snapper ${GREEN}sync_id${MAGENTA} found on ${GREEN}'%s'${MAGENTA} is ${GREEN}'%s'${MAGENTA} ...${NO_COLOR}\n" \ + "$remote_host" "$snapper_sync_id" + else + printf "${MAGENTA}Matching snapper ${GREEN}sync_id${MAGENTA} found on ${GREEN}source${MAGENTA} is ${GREEN}'%s'${MAGENTA} ...${NO_COLOR}\n" \ + "$snapper_sync_id" + fi fi - if [ ${#snapper_sync_id} -ge 1 ]; then - # ok, matching snapshot found - if [ "$SUBVOLUME" = "/" ]; then - SUBVOLUME="" - fi - snapper_sync_snapshot="$SUBVOLUME"/.snapshots/"$snapper_sync_id"/"$snapper_snapshot_name" - if [ "$verbose" -ge 3 ]; then - printf "${MAGENTA}snapper_sync_snapshot=${GREEN}'%s'${NO_COLOR}\n" "$snapper_sync_snapshot" \ - "$run_ssh $cmd" - fi - else - # no snapshot available - snapper_sync_id=0 - fi + if [ ${#snapper_sync_id} -ge 1 ]; then + # ok, matching snapshot found + if [ "$SUBVOLUME" = "/" ]; then + SUBVOLUME="" + fi + snapper_sync_snapshot="$SUBVOLUME"/.snapshots/"$snapper_sync_id"/"$snapper_snapshot_name" + if [ "$verbose" -ge 3 ]; then + printf "${MAGENTA}snapper_sync_snapshot=${GREEN}'%s'${NO_COLOR}\n" "$snapper_sync_snapshot" \ + "$run_ssh $cmd" + fi + else + # no snapshot available + snapper_sync_id=0 + fi fi } @@ -901,38 +902,38 @@ get_snapper_sync_id () { ret= if [ "$verbose" -ge 2 ]; then - printf "${BLUE}get_snapper_sync_id() ...${NO_COLOR}\n" + printf "${BLUE}get_snapper_sync_id() ...${NO_COLOR}\n" fi if [ "$verbose" -ge 3 ]; then - if [ ${#remote_host} -ge 1 ]; 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" + if [ ${#remote_host} -ge 1 ]; 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 + 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_host} -ge 1 ] && run_ssh="$ssh" cmd="snapper --config $snapper_config list --type single \ - | awk -F '|' ' \$1 == $snapper_sync_id { gsub(/ /,_); print \$1} '" + | awk -F '|' ' \$1 == $snapper_sync_id { gsub(/ /,_); print \$1} '" if [ "$verbose" -ge 3 ]; then - printf "${MAGENTA}Search cmd: '%s'${NO_COLOR}\n" "cmd" + printf "${MAGENTA}Search cmd: '%s'${NO_COLOR}\n" "cmd" fi 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 + # 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 } @@ -940,41 +941,41 @@ get_snapper_target_backupdir () { backupdir_cmdline=$1 if [ "$verbose" -ge 2 ]; then - printf "${BLUE}get_snapper_target_backupdir() ...${NO_COLOR}\n" $snapper_config + printf "${BLUE}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}') + # 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 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 - backupdir="$backupdir_cmdline" - #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 + 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 + backupdir="$backupdir_cmdline" + #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_tape_infos () { @@ -983,36 +984,36 @@ get_tape_infos () { tape_fs_option if [ "$verbose" -ge 3 ]; then - printf "${BLUE}get_tape_infos() ...${NO_COLOR}\n" + 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 + # 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 + # 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 + printf "${RED}Error: ${MAGENTA}No suitable LTFS tape mounted yet${NO_COLOR}\n" + return 1 fi # Posix Shells do not support Array. Therefore using ... @@ -1030,46 +1031,46 @@ get_tape_infos () { 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)) + 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)) + 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)) + eval "fs_options_$i='$fs_option'" + i=$((i+1)) done } notify () { # estimation: batch calls should just log if [ $donotify -gt 0 ]; then - for u in $(users | sed 's/ /\n/g' | 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 + for u in $(users | sed 's/ /\n/g' | 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" + printf "%s\n" "$2" fi } @@ -1087,275 +1088,275 @@ parse_params () { # 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 + 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 - cmd="snapper list-configs \ - | awk -F '|' 'FNR>2 {print \$1} '" - SNAPPER_CONFIGS=$(eval "$cmd") + cmd="snapper list-configs \ + | awk -F '|' 'FNR>2 {print \$1} '" + SNAPPER_CONFIGS=$(eval "$cmd") - #. "$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 + #. "$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 @@ -1369,73 +1370,73 @@ parse_params () { backupdir_cmdline=${backupdir_cmdline:-''} if [ -n "$remote" ]; then - ssh="ssh $remote" - scp="scp" - if [ -n "$port" ]; then - ssh="$ssh -p $port" - scp="$scp -P $port" - else - port=22 - fi - if [ $nc_cmd ]; then - nc -w 3 -z "$remote" "$port" > /dev/null || \ - die "Can't connect to remote host." - else - "$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 + ssh="ssh $remote" + scp="scp" + if [ -n "$port" ]; then + ssh="$ssh -p $port" + scp="$scp -P $port" + else + port=22 + fi + if [ $nc_cmd ]; then + nc -w 3 -z "$remote" "$port" > /dev/null || \ + die "Can't connect to remote host." + else + "$ssh" command -pv sh >/dev/null 2>&1 || \ + { printf "'remote shell' is not working!\n \ + Please correct your public authentication and try again.\n" && exit 1; } + 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' + # 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 "${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" + 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}" + 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 } @@ -1445,20 +1446,20 @@ quote_args () { 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}" + # 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" + printf "${BLUE}Prepare configuration structures...${NO_COLOR}\n" fi SNAP_SYNC_EXCLUDE=no @@ -1466,635 +1467,635 @@ run_config_preparation () { # 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" - if [ $? -eq 0 ]; then - # cleanup failed former runs - cleanup_snapper_failed_ids "$selected_config" "$batch" + # only process on existing configurations + selected_config=$(eval echo \$selected_config_$i) + verify_snapper_config "$selected_config" + if [ $? -eq 0 ]; then + # cleanup failed former runs + cleanup_snapper_failed_ids "$selected_config" "$batch" - if [ $SNAP_SYNC_EXCLUDE = "yes" ]; then - continue - fi + if [ $SNAP_SYNC_EXCLUDE = "yes" ]; then + continue + fi - # parse selected_config and set $snapper_target_config appropriately - get_snapper_backup_type "$selected_config" + # parse selected_config and set $snapper_target_config appropriately + get_snapper_backup_type "$selected_config" - # parse backupdir - get_backupdir "$backupdir_cmdline" + # parse backupdir + get_backupdir "$backupdir_cmdline" - if [ -z "$remote" ]; then - eval "backup_host='localhost'" - else - eval "backup_host='$remote'" - fi + if [ -z "$remote" ]; then + eval "backup_host='localhost'" + else + eval "backup_host='$remote'" + fi - # get latest successfully finished snapshot on 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=" \ - "snapper_backupdir=${backupdir}" "remote=" - ;; - btrfs-clone) - get_snapper_last_sync_id "snapper_config=${selected_config}" "snapper_description=${snap_description_synced}" \ - "snapper_uuid=" "snapper_subvolid=" "snapper_tapeid=" \ - "snapper_backupdir=" "remote=" - #"snapper_uuid=" "snapper_subvolid=" "snapper_tapeid=" "snapper_backupdir=" "remote=$backup_host" - ;; - btrfs-archive) - get_snapper_last_sync_id "snapper_config=${selected_config}" "snapper_description=${snap_description_synced}" \ - "snapper_uuid=" "snapper_subvolid=" "snapper_tapeid=" \ - "snapper_backupdir=" "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 + # get latest successfully finished snapshot on 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=" \ + "snapper_backupdir=${backupdir}" "remote=" + ;; + btrfs-clone) + get_snapper_last_sync_id "snapper_config=${selected_config}" "snapper_description=${snap_description_synced}" \ + "snapper_uuid=" "snapper_subvolid=" "snapper_tapeid=" \ + "snapper_backupdir=" "remote=" + #"snapper_uuid=" "snapper_subvolid=" "snapper_tapeid=" "snapper_backupdir=" "remote=$backup_host" + ;; + btrfs-archive) + get_snapper_last_sync_id "snapper_config=${selected_config}" "snapper_description=${snap_description_synced}" \ + "snapper_uuid=" "snapper_subvolid=" "snapper_tapeid=" \ + "snapper_backupdir=" "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 synced snapshot available for snapper config ${GREEN}'%s'${MAGENTA}...${NO_COLOR}\n" \ - "$selected_config" - fi + if [ "$snapper_sync_id" -eq 0 ]; then + if [ "$verbose" -ge 2 ]; then + printf "${MAGENTA}No previous synced 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 - if [ "$value" = "/" ]; then - snapper_source_sync_snapshot="/.snapshots/$snapper_source_sync_id/$snapper_snapshot_name" - else - snapper_source_sync_snapshot="$value/.snapshots/$snapper_source_sync_id/$snapper_snapshot_name" - fi - 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 + 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 + if [ "$value" = "/" ]; then + snapper_source_sync_snapshot="/.snapshots/$snapper_source_sync_id/$snapper_snapshot_name" + else + snapper_source_sync_snapshot="$value/.snapshots/$snapper_source_sync_id/$snapper_snapshot_name" + fi + 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=${backup_host}" - ;; - *) - get_snapper_last_sync_id "snapper_config=${snapper_target_config}" \ - "snapper_description=${snap_description_synced}" \ - "snapper_uuid=" "snapper_subvolid=" "snapper_tapeid=" \ - "snapper_backupdir=" \ - "remote=${backup_host}" - ;; - esac - if [ $? -eq 0 ]; then - snapper_target_sync_id="$snapper_sync_id" - else - snapper_target_sync_id=0 - 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=${backup_host}" + ;; + *) + get_snapper_last_sync_id "snapper_config=${snapper_target_config}" \ + "snapper_description=${snap_description_synced}" \ + "snapper_uuid=" "snapper_subvolid=" "snapper_tapeid=" \ + "snapper_backupdir=" \ + "remote=${backup_host}" + ;; + 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" + # 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 + 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 [ -z "$remote" ]; then - printf "${MAGENTA}No synced ${GREEN}target snapshot${MAGENTA} available for snapper config ${GREEN}'%s'${MAGENTA} ...${NO_COLOR}\n" \ - "$selected_config" - else - printf "${MAGENTA}No synced ${GREEN}target snapshot${MAGENTA} available for snapper config ${GREEN}'%s'${MAGENTA} on '%s' ...${NO_COLOR}\n" \ - "$selected_config" "$remote" - 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" - if [ "$verbose" -ge 3 ]; then - if [ -z "$remote" ]; then - printf "${MAGENTA}backupdir: ${GREEN}'%s'${NO_COLOR}\n" \ - "$backupdir" - else - printf "${MAGENTA}backupdir on remote '%s': ${GREEN}'%s'${NO_COLOR}\n" \ - "$remote" "$backupdir" - fi - fi - # 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 - backup_root="$value" - snapper_target_sync_snapshot="$value"/.snapshots/"$snapper_target_sync_id"/"$snapper_snapshot_name" - fi - # commandline settings for backupdir on selected_target will have priority - if [ "$backup_root" != "$selected_target/$backupdir" ]; then - backup_root="$selected_target"/"$backupdir"/"$snapper_target_config" - snapper_target_sync_snapshot="$backup_root"/.snapshots/"$snapper_target_sync_id"/"$snapper_snapshot_name" - fi - if [ "$verbose" -ge 3 ]; then - if [ "$remote" ]; then - printf "${MAGENTA}backup_root on remote '%s': ${GREEN}'%s'${NO_COLOR}\n" \ - "$remote" "$backup_root" - else - printf "${MAGENTA}backup_root: ${GREEN}'%s'${NO_COLOR}\n" \ - "$backup_root" - fi - fi - ;; - *) - # use snapper_target_snapshot - backup_root="$selected_target"/"$backupdir"/"$snapper_target_config" - ;; - esac + if [ "$snapper_target_sync_id" -eq 0 ]; then + if [ "$verbose" -ge 2 ]; then + if [ -z "$remote" ]; then + printf "${MAGENTA}No synced ${GREEN}target snapshot${MAGENTA} available for snapper config ${GREEN}'%s'${MAGENTA} ...${NO_COLOR}\n" \ + "$selected_config" + else + printf "${MAGENTA}No synced ${GREEN}target snapshot${MAGENTA} available for snapper config ${GREEN}'%s'${MAGENTA} on '%s' ...${NO_COLOR}\n" \ + "$selected_config" "$remote" + 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" + if [ "$verbose" -ge 3 ]; then + if [ -z "$remote" ]; then + printf "${MAGENTA}backupdir: ${GREEN}'%s'${NO_COLOR}\n" \ + "$backupdir" + else + printf "${MAGENTA}backupdir on remote '%s': ${GREEN}'%s'${NO_COLOR}\n" \ + "$remote" "$backupdir" + fi + fi + # 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 + backup_root="$value" + snapper_target_sync_snapshot="$value"/.snapshots/"$snapper_target_sync_id"/"$snapper_snapshot_name" + fi + # commandline settings for backupdir on selected_target will have priority + if [ "$backup_root" != "$selected_target/$backupdir" ]; then + backup_root="$selected_target"/"$backupdir"/"$snapper_target_config" + snapper_target_sync_snapshot="$backup_root"/.snapshots/"$snapper_target_sync_id"/"$snapper_snapshot_name" + fi + if [ "$verbose" -ge 3 ]; then + if [ "$remote" ]; then + printf "${MAGENTA}backup_root on remote '%s': ${GREEN}'%s'${NO_COLOR}\n" \ + "$remote" "$backup_root" + else + printf "${MAGENTA}backup_root: ${GREEN}'%s'${NO_COLOR}\n" \ + "$backup_root" + fi + 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 + 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'" - eval "backup_host_$i='$backup_host'" + # 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'" + eval "backup_host_$i='$backup_host'" - 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 - fi + 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 + fi - i=$((i+1)) + i=$((i+1)) done } run_backup () { if [ "$verbose" -ge 1 ]; then - printf "${BLUE}Performing backups...${NO_COLOR}\n" + 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) + # 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 + if [ "$verbose" -ge 1 ]; then + printf "${MAGENTA}Performing backup for config ${GREEN}'%s'${MAGENTA}...${NO_COLOR}\n" \ + "${selected_config}" + fi - SNAP_SYNC_EXCLUDE=no + SNAP_SYNC_EXCLUDE=no - if [ -f "/etc/snapper/configs/$selected_config" ]; then - . "$snapper_config_dir/$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 + if [ -f "/etc/snapper/configs/$selected_config" ]; then + . "$snapper_config_dir/$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)" - cont_backup=\$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 + #cont_backup="$(echo \$snapper_activate_$i)" + cont_backup=\$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 + 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) - backup_host=$(eval echo \$backup_host_$i) + # 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) + backup_host=$(eval echo \$backup_host_$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_common_sync_id: ${GREEN}'%s'${NO_COLOR}\n" \ - "$snapper_common_sync_id" - printf "${MAGENTA}backup_dir: ${GREEN}'%s'${NO_COLOR}\n" "$backup_dir" - printf "${MAGENTA}backup_root: ${GREEN}'%s'${NO_COLOR}\n" "$backup_root" - printf "${MAGENTA}backup_host: ${GREEN}'%s'${NO_COLOR}\n" "$backup_host" - fi + 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_common_sync_id: ${GREEN}'%s'${NO_COLOR}\n" \ + "$snapper_common_sync_id" + printf "${MAGENTA}backup_dir: ${GREEN}'%s'${NO_COLOR}\n" "$backup_dir" + printf "${MAGENTA}backup_root: ${GREEN}'%s'${NO_COLOR}\n" "$backup_root" + printf "${MAGENTA}backup_host: ${GREEN}'%s'${NO_COLOR}\n" "$backup_host" + fi - # create the needed snapper structure on the target - case $snapper_backup_type in - btrfs-snapshot) - create_snapshot - if [ $snapper_source_id -lt 0 ]; then - return 1 - fi + # create the needed snapper structure on the target + case $snapper_backup_type in + btrfs-snapshot) + create_snapshot + if [ $snapper_source_id -lt 0 ]; then + return 1 + fi - # 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" + # 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" + # 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" + # 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 - if [ "$snapper_source_id" -lt 0 ]; then - return 1 - fi - 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 + # 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 + if [ "$snapper_source_id" -lt 0 ]; then + return 1 + fi + 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" + # 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 + # 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 - # 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 + # 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 - # prepare pipe command - if [ "$dryrun" -eq 0 ]; then - if [ "$snapper_source_sync_id" -eq 0 ] \ - || [ "$snapper_target_sync_id" -eq 0 ] \ - || [ "$backup_mode" = "full" ] ; then + # prepare pipe command + if [ "$dryrun" -eq 0 ]; then + if [ "$snapper_source_sync_id" -eq 0 ] \ + || [ "$snapper_target_sync_id" -eq 0 ] \ + || [ "$backup_mode" = "full" ] ; then - # get size of stream that needs to be transfered - check_transfer_size "source_snapshot=$snapper_source_snapshot" + # get size of stream that needs to be transfered + check_transfer_size "source_snapshot=$snapper_source_snapshot" - # prepare send pipe command - create_pv_cmd - case $selected_fstype in - btrfs) - cmd="btrfs send $btrfs_verbose_flag $snapper_source_snapshot 2>$BTRFS_PIPE \ - | $cmd_pv \ - $cmd_ionice $ssh btrfs receive $btrfs_verbose_flag $snapper_target_snapshot/ 1>$BTRFS_PIPE 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>$BTRFS_PIPE " - 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 + # prepare send pipe command + create_pv_cmd + case $selected_fstype in + btrfs) + cmd="btrfs send $btrfs_verbose_flag $snapper_source_snapshot 2>$BTRFS_PIPE \ + | $cmd_pv \ + $cmd_ionice $ssh btrfs receive $btrfs_verbose_flag $snapper_target_snapshot/ 1>$BTRFS_PIPE 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>$BTRFS_PIPE " + 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 - # send full snapshot to target - if [ "$verbose" -ge 2 ]; then - if [ ${#transfer_size} -gt 0 ]; then - printf "${MAGENTA}Sending ${GREEN}snapshot${NO_COLOR} for snapper config ${GREEN}'%s' ${MAGENTA}(id='${GREEN}%s${MAGENTA}', size='${GREEN}%s${MAGENTA}')${NO_COLOR} ...\n" \ - "$selected_config" "$snapper_source_id" "$transfer_size" - else - printf "${MAGENTA}Sending ${GREEN}snapshot${NO_COLOR} for snapper config ${GREEN}'%s' ${MAGENTA}(id='${GREEN}%s${MAGENTA}')${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 <$BTRFS_PIPE)" - 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 + # send full snapshot to target + if [ "$verbose" -ge 2 ]; then + if [ ${#transfer_size} -gt 0 ]; then + printf "${MAGENTA}Sending ${GREEN}snapshot${NO_COLOR} for snapper config ${GREEN}'%s' ${MAGENTA}(id='${GREEN}%s${MAGENTA}', size='${GREEN}%s${MAGENTA}')${NO_COLOR} ...\n" \ + "$selected_config" "$snapper_source_id" "$transfer_size" + else + printf "${MAGENTA}Sending ${GREEN}snapshot${NO_COLOR} for snapper config ${GREEN}'%s' ${MAGENTA}(id='${GREEN}%s${MAGENTA}')${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 <$BTRFS_PIPE)" + 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 + # 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="stat --format %i $snapper_source_snapshot 2>/dev/null" - ret=$(eval "$cmd") - if [ $? -eq 0 ]; then - # get size of stream that needs to be transfered - check_transfer_size "source_snapshot=$snapper_source_snapshot" "clone_snapshot=$snapper_common_sync_snapshot" - create_pv_cmd + cmd="stat --format %i $snapper_source_snapshot 2>/dev/null" + ret=$(eval "$cmd") + if [ $? -eq 0 ]; then + # get size of stream that needs to be transfered + check_transfer_size "source_snapshot=$snapper_source_snapshot" "clone_snapshot=$snapper_common_sync_snapshot" + create_pv_cmd - 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>$BTRFS_PIPE \ - | $cmd_pv \ - $cmd_ionice $ssh btrfs receive $btrfs_verbose_flag $snapper_target_snapshot/ 1>$BTRFS_PIPE 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>$BTRFS_PIPE \ - | $cmd_pv \ - $cmd_ionice cat > $snapper_target_snapshot/$snapper_target_stream 2>$BTRFS_PIPE" - else - cmd="btrfs send $btrfs_verbose_flag -p $snapper_common_sync_snapshot $snapper_source_snapshot 2>$BTRFS_PIPE \ - | $cmd_pv \ - $cmd_ionice $ssh 'cat > $snapper_target_snapshot/$snapper_target_stream' 2>$BTRFS_PIPE" - 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 + 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>$BTRFS_PIPE \ + | $cmd_pv \ + $cmd_ionice $ssh btrfs receive $btrfs_verbose_flag $snapper_target_snapshot/ 1>$BTRFS_PIPE 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>$BTRFS_PIPE \ + | $cmd_pv \ + $cmd_ionice cat > $snapper_target_snapshot/$snapper_target_stream 2>$BTRFS_PIPE" + else + cmd="btrfs send $btrfs_verbose_flag -p $snapper_common_sync_snapshot $snapper_source_snapshot 2>$BTRFS_PIPE \ + | $cmd_pv \ + $cmd_ionice $ssh 'cat > $snapper_target_snapshot/$snapper_target_stream' 2>$BTRFS_PIPE" + 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 - # TODO: handle error if snapshot already exists - # cmd="$ssh stat --format %i $snapper_target_snapshot 2>/dev/null" - # ret=$(eval "$cmd") - # if [ $? -eq 1 ]; then - # printf "${RED}Error: ${MAGENTA}target snapshot ${GREEN}'%i'${MAGENTA} already exists.${NO_COLOR}\n" - # printf "${GREEN}btrfs command:${NO_COLOR} '%s'\n" "$cmd" - # fi + # TODO: handle error if snapshot already exists + # cmd="$ssh stat --format %i $snapper_target_snapshot 2>/dev/null" + # ret=$(eval "$cmd") + # if [ $? -eq 1 ]; then + # printf "${RED}Error: ${MAGENTA}target snapshot ${GREEN}'%i'${MAGENTA} already exists.${NO_COLOR}\n" + # printf "${GREEN}btrfs command:${NO_COLOR} '%s'\n" "$cmd" + # fi - $(eval $cmd) - ret=$? - case "$ret" in - 0) - ;; - 1) - # empty stream, error, no changes - printf "${MAGENTA}btrfs pipe return-code: ${RED}'%s'${NO_COLOR}\n" "$ret" - printf "${RED}%s${NO_COLOR}\n" "$(cat <$BTRFS_PIPE)" - 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 <$BTRFS_PIPE)" - 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 common 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 + $(eval $cmd) + ret=$? + case "$ret" in + 0) + ;; + 1) + # empty stream, error, no changes + printf "${MAGENTA}btrfs pipe return-code: ${RED}'%s'${NO_COLOR}\n" "$ret" + printf "${RED}%s${NO_COLOR}\n" "$(cat <$BTRFS_PIPE)" + 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 <$BTRFS_PIPE)" + 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 common 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 + 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 "$backup_host" ] && [ "$backup_host" != "localhost" ]; then - if [ "$verbose" -ge 2 ]; then - printf "${MAGENTA}Send snapper source metadata ${GREEN}'%s'${MAGENTA} to target snapshot ${GREEN}'%s'${MAGENTA} ...${NO_COLOR}\n" \ - "$snapper_source_info" "$snapper_target_snapshot/info.xml" - fi - cp "$snapper_source_info" "$snapper_target_snapshot/info.xml" - else - if [ "$verbose" -ge 2 ]; then - printf "${MAGENTA}Send snapper source metadata ${GREEN}'%s'${MAGENTA} to remote target snapshot ${GREEN}'%s'${MAGENTA} ...${NO_COLOR}\n" \ - "$snapper_source_info" "$backup_host:$snapper_target_snapshot/info.xml" - fi - cmd="$scp $snapper_source_info root@$backup_host:$snapper_target_snapshot/info.xml" - if [ "$verbose" -ge 2 ]; then - printf "${MAGENTA}cmd: ${GREEN}'%s'${NO_COLOR}\n" \ - "$cmd" - fi - $(eval "$cmd") 1>/dev/null - fi - else - printf "${MAGENTA}dryrun${NO_COLOR}: Would copy info metadata '%s' to target.\n" \ - "$snapper_source_info" - fi + # send the snapper info metadata + if [ "$dryrun" -eq 0 ]; then + if [ -z "$backup_host" ] && [ "$backup_host" != "localhost" ]; then + if [ "$verbose" -ge 2 ]; then + printf "${MAGENTA}Send snapper source metadata ${GREEN}'%s'${MAGENTA} to target snapshot ${GREEN}'%s'${MAGENTA} ...${NO_COLOR}\n" \ + "$snapper_source_info" "$snapper_target_snapshot/info.xml" + fi + cp "$snapper_source_info" "$snapper_target_snapshot/info.xml" + else + if [ "$verbose" -ge 2 ]; then + printf "${MAGENTA}Send snapper source metadata ${GREEN}'%s'${MAGENTA} to remote target snapshot ${GREEN}'%s'${MAGENTA} ...${NO_COLOR}\n" \ + "$snapper_source_info" "$backup_host:$snapper_target_snapshot/info.xml" + fi + cmd="$scp $snapper_source_info root@$backup_host:$snapper_target_snapshot/info.xml" + if [ "$verbose" -ge 2 ]; then + printf "${MAGENTA}cmd: ${GREEN}'%s'${NO_COLOR}\n" \ + "$cmd" + fi + $(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'" + # 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" + # 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 + 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)) + i=$((i+1)) done } @@ -2103,20 +2104,20 @@ run_cleanup () { batch=${2:-$false} if [ "$verbose" -ge 1 ]; then - printf "${MAGENTA}Performing cleanup for config ${GREEN}'%s'${MAGENTA} ...${NO_COLOR}\n" \ - "$selected_config" + printf "${MAGENTA}Performing cleanup for config ${GREEN}'%s'${MAGENTA} ...${NO_COLOR}\n" \ + "$selected_config" fi if [ "$dryrun" -eq 0 ]; then - # cleanup failed runs - cleanup_snapper_failed_ids "$selected_config" "$batch" + # cleanup failed runs + cleanup_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 + # 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" + printf "${MAGENTA}dryrun${NO_COLOR}: Would cleanup failed snapshot IDs ...\n" fi } @@ -2124,29 +2125,29 @@ run_finalize () { selected_config=${1} if [ "$verbose" -ge 1 ]; then - printf "${MAGENTA}Finalize backups for config ${GREEN}'%s'${MAGENTA}...${NO_COLOR}\n" \ - "${selected_config}" + printf "${MAGENTA}Finalize backups for config ${GREEN}'%s'${MAGENTA}...${NO_COLOR}\n" \ + "${selected_config}" fi SNAP_SYNC_EXCLUDE=no if [ -f "$snapper_config_dir/$selected_config" ]; then - . "$snapper_config_dir/$selected_config" + . "$snapper_config_dir/$selected_config" else - printf "${RED}Error: ${MAGENTA}Selected snapper configuration ${GREEN}'$selected_config'${MAGENTA} does not exist in '$snapper_config_dir'!\n${NO_COLOR}" - return 1 + printf "${RED}Error: ${MAGENTA}Selected snapper configuration ${GREEN}'$selected_config'${MAGENTA} does not exist in '$snapper_config_dir'!\n${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 - return + if [ "$donotify" -gt 0 ]; then + notify_info "Finalize backup" "NOTE: Skipping '$selected_config' configuration." + fi + return fi if [ "$donotify" -gt 0 ]; then - notify_info "Finalize backup" "Cleanup tasks for configuration '$selected_config'." + notify_info "Finalize backup" "Cleanup tasks for configuration '$selected_config'." fi # retrieve config specific infos from pseudo Arrays @@ -2174,189 +2175,189 @@ run_finalize () { # Tag new snapshots key/value parameter if [ "$verbose" -ge 2 ]; then - printf "${MAGENTA}Tagging target ...${NO_COLOR}\n" + 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 + # 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. - ii=1 - ii_max=20 - ii_sleep=15 + # !!! 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. + ii=1 + ii_max=20 + 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) + # 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 + 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" + # 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 + 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 + # 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}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 [ "$backup_host" = "localhost" ]; then - 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") - else - 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"\') - 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 + 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}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 [ "$backup_host" = "localhost" ]; then + 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") + else + 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"\') + 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" + # 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 "${MAGENTA}calling: ${GREEN}'%s'${NO_COLOR}\n" "$cmd" - fi - eval "$cmd" - if [ $? -gt 0 ]; then - printf "${RED}ERROR: ${MAGENTA}Updating snapper metadata for source snapshot id ${GREEN}'%s'${NO_COLOR}${MAGENTA} failed.${NO_COLOR}\n" \ - "Please check for sufficiant space.${NO_COLOR}\n" \ - "$snapper_source_id" - 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 id ${GREEN}'%s'${NO_COLOR} on source for configuration ${GREEN}'%s'${NO_COLOR} ...\n" \ + "$snapper_source_id" "$selected_config" + printf "${MAGENTA}calling: ${GREEN}'%s'${NO_COLOR}\n" "$cmd" + fi + eval "$cmd" + if [ $? -gt 0 ]; then + printf "${RED}ERROR: ${MAGENTA}Updating snapper metadata for source snapshot id ${GREEN}'%s'${NO_COLOR}${MAGENTA} failed.${NO_COLOR}\n" \ + "Please check for sufficiant space.${NO_COLOR}\n" \ + "$snapper_source_id" + 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 "$ {MAGENTA}calling: ${GREEN}'%s'${NO_COLOR}\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 + 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 "$ {MAGENTA}calling: ${GREEN}'%s'${NO_COLOR}\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 - case $snapper_backup_type in - btrfs-archive) - printf "${MAGENTA}dryrun${NO_COLOR}: %s snapper --config %s modify\\ \n \ + # dry-run output + case $snapper_backup_type in + btrfs-archive) + printf "${MAGENTA}dryrun${NO_COLOR}: %s snapper --config %s modify\\ \n \ \t--cleanup-algorithm 'dsnap-sync'\\ \n \ \t--userdata 'tapeid=%s'\\ \n \ \t'snapper_source_id'\n" \ - "$ssh" "$selected_config" "$volume_name" - ;; - btrfs-snapshot) - printf "${MAGENTA}dryrun${NO_COLOR}: %s snapper --config %s modify \\ \n \ + "$ssh" "$selected_config" "$volume_name" + ;; + btrfs-snapshot) + printf "${MAGENTA}dryrun${NO_COLOR}: %s snapper --config %s modify \\ \n \ \t--description '%s' \\ \n \ \t--cleanup-algorithm 'dsnap-sync' \\ \n \ \t--userdata 'host=%s, subvolid=%s, uuid=%s' \\ \n \ \t'snapper_source_id'\n" \ - "$ssh" "$selected_config" "$snap_description_finished" \ - "$src_host" "src_subvolid" "$src_uuid" - ;; - esac - 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" + "$ssh" "$selected_config" "$snap_description_finished" \ + "$src_host" "src_subvolid" "$src_uuid" + ;; + esac + 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 } @@ -2367,122 +2368,122 @@ select_target () { subvol='' if [ "$verbose" -ge 1 ]; then - printf "${BLUE}Select backup target...${NO_COLOR}\n" + 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 ${GREEN}'localhost'${NO_COLOR}.\n" - else - printf "Selecting a mounted device for backups on ${GREEN}'%s'${NO_COLOR}.\n" "$remote" - fi + if [ -z "$remote" ]; then + printf "Selecting a mounted device for backups on ${GREEN}'localhost'${NO_COLOR}.\n" + else + printf "Selecting a mounted device for backups on ${GREEN}'%s'${NO_COLOR}.\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 - if [ "$interactive" -eq 0 ] && [ "$target_count" -gt 1 ] ; then - printf "${RED}Error: ${MAGENTA}Can't determine unique target in batch mode. Got ${GREEN}'%i'${MAGENTA} possible targets.${NO_COLOR}\n" "$target_count" - printf "Please run in ${GREEN}interactive mode${NO_COLOR}, or define unique target ${GREEN}'UUID'${NO_COLOR} or ${GREEN}'Subvolume'${NO_COLOR} via cmdline arguments.\n" - exit 1 - fi + 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 + if [ "$interactive" -eq 0 ] && [ "$target_count" -gt 1 ] ; then + printf "${RED}Error: ${MAGENTA}Can't determine unique target in batch mode. Got ${GREEN}'%i'${MAGENTA} possible targets.${NO_COLOR}\n" "$target_count" + printf "Please run in ${GREEN}interactive mode${NO_COLOR}, or define unique target ${GREEN}'UUID'${NO_COLOR} or ${GREEN}'Subvolume'${NO_COLOR} via cmdline arguments.\n" + exit 1 + fi - # present all mounted BTRFS filesystems - if [ "$verbose" -ge 3 ]; then - printf "${MAGENTA}Found ${GREEN}'%s'${MAGENTA} possible targets.${NO_COLOR}\n" \ - "$target_count" - fi - while [ "$target_id" -lt "$target_count" ]; do - # 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 - ;; - *) - # TODO: implement a timer that will stop exececution, if input isn't given in an appropriate time-frame - printf "\nNo disk selected. Select a disk to continue.\n" - target_id=0 - i=0 - ;; - esac + # present all mounted BTRFS filesystems + if [ "$verbose" -ge 3 ]; then + printf "${MAGENTA}Found ${GREEN}'%s'${MAGENTA} possible targets.${NO_COLOR}\n" \ + "$target_count" + fi + while [ "$target_id" -lt "$target_count" ]; do + # 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 + ;; + *) + # TODO: implement a timer that will stop exececution, if input isn't given in an appropriate time-frame + printf "\nNo disk selected. Select a disk to continue.\n" + target_id=0 + i=0 + ;; + esac done if [ "$target_selected" = x ]; then - exit 0 + exit 0 fi selected_target=$(eval echo \$target_$target_selected) @@ -2491,45 +2492,45 @@ select_target () { 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 + 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 + 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 } @@ -2542,87 +2543,87 @@ select_target_disk () { subvol='' if [ "$verbose" -ge 1 ]; then - printf "${BLUE}Select backup target ...${NO_COLOR}\n" + 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 + 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 + 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 + exit 0 fi selected_uuid=$(eval echo \$disk_uuid_$disk_selected) @@ -2630,37 +2631,37 @@ select_target_disk () { 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 + 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 + 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 } @@ -2673,88 +2674,88 @@ select_target_tape () { subvol='' if [ "$verbose" -ge 1 ]; then - printf "${BLUE}Select target tape ...${NO_COLOR}\n" + 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 + 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 + 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 + exit 0 fi selected_tape_id=$(eval echo \$tape_id_$tape_selected) @@ -2762,37 +2763,37 @@ select_target_tape () { #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 + 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 + 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 } @@ -2802,9 +2803,9 @@ set_config(){ config_value=${3:-/var/lib/dsnap-sync} if [ -n "$remote" ]; then - "$ssh" sed -i \'"s#^\($config_key\s*=\s*\).*\$#\1\"$config_value\"#"\' $config + "$ssh" sed -i \'"s#^\($config_key\s*=\s*\).*\$#\1\"$config_value\"#"\' $config else - sed -i "s#^\($config_key\s*=\s*\).*\$#\1\"$config_value\"#" $config + sed -i "s#^\($config_key\s*=\s*\).*\$#\1\"$config_value\"#" $config fi } @@ -2832,19 +2833,19 @@ Options: -a, --automount start automount for given path to get a valid target mountpoint -b, --backupdir backupdir is a relative path that will be appended to target backup-root --backuptype Specify backup type , default: parent - archive: btrfs-snapshot to non btrfs-filesystem - child: clone the btrfs-snapshot to target btrfs-filesystem - parent: disk2disk the btrfs-snapshot to target btrfs-filesystem + archive: btrfs-snapshot to non btrfs-filesystem + child: clone the btrfs-snapshot to target btrfs-filesystem + parent: disk2disk the btrfs-snapshot to target btrfs-filesystem --batch no user interaction -d, --description Change the snapper description. Default: "latest incremental backup" --label-finished snapper description tagging successful jobs. Default: "dsnap-sync backup" --label-running snapper description tagging active jobs. Default: "dsnap-sync in progress" --label-synced snapper description tagging last synced jobs - Default: "dsnap-sync last incremental" + Default: "dsnap-sync last incremental" --color Enable colored output messages -c, --config Specify snapper configurations. - Default: Perform snapshots for each available snapper configuration. - (e.g. -c "root" -c "home"; --config root --config home) + Default: Perform snapshots for each available snapper configuration. + (e.g. -c "root" -c "home"; --config root --config home) --config-postfix 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 @@ -2855,16 +2856,16 @@ Options: --noionice Disable setting of I/O class and priority options on target -p, --port The remote port -r, --remote
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 + 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 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 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 + If multiple mount points are found with the same UUID, will prompt user selection -t, --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 + --version show program version EOF exit 0 @@ -2877,40 +2878,40 @@ verify_archive_structure () { remote_host=${4##remote=} if [ "$verbose" -ge 2 ]; then - printf "${BLUE}verify-archive_structure() ...${NO_COLOR}\n" + printf "${BLUE}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" + 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 "$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 + # strip last dir from backup_root + #base_path=${backup_root%/*}; echo $base_path + if [ "$dryrun" -eq 0 ]; then + "$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 @@ -2922,95 +2923,95 @@ verify_archive_structure () { # 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 [ "$("$ssh" mkdir --mode=0700 "$backup_root/$snapper_id")" -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 + 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 [ "$("$ssh" mkdir --mode=0700 "$backup_root/$snapper_id")" -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" + 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 2 ]; then - printf "${BLUE}verify_backupdir() ...${NO_COLOR}\n" + printf "${BLUE}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 + # 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 2 ]; then - printf "${BLUE}verify_snapper_config() ...${NO_COLOR}\n" + printf "${BLUE}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} ${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 + 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} ${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 - . "$snapper_config_dir/$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" - return - fi + . "$snapper_config_dir/$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" + return + fi fi } @@ -3021,264 +3022,264 @@ verify_snapper_structure () { remote_host=${4##remote=} if [ "$verbose" -ge 2 ]; then - printf "${BLUE}verify-snapper_structure() ...${NO_COLOR}\n" + printf "${BLUE}verify-snapper_structure() ...${NO_COLOR}\n" fi if [ "$verbose" -ge 3 ]; then - if [ "$remote_host" ]; then - printf "${MAGENTA}Verify snapper filesystem structure${NO_COLOR} on remote target ${GREEN}'%s'${NO_COLOR}...\n" \ - "$remote_host" - else - printf "${MAGENTA}Verify snapper filesystem structure${NO_COLOR} on local target ${GREEN}'%s'${NO_COLOR}...\n" \ - "$backup_root" - fi + if [ "$remote_host" ]; then + printf "${MAGENTA}Verify snapper filesystem structure${NO_COLOR} on remote target ${GREEN}'%s'${NO_COLOR}...\n" \ + "$remote_host" + else + printf "${MAGENTA}Verify snapper filesystem structure${NO_COLOR} on local target ${GREEN}'%s'${NO_COLOR}...\n" \ + "$backup_root" + fi 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 + 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 - # if available: install dsnap-sync default config - if [ -f "$snapper_template_dir/$snapper_template_dsnap_sync" ]; then - cp "$snapper_template/$snapper_template_dsnap-sync" "$snapper_config_dir/$snapper_subvolume_template" - else - printf "${MAGENTA}Missing a snapper template ${GREEN}%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 - fi + # 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 + # if available: install dsnap-sync default config + if [ -f "$snapper_template_dir/$snapper_template_dsnap_sync" ]; then + cp "$snapper_template/$snapper_template_dsnap-sync" "$snapper_config_dir/$snapper_subvolume_template" + else + printf "${MAGENTA}Missing a snapper template ${GREEN}%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 + fi - #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 [ $? -ne 0 ]; 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 [ $? -ne 0 ]; then - printf "${RED}Error: ${MAGENTA}Changing directory-mode for BTRFS subvolume (backup-root) ${GREEN}%s:%s${MAGENTA} failed. Return-Code: '%s'${NO_COLOR}\n" \ - "$remote_host" "$backup_root" "$ret" - 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 + #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 [ $? -ne 0 ]; 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 [ $? -ne 0 ]; then + printf "${RED}Error: ${MAGENTA}Changing directory-mode for BTRFS subvolume (backup-root) ${GREEN}%s:%s${MAGENTA} failed. Return-Code: '%s'${NO_COLOR}\n" \ + "$remote_host" "$backup_root" "$ret" + 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 + # 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 ]; 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 + 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 ]; 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" + 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 -ne 256 ]; then - # a snapshot path exists, but is not a btrfs snapshot - if [ -z "$remote" ]; then - printf "${RED}Cancel snapshot creation${NO_COLOR}: Directory with id ${GREEN}'%s'${NO_COLOR} already exist in ${BLUE}'%s'${NO_COLOR}, but isn't a btrfs snapshot\n" \ - "$snapper_id" "$backup_root/$snapper_snapshots" - else - printf "${RED}Cancel snapshot creation${NO_COLOR}: Directory with id ${GREEN}'%s'${NO_COLOR} already exists on ${BLUE}'%s'${NO_COLOR} in ${BLUE}'%s'${NO_COLOR}, but isn't a btrfs snapshot\n" \ - "$snapper_id" "$remote" "$backup_root/$snapper_snapshots" - fi - # cleanup generated snapper entry - cleanup_snapper_failed_ids "$selected_config" "$batch" - return 1 - fi - fi + 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 -ne 256 ]; then + # a snapshot path exists, but is not a btrfs snapshot + if [ -z "$remote" ]; then + printf "${RED}Cancel snapshot creation${NO_COLOR}: Directory with id ${GREEN}'%s'${NO_COLOR} already exist in ${BLUE}'%s'${NO_COLOR}, but isn't a btrfs snapshot\n" \ + "$snapper_id" "$backup_root/$snapper_snapshots" + else + printf "${RED}Cancel snapshot creation${NO_COLOR}: Directory with id ${GREEN}'%s'${NO_COLOR} already exists on ${BLUE}'%s'${NO_COLOR} in ${BLUE}'%s'${NO_COLOR}, but isn't a btrfs snapshot\n" \ + "$snapper_id" "$remote" "$backup_root/$snapper_snapshots" + fi + # cleanup generated snapper entry + cleanup_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" + printf "${MAGENTA}dryrun${NO_COLOR}: Would check/create path %s to store target snapshot ...\n" \ + "$backup_root/$snapper_snapshots/$snapper_id" fi } @@ -3297,7 +3298,7 @@ trap trapkill TERM INT check_prerequisites # validate commandline options, set resonable defaults -parse_params $@ +parse_params "$@" # parse infos for backup medium get_media_infos @@ -3314,8 +3315,8 @@ run_backup # cleanup if [ -d "$TMPDIR" ]; then if [ "$verbose" -ge 2 ]; then - printf "${MAGENTA}Cleanup temporary directory ${GREEN}'%s'${NO_COLOR}\n" \ - $TMPDIR + printf "${MAGENTA}Cleanup temporary directory ${GREEN}'%s'${NO_COLOR}\n" \ + $TMPDIR fi rm -rf "$TMPDIR" || die "Failed to cleanup temporary directory '%s'\n" "$TMPDIR" fi @@ -3328,24 +3329,24 @@ 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 + 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 + 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 + 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