#!/usr/bin/ruby # # Program to free up space on the backup-storage volume, by removing # backups (whether by age, or importance). # $LOAD_PATH.unshift("/usr/lib/byteback") require 'trollop' require 'byteback' require 'sys/filesystem' include Byteback include Byteback::Log include Byteback::Util opts = Trollop::options do banner "Prune old backup directories to ensure there's enough space" opt :minpercent, "Start prune when disk has less than this %age free", :type => :integer, :default => 5 opt :maxpercent, "Stop prune when disk has more than this %age free", :type => :integer, :default => 10 opt :list, "List backups in pruning order, no other action" opt :prune, "Prune the next backup if necessary" opt :prune_force, "Prune the next backup regardless" opt :order, "Order backups by 'age' or 'importance'", :type => :string, :default => "importance" opt :verbose, "Show debugging messages" end @order = opts[:order] @verbose = opts[:verbose] @do_list = opts[:list] @do_prune = opts[:prune] @do_prune_force = opts[:prune_force] @minpercent = opts[:minpercent] @maxpercent = opts[:maxpercent] @do_prune = true if @do_prune_force fatal("Must specify one of --prune or --list") unless (@do_prune || @do_list) && !(@do_prune && @do_list) fatal("Must specify --order as 'age' or 'importance'") unless @order == 'age' || @order == 'importance' if BackupDirectory.all.empty? fatal("No backup directories found, need to run byteback-snapshot") end lock_out_other_processes("byteback-prune") @df_history = DiskFreeHistory.new(ENV['HOME']) begin @df_history.new_reading! rescue Errno::ENOSPC if @do_list warn("Couldn't write disk history file due to lack of space, ignoring") else warn("Couldn't write disk history file due to lack of space, going to --prune-force") @do_prune = @do_prune_force = true end rescue => anything_else error("Couldn't record disk history of #{@df_history.mountpoint} in #{@df_history.history_file}, installation problem?") raise end gradient_30m = @df_history.gradient(1800) # Check whether we should still be pruning # @free = @df_history.list.last.percent_free PRUNING_FLAG = "#{ENV['HOME']}/.byteback.pruning" if @do_prune_force info("Forcing prune") elsif @free <= @minpercent && !File.exists?(PRUNING_FLAG) info("Starting prune #{@free}% -> #{@maxpercent} free") File.write(PRUNING_FLAG,"") elsif @free >= @maxpercent && File.exists?(PRUNING_FLAG) info("Stopping prune, reached #{@free}% free") File.unlink(PRUNING_FLAG) elsif File.exists?(PRUNING_FLAG) info("Continuing prune #{@free}% -> #{@maxpercent}, gradient = #{gradient_30m}") end debug("Disc free #{@free}%, 30m gradient = #{gradient_30m}") def snapshots_in_order list = BackupDirectory.all_snapshots if @order == 'importance' Snapshot.sort_by_importance(list) elsif @order == 'age' list.sort.reverse else raise ArgumentError.new("Unknown snapshot sort method #{method}") end end snapshots = snapshots_in_order if @do_list print "Backups by #{@order}:\n" snapshots.each_with_index do |snapshot, index| print "#{sprintf('% 3d',index)}: #{snapshot.path}\n" end end # Don't do anything if we've not got two hours of readings # if !@do_prune_force if @df_history.list.last.time - @df_history.list.first.time < 1800 warn("Not enough disc space history to make a decision") exit 0 end end exit 0 unless (@do_prune && File.exists?(PRUNING_FLAG)) || @do_prune_force exit 0 unless @do_prune_force || gradient_30m == 0 if snapshots.empty? error("No snapshots to delete, is there enough disc space?") exit 1 end info("Deleting #{snapshots.last.path}") log_system("/sbin/btrfs subvolume delete #{snapshots.last.path}")