i’d made a big mistake. created 5 min snapshot the pool level, and its recursive.
As i’m trying to remove this from GUI, it take ages to remove 100 at a time. Also most of the time, Snapshots page seems like loading/in hung state, trying to list the snapshots…
Need some help on how to clean this up. From the looks, it wont auto delete.
I have this script on my system but I do not remember where I got it from. Google might find the source. I take no responsibility for your use.
#!/usr/bin/env bash
usage="$(basename "$0") -s SECONDS -m MINUTES -h HOURS -d DAYS -p GREP_PATTERN [-t]\n
arguments:\n
-s delete snapshots older than SECONDS\n
-m delete snapshots older than MINUTES\n
-h delete snapshots older than HOURS\n
-d delete snapshots older than DAYS\n
-p the grep pattern to use\n
-t test run, do not destroy the snapshots just print them
"
while getopts ":ts:m:h:d:p:" opt; do
case $opt in
s)
seconds=$OPTARG
;;
m)
minutes=$OPTARG
;;
h)
hours=$OPTARG
;;
d)
days=$OPTARG
;;
p)
pattern=$OPTARG
;;
t)
test_run=true
;;
\?)
echo "Invalid option: -$OPTARG" 1>&2
echo -e $usage 1>&2
exit 1
;;
:)
echo "Option -$OPTARG requires an argument." 1>&2
echo -e $usage 1>&2
exit 1
;;
esac
done
if [[ -z $pattern ]]; then
echo -e "No grep pattern supplied\n" 1>&2
echo -e $usage 1>&2
exit 1
fi
if [[ -z $days ]]; then
days=0
fi
if [[ -z $hours ]]; then
hours=0
fi
if [[ -z $minutes ]]; then
minutes=0
fi
if [[ -z $seconds ]]; then
seconds=0
fi
# Figure out which platform we are running on, more specifically, whic version of date
# we are using. GNU date behaves different thant date on OSX and FreeBSD
platform='unknown'
unamestr=$(uname)
if [[ "$unamestr" == 'Linux' ]]; then
platform='linux'
elif [[ "$unamestr" == 'FreeBSD' ]]; then
platform='bsd'
elif [[ "$unamestr" == 'OpenBSD' ]]; then
platform='bsd'
elif [[ "$unamestr" == 'Darwin' ]]; then
platform='bsd'
else
echo -e "unknown platform $unamestr 1>&2"
exit 1
fi
compare_seconds=$(($days * 24 * 60 * 60 + $hours * 60 * 60 + $minutes * 60 + $seconds))
if [ $compare_seconds -lt 1 ]; then
echo -e time has to be in the past 1>&2
echo -e $usage 1>&2
exit 1
fi
if [[ "$platform" == 'linux' ]]; then
compare_timestamp=`date --date="-$(echo $compare_seconds) seconds" +"%s"`
else
compare_timestamp=`date -j -v-$(echo $compare_seconds)S +"%s"`
fi
# get a list of snapshots sorted by creation date, so that we get the oldest first
# This will allow us to skip the loop early
snapshots=`zfs list -H -t snapshot -o name,creation -s creation | grep $pattern`
if [[ -z $snapshots ]]; then
echo "no snapshots found for pattern $pattern"
exit 0
fi
# for in uses \n as a delimiter
old_ifs=$IFS
IFS=$'\n'
for line in $snapshots; do
snapshot=`echo $line | cut -f 1`
creation_date=`echo $line | cut -f 2`
if [[ "$platform" == 'linux' ]]; then
creation_date_timestamp=`date --date="$creation_date" "+%s"`
else
creation_date_timestamp=`date -j -f "%a %b %d %H:%M %Y" "$creation_date" "+%s"`
fi
# Check if the creation date of a snapshot is less than our compare date
# Meaning if it is older than our compare date
# It is younger, we can stop processing since we the list is sorted by
# compare date '-s creation'
if [ $creation_date_timestamp -lt $compare_timestamp ]
then
if [[ -z $test_run ]]; then
echo "DELETE: $snapshot from $creation_date"
zfs destroy $snapshot
else
echo "WOULD DELETE: $snapshot from $creation_date"
fi
else
echo "KEEP: $snapshot from $creation_date"
echo "No more snapshots to be processed for $pattern. Skipping.."
break
fi
done
IFS=$old_ifs
root@barrel[~/scripts]# ./clean.sh
No grep pattern supplied
clean.sh -s SECONDS -m MINUTES -h HOURS -d DAYS -p GREP_PATTERN [-t]
arguments:
-s delete snapshots older than SECONDS
-m delete snapshots older than MINUTES
-h delete snapshots older than HOURS
-d delete snapshots older than DAYS
-p the grep pattern to use
-t test run, do not destroy the snapshots just print them
root@barrel[~/scripts]# vi clean.sh
root@barrel[~/scripts]# cat clean.sh
#!/usr/bin/env bash
usage="$(basename "$0") -s SECONDS -m MINUTES -h HOURS -d DAYS -p GREP_PATTERN [-t]\n
arguments:\n
-s delete snapshots older than SECONDS\n
-m delete snapshots older than MINUTES\n
-h delete snapshots older than HOURS\n
-d delete snapshots older than DAYS\n
-p the grep pattern to use\n
-t test run, do not destroy the snapshots just print them
"
while getopts ":ts:m:h:d:p:" opt; do
case $opt in
s)
seconds=$OPTARG
;;
m)
minutes=$OPTARG
;;
h)
hours=$OPTARG
;;
d)
days=$OPTARG
;;
p)
pattern=$OPTARG
;;
t)
test_run=true
;;
\?)
echo "Invalid option: -$OPTARG" 1>&2
echo -e $usage 1>&2
exit 1
;;
:)
echo "Option -$OPTARG requires an argument." 1>&2
echo -e $usage 1>&2
exit 1
;;
esac
done
if [[ -z $pattern ]]; then
echo -e "No grep pattern supplied\n" 1>&2
echo -e $usage 1>&2
exit 1
fi
if [[ -z $days ]]; then
days=0
fi
if [[ -z $hours ]]; then
hours=0
fi
if [[ -z $minutes ]]; then
minutes=0
fi
if [[ -z $seconds ]]; then
seconds=0
fi
# Figure out which platform we are running on, more specifically, whic version of date
# we are using. GNU date behaves different thant date on OSX and FreeBSD
platform='unknown'
unamestr=$(uname)
if [[ "$unamestr" == 'Linux' ]]; then
platform='linux'
elif [[ "$unamestr" == 'FreeBSD' ]]; then
platform='bsd'
elif [[ "$unamestr" == 'OpenBSD' ]]; then
platform='bsd'
elif [[ "$unamestr" == 'Darwin' ]]; then
platform='bsd'
else
echo -e "unknown platform $unamestr 1>&2"
exit 1
fi
compare_seconds=$(($days * 24 * 60 * 60 + $hours * 60 * 60 + $minutes * 60 + $seconds))
if [ $compare_seconds -lt 1 ]; then
echo -e time has to be in the past 1>&2
echo -e $usage 1>&2
exit 1
fi
if [[ "$platform" == 'linux' ]]; then
compare_timestamp=`date --date="-$(echo $compare_seconds) seconds" +"%s"`
else
compare_timestamp=`date -j -v-$(echo $compare_seconds)S +"%s"`
fi
# get a list of snapshots sorted by creation date, so that we get the oldest first
# This will allow us to skip the loop early
snapshots=`zfs list -H -t snapshot -o name,creation -s creation | grep $pattern`
if [[ -z $snapshots ]]; then
echo "no snapshots found for pattern $pattern"
exit 0
fi
# for in uses \n as a delimiter
old_ifs=$IFS
IFS=$'\n'
for line in $snapshots; do
snapshot=`echo $line | cut -f 1`
creation_date=`echo $line | cut -f 2`
if [[ "$platform" == 'linux' ]]; then
creation_date_timestamp=`date --date="$creation_date" "+%s"`
else
creation_date_timestamp=`date -j -f "%a %b %d %H:%M %Y" "$creation_date" "+%s"`
fi
# Check if the creation date of a snapshot is less than our compare date
# Meaning if it is older than our compare date
# It is younger, we can stop processing since we the list is sorted by
# compare date '-s creation'
if [ $creation_date_timestamp -lt $compare_timestamp ]
then
if [[ -z $test_run ]]; then
echo "DELETE: $snapshot from $creation_date"
zfs destroy $snapshot
else
echo "WOULD DELETE: $snapshot from $creation_date"
fi
else
echo "KEEP: $snapshot from $creation_date"
echo "No more snapshots to be processed for $pattern. Skipping.."
break
fi
done
IFS=$old_ifs
This will destroy all snapshots on the dataset from @auto-2023-03-01 until @auto-2024-06-30
To do any of the above “recursively” (such as from a parent or root dataset), add the -r flag to the command.
WARNING: This will delete all snapshots within the range, regardless of their “name”. It does not discriminate between "auto" or "manual" or any custom naming schema.
Do not remove the -n flag, until you are sure you want to commit to the deletions. Better to be safe with a “dry run”, rather than just go for it.
I highly recommend you create a checkpoint of your pool before destroying any snapshots in bulk.
Seems the command above not working for me in the web shell. i used this…
sudo zfs destroy -r SwimmingPool@5minbackup-2025-02-09_12-00
and changed the dates… so partially i managed to remove. however on the nested folder, i had to do it from the GUI… 20k+ left on the Snapshots list…
I guess you don’t want to hear that if you set the expiry to say… 1m on your 5 minute snap task, it will solve the problem the next time it runs the snapshot task.
You might want to take a look a my my Tiered Snapshots video
Where in my post did I demonstrate the command without the % symbol?
Did you even test the command with -n and -v to assess what it will do and how much space it will free up?
Did you create a checkpoint before doing this? You could accidentally delete the wrong items, or mistype the command, and regret it with no way to undo the damage in a worst-case scenario.
I guess you don’t want to hear that if you set the expiry to say… 1m on your 5 minute snap task, it will solve the problem the next time it runs the snapshot task.
When I first started using TrueNAS Scale, this was something that confused me until I realized how it worked.
As a newcomer at the time, I assumed that snapshots themselves stored their expiry date (that you set upon creation).
I eventually figured out that snapshots don’t hold any info on when they expire. It’s the snapshot task itself that handles the deletion of expired snapshots. I figured it out after I deleted a snapshot task and noticed the related snapshots lost their expiry date in the GUI.
Just to point out to anyone reading this that isn’t aware yet, snapshot tasks will delete all snapshots from non-excluded datasets that have a matching naming schema. Furthermore, the snapshot task needs to be scheduled to run on the exact hour:minute:second as a snapshot has been taken on (on another day) to have the deletion logic match up and work properly.
Well before this i already have a daily snapshot for specific dataset, same like in your demo video. Seems after i remove it from Data Protection page, it is no longer there… perhaps once we delete a schedule for the retaining period it will be auto deleted?
i did run the commands that you shared. But it shows some error. After reading the manual page eventually i go for the -r command and deleted some specific dates snapshots…didnt get all, yet i had to remove half from the 40k+ manually.
i was having the same thought earlier either it have some expiry date based on the retention… eventually on the system backend, the create snapshot command added 1 new, and deletes the oldest.