dated_backup 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +40 -0
- data/COPYRIGHT +621 -0
- data/README +186 -0
- data/RELEASES +21 -0
- data/bin/dbackup +22 -0
- data/{example_scripts/example.com.rb → example_configs/example.com} +4 -10
- data/example_configs/local_etc_backup +10 -0
- data/{example_scripts/samba_shares.rb → example_configs/samba_shares} +14 -18
- data/lib/dated_backup/core/backup_remover.rb +43 -0
- data/lib/dated_backup/core/backup_set.rb +118 -0
- data/lib/dated_backup/core/command_line.rb +11 -0
- data/lib/dated_backup/core/dated_backup.rb +71 -0
- data/lib/dated_backup/core/tasks.rb +44 -0
- data/lib/dated_backup/core/warnings.rb +15 -0
- data/lib/dated_backup/core.rb +6 -0
- data/lib/dated_backup/dsl/execution_context.rb +56 -0
- data/lib/dated_backup/dsl/main.rb +41 -0
- data/lib/dated_backup/dsl/time_extensions.rb +122 -0
- data/lib/dated_backup/dsl.rb +4 -0
- data/lib/dated_backup/extensions/array.rb +27 -0
- data/lib/dated_backup/extensions/error.rb +10 -0
- data/lib/dated_backup/extensions/string.rb +8 -0
- data/lib/dated_backup/extensions/time.rb +50 -0
- data/lib/dated_backup/extensions/time_symbol.rb +54 -0
- data/lib/dated_backup/extensions.rb +6 -0
- data/lib/dated_backup.rb +21 -1
- metadata +52 -15
- data/example_scripts/local_etc_backup.rb +0 -10
- data/lib/dated_backup/command_line.rb +0 -8
- data/lib/dated_backup/dated_backup.rb +0 -80
data/README
ADDED
@@ -0,0 +1,186 @@
|
|
1
|
+
|
2
|
+
= Introduction
|
3
|
+
|
4
|
+
Dated Backup is a program which does exactly what it's name says:
|
5
|
+
It creates backups of any directory, timestamping the backups. It then
|
6
|
+
performs incremental backups on every subsequent run. The really nice thing here
|
7
|
+
is that those backups are fully viewable as snapshots, even though they are
|
8
|
+
also incremental.
|
9
|
+
|
10
|
+
This method of backup uses the hard-link technique in combination
|
11
|
+
with rsync. For more information on this technique, see:
|
12
|
+
|
13
|
+
http://www.mikerubel.org/computers/rsync_snapshots/
|
14
|
+
|
15
|
+
At the moment, this program can be thought of as a limited, Ruby version of
|
16
|
+
the popular unix utility rsnapshot. Dated Backup's feature set already does
|
17
|
+
things a little different from rsnapshot, and in the future will diverge widely.
|
18
|
+
|
19
|
+
Dated Backup no longer depends on GNU cp, but instead uses rsync's --link-dest
|
20
|
+
option to simulate the hard-link method.
|
21
|
+
|
22
|
+
|
23
|
+
== Backup Assumptions
|
24
|
+
|
25
|
+
* Your backup *server* is POSIX compliant (a modern day UNIX - Linux, *BSD, Mac OS X)
|
26
|
+
* You would like to perform incremental snapshots with timestamps
|
27
|
+
|
28
|
+
= Installation:
|
29
|
+
|
30
|
+
* sudo gem install 'datedbackup' --include-dependencies
|
31
|
+
|
32
|
+
== Dependencies
|
33
|
+
|
34
|
+
Dated Backup has the following dependencies:
|
35
|
+
|
36
|
+
* Ruby
|
37
|
+
* Rubygems
|
38
|
+
* Rails' 'ActiveSupport' gem
|
39
|
+
* A copy of rsync, which supports the --link-dest option.
|
40
|
+
|
41
|
+
Rsync is not required on the machine to be backed up - only on the machine which stores
|
42
|
+
the backup.
|
43
|
+
|
44
|
+
|
45
|
+
= HOWTO Backup with Dated Backup
|
46
|
+
|
47
|
+
Each backup source will correspond to a configuration file, which defines the source
|
48
|
+
directory (or remote location), and the local destination directory. These two
|
49
|
+
parameters are the only requirements for a backup configuration file to be valid.
|
50
|
+
The script can be run with the executable dbackup:
|
51
|
+
|
52
|
+
% dbackup my_script
|
53
|
+
|
54
|
+
Other scripts can be run sequentially by listing them in order:
|
55
|
+
|
56
|
+
% dbackup my_first_script my_second_script
|
57
|
+
|
58
|
+
All of the configuration occurs in the configuration file.
|
59
|
+
|
60
|
+
|
61
|
+
== The DSL, or How To Write A Configuration File
|
62
|
+
|
63
|
+
Here are the valid key words which can be set in the main section of the configuration file:
|
64
|
+
|
65
|
+
* source
|
66
|
+
* sources
|
67
|
+
* destination
|
68
|
+
* options
|
69
|
+
* user_domain
|
70
|
+
|
71
|
+
The values for these should be strings. They are specified like so:
|
72
|
+
|
73
|
+
source '/etc'
|
74
|
+
|
75
|
+
Multiple values can also be given:
|
76
|
+
|
77
|
+
sources '/etc', '/home'
|
78
|
+
|
79
|
+
The destination keyword only takes one value, but in the next release (0.3), this should
|
80
|
+
be fixed to allow backups to be copied to multiple locations.
|
81
|
+
|
82
|
+
The 'source' (or 'sources') keyword and the 'destination' keyword
|
83
|
+
are the only ones needed for a valid backup config file. The 'user_domain' keyword is used
|
84
|
+
when the source is not a local directory (or local file). The user_domain should be in user@server
|
85
|
+
style. As for the 'options' keyword, this should be specified as a string for extra options to be
|
86
|
+
feed into rsync. It too, is optional.
|
87
|
+
|
88
|
+
|
89
|
+
=== Before and After Filters
|
90
|
+
|
91
|
+
It is very convenient, and often necessary to perform something before or after a backup script
|
92
|
+
runs. Any actions must be inside a 'before' or 'after' block:
|
93
|
+
|
94
|
+
before do
|
95
|
+
...some before action here
|
96
|
+
end
|
97
|
+
|
98
|
+
after {
|
99
|
+
..some other action here
|
100
|
+
}
|
101
|
+
|
102
|
+
You have the pick of do...end, or { ... }, thanks to Matz. No doubt, this will be familiar to any
|
103
|
+
Ruby programmer.
|
104
|
+
|
105
|
+
At the time of this release (0.2), there is only one valid action - 'remove_old'. Other actions, such
|
106
|
+
as running a script (or any number of scripts, in sequence), as well as running a command specified
|
107
|
+
in the configuration file itself, should be coming in subsequent releases.
|
108
|
+
|
109
|
+
For some example configuration files, see the examples bundled with RDoc, or in the example_configs
|
110
|
+
directory.
|
111
|
+
|
112
|
+
|
113
|
+
=== remove_old
|
114
|
+
|
115
|
+
The remove_old block takes several different natural language time forms. All of the statements inside
|
116
|
+
a remove_old block must begin with 'keep'. An example would work best to illustrate how to use this:
|
117
|
+
|
118
|
+
after do
|
119
|
+
remove_old do
|
120
|
+
keep this months backups
|
121
|
+
keep last months backups
|
122
|
+
keep monthly backups
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
This says the following: After the backup runs, remove all backups that do not conform to the criteria
|
127
|
+
given. The first line will save all backups which have occured this month, regardless of what time
|
128
|
+
they occurred (as long as they occurred in the current month). The next line says the same, except for
|
129
|
+
last month's backups.
|
130
|
+
|
131
|
+
The last line, "keep monthly backups", will keep one backup from each month not already kept. If you perform
|
132
|
+
daily backups every day, this would end of keeping the last day's backup from every month (the 29, 30, or 31,
|
133
|
+
according to the month)
|
134
|
+
|
135
|
+
A few things should be notice here: These config files read very easily, so don't let them fool you.
|
136
|
+
They will delete your backups, forever lost. I've already burned myself with the following:
|
137
|
+
|
138
|
+
after {
|
139
|
+
remove_old {
|
140
|
+
keep this weeks backups
|
141
|
+
keep last weeks backups
|
142
|
+
keep weekly backups from this month # or: keep this months weekly backups
|
143
|
+
keep monthly backups # or: keep all monthly backups
|
144
|
+
}
|
145
|
+
}
|
146
|
+
|
147
|
+
After a month of backups, the month rolled over, and the next backup on the first of the month deleted
|
148
|
+
all backups, except the one just performed, and the one from the last day of the month before.
|
149
|
+
So beware, and think before you specify any remove_old block at all.
|
150
|
+
|
151
|
+
Another thing to notice (for any of you non-ruby programmers out there): The config file must be in valid
|
152
|
+
ruby, so specifying a "week's backups" should be written as a 'weeks backups',
|
153
|
+
i.e., don't put any apostrophes in there.
|
154
|
+
|
155
|
+
The config file shown above should give you some hints on the possibilities. Here are the valid keywords:
|
156
|
+
|
157
|
+
Time specifiers:
|
158
|
+
* this
|
159
|
+
* last
|
160
|
+
Time ranges (these can also be pluralized):
|
161
|
+
* day
|
162
|
+
* month
|
163
|
+
* week
|
164
|
+
* year
|
165
|
+
Incremental time slices (one per * methods):
|
166
|
+
* daily
|
167
|
+
* weekly
|
168
|
+
* monthly
|
169
|
+
* yearly
|
170
|
+
And some placeholders, which have no affect, but allow the file to read nicely:
|
171
|
+
* backup
|
172
|
+
* backups
|
173
|
+
* from
|
174
|
+
* all
|
175
|
+
|
176
|
+
Some other keywords, such as 'yesterday', and 'today' will be added in subsequent releases.
|
177
|
+
|
178
|
+
And finally, a final warning: If a remove_old block is given, but no keep rules are given, every backup
|
179
|
+
will be deleted! This may change in a subsequent release, but for now, beware!
|
180
|
+
|
181
|
+
|
182
|
+
= Contributions
|
183
|
+
|
184
|
+
If you are interested in contributing code or documentation, please contact me,
|
185
|
+
Scott Taylor, at scott AT railsnewbie DOT com.
|
186
|
+
|
data/RELEASES
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
|
2
|
+
= 0.2.0
|
3
|
+
=======
|
4
|
+
|
5
|
+
- A Script Runner/binary has been added (dbackup)
|
6
|
+
- A DSL has been added for configuring backups
|
7
|
+
- Removal of old backups can now be specified in the DSL, using
|
8
|
+
natural language forms
|
9
|
+
- Before + After actions for the script runner
|
10
|
+
- RSpec + RCov = Bug free code
|
11
|
+
- Documentation added in the README file
|
12
|
+
- Developer Documentation in Rake and RSpec Report
|
13
|
+
- A series of example config files have been attached for Documentation
|
14
|
+
- Root backup directories are now created automatically
|
15
|
+
- Removed dependency on GNU's cp, so now this utility can be used
|
16
|
+
on any *NIX, including Mac OS X
|
17
|
+
|
18
|
+
= 0.1.0
|
19
|
+
=======
|
20
|
+
|
21
|
+
- Initial Release
|
data/bin/dbackup
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# DatedBackup, A snapshot backup utility
|
4
|
+
# Copyright (C) Scott Taylor (scott@railsnewbie.com), 2007
|
5
|
+
#
|
6
|
+
# This program is free software: you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation, either version 3 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# This program is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
#
|
19
|
+
|
20
|
+
require File.dirname(__FILE__) + "/../lib/dated_backup"
|
21
|
+
|
22
|
+
DatedBackup::ExecutionContext.new :main, *ARGV
|
@@ -1,5 +1,3 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
1
|
# A script which will copy the directories /etc and /home
|
4
2
|
# into /var/backups/network/backups/example.com/#{date}. Note that
|
5
3
|
# we wanted to get everything - even private keys owned by root
|
@@ -28,11 +26,7 @@
|
|
28
26
|
#
|
29
27
|
|
30
28
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
:options => '-v -e "ssh -i /root/.ssh/rsync-key" --rsync-path="sudo rsync"',
|
36
|
-
:sources => %w(/etc /home),
|
37
|
-
:destination => "/var/backups/network/backups/example.com"
|
38
|
-
).run
|
29
|
+
user_domain "nbackup@example.com"
|
30
|
+
options "-v -e 'ssh -i /root/.ssh/rsync-key' --rsync-path='sudo rsync'"
|
31
|
+
sources "/etc", "/home"
|
32
|
+
destination "/var/backups/network/backups/example.com"
|
@@ -1,5 +1,3 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
1
|
# A general purpose script to copy some WinXP/2000 shares
|
4
2
|
# on a local network. The shares are the 'C' drives
|
5
3
|
# of the various nodes on the network. They are mounted
|
@@ -16,23 +14,21 @@
|
|
16
14
|
# there is no reason that it couldn't also be used for mounted
|
17
15
|
# NFS drives, or even for a rudimentary Version Control
|
18
16
|
# for any set of files (locally, or remotely)
|
19
|
-
|
20
|
-
require "dated_backup"
|
21
17
|
|
22
|
-
puts "* mounting the samba clients"
|
23
|
-
%x(mount //teresa2/c)
|
24
|
-
%x(mount //jay/c)
|
25
|
-
%x(mount //claudio/c)
|
26
18
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
:options => "-v"
|
31
|
-
).run
|
19
|
+
source '/mnt/shares'
|
20
|
+
destination '/var/backups/network/backups/shares'
|
21
|
+
options '-v'
|
32
22
|
|
33
|
-
|
34
|
-
puts "* umounting samba clients"
|
35
|
-
%x(umount //teresa2/c)
|
36
|
-
%x(umount //jay/c)
|
37
|
-
%x(umount //claudio/c)
|
23
|
+
after do
|
38
24
|
|
25
|
+
# CAREFUL! This will remove all old backups
|
26
|
+
# except the ones specified inside the block by the 'keep' rules
|
27
|
+
remove_old do
|
28
|
+
keep this weeks backups
|
29
|
+
keep last weeks backups
|
30
|
+
keep weekly backups from this month # or: keep this months weekly backups
|
31
|
+
keep monthly backups # or: keep all monthly backups
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module DatedBackup
|
4
|
+
class Core
|
5
|
+
class BackupRemover
|
6
|
+
class << self
|
7
|
+
|
8
|
+
include DatedBackup::Core::CommandLine
|
9
|
+
|
10
|
+
def remove!(dir, keep_rules=[])
|
11
|
+
find_removable_sets(dir, keep_rules)
|
12
|
+
|
13
|
+
unless no_sets_to_remove?
|
14
|
+
execute("rm -rf #{to_remove.map{ |element| "#{element} " }.to_s.strip}")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def find_removable_sets(dir, rules)
|
21
|
+
complete_set = BackupSet.find_files_in_directory dir
|
22
|
+
@to_remove = set_to_remove(complete_set, rules)
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_reader :to_remove
|
26
|
+
|
27
|
+
def no_sets_to_remove?
|
28
|
+
to_remove.empty?
|
29
|
+
end
|
30
|
+
|
31
|
+
def set_to_remove(set, keep_rules)
|
32
|
+
if keep_rules.empty?
|
33
|
+
set
|
34
|
+
else
|
35
|
+
to_remove = set - set.filter_by_rule(keep_rules.car)
|
36
|
+
return set_to_remove(to_remove, keep_rules.cdr)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
|
2
|
+
module DatedBackup
|
3
|
+
class Core
|
4
|
+
|
5
|
+
class BackupSet < ReverseSortedUniqueArray
|
6
|
+
|
7
|
+
class << self
|
8
|
+
# Given the base of the backup directories as a string, this method should find all of the Backup Directories,
|
9
|
+
# and return these Directories as a BackupSet
|
10
|
+
def find_files_in_directory(dir)
|
11
|
+
raise InvalidDirectoryError, "A valid directory must be given." unless File.directory?(dir)
|
12
|
+
new(Dir.glob "#{dir}/*")
|
13
|
+
end
|
14
|
+
|
15
|
+
# Creates the boolean include_*? methods (include_month?, include_year? and so on).
|
16
|
+
# See the notes on the create_per_time_methods, and TimeSymbol.valid_symbols
|
17
|
+
def create_include_time_boolean_methods(*methods)
|
18
|
+
methods.each do |method|
|
19
|
+
define_method "include_#{method}?" do |time_value|
|
20
|
+
truth_value = false
|
21
|
+
self.each_as_time do |t|
|
22
|
+
truth_value = true if t.send(method) == time_value
|
23
|
+
end
|
24
|
+
truth_value
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Creates the one_per_* (one_per_month, one_per_year, one_per_week, and one_per_day)
|
30
|
+
# methods. Each of those methods will call the appropriate include_* methods,
|
31
|
+
# which is also dynamically defined
|
32
|
+
def create_one_per_time_methods(*methods)
|
33
|
+
methods.each do |method|
|
34
|
+
define_method "one_per_#{method}" do
|
35
|
+
set = BackupSet.new
|
36
|
+
reject_with_string_and_timestamp do |string, timestamp|
|
37
|
+
set.push string unless set.send("include_#{method}?", timestamp.send("#{method}"))
|
38
|
+
end
|
39
|
+
set
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Creates the many similar time methods:
|
45
|
+
#
|
46
|
+
# * include_year?
|
47
|
+
# * include_month?
|
48
|
+
# * include_day?
|
49
|
+
# * include_week
|
50
|
+
#
|
51
|
+
# * one_per_year
|
52
|
+
# * one_per_month
|
53
|
+
# * one_per_day
|
54
|
+
# * one_per_week
|
55
|
+
#
|
56
|
+
def create_dynamic_time_methods(time_array=[])
|
57
|
+
create_include_time_boolean_methods *time_array
|
58
|
+
create_one_per_time_methods *time_array
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
create_dynamic_time_methods TimeSymbol.valid_symbols
|
63
|
+
|
64
|
+
def filter_by_rule(rule)
|
65
|
+
obj = self.dup
|
66
|
+
obj = obj.filter_by_range(rule[:constraint]) if rule[:constraint]
|
67
|
+
obj = obj.filter_by_scope(rule[:scope]) if rule[:scope]
|
68
|
+
return obj
|
69
|
+
end
|
70
|
+
|
71
|
+
def filter_by_scope(scope)
|
72
|
+
case scope
|
73
|
+
when :yearly
|
74
|
+
one_per_year
|
75
|
+
when :monthly
|
76
|
+
one_per_month
|
77
|
+
when :weekly
|
78
|
+
one_per_week
|
79
|
+
when :daily
|
80
|
+
one_per_day
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def filter_by_range(time_range)
|
85
|
+
reject_with_timestamp do |timestamp|
|
86
|
+
!(time_range.include? timestamp)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def reject_with_timestamp &blk
|
91
|
+
reject do |element|
|
92
|
+
yield element.to_time
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def reject_with_string_and_timestamp &blk
|
97
|
+
reject do |element|
|
98
|
+
yield element, element.to_time
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def each_as_time &blk
|
103
|
+
self.each do |obj|
|
104
|
+
yield obj.to_time
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# BackupSet#- should return a new BackupSet,
|
109
|
+
# not an array
|
110
|
+
define_method "-" do |obj|
|
111
|
+
(self.to_a - obj.to_a).to_backup_set
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
@@ -0,0 +1,71 @@
|
|
1
|
+
|
2
|
+
module DatedBackup
|
3
|
+
class Core
|
4
|
+
|
5
|
+
BACKUP_REGEXP = /[12][0-9]{3}\-[01][0-9]\-[0-3][0-9]\-[0-2][0-9]h\-[0-6][0-9]m\-[0-6][0-9]s/
|
6
|
+
|
7
|
+
include DatedBackup::Core::CommandLine
|
8
|
+
include DatedBackup::Core::Tasks
|
9
|
+
|
10
|
+
attr_accessor :sources, :destination, :options, :backup_root, :user_domain
|
11
|
+
attr_reader :pre_run_commands, :kernel
|
12
|
+
attr_reader :before_run, :after_run
|
13
|
+
|
14
|
+
def initialize(procs={}, kernel=Kernel)
|
15
|
+
@kernel = kernel
|
16
|
+
@before_run = procs[:before] || Proc.new {}
|
17
|
+
@after_run = procs[:after] || Proc.new {}
|
18
|
+
end
|
19
|
+
|
20
|
+
def set_attributes(h={})
|
21
|
+
parse_command_options(h)
|
22
|
+
@destination = generate_backup_filename
|
23
|
+
if @user_domain
|
24
|
+
@sources.map! { |src| "#{@user_domain}:#{src}" }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def check_for_directory_errors
|
29
|
+
if sources.nil? || sources.empty?
|
30
|
+
raise DirectoryError, "No source directory given"
|
31
|
+
elsif backup_root.nil? || backup_root.empty?
|
32
|
+
raise DirectoryError, "No destination directory given"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# create the first backup, if non-existent
|
37
|
+
# otherwise cp -al (or # replace cp -al a b with cd a && find . -print | cpio -dpl ../b )
|
38
|
+
# and then create the backup of the dirs using rsync -a --delete
|
39
|
+
# the files, in the end, should be read only and undeletable
|
40
|
+
def run
|
41
|
+
DatedBackup::ExecutionContext.new :before, &@before_run
|
42
|
+
run_tasks
|
43
|
+
DatedBackup::ExecutionContext.new :after, &@after_run
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def generate_backup_filename
|
49
|
+
timestamp = Time.now.strftime "%Y-%m-%d-%Hh-%Mm-%Ss"
|
50
|
+
"#{@backup_root}/#{timestamp}"
|
51
|
+
end
|
52
|
+
|
53
|
+
protected
|
54
|
+
|
55
|
+
def parse_command_options(h={})
|
56
|
+
@pre_run_commands = h[:pre_run_commands]
|
57
|
+
@pre_run_commands = h[:pre_run_command].to_a if h[:pre_run_command]
|
58
|
+
|
59
|
+
@backup_root = *h[:destination]
|
60
|
+
@options = h[:options] ? h[:options].map { |e| "#{e} "}.to_s.strip : ""
|
61
|
+
|
62
|
+
@user_domain = h[:user_domain]
|
63
|
+
@sources = h[:sources] || h[:source]
|
64
|
+
check_for_directory_errors
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
|
@@ -0,0 +1,44 @@
|
|
1
|
+
|
2
|
+
module DatedBackup
|
3
|
+
class Core
|
4
|
+
module Tasks
|
5
|
+
|
6
|
+
def run_tasks
|
7
|
+
create_main_backup_directory
|
8
|
+
create_backup
|
9
|
+
end
|
10
|
+
|
11
|
+
def create_backup
|
12
|
+
additional_options = has_backup? ? "--link-dest #{latest_backup} " : ""
|
13
|
+
|
14
|
+
sources.each do |source|
|
15
|
+
cmd = "rsync -a --delete #{additional_options}#{options} #{source} #{destination}"
|
16
|
+
execute cmd, kernel
|
17
|
+
kernel.puts "\n\n"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def create_main_backup_directory
|
22
|
+
unless File.exists? backup_root
|
23
|
+
kernel.puts "* Creating main backup directory #{backup_root}"
|
24
|
+
Dir.mkdir backup_root
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def find_latest_backup
|
29
|
+
backup_directories.sort.reverse.first
|
30
|
+
end
|
31
|
+
|
32
|
+
alias :latest_backup :find_latest_backup
|
33
|
+
|
34
|
+
def backup_directories
|
35
|
+
Dir.glob("#{backup_root}/*").grep(BACKUP_REGEXP)
|
36
|
+
end
|
37
|
+
|
38
|
+
def has_backup?
|
39
|
+
backup_directories.empty? ? false : true
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,6 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/core/warnings"
|
2
|
+
require File.dirname(__FILE__) + "/core/backup_set"
|
3
|
+
require File.dirname(__FILE__) + "/core/tasks"
|
4
|
+
require File.dirname(__FILE__) + "/core/command_line"
|
5
|
+
require File.dirname(__FILE__) + "/core/backup_remover"
|
6
|
+
require File.dirname(__FILE__) + "/core/dated_backup"
|
@@ -0,0 +1,56 @@
|
|
1
|
+
|
2
|
+
module DatedBackup
|
3
|
+
class ExecutionContext
|
4
|
+
|
5
|
+
def initialize(name, *params, &blk)
|
6
|
+
DatedBackup::Warnings.execute_silently do
|
7
|
+
if name == :main
|
8
|
+
params.each do |filename|
|
9
|
+
Main.load filename
|
10
|
+
end
|
11
|
+
elsif name == :before || name == :after
|
12
|
+
Around.new &blk
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class Main
|
18
|
+
class << self
|
19
|
+
def load(filename)
|
20
|
+
klass = Class.new
|
21
|
+
klass.send(:include, DSL::Main)
|
22
|
+
instance = klass.new
|
23
|
+
|
24
|
+
File.open filename, "r" do |file|
|
25
|
+
instance.instance_eval file.read
|
26
|
+
end
|
27
|
+
|
28
|
+
@main_instance = DatedBackup::Core.new(instance.procs)
|
29
|
+
@main_instance.set_attributes(instance.hash)
|
30
|
+
@main_instance.run
|
31
|
+
end
|
32
|
+
|
33
|
+
attr_reader :main_instance
|
34
|
+
alias :core_instance :main_instance
|
35
|
+
alias :instance :main_instance
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class Around
|
40
|
+
def initialize(around=self, &blk)
|
41
|
+
around.instance_eval &blk
|
42
|
+
end
|
43
|
+
|
44
|
+
def remove_old(&blk)
|
45
|
+
klass = Class.new
|
46
|
+
klass.send(:include, DSL::TimeExtensions)
|
47
|
+
instance = klass.new
|
48
|
+
|
49
|
+
instance.instance_eval &blk
|
50
|
+
|
51
|
+
Core::BackupRemover.remove!(Main.instance.backup_root, instance.kept)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|