From 1c561dc5e324cf82dcf70a91b681967b2fd35874 Mon Sep 17 00:00:00 2001 From: Max Christian Pohle Date: Mon, 19 Apr 2021 01:19:53 +0200 Subject: Improved version of the squash script --- zfs-squash-datasets.sh | 191 ++++++++++++++++++------------------------------- 1 file changed, 68 insertions(+), 123 deletions(-) diff --git a/zfs-squash-datasets.sh b/zfs-squash-datasets.sh index 3623edf..0e6d602 100755 --- a/zfs-squash-datasets.sh +++ b/zfs-squash-datasets.sh @@ -1,139 +1,84 @@ -#!/usr/local/bin/bash - -# DO NOT USE THIS SCRIPT -# It is in a state of hot garbage and will probably be deleted soon - - -print_help_and_exit() { - echo "Please run this program with a source and a target dataset name as arguments" - echo "e.g. $0 zpool/dataset1 zpool/dataset2" - exit +#!/bin/sh +# This script expects a source dataset and a target dataset as arguments. It +# will then copy each snapshot of the source dataset into the target as a +# subfolder with the name of the dataset. This is being archived by cloning the +# source-dataset first into a sub-dataset of the source dataset named after the +# snapshot. From there rsync is used to copy files into the target location. +# After that finishes a snapshot will be created in the target with the original +# snapshot-name and a suffix, which is the last part of the source dataset. +# +# Known Bugs: +# +# * This script will immidiatly stop when any command returns an error and it +# will not clean up automatically the clones it created. But these clones are +# not promoted by the script and can be deleted without altering the source +# dataset and in order to re-execute the script after fixing the reason for the +# error. +# +# * There is currently no sanity checking. Please do not copy files from a +# dataset, which is a child of the source dataset. + +# +# Example: +# +# ./zfs-squash-datasets.sh zpool/software zpool/projects +# +# would create a folder /zpool/projects/software within the dataset +# zpool/projects + + + +set -o errexit + +getMountPoint() { + zfs get -H -o value mountpoint "$1" ||\ + (echo "mountpoint for dataset '$1' not found" ; exit 1) } -SIMULATION=0 - -args=$(getopt n $*) -test $? == 0 || print_help_and_exit - -set -- $args - -while :; do - sleep 1 - echo $1 - case "$1" in - --) shift; break ;; - -n) SIMULATION=1 ; shift ;; - esac -done +listAllSnapshots() { + zfs list -H -o name -tsnap -r "$1" ||\ + (echo "Could not find snapshots under $1" ; exit 2) +} -DATASET_ROOT=$1 +DATASET=$1 +DATASET_NAME=`basename $DATASET` DATASET_TARGET=$2 +TARGET_PATH=`basename $DATASET_TARGET` -if ! zfs get mountpoint $DATASET_TARGET >/dev/null; then - echo "Dataset $DATASET_TARGET does not exist and will be created." - echo "You have 10 seconds to abort this with CTRL-C" - sleep 10 - if ! $SIMULATION; then - zfs create $DATASET_TARGET - fi -fi - -if zfs get -H -t filesystem -o name,value -r snapdir zeus/data/projects | grep -v visible >/dev/null; then - echo Please make all snapdirs visible: - zfs get -H -t filesystem -o name,value -r snapdir zeus/data/projects - exit 2 -fi - - -MOUNTPOINT_TARGET=$(zfs get -o value -H mountpoint $DATASET_TARGET) -test ${SIMULATION:0} = 1 && test $MOUNTPOINT_TARGET == "" && MOUNTPOINT_TARGET="datapool/simulation" - -test -d "$MOUNTPOINT_TARGET" || print_help_and_exit +MP_TARGET=`getMountPoint $2` -# make sure, that ctrl-c also works in the inner loops -trap exit INT +test -d "$MP_TARGET" || (echo "failed to find mountpoint for $DATASET_TARGET: Its not $MP_TARGET" ; exit 3) -# recursively contains all snapshot names (the part after the @) from root on, sorted and only once -SNAPSHOTS=$(zfs list -H -oname -tsnap -r $DATASET_ROOT | cut -d@ -f2 | sort -u) -DATASETS=$(zfs list -H -oname -t filesystem -r $DATASET_ROOT) +for i in `listAllSnapshots $DATASET`; do + SNAPSHOT=`echo "$i" | cut -d@ -f2` -test "${SIMULATION:-0}" = 1 && echo "SIMULATION MODE" + CLONETMP="$DATASET/$SNAPSHOT" -$DATASETS + zfs clone "$i" "$CLONETMP" ||\ + (echo "Failed to create clone" ; exit 4) + MP_CLONE=`getMountPoint $CLONETMP` -echo "We are going to flatten the hierachy of this dataset:" -printf "\t%s\n" $DATASETS -echo -e "into the dataset with the name:" -echo -e "\t$DATASET_TARGET" -echo -e "mounted under:" -echo -e "\t$MOUNTPOINT_TARGET" -echo -e "with the following snapshots:" -printf "\t%s\n" $SNAPSHOTS -echo "" -echo "You have 10 seconds to abort this with CTRL-C" -sleep 10 + test -d "$MP_CLONE" ||\ + (echo "$CLONETMP: clones mount point not found" ; exit 5) -set -- $SNAPSHOTS -SNAPSHOTS_TOTAL=$# -SNAPSHOT_CURRENT=0 -set -- $DATASETS -shift -SUBDATASETS=$@ + # copy all files to the target location + (set -x ; rsync -ahox --info=progress2 --delete "$MP_CLONE/" "$MP_TARGET/$TARGET_PATH" ||\ + (echo "rsync exit status was $?" ; exit 6) + ) + + (set -x ; zfs snap "$DATASET_TARGET@$SNAPSHOT-$DATASET_NAME" ||\ + (echo "failed to create a snapshot $DATASET_TARGET: $SNAPSHOT-$DATASET_NAME" ; exit 7) + ) -for SNAPSHOT in $SNAPSHOTS; do - echo "" - echo "" - SNAPSHOT_CURRENT=$((SNAPSHOT_CURRENT + 1)) - echo "[$SNAPSHOT_CURRENT/$SNAPSHOTS_TOTAL] Next snapshot is: $SNAPSHOT" + # move clone out of the source dataset, so that we can resume + # zfs promote "$CLONETMP" ||\ + # (echo "Failed to promote clone '$CLONETMP'" ; exit 8) + zfs destroy "$CLONETMP" ||\ + (echo "Failed to destroy clone '$CLONETMP'" ; exit 8) - for DATASET in $SUBDATASETS; do - - DATASET_RELATIVE=${DATASET##$DATASET_ROOT} - - MOUNTPOINT=$(zfs get -H -o value mountpoint $DATASET) - echo "RELATIVE=$DATASET_RELATIVE" - - if [[ $MOUNTPOINT == "legacy" ]]; then - MOUNTPOINT=$(findmnt -n -o target $DATASET) - if test -d $MOUNTPOINT; then - >&2 echo "Dataset has a legacy mountpoint, but was not found in the fstab: $DATASET" - continue - fi - fi - - if [[ $MOUNTPOINT == "-" ]]; then - >&2 echo "Dataset does not have a mount point or is a vdev: $DATASET" - continue - fi - - SNAPDIR=$MOUNTPOINT/.zfs/snapshot/$SNAPSHOT - if test -d $SNAPDIR; then - - TARGET_DIR="${MOUNTPOINT_TARGET}${DATASET_RELATIVE}" - - echo -e \\trsync --info=stats3 --delete -a "$SNAPDIR/" "$TARGET_DIR/" - if test "${SIMULATION:-0}" = 0; then - - mkdir -p "$TARGET_DIR" - - if ! rsync --info=stats3 --delete -a "$SNAPDIR/" "$TARGET_DIR/" ; then - >&2 echo "rsync could not entirely copy from $SNAPDIR to $DATASET_TARGET" - fi - fi - else - echo -e "\tSKIP: Snapshot $SNAPSHOT does not exist for dataset $DATASET" - fi - done - - NEW_SNAPSHOT="$DATASET_TARGET@$SNAPSHOT" - echo "" - echo -e "\t> Concluding snapshot: ${NEW_SNAPSHOT} <" - echo "" - - if test "${SIMULATION:-0}" = 0; then - zfs snap $NEW_SNAPSHOT - fi + # for better readability add two blank lines... + echo + echo done - -- cgit v1.2.3