Rev 48 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed
#!/usr/bin/env perl
# cleanSnaps - detect and remove old ZFS snapshots named like YYYY-MM-DD-<N><d|w|m>
# Usage: cleanSnaps [-n] [-f] [-v]
# -n dry-run (default)
# -f actually destroy snapshots
# -v verbose
use strict;
use warnings;
use Getopt::Std;
use Time::Piece;
my %opts;
getopts('nfv', \%opts);
my $DRY_RUN = $opts{f} ? 0 : 1; # default dry-run, -f disables dry-run
my $FORCE = $opts{f} ? 1 : 0;
my $VERBOSE = $opts{v} ? 1 : 0;
my $ZFS_CMD = $ENV{ZFS_CMD} // 'zfs';
sub logmsg { print @_, "\n" if $VERBOSE }
# gather snapshots
my $now = time();
my @candidates;
open my $fh, '-|', "$ZFS_CMD list -H -o name -t snapshot" or die "Failed to run $ZFS_CMD: $!";
while (my $snap = <$fh>) {
chomp $snap;
next unless defined $snap && $snap =~ /\S/;
unless ($snap =~ /@/) {
logmsg("skipping invalid snapshot name: $snap");
next;
}
my ($dataset, $snapname) = split /@/, $snap, 2;
# match date + retention tag
unless ($snapname =~ m/^(\d{4}-\d{2}-\d{2}).*(\d+)([dwm])$/) {
logmsg("snapshot does not match YYYY-MM-DD-<N><d|w|m>: $snap");
next;
}
my ($snap_date, $num, $unit) = ($1, $2, $3);
# parse snapshot date using Time::Piece
my $snap_epoch;
eval {
my $tp = Time::Piece->strptime($snap_date, '%Y-%m-%d');
$snap_epoch = $tp->epoch;
1;
} or do {
logmsg("failed to parse date $snap_date for $snap");
next;
};
my $days;
if ($unit eq 'd') { $days = $num }
elsif ($unit eq 'w') { $days = $num * 7 }
elsif ($unit eq 'm') { $days = $num * 30 }
elsif ($unit eq 'y') { $days = $num * 365}
else { logmsg("unknown unit $unit for $snap"); next }
my $retention_seconds = $days * 86400;
my $age = $now - $snap_epoch;
if ($age > $retention_seconds) {
push @candidates, $snap;
} else {
logmsg("keep: $snap (age " . int($age/86400) . "d <= ${days}d)");
}
}
close $fh;
if (!@candidates) {
print "No snapshots to remove.\n";
exit 0;
}
printf "Snapshots to remove (%d):\n", scalar @candidates;
for my $s (@candidates) { printf " %s\n", $s }
if ($DRY_RUN) {
print "\nDry-run: no snapshots were destroyed. Use -f to actually remove them.\n";
exit 0;
}
# actual removal
my $failed = 0;
for my $s (@candidates) {
printf "Destroying: %s\n", $s;
my $rc = system($ZFS_CMD, 'destroy', $s);
if ($rc != 0) {
printf STDERR "Failed to destroy %s\n", $s;
$failed = 1;
}
}
if ($failed) {
print STDERR "One or more destroys failed.\n";
exit 1;
}
print "Done.\n";
Generated by GNU Enscript 1.6.5.90.