#!/bin/sh # simple script for removing old zfs snapshots automatically (call me with cron, e.g. daily) # caution, this is made with FreeBSD's `date', won't work with linux's and other OSes `date's... # rev 1.13 lopez@yellowspace.net 17.03.2013 # abstract: # given pool $POOL, destroy as many old snapshots as necessary to keep MINSPACE space free, # but do not destroy anything younger than KEEPTIME days old (KEEPTIME has prio over MINSPACE). # the two values will have to be tuned over time, usually you will # decrease MINSPACE and/or KEEPTIME as data grows... # known issues: # not really an issue, but right now this script is dataset agnostic, that is, it will sort # the snapshots by date asc, no matter in which dataset they are. more fine grained tuning # (with different retention policies for different datasets) could be implemented easilly.. # Specify the following variables in separate zfs_housekeeping.inc.sh (kept in same dir): # MINSPACE=4398046511104 # in BYTES (4T) # KEEPTIME=60 # in days # POOL="tank" # pool name HERE=$(/usr/bin/dirname "$0") . "$HERE/zfs_housekeeping.inc.sh" if [ -z "$MINSPACE" -o -z "$KEEPTIME" -o -z "$POOL" ] ; then echo "Missing MINSPACE or KEEPTIME or POOL - zfs_housekeeping.inc.sh not found?" exit 1 fi bcmp() { [ `echo "$1 $2 $3" | /usr/bin/bc` = "1" ] } giveStamp() { /bin/date -j -f "%a %b %d %T %Z %Y" "$1" "+%s" } one_destroyed() { touch "/tmp/zfs_housekeeper.$1" } did_destroy() { if [ -f "/tmp/zfs_housekeeper.$1" ] ; then d=1 /bin/rm "/tmp/zfs_housekeeper.$1" else d=0 fi [ "$d" = "1" ] } removeSnap() { if [ "$3" = "1" ] ; then echo "Removing $1, it is old, made on $2"; fi snaponly=`echo "$1" | /usr/bin/egrep -c '@.+'` if [ $snaponly -gt 0 ] ; then echo -n "Destroying $1 snapshot..." if [ $ND -eq 0 ] ; then /sbin/zfs destroy "$1" echo " done" else echo " (dry)" fi removedsomething=1 else echo "$1 is not a valid snapshot name, dont destroy your pool! *g*" exit 1 fi } # pid mypid=$$ destroyed=0 # nd mode? ND=0 ZHKVV=0 if [ "$1" = "-n" ] ; then ND=1 ZHKVV=1 echo "dry-run!" fi # see how many bytes are available availbytes=$(/sbin/zfs get -Hp avail "$POOL" | awk '{print $3}') if bcmp "$availbytes" ">" "$MINSPACE" ; then if [ "$ZHKVV" -gt 0 ] ; then echo "$availbytes B available, $MINSPACE B required, no need to delete"; fi exit 0 fi # get the timestamp of the newest snapshot # damned list verb does not accept a pool name with -t snapshot lastsnapdate=$(/sbin/zfs list -t snapshot -H -o name,creation -s creation | \ /usr/bin/egrep "^${POOL}" | \ /usr/bin/cut -f 2 | \ /usr/bin/tail -n 1) lastsnapstamp=`giveStamp "$lastsnapdate"` subtrsec=$((KEEPTIME*86400)) minstamp=$((lastsnapstamp-subtrsec)) export removedsomething=0 export MINSPACE export POOL export ZHKVV export ND /sbin/zfs list -t snapshot -H -o name,creation -s creation | /usr/bin/egrep "^${POOL}" | while IFS=" " read -r snapname snapdate ; do if [ "$ZHKVV" -gt 0 ] ; then echo "Name: $snapname, Date is $snapdate"; fi stamp=`giveStamp "$snapdate"` if bcmp "$stamp" "<" "$minstamp" ; then nowavailbytes=$(/sbin/zfs get -Hp avail $POOL | awk '{print $3}') if bcmp "$nowavailbytes" "<" "$MINSPACE" ; then removeSnap "$snapname" "$stamp" "$ZHKVV" one_destroyed $mypid fi fi done if did_destroy "$mypid" ; then echo "Destroyed enough snapshots" else echo -n "WARNING: $0 did not find snapshots of $POOL older than $KEEPTIME days, " echo "yet available space is $availbytes, which is below $MINSPACE." exit 1 fi exit 0