As I'm using this script within the "User scripts"-plugin I just want to post some improvements I have implemented:
- the script can now process a directory with spaces in its name
- the script checks if it is really processing a directory (the former script had some trouble with unwanted files under /mnt/diskX/)
Feel free to check out but keep in mind: Always make a backup of your data first!
Note: The snapshotted Unraid share must be on a BTRFS-disk and this disk must be configured as a included "Primary" or "Secondary" storage for this share (see configuration on Unraid web panel -> "Shares" ... click on share ... check "Share setting")
#!/bin/bash
#description=This script implements snapshots on btrfs array drives.
#arrayStarted=true
## Credits
# catapultam_habeo - Initial script
# Tomr - Modified version with SNAPSHOT_TYPE retention policy
# studmw - Modified script for spaces in directory names and a directory/file check (and German console output)
#if you change the type you'll have to delete the old snapshots manually
#valid values are: hourly, daily, weekly, monthly
SNAPSHOT_TYPE=hourly
#how many snapshots should be kept
MAX_SNAPS=2
#name of the shares to exclude stored as an array: EXCLUDE=('share 1' 'share 2')
EXCLUDE=('tm macbook air' 'tm macbook pro')
#name of the snapshot folder and delimeter. Do not change.
#https://www.samba.org/samba/docs/current/man-html/vfs_shadow_copy2.8.html
SNAPSHOT_DELIMITER="_UTC_"
SNAPSHOT_FORMAT="$(TZ=UTC date +${SNAPSHOT_TYPE}${SNAPSHOT_DELIMITER}%Y.%m.%d-%H.%M.%S)"
#make empty directories not freak out
shopt -s nullglob
#btrfs check
is_btrfs_subvolume() {
local dir=$1
[ "$(stat -f --format="%T" "$dir")" == "btrfs" ] || return 1
inode="$(stat --format="%i" "$dir")"
case "$inode" in
2|256)
return 0;;
*)
return 1;;
esac
}
#part from original script (not used)
#POSITIONAL=()
#while [[ $# -gt 0 ]]
#do
#key="$1"
#case $key in
# -n|--number)
# MAX_SNAPS="$2"
# shift # past argument
# shift # past value
# ;;
# -e|--exclude)
# EXCLUDE="$2"
# shift # past argument
# shift # past value
# ;;
# *)
# POSITIONAL+=("$1") # save it in an array for later
# shift # past argument
# ;;
#esac
#done
#set -- "${POSITIONAL[@]}" # restore positional parameters
#adjust MAX_SNAPS to prevent off-by-1
MAX_SNAPS=$((MAX_SNAPS+1))
#tokenize exclude list
declare -A excludes
for token in "${EXCLUDE[@]}"; do
excludes[$token]=1
#debug echo line
echo "Bearbeite das EXCLUDE-Verzeichnis \"$token\" und habe hier den Wert \"${excludes[$token]}\" gesetzt"
done
#iterate over all disks on array
for disk in /mnt/disk*[0-9]* ; do
#examine disk for btrfs-formatting
if is_btrfs_subvolume $disk ; then
echo "\"$(basename "${disk}")\" ist ein BTRFS-Laufwerk und wird bearbeitet:"
#iterate over shares present on disk
for share in ${disk}/* ; do
#test for exclusion
if [ ! -n "${excludes[$(basename "${share}")]}" ]; then
#check for .snapshots directory prior to generating actual snapshot
if [ -d "$disk" ]; then
if [ ! -d "$disk/.snapshots/" ] ; then
echo "Erstelle ein neues BTRFS-Subvolume \"$disk/.snapshots/\" als Snapshot-Hauptverzeichnis"
btrfs subvolume create "${disk}/.snapshots"
fi
if [ ! -d "$disk/.snapshots/$SNAPSHOT_FORMAT/" ] ; then
echo "Erstelle neues BTRFS-Subvolume \"$disk/.snapshots/$SNAPSHOT_FORMAT/\" als Snapshot-Unterverzeichnis"
btrfs subvolume create "$disk/.snapshots/$SNAPSHOT_FORMAT"
fi
fi
echo "Bearbeite \"$share\":"
#check if it is a directory
if [ ! -d "$share" ]; then
echo "$share ist kein Verzeichnis und wird übersprungen..."
else
is_btrfs_subvolume "$share"
if [ ! "$?" -eq 0 ]; then
echo "\"$share\" ist kein BTRFS-Subvolume und wird nun in ein neues BTRFS-Subvolume verschoben..."
mv -v "${share}" "${share}_TEMP"
btrfs subvolume create "$share"
cp -axvT --reflink=always "${share}_TEMP" "$share"
rm -vrf "${share}_TEMP"
fi
btrfs subvolume snap "${share}" "${disk}/.snapshots/${SNAPSHOT_FORMAT}/$(basename "${share}")" #read only use: -r
fi
else
echo "\"$share\" ist auf der EXCLUDE-Liste und wird übersprungen..."
fi
done
#find old snaps to delete
echo "Ich habe $(find "${disk}/.snapshots/${SNAPSHOT_TYPE}${SNAPSHOT_DELIMITER}"*/ -maxdepth 0 -mindepth 0 | sort -nr | tail -n +$MAX_SNAPS | wc -l) überzählige(n) \"$SNAPSHOT_TYPE\" Snapshot(s) gefunden"
for snap in $(find "${disk}/.snapshots/${SNAPSHOT_TYPE}${SNAPSHOT_DELIMETER}"*/ -maxdepth 0 -mindepth 0 | sort -nr | tail -n +$MAX_SNAPS); do
for share_snap in ${snap}/*; do
echo ""$share_snap" wird gelöscht"
btrfs subvolume delete -c "$share_snap"
done
btrfs subvolume delete -c "$snap"
done
fi
done