summaryrefslogtreecommitdiff
path: root/byteback-snapshot
diff options
context:
space:
mode:
Diffstat (limited to 'byteback-snapshot')
-rwxr-xr-xbyteback-snapshot175
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"