diff options
Diffstat (limited to 'byteback-snapshot')
| -rwxr-xr-x | byteback-snapshot | 175 | 
1 files changed, 14 insertions, 161 deletions
| diff --git a/byteback-snapshot b/byteback-snapshot index 1960085..37834d4 100755 --- a/byteback-snapshot +++ b/byteback-snapshot @@ -3,158 +3,11 @@  # Program to create a snapshot and/or rotate a directory of backup snapshots   # using btrfs subvolume commands. +$LOAD_PATH.unshift("/usr/lib/byteback")  require 'trollop' -require 'time' - -def error(message) -	STDERR.print "*** #{message}\n" -	exit 1 -end - -def verbose(message) -	print "#{Time.now}: #{message}\n" if @verbose -end - -# Icky way to find out free disc space on our mount -# -class DiskFree -	def initialize(mount) -		@mount = mount -	end - -	def total -		all[2] -	end - -	def used -		all[3] -	end - -	def available -		all[4] -	end - -	def fraction_used -		disk_device, disk_fs, disk_total, disk_used, disk_available, *rest = all -		disk_used.to_f / disk_available -	end - -	protected - -	def all -		disk_device, disk_fs, disk_total, disk_used, disk_available, *rest =  -			df. -			split("\n")[1]. -			split(/\s+/). -			map { |i| /^[0-9]+$/.match(i) ? i.to_i : i } -	end - -	def df -		`/bin/df -T -P -B1 #{@mount}` -	end -end - -# Represent a directory full of backups where "current"  is a subvolume -# which is snapshotted to frozen backup directories called e.g.  -# "yyyy-mm-ddThh:mm+zzzz". -# -class BackupDirectory -	attr_reader :dir - -	def initialize(dir) -		@dir = Dir.new(dir) -		current -	end - -	# Return total amount of free space in backup directory (bytes) -	# -	def free -		df = DiskFree.new(@dir.path) -		df.total - df.used -	end - -	# Return an array of Times representing the current list of  -	# snapshots. -	# -	def snapshot_times -		@dir.entries.map do |entry| -			begin -				Time.parse(entry) -			rescue ArgumentError => error -				nil -			end -		end. -		compact. -		sort -	end - -	# What order to remove snapshots in to regain disk space? -	# -	# Order backups by their closeness to defined backup times, which are -	# listed in a set order (i.e. today's backup is more important than yesterday's). -	# -	BACKUP_IMPORTANCE = [0, 1, 2, 3, 7, 14, 21, 28, 56, 112] -	def snapshot_times_by_importance -		now = Time.now -		snapshot_times_unsorted = snapshot_times -		snapshot_times_sorted = [] -		while !snapshot_times_unsorted.empty? -			BACKUP_IMPORTANCE.each do |days| -				target_time = now + (days*86400) -				closest = snapshot_times_unsorted.inject(nil) do |best, time| -					if best.nil? || (time-target_time).abs < (best-target_time).abs -						time -					else -						best -					end -				end -				break unless closest -				snapshot_times_sorted << snapshot_times_unsorted.delete(closest) -			end -		end -		snapshot_times_sorted -	end - -	# Returns the size of the given snapshot (runs du, may be slow) -	# -	# Would much prefer to take advantage of this feature: -	#   http://dustymabe.com/2013/09/22/btrfs-how-big-are-my-snapshots/ -	# but it's not currently in Debian/wheezy. -	# -	def snapshot_size(time=snapshot_times.last) -		`du -s -b #{snapshot_path(time)}`.to_i -	end - -	def average_snapshot_size(number=10) -		snapshot_times.sort[0..number].inject(0) { |total, time| snapshot_size(time) } / number -	end - -	# Create a new snapshot of 'current' -	# -	def new_snapshot! -		system_no_error("btrfs subvolume snapshot -r #{current.path} #{snapshot_path}") -	end - -	def delete_snapshot!(time) -		system_no_error("btrfs subvolume delete #{snapshot_path(time)}") -	end - -	def current -		Dir.new("#{dir.path}/current") -	end - -	def snapshot_path(time=Time.now) -		"#{dir.path}/#{time.strftime("%Y-%m-%dT%H:%M%z")}" -	end - -	protected - -	def system_no_error(*args) -      args[-1] += " > /dev/null" unless @verbose -		raise RuntimeError.new("Command failed: "+args.join(" ")) unless -		  system(*args) -	end -end +require 'byteback' +include Byteback +include Byteback::Log  opts = Trollop::options do @@ -179,9 +32,9 @@ end  @do_list = opts[:list]  @do_prune = opts[:prune] -error("Must specify snapshot, prune or list") unless @do_snapshot || @do_prune || @do_list +fatal("Must specify snapshot, prune or list") unless @do_snapshot || @do_prune || @do_list -error("--root not readable") unless File.directory?(@root) +fatal("--root not readable") unless File.directory?("#{@root}")  @backups = BackupDirectory.new(@root) @@ -197,11 +50,11 @@ end  if @do_snapshot  	last_snapshot_time = @backups.snapshot_times.last -	error("Last snapshot was less than six hours ago") unless  +	fatal("Last snapshot was less than six hours ago") unless   		!last_snapshot_time ||   		Time.now - @backups.snapshot_times.last >= 6*60*60 # FIXME: make configurable -	verbose "Making new snapshot" +	info "Making new snapshot"  	@backups.new_snapshot!   end @@ -214,22 +67,22 @@ if @do_list  end  if @do_prune -	verbose "Counting last 10 backups" +	info "Counting last 10 backups"  	target_free_space = 1.5 * @backups.average_snapshot_size(10) -	verbose "Want to ensure we have #{target_free_space}" +	info "Want to ensure we have #{target_free_space}"  	if @backups.free >= target_free_space -		verbose "(we have #{@backups.free} so no action needed)" +		info "(we have #{@backups.free} so no action needed)"  	else  		list = get_snapshots_by(@do_prune)  		while @backups.free < target_free_space && !list.empty?  			to_delete = list.pop -			verbose "Deleting #{to_delete}" +			info "Deleting #{to_delete}"  			@backups.delete_snapshot!(to_delete) -			verbose "Leaves us with #{@backups.free}" +			info "Leaves us with #{@backups.free}"  		end  	end  end -verbose "Finished" +info "Finished" | 
