summaryrefslogtreecommitdiff
path: root/bin/byteback-prune
diff options
context:
space:
mode:
Diffstat (limited to 'bin/byteback-prune')
-rwxr-xr-xbin/byteback-prune140
1 files changed, 140 insertions, 0 deletions
diff --git a/bin/byteback-prune b/bin/byteback-prune
new file mode 100755
index 0000000..d005289
--- /dev/null
+++ b/bin/byteback-prune
@@ -0,0 +1,140 @@
+#!/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.exist?(PRUNING_FLAG)
+ info("Starting prune #{@free}% -> #{@maxpercent} free")
+ File.write(PRUNING_FLAG, '')
+elsif @free >= @maxpercent && File.exist?(PRUNING_FLAG)
+ info("Stopping prune, reached #{@free}% free")
+ File.unlink(PRUNING_FLAG)
+elsif File.exist?(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
+ fail 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
+#
+unless @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.exist?(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}")