summaryrefslogtreecommitdiff
path: root/bin/byteback-prune
blob: ddb06b4f9f3dead7c2558ac71cac8b1d68da71a5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#!/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

# Check whether we should still be pruning
#
@free = BackupDirectory.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}")
end

debug("Disc free #{@free}%")

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

unless @do_prune_force
  #
  # Do not prune unless at least one snapshot is a week old
  #
  last_week = Time.now - (7*86400)
  unless snapshots.any?{|snapshot| last_week > snapshot.time}
    warn('There are no snapshots older than a week.  Not pruning.')
    exit 0
  end
end


exit 0 unless
  (@do_prune && File.exist?(PRUNING_FLAG)) ||
  @do_prune_force

exit 0 unless @do_prune_force 

if snapshots.empty?
  error('No snapshots to delete, is there enough disc space?')
  exit 1
end

info("Deleting #{snapshots.last.path}")
log_system("#{btrfs_bin} subvolume delete --commit-after #{snapshots.last.path} ; #{btrfs_bin} subvolume sync -n 10 #{ENV['HOME']}/ ")