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
142
143
144
145
146
147
148
149
150
|
#!/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
#
# 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 || 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("#{btrfs_bin} subvolume delete #{snapshots.last.path}")
|