Compare commits
26 Commits
Author | SHA1 | Date |
---|---|---|
Dave Eddy | eb9351b711 | |
Dave Eddy | 89b9c8a416 | |
Dave Eddy | 701762f1ec | |
Ryan Petschek | a9d24fc81a | |
Dave Eddy | f1be6dc83d | |
Dave Eddy | 547d01009e | |
Dave Eddy | 7867fc3d07 | |
Dave Eddy | 555988063b | |
Dave Eddy | 198ba41245 | |
Dave Eddy | e8e035c9a2 | |
Dave Eddy | 5c3f3d934f | |
Dave Eddy | e4ba772f2c | |
Dave Eddy | b5f0f8f097 | |
Dave Eddy | 3754b8f063 | |
Dave Eddy | 6e3891ddd1 | |
Dave Eddy | 68e65a66a3 | |
Dave Eddy | 482420faba | |
Dave Eddy | 975ed41681 | |
lepokle | 1502ce3c80 | |
Dave Eddy | d751608bc5 | |
Dave Eddy | 06c93f7b83 | |
Dave Eddy | 2ccb235ffb | |
Csaba Lehoczki | 64b4a9b379 | |
Dave Eddy | d10b865f23 | |
Dave Eddy | abe39b32c0 | |
Andrew J. Hesford | e7aa72160f |
38
CHANGES.md
38
CHANGES.md
|
@ -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`
|
||||
--------
|
||||
|
||||
|
|
25
README.md
25
README.md
|
@ -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
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
----
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue