1
0
Files
zfssnap/zfsbk.sh

206 lines
6.2 KiB
Bash
Executable File

#! /bin/sh
# change this to the name of your ZFS pool. Or set ZPOOL envvar at runtime
zpool=${ZPOOL:-"rpool"}
# name of backup to take
bkname=${1:-"default"}
# make a backup collection having 1 full and N-1 incremental backups.
# set to 1 to avoid incrementals and always take full dumps.
max_incremental=${2:-"4"}
# names of the DATASETs to exclude (datasets, not mountpoints!)
# can override this list at runtime with EXCLUDES envvar.
# can extend this list at runtime with EXTRA_EXCLUDES envvar.
excludes=${EXCLUDES:-"rpool rpool/ROOT rpool/data"}
UPLOAD_PATH="b2://arclightbackup/zfssnap"
usage () {
echo "Usage:"
echo "zfsbk.sh <bkname> [numincr]"
echo "Takes & sends backup with given name. If snapshots with"
echo "this name exist, backup incrementally. Limit incremental"
echo "backup sequences to 'numincr' steps (default 1 = alw. full)."
echo
echo "Optional envvars:"
echo "UPLOAD_PATH upload generated backup to this path. scp:// or rsync://"
echo "CLEAR_BKFILE remove local backup file at the end of the process."
}
failmsg () {
echo $*
echo
usage
exit 1
}
verify_zpool () {
zpool status $zpool >/dev/null 2>&1 || failmsg "ZFS pool '$zpool' not found. Fix your \$ZPOOL envvar."
}
verify_zpool
echo $bkname | grep -qiE '^[a-z0-9]+$' || failmsg "Given bkname is invalid. Want alphanumeric."
echo "$max_incremental" | grep -qE '^[1-9][0-9]*$' || failmsg "Invalid number of backups sequence steps, want positive integer."
export PATH=$PATH:/usr/local/bin
# list snapshots by decreasing time. Arguments: [name] restrict listing to this tag
# list_named_snaps () {
# local bkname=$1
# snapdir="/.zfs/snapshot"
# for i in $snapdir/zbk-$bkname-*
# do
# test -e "$i" && (echo $i |sed "s@^$snapdir/@@")
# done | sort -r
# }
# list all snapshots for the tag by decreasing time.
# Arguments:
# [name] restrict listing to this tag
# [set] restrict listing to this set
all_snaps () {
local bkname=$1
zfs list -t snapshot | grep $bkname | cut -f1 -d' ' | sort -r
}
# list snapshots for the set by decreasing time.
# Arguments:
# [name] restrict listing to this tag
# [set] restrict listing to this set
snaps_for_set () {
local bkname=$1
local set=$2
zfs list -t snapshot | grep $bkname | grep $set | cut -f1 -d' ' | sort -r
}
# list datasets with snapshots. Arguments: [name] restrict listing to this tag
list_snap_sets () {
local bkname=$1
zfs list -t snapshot | grep $bkname | cut -f1 -d' ' | cut -f1 -d'@' | uniq
}
# list the datasets we want to backup.
# this is snap sets minus excludes
list_backup_snap_sets () {
local bkname=$1
list_snap_sets $bkname | while read set
do
matched=0
for excl in $excludes
do
if [ "x$set" = "x$excl" ]
then
matched=1
break
fi
done
if [ $matched -ne 1 ]
then
echo -n "$set "
fi
done
}
# remove all datasets for a given tag. Arguments: $1 -> bkname
reset_sequence () {
local bkname=$1
all_snaps $bkname | while read snapname
do
zfs destroy $snapname
done
}
# take snapshot
echo ./zfssnap.sh $bkname $max_incremental
if ! ./zfssnap.sh $bkname $max_incremental
then
echo "Error creating snapshot! Code $?"
exit 1
fi
# iterate all the sets we want to back up
# and write dump files
num_snaps=0 # save for later
for set in `list_backup_snap_sets $bkname` ; do
snaps=`snaps_for_set $bkname $set`
num_snaps=`echo $snaps | awk 'BEGIN {RS=" "} END {print NR}'`
label_latest=`echo $snaps | awk 'BEGIN {RS=" "} NR==1'`
#echo "$set: $num_snaps snaps, latest: $label_latest"
# decide what to do based on how many snapshots were found
if [ $num_snaps -eq 0 ]
then
# no snapshots. Something's wrong
echo "Error, no snapshots found for $set $bkname!"
echo $snaps
exit 1
elif [ $num_snaps -eq 1 ]
then
# first snapshot. Send full
echo "first snapshot. Send full"
file_latest=`echo $label_latest | sed 's@/@_@g'`
bkfile="/backups/${file_latest}.dump"
echo "zfs send -R $label_latest > $bkfile"
zfs send -R $label_latest 2>/dev/null > $bkfile
else
# n-th snapshot. Send incrementally
echo "n-th snapshot. Send incrementally"
file_latest=`echo $label_latest | sed 's@/@_@g'`
label_2ndlatest=`echo $snaps | awk 'BEGIN {RS=" "} NR==2'`
file_2ndlatest=`echo $label_2ndlatest | sed 's@/@_@g'`
bkfile="/backups/${file_latest}--${file_2ndlatest}.dump"
echo "zfs send -i $label_2ndlatest -R $label_latest > $bkfile"
zfs send -i $label_2ndlatest -R $label_latest 2>/dev/null > $bkfile
fi
done
if [ $num_snaps -eq $max_incremental ]
then
# finished the incremental sequence. Take full backup next time
echo "Sequence of $max_incremental increm steps complete. Resetting."
reset_sequence $bkname
fi
# upload backup to remote location
if [ "x$UPLOAD_PATH" != x ]
then
upload_excode=1 # assume failure, override with actual outcome
#echo "Archiving $bkfile remotely..."
if echo "$UPLOAD_PATH" | grep -qE '^rsync://'
then
rsync -qz $bkfile ${UPLOAD_PATH#rsync://}
upload_excode=$?
elif echo "$UPLOAD_PATH" | grep -qE '^scp://'
then
SCP_PATH=${UPLOAD_PATH#scp://}
if [ "$num_snaps" -eq 1 -a "x$DELETE_OUTDATED" != x ]
then
echo "Clearing old zbk-${bkname}* sequence remotely..."
echo "rm zbk-${bkname}*" | sftp -q -b- $SCP_PATH
fi
scp -BCq $bkfile "$SCP_PATH"
upload_excode=$?
elif echo "$UPLOAD_PATH" | grep -qE '^b2://'
then
#B2_PATH=${UPLOAD_PATH#b2://}
echo "Uploading to b2: $bkfile"
#echo "b2 upload-file --noProgress $bkfile zfssnap/$B2_PATH"
echo "b2 sync --delete --noProgress /backups $UPLOAD_PATH"
upload_excode=$?
else
echo "UPLOAD_PATH not understood! '$UPLOAD_PATH'"
echo "Expecting rsync://.. or scp://.."
exit 1
fi
# remove local backup if requested & upload was successful
if [ $upload_excode -eq 0 -a "x$CLEAR_BKFILE" != x ]
then
# remove backup file if requested
rm -f $bkfile
fi
fi