#!/usr/bin/env ruby require 'date' SNAPSHOTS_TO_RETAIN = 7 SNAPSHOT_FORMAT = "daily-%Y-%m-%d" input = ARGV def usage puts "Usage: " puts " #{$PROGRAM_NAME} " exit -1 end def run_command(*cmd) pipe = IO.popen(cmd) stdout = pipe.read puts stdout pipe.close return stdout, $?.success? end class Array def drop_tail(n) slice(0...(length - n)) end end class Dataset def initialize(name) _, dataset_exists = run_command("zfs", "list", "-H", name) raise "No such pool: #{name}" unless dataset_exists @name = name end def has_snapshot(name) _, snapshot_exists = run_command("zfs", "list", "-H", "#{@name}@#{name}") return snapshot_exists end def create_snapshot_today current_snapshot_name = Date.today.strftime(SNAPSHOT_FORMAT) raise "Snapshot already created: #{@name}@#{current_snapshot_name}" if has_snapshot(current_snapshot_name) _, made_snapshot = run_command("zfs", "snapshot", "#{@name}@#{current_snapshot_name}") raise "Could not create new snapshot #{@name}@#{current_snapshot_name}" unless made_snapshot end def get_all_snapshots all_snapshots, could_list_all_snapshot = run_command("zfs", "list", "-t", "snapshot", "-H", @name) raise "Could not list snapshots for #{@name}" unless could_list_all_snapshot return all_snapshots.lines.map { |line| line.split("\t").first }.map { |snapshot_name| snapshot_name.split("@").last } end def clean_up_old_snapshots snapshots_to_delete = get_all_snapshots.drop_tail(SNAPSHOTS_TO_RETAIN) p snapshots_to_delete return if snapshots_to_delete.length == 0 output, could_delete_old_snapshots = run_command("zfs", "destroy", "#{@name}@#{snapshots_to_delete.first}%#{snapshots_to_delete.last}") raise "Could not delete old snapshots for #{@name}" unless could_delete_old_snapshots end end usage unless input.length == 1 usage if input[0] == "-h" or input[0] == "--help" dataset = Dataset.new input[0] dataset.create_snapshot_today dataset.clean_up_old_snapshots