Compare commits

...

26 Commits

Author SHA1 Message Date
Dave Eddy eb9351b711 bump version in code 2022-06-17 02:12:02 -04:00
Dave Eddy 89b9c8a416 validate datasets exist before running
- this allows for early exit as well as better error messaging
2022-06-12 15:05:07 -04:00
Dave Eddy 701762f1ec bump version 2022-06-12 14:34:55 -04:00
Ryan Petschek a9d24fc81a
Allow snapshot datasets to contain spaces (#18)
Previously, the `read -r` command ignored content after the first space in a dataset/snapshot name. For example, when trying to remove `MyPool/Media/TV Shows@snapshot-name`, the program would try to instead destroy `MyPool/Media/TV` which (probably) doesn't exist and is a dataset as opposed to a snapshot. This change should be safe because the `zfs list` command will not return any extra information after the creation time, space used, and snapshot name, in that order.
2022-06-12 14:33:29 -04:00
Dave Eddy f1be6dc83d rearrange zpool args, style 2021-12-04 17:37:24 -05:00
Dave Eddy 547d01009e put binary check below argument parsing 2021-12-04 12:31:09 -05:00
Dave Eddy 7867fc3d07 use posix compatible `command -v` instead of `type -P` 2021-12-04 12:30:21 -05:00
Dave Eddy 555988063b bump v1.4.0, fixes #15 and #12 2021-12-04 12:28:46 -05:00
Dave Eddy 198ba41245 add more robust code to get epoch time, fixes #13 2021-12-04 12:23:05 -05:00
Dave Eddy e8e035c9a2 accidently left in testin code 2021-12-01 15:47:55 -05:00
Dave Eddy 5c3f3d934f clean up docs, add docs for `-l` 2021-11-30 23:05:41 -05:00
Dave Eddy e4ba772f2c add -l for "list" mode, '-q' now fully squelches stdout 2021-11-30 22:55:57 -05:00
Dave Eddy b5f0f8f097 total snapshots and size before running 2021-11-30 13:16:06 -05:00
Dave Eddy 3754b8f063 only print status line if ! quiet 2021-11-28 13:43:13 -05:00
Dave Eddy 6e3891ddd1 show snapshot size and number of snapshots processed 2021-11-27 19:37:07 -05:00
Dave Eddy 68e65a66a3 bump to v1.3.0 2021-11-27 19:18:19 -05:00
Dave Eddy 482420faba add -R option for recursive deleting 2021-11-27 19:17:32 -05:00
Dave Eddy 975ed41681 bump to v1.2.0 2021-11-27 18:58:45 -05:00
lepokle 1502ce3c80
Support inverting of prefix/suffix match (#17) 2021-11-27 18:53:51 -05:00
Dave Eddy d751608bc5 add changelog entry 2020-02-20 17:30:07 -05:00
Dave Eddy 06c93f7b83 fix indentation 2020-02-20 17:29:27 -05:00
Dave Eddy 2ccb235ffb
Merge pull request #8 from lehoczkics/check_zfs_binary
Add zfs binary check
2020-02-20 17:22:54 -05:00
Csaba Lehoczki 64b4a9b379 Add zfs binary check 2020-02-13 17:20:17 +01:00
Dave Eddy d10b865f23 bump 2019-12-16 14:09:54 -05:00
Dave Eddy abe39b32c0
Merge pull request #5 from ahesford/suffix_matching
Support suffix matching in the same style as prefix matching.
2019-12-16 14:08:04 -05:00
Andrew J. Hesford e7aa72160f Support suffix matching in the same style as prefix matching. 2019-12-16 11:19:53 -05:00
5 changed files with 358 additions and 58 deletions

View File

@ -4,6 +4,44 @@ ZFS Prune Snapshots Changes
Not Yet Released
----------------
(nothing yet)
`v1.5.0`
--------
- Sanity check datasets existence before running
- `-q` will hide warnings for datesets not existing
- Based on ([#9](https://github.com/bahamas10/zfs-prune-snapshots/pull/9))
`v1.4.1`
--------
- Allow snapshot datasets to contain spaces
([#18](https://github.com/bahamas10/zfs-prune-snapshots/pull/18))
`v1.4.0`
--------
- Show snapshot size (used) and number of snapshots processed (6e3891ddd1)
- Add list only option (`-l`) to just list datasets that match (e4ba772f2c)
`v1.3.0`
--------
- Add recursive deletion option (`-R`) (482420faba)
`v1.2.0`
--------
- Add zfs binary check ([#8](https://github.com/bahamas10/zfs-prune-snapshots/pull/8))
- Support inverting of prefix/suffix match (`-i`)
([#17](https://github.com/bahamas10/zfs-prune-snapshots/pull/17))
`v1.1.0`
--------
- Support suffix matching (`-s`) (e7aa72160f8)
`v1.0.1`
--------

View File

@ -26,6 +26,16 @@ with the string "autosnap_"
zfs-prune-snapshots -p 'autosnap_' 1M zones
Remove snapshots older than two months on the tank pool that end
with the string "_frequent"
zfs-prune-snapshots -s '_frequent' 2M tank
Remove snapshots older than a month on the zones pool that do not
start with the string "autosnap_"
zfs-prune-snapshots -i -p 'autosnap_' 1M zones
Timespec
--------
@ -43,7 +53,7 @@ be considered for deletion - possible specifiers are
Usage
-----
usage: zfs-prune-snapshots [-hnv] [-p <prefix] <time> [[dataset1] ...]
usage: zfs-prune-snapshots [-hnliqRvV] [-p <prefix>] [-s <suffix>] <time> [[dataset1] ...]
remove snapshots from one or more zpools that match given criteria
@ -64,6 +74,14 @@ Usage
remove snapshots older than a month on the zones pool that start
with the string "autosnap_"
# zfs-prune-snapshots -s '_frequent' 2M tank
remove snapshots older than two months on the tank pool that end
with the string "_frequent"
# zfs-prune-snapshots -i -p 'autosnap_' 1M zones
remove snapshots older than a month on the zones pool that do not
start with the string "autosnap_"
timespec
the first argument denotes how old a snapshot must be for it to
be considered for deletion - possible specifiers are
@ -79,8 +97,13 @@ Usage
options
-h print this message and exit
-n dry-run, don't actually delete snapshots
-l list only mode, just list matching snapshots names
without deleting (like dry-run mode with machine-parseable output)
-p <prefix> snapshot prefix string to match
-s <suffix> snapshot suffix string to match
-i invert matching of prefix and suffix
-q quiet, do not printout removed snapshots
-R recursively delete, pass '-R' directly to 'zfs destroy'
-v increase verbosity
-V print the version number and exit

View File

@ -18,12 +18,25 @@ print this message and exit
\fB\fC\-n\fR
dry\-run, don't actually delete snapshots
.TP
\fB\fC\-l\fR
list only mode, just list matching snapshots names without deleting (like
dry\-run mode with machine\-parseable output)
.TP
\fB\fC\-p <prefix>\fR
snapshot prefix string to match
.TP
\fB\fC\-s <suffix>\fR
snapshot suffix string to match
.TP
\fB\fC\-i\fR
invert matching of prefix and suffix
.TP
\fB\fC\-q\fR
quiet, do not printout removed snapshots
.TP
\fB\fC\-R\fR
recursively delete, pass '\-R' directly to 'zfs destroy'
.TP
\fB\fC\-v\fR
increase verbosity
.TP
@ -65,6 +78,14 @@ tank2/backup
\fB\fCzfs\-prune\-snapshots \-p 'autosnap_' 1M zones\fR
Remove snapshots older than a month on the zones pool that start with the
string \fB\fC"autosnap_"\fR
.TP
\fB\fCzfs\-prune\-snapshots \-s '_frequent' 2M tank\fR
Remove snapshots older than two months on the tank pool that end with the
string \fB\fC"_frequent"\fR
.TP
\fB\fCzfs\-prune\-snapshots \-i \-p 'autosnap_' 1M zones\fR
Remove snapshots older than a month on the zones pool that do not start
with the string \fB\fC"autosnap_"\fR
.SH BUGS
.PP
\[la]https://github.com/bahamas10/zfs-prune-snapshots\[ra]

View File

@ -27,12 +27,25 @@ OPTIONS
`-n`
dry-run, don't actually delete snapshots
`-l`
list only mode, just list matching snapshots names without deleting (like
dry-run mode with machine-parseable output)
`-p <prefix>`
snapshot prefix string to match
`-s <suffix>`
snapshot suffix string to match
`-i`
invert matching of prefix and suffix
`-q`
quiet, do not printout removed snapshots
`-R`
recursively delete, pass '-R' directly to 'zfs destroy'
`-v`
increase verbosity
@ -77,6 +90,14 @@ EXAMPLES
Remove snapshots older than a month on the zones pool that start with the
string `"autosnap_"`
`zfs-prune-snapshots -s '_frequent' 2M tank`
Remove snapshots older than two months on the tank pool that end with the
string `"_frequent"`
`zfs-prune-snapshots -i -p 'autosnap_' 1M zones`
Remove snapshots older than a month on the zones pool that do not start
with the string `"autosnap_"`
BUGS
----

View File

@ -6,61 +6,116 @@
# Date: November 20, 2015
# License: MIT
VERSION='v1.0.1'
VERSION='v1.5.0'
usage() {
local prog=${0##*/}
cat <<-EOF
usage: $prog [-hnv] [-p <prefix] <time> [[dataset1] ...]
cat <<EOF
usage: $prog [-hnliqRvV] [-p <prefix>] [-s <suffix>] <time> [[dataset1] ...]
remove snapshots from one or more zpools that match given criteria
remove snapshots from one or more zpools that match given criteria
examples
# $prog 1w
remove snapshots older than a week across all zpools
examples
# $prog 1w
remove snapshots older than a week across all zpools
# $prog -vn 1w
same as above, but with increased verbosity and without
actually deleting any snapshots (dry-run)
# $prog -vn 1w
same as above, but with increased verbosity and without
actually deleting any snapshots (dry-run)
# $prog 3w tank1 tank2/backup
remove snapshots older than 3 weeks on tank1 and tank2/backup.
note that this script will recurse through *all* of tank1 and
*all* datasets below tank2/backup
# $prog 3w tank1 tank2/backup
remove snapshots older than 3 weeks on tank1 and tank2/backup.
note that this script will recurse through *all* of tank1 and
*all* datasets below tank2/backup
# $prog -p 'autosnap_' 1M zones
remove snapshots older than a month on the zones pool that start
with the string "autosnap_"
# $prog -p 'autosnap_' 1M zones
remove snapshots older than a month on the zones pool that start
with the string "autosnap_"
timespec
the first argument denotes how old a snapshot must be for it to
be considered for deletion - possible specifiers are
# $prog -s '_frequent' 2M tank
remove snapshots older than two months on the tank pool that end
with the string "_frequent"
s seconds
m minutes
h hours
d days
w weeks
M months
y years
# $prog -i -p 'autosnap_' 1M zones
remove snapshots older than a month on the zones pool that do not
start with the string "autosnap_"
options
-h print this message and exit
-n dry-run, don't actually delete snapshots
-p <prefix> snapshot prefix string to match
-q quiet, do not printout removed snapshots
-v increase verbosity
-V print the version number and exit
EOF
timespec
the first argument denotes how old a snapshot must be for it to
be considered for deletion - possible specifiers are
s seconds
m minutes
h hours
d days
w weeks
M months
y years
options
-h print this message and exit
-n dry-run, don't actually delete snapshots
-l list only mode, just list matching snapshots names
without deleting (like dry-run mode with machine-parseable
output)
-p <prefix> snapshot prefix string to match
-s <suffix> snapshot suffix string to match
-i invert matching of prefix and suffix
-q quiet, do not printout removed snapshots
-R recursively delete, pass '-R' directly to 'zfs destroy'
-v increase verbosity
-V print the version number and exit
EOF
}
debug() {
((verbosity >= 1)) && echo "$@"
((verbosity >= 1)) && echo '>' "$@" >&2
return 0
}
# get epoch time
get-epoch() {
local time=0
# try bash built-in, date(1), and finally perl
time=$(printf '%(%s)T\n' -1 2>/dev/null)
_validate-epoch "$time" 'printf' && return 0
time=$(date '+%s' 2>/dev/null)
_validate-epoch "$time" 'date' && return 0
time=$(perl -le 'print time' 2>/dev/null)
_validate-epoch "$time" 'perl' && return 0
return 1
}
# validate a given epoch time for sanity
_validate-epoch() {
local time=$1
local method=$2
local num_re='^([0-9]+)$'
debug "checking time received from $method"
if [[ -z $time ]]; then
debug "time invalid - empty"
return 1
elif ! [[ $time =~ $num_re ]]; then
debug "time invalid - not a number :: '$time'"
return 1
elif ! (( time > 0 )); then
debug "time invalid - not greater than 0 :: '$time'"
return 1
fi
debug "successfully got epoch time :: $time"
echo "$time"
return 0
}
# given a time in seconds, return the "human readable" string
human() {
human-time() {
local seconds=$1
if ((seconds < 0)); then
((seconds *= -1))
@ -90,16 +145,46 @@ human() {
echo '0 seconds'
}
# convert bytes to a human-readable string
human-size() {
local bytes=$1
local times=(
$((bytes / 1024 / 1024 / 1024)) # gb
$((bytes / 1024 / 1024)) # mb
$((bytes / 1024)) # kb
$((bytes)) # b
)
local names=(GB MB KB B)
local i
for ((i = 0; i < ${#names[@]}; i++)); do
if ((${times[$i]} >= 1)); then
echo "${times[$i]} ${names[$i]}"
return
fi
done
echo '0 B'
}
recursive=false
dryrun=false
listonly=false
verbosity=0
prefix=
suffix=
invert=false
quiet=false
while getopts 'hnqp:vV' option; do
while getopts 'hniqlRp:s:vV' option; do
case "$option" in
h) usage; exit 0;;
n) dryrun=true;;
i) invert=true;;
l) listonly=true;;
p) prefix=$OPTARG;;
q) quiet=true;;
s) suffix=$OPTARG;;
q) quiet=true; exec 1>/dev/null;;
R) recursive=true;;
v) ((verbosity++));;
V) echo "$VERSION"; exit 0;;
*) usage; exit 1;;
@ -136,37 +221,149 @@ else
fi
shift
pools=("$@")
now=$(date +%s)
destroyargs=()
code=0
while read -r creation snapshot; do
totalused=0
numsnapshots=0
if $recursive; then
destroyargs+=('-R')
fi
if ! now=$(get-epoch); then
echo 'failed to get epoch time' >&2
exit 1
fi
if ! command -v zfs &>/dev/null; then
echo "Error! zfs command not found. Are you on the right machine?" >&2
exit 1
fi
# validate pools given, print warnings if not in quiet mode
pools=()
for arg in "$@"; do
debug "checking '$arg' exists"
error=$(zfs list "$arg" 2>&1 1>/dev/null)
code=$?
debug "zfs list '$arg' -> exited $code"
if ((code == 0)) && [[ -z $error ]]; then
debug "adding dataset '$arg'"
pools+=("$arg")
else
msg="dataset '$arg' invalid: $error"
debug "$msg"
if ! $quiet; then
echo "$msg" >&2
fi
fi
done
# it is an error if arguments were given but all datasets were invalidated
if [[ -n $1 && -z ${pools[0]} ]]; then
echo 'no valid dataset names provided' >&2
exit 1
fi
humanpools=${pools[*]}
humanpools=${humanpools:-<all>}
# first pass of the pools (to calculate totals and filter unwanted datasets
lines=()
while read -r line; do
read -r creation used snapshot <<< "$line"
# ensure optional prefix matches
snapname=${snapshot#*@}
if [[ -n $prefix && $prefix != "${snapname:0:${#prefix}}" ]]; then
debug "skipping $snapshot: doesn't match prefix $prefix"
continue
if [[ -n $prefix ]]; then
match=false
if [[ $prefix == "${snapname:0:${#prefix}}" ]]; then
match=true
fi
if $invert && $match; then
debug "skipping $snapshot: does match prefix '$prefix'"
continue
fi
if ! $invert && ! $match; then
debug "skipping $snapshot: doesn't match prefix '$prefix'"
continue
fi
fi
# ensure optional suffix matches
if [[ -n $suffix ]]; then
match=false
if [[ $suffix == "${snapname: -${#suffix}}" ]]; then
match=true
fi
if $invert && $match; then
debug "skipping $snapshot: does match suffix '$suffix'"
continue
fi
if ! $invert && ! $match; then
debug "skipping $snapshot: doesn't match suffix '$suffix'"
continue
fi
fi
# ensure snapshot is older than the cutoff time
delta=$((now - creation))
human=$(human "$delta")
ht=$(human-time "$delta")
if ((delta <= seconds)); then
debug "skipping $snapshot: $human old"
debug "skipping $snapshot: $ht old"
continue
fi
# remove the snapshot
if ! $quiet || $dryrun; then
echo -n "removing $snapshot: $human old"
# print the snapshot here if `-l`
if $listonly; then
echo "$snapshot"
fi
# we care about this dataset
((totalused += used))
((numsnapshots++))
lines+=("$line")
done < <(zfs list -Hpo creation,used,name -t snapshot -r "${pools[@]}")
# finish if running with `-l`
if $listonly; then
debug "running in '-l' mode - exiting here"
exit 0
fi
humantotal=$(human-size "$totalused")
echo "found $numsnapshots snapshots ($humantotal) on pools: $humanpools"
# process snapshots found
i=0
for line in "${lines[@]}"; do
read -r creation used snapshot <<< "$line"
((i++))
delta=$((now - creation))
ht=$(human-time "$delta")
hu=$(human-size "$used")
if $dryrun; then
echo ' <dry-run: no action taken>'
else
if ! $quiet; then
echo
fi
zfs destroy "$snapshot" || code=1
echo -n '[dry-run] '
fi
done < <(zfs list -Hpo creation,name -t snapshot -r "${pools[@]}")
echo "[$i/$numsnapshots] removing $snapshot: $ht old ($hu)"
if ! $dryrun; then
zfs destroy "${destroyargs[@]}" "$snapshot" || code=1
fi
done
echo "removed $numsnapshots snapshots ($humantotal)"
exit "$code"