dated_backup 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|