backup 3.0.14 → 3.0.15

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -205,6 +205,8 @@ The __Mail__ notifier. I have not provided the SMTP options to use my Gmail acco
205
205
 
206
206
  The __Twitter__ notifier. You will require your consumer and oauth credentials, which I have also left out of this example.
207
207
 
208
+ MongoDB backup utility (mongodump) by default does not fsync & lock the database, opening a possibility for inconsistent data dump. This is addressed by setting lock = true which causes mongodump to be wrapped with lock&fsync calls (with a lock takedown after the dump). Please check the Wiki on this subject and remember this is a very fresh feature, needing some more real-world testing. Disabled at default.
209
+
208
210
  Check out the Wiki for more information on all the above subjects.
209
211
 
210
212
  ### And that's it!
data/bin/backup CHANGED
@@ -31,11 +31,12 @@ class BackupCLI < Thor
31
31
  # Performs the backup process. The only required option is the --trigger [-t].
32
32
  # If the other options (--config_file, --data_path, --tmp_path) aren't specified
33
33
  # it'll fallback to the (good) defaults
34
- method_option :trigger, :type => :string, :aliases => ['-t', '--triggers'], :required => true
35
- method_option :config_file, :type => :string, :aliases => '-c'
36
- method_option :data_path, :type => :string, :aliases => '-d'
37
- method_option :log_path, :type => :string, :aliases => '-l'
34
+ method_option :trigger, :type => :string, :aliases => ['-t', '--triggers'], :required => true
35
+ method_option :config_file, :type => :string, :aliases => '-c'
36
+ method_option :data_path, :type => :string, :aliases => '-d'
37
+ method_option :log_path, :type => :string, :aliases => '-l'
38
38
  method_option :tmp_path, :type => :string
39
+ method_option :quiet, :type => :boolean, :aliases => '-q'
39
40
  desc 'perform', "Performs the backup for the specified trigger.\n" +
40
41
  "You may perform multiple backups by providing multiple triggers, separated by commas.\n\n" +
41
42
  "Example:\n\s\s$ backup perform --triggers backup1,backup2,backup3,backup4\n\n" +
@@ -76,6 +77,12 @@ class BackupCLI < Thor
76
77
  FileUtils.mkdir_p(path)
77
78
  end
78
79
 
80
+ ##
81
+ # Silence Backup::Logger from printing to STDOUT, if --quiet was specified
82
+ if options[:quiet]
83
+ Backup::Logger.send(:const_set, :QUIET, options[:quiet])
84
+ end
85
+
79
86
  ##
80
87
  # Process each trigger
81
88
  options[:trigger].split(",").map(&:strip).each do |trigger|
@@ -257,4 +264,4 @@ end
257
264
 
258
265
  ##
259
266
  # Enable the CLI for the Backup binary
260
- BackupCLI.start
267
+ BackupCLI.start
@@ -8,6 +8,10 @@ module Backup
8
8
  # through a ruby method. This helps with test coverage and
9
9
  # improves readability.
10
10
  #
11
+ # It'll first remove all prefixing slashes ( / ) by using .gsub(/^\s+/, '')
12
+ # This allows for the EOS blocks to be indented without actually using any
13
+ # prefixing spaces. This cleans up the implementation code.
14
+ #
11
15
  # Every time the Backup::CLI#run method is invoked, it'll invoke
12
16
  # the Backup::CLI#raise_if_command_not_found method after running the
13
17
  # requested command on the OS.
@@ -17,6 +21,7 @@ module Backup
17
21
  # name (e.g. mongodump, pgdump, etc) from a command like "/usr/local/bin/mongodump <options>"
18
22
  # and pass that in to the Backup::CLI#raise_if_command_not_found
19
23
  def run(command)
24
+ command.gsub!(/^\s+/, '')
20
25
  %x[#{command}]
21
26
  raise_if_command_not_found!(
22
27
  command.slice(0, command.index(/\s/)).split('/')[-1]
@@ -67,7 +72,9 @@ module Backup
67
72
  # and notify the user via the built-in notifiers if these are set.
68
73
  def raise_if_command_not_found!(utility)
69
74
  if $?.to_i.eql?(32512)
70
- raise Exception::CommandNotFound , "Could not find the utility \"#{utility}\" on \"#{RUBY_PLATFORM}\"."
75
+ raise Exception::CommandNotFound , "Could not find the utility \"#{utility}\" on \"#{RUBY_PLATFORM}\".\n" +
76
+ "If this is a database utility, try defining the 'utility_path' option in the configuration file.\n" +
77
+ "See the Database Wiki for more information about the Utility Path option."
71
78
  end
72
79
  end
73
80
 
@@ -30,6 +30,10 @@ module Backup
30
30
  # Additional "mongodump" options
31
31
  attr_accessor :additional_options
32
32
 
33
+ ##
34
+ # 'lock' dump meaning wrapping mongodump with fsync & lock
35
+ attr_accessor :lock
36
+
33
37
  end
34
38
  end
35
39
  end
@@ -28,6 +28,10 @@ module Backup
28
28
  # Additional "mongodump" options
29
29
  attr_accessor :additional_options
30
30
 
31
+ ##
32
+ # 'lock' dump meaning wrapping mongodump with fsync & lock
33
+ attr_accessor :lock
34
+
31
35
  ##
32
36
  # Creates a new instance of the MongoDB database object
33
37
  def initialize(&block)
@@ -36,6 +40,7 @@ module Backup
36
40
  @only_collections ||= Array.new
37
41
  @additional_options ||= Array.new
38
42
  @ipv6 ||= false
43
+ @lock ||= false
39
44
 
40
45
  instance_eval(&block)
41
46
  prepare!
@@ -109,10 +114,17 @@ module Backup
109
114
  def perform!
110
115
  log!
111
116
 
112
- if collections_to_dump.is_a?(Array) and not collections_to_dump.empty?
113
- specific_collection_dump!
114
- else
115
- dump!
117
+ begin
118
+ lock_database if @lock.eql?(true)
119
+ if collections_to_dump.is_a?(Array) and not collections_to_dump.empty?
120
+ specific_collection_dump!
121
+ else
122
+ dump!
123
+ end
124
+ unlock_database if @lock.eql?(true)
125
+ rescue => exception
126
+ unlock_database if @lock.eql?(true)
127
+ raise exception
116
128
  end
117
129
  end
118
130
 
@@ -132,6 +144,36 @@ module Backup
132
144
  end
133
145
  end
134
146
 
147
+ ##
148
+ # Builds the full mongo string based on all attributes
149
+ def mongo_shell
150
+ [utility(:mongo), database, credential_options, connectivity_options, ipv6].join(' ')
151
+ end
152
+
153
+ ##
154
+ # Locks and FSyncs the database to bring it up to sync
155
+ # and ensure no 'write operations' are performed during the
156
+ # dump process
157
+ def lock_database
158
+ lock_command = <<-EOS
159
+ echo 'use admin
160
+ db.runCommand({"fsync" : 1, "lock" : 1})' | #{mongo_shell}
161
+ EOS
162
+
163
+ run(lock_command)
164
+ end
165
+
166
+ ##
167
+ # Unlocks the (locked) database
168
+ def unlock_database
169
+ unlock_command = <<-EOS
170
+ echo 'use admin
171
+ db.$cmd.sys.unlock.findOne()' | #{mongo_shell}
172
+ EOS
173
+
174
+ run(unlock_command)
175
+ end
176
+
135
177
  end
136
178
  end
137
179
  end
@@ -6,21 +6,21 @@ module Backup
6
6
  ##
7
7
  # Outputs a messages to the console and writes it to the backup.log
8
8
  def self.message(string)
9
- puts loggify(:message, string, :green)
9
+ puts loggify(:message, string, :green) unless quiet?
10
10
  to_file loggify(:message, string)
11
11
  end
12
12
 
13
13
  ##
14
14
  # Outputs an error to the console and writes it to the backup.log
15
15
  def self.error(string)
16
- puts loggify(:error, string, :red)
16
+ puts loggify(:error, string, :red) unless quiet?
17
17
  to_file loggify(:error, string)
18
18
  end
19
19
 
20
20
  ##
21
21
  # Outputs a notice to the console and writes it to the backup.log
22
22
  def self.warn(string)
23
- puts loggify(:warning, string, :yellow)
23
+ puts loggify(:warning, string, :yellow) unless quiet?
24
24
  to_file loggify(:warning, string)
25
25
  end
26
26
 
@@ -28,7 +28,7 @@ module Backup
28
28
  # Outputs the data as if it were a regular 'puts' command,
29
29
  # but also logs it to the backup.log
30
30
  def self.normal(string)
31
- puts string
31
+ puts string unless quiet?
32
32
  to_file string
33
33
  end
34
34
 
@@ -90,5 +90,13 @@ module Backup
90
90
  "\e[#{code}m#{string}\e[0m"
91
91
  end
92
92
 
93
+ ##
94
+ # Returns 'true' (boolean) if the QUIET constant is defined
95
+ # By default it isn't defined, only when initializing Backup using
96
+ # the '--quite' (or '-q') option in the CLI (e.g. backup perform -t my_backup --quiet)
97
+ def self.quiet?
98
+ const_defined?(:QUIET) && QUIET
99
+ end
100
+
93
101
  end
94
102
  end
@@ -1,5 +1,9 @@
1
1
  # encoding: utf-8
2
2
 
3
+ ##
4
+ # Require the tempfile Ruby library when Backup::Storage::RSync is loaded
5
+ require 'tempfile'
6
+
3
7
  ##
4
8
  # Only load the Net::SSH library when the Backup::Storage::RSync class is loaded
5
9
  Backup::Dependency.load('net-ssh')
@@ -32,6 +36,7 @@ module Backup
32
36
  @path ||= 'backups'
33
37
 
34
38
  instance_eval(&block) if block_given?
39
+ write_password_file!
35
40
 
36
41
  @time = TIME
37
42
  @path = path.sub(/^\~\//, '')
@@ -47,6 +52,7 @@ module Backup
47
52
  # Performs the backup transfer
48
53
  def perform!
49
54
  transfer!
55
+ remove_password_file!
50
56
  end
51
57
 
52
58
  private
@@ -58,7 +64,7 @@ module Backup
58
64
  # gets invoked once per object for a #transfer! and once for a remove! Backups run in the
59
65
  # background anyway so even if it were a bit slower it shouldn't matter.
60
66
  def connection
61
- Net::SSH.start(ip, username, :password => password, :port => port)
67
+ Net::SSH.start(ip, username, :password => @password, :port => port)
62
68
  end
63
69
 
64
70
  ##
@@ -66,7 +72,7 @@ module Backup
66
72
  def transfer!
67
73
  Logger.message("#{ self.class } started transferring \"#{ remote_file }\".")
68
74
  create_remote_directories!
69
- run("#{ utility(:rsync) } #{ options } '#{ File.join(local_path, local_file) }' '#{ username }@#{ ip }:#{ File.join(remote_path, remote_file[20..-1]) }'")
75
+ run("#{ utility(:rsync) } #{ options } #{ password } '#{ File.join(local_path, local_file) }' '#{ username }@#{ ip }:#{ File.join(remote_path, remote_file[20..-1]) }'")
70
76
  end
71
77
 
72
78
  ##
@@ -94,6 +100,30 @@ module Backup
94
100
  "-z --port='#{ port }'"
95
101
  end
96
102
 
103
+ ##
104
+ # Returns Rsync syntax for using a password file
105
+ def password
106
+ "--password-file='#{@password_file.path}'" unless @password.nil?
107
+ end
108
+
109
+ ##
110
+ # Writes the provided password to a temporary file so that
111
+ # the rsync utility can read the password from this file
112
+ def write_password_file!
113
+ unless @password.nil?
114
+ @password_file = Tempfile.new('backup-rsync-password')
115
+ @password_file.write(@password)
116
+ @password_file.close
117
+ end
118
+ end
119
+
120
+ ##
121
+ # Removes the previously created @password_file
122
+ # (temporary file containing the password)
123
+ def remove_password_file!
124
+ @password_file.unlink unless @password.nil?
125
+ end
126
+
97
127
  end
98
128
  end
99
129
  end
@@ -1,5 +1,9 @@
1
1
  # encoding: utf-8
2
2
 
3
+ ##
4
+ # Require the tempfile Ruby library when Backup::Syncer::RSync is loaded
5
+ require 'tempfile'
6
+
3
7
  module Backup
4
8
  module Syncer
5
9
  class RSync < Base
@@ -52,6 +56,7 @@ module Backup
52
56
  @compress ||= false
53
57
 
54
58
  instance_eval(&block) if block_given?
59
+ write_password_file!
55
60
 
56
61
  @path = path.sub(/^\~\//, '')
57
62
  end
@@ -61,13 +66,17 @@ module Backup
61
66
  # debug options: -vhP
62
67
  def perform!
63
68
  Logger.message("#{ self.class } started syncing #{ directories }.")
64
- Logger.silent( run("#{ utility(:rsync) } -vhP #{ options } #{ directories } '#{ username }@#{ ip }:#{ path }'") )
69
+ Logger.silent(
70
+ run("#{ utility(:rsync) } -vhP #{ options } #{ directories } '#{ username }@#{ ip }:#{ path }'")
71
+ )
72
+
73
+ remove_password_file!
65
74
  end
66
75
 
67
76
  ##
68
77
  # Returns all the specified Rsync options, concatenated, ready for the CLI
69
78
  def options
70
- ([archive, mirror, compress, port] + additional_options).compact.join("\s")
79
+ ([archive, mirror, compress, port, password] + additional_options).compact.join("\s")
71
80
  end
72
81
 
73
82
  ##
@@ -94,6 +103,12 @@ module Backup
94
103
  "--port='#{@port}'"
95
104
  end
96
105
 
106
+ ##
107
+ # Returns Rsync syntax for setting a password (via a file)
108
+ def password
109
+ "--password-file='#{@password_file.path}'" unless @password.nil?
110
+ end
111
+
97
112
  ##
98
113
  # If no block has been provided, it'll return the array of @directories.
99
114
  # If a block has been provided, it'll evaluate it and add the defined paths to the @directories
@@ -112,6 +127,26 @@ module Backup
112
127
  @directories << path
113
128
  end
114
129
 
130
+ private
131
+
132
+ ##
133
+ # Writes the provided password to a temporary file so that
134
+ # the rsync utility can read the password from this file
135
+ def write_password_file!
136
+ unless @password.nil?
137
+ @password_file = Tempfile.new('backup-rsync-password')
138
+ @password_file.write(@password)
139
+ @password_file.close
140
+ end
141
+ end
142
+
143
+ ##
144
+ # Removes the previously created @password_file
145
+ # (temporary file containing the password)
146
+ def remove_password_file!
147
+ @password_file.unlink unless @password.nil?
148
+ end
149
+
115
150
  end
116
151
  end
117
152
  end
@@ -13,7 +13,7 @@ module Backup
13
13
  # Defines the minor version
14
14
  # PATCH:
15
15
  # Defines the patch version
16
- MAJOR, MINOR, PATCH = 3, 0, 14
16
+ MAJOR, MINOR, PATCH = 3, 0, 15
17
17
 
18
18
  ##
19
19
  # Returns the major version ( big release based off of multiple minor releases )
@@ -7,4 +7,5 @@
7
7
  db.ipv6 = false
8
8
  db.only_collections = ['only', 'these' 'collections']
9
9
  db.additional_options = []
10
+ db.lock = false
10
11
  end
@@ -129,6 +129,43 @@ describe Backup::Database::MongoDB do
129
129
  db.perform!
130
130
  end
131
131
 
132
+ it 'should lock database before dump if lock mode is enabled' do
133
+ db.lock = true
134
+ db.expects(:lock_database)
135
+
136
+ db.perform!
137
+ end
138
+
139
+ it 'should not lock database before dump if lock mode is disabled' do
140
+ db.lock = false
141
+ db.expects(:lock_database).never
142
+
143
+ db.perform!
144
+ end
145
+
146
+ it 'should unlock database after dump if lock mode is enabled' do
147
+ db.lock = true
148
+ db.expects(:unlock_database)
149
+
150
+ db.perform!
151
+ end
152
+
153
+ it 'should unlock the database if an exception is raised after it was locked' do
154
+ db.lock = true
155
+ db.expects(:unlock_database)
156
+ db.expects(:lock_database).raises(RuntimeError, 'something went wrong')
157
+ db.expects(:raise)
158
+
159
+ db.perform!
160
+ end
161
+
162
+ it 'should not unlock database after dump if lock mode is disabled' do
163
+ db.lock = false
164
+ db.expects(:unlock_database).never
165
+
166
+ db.perform!
167
+ end
168
+
132
169
  it 'should dump only the provided collections' do
133
170
  db.only_collections = %w[users admins profiles]
134
171
  db.expects(:specific_collection_dump!)
@@ -43,4 +43,16 @@ describe Backup::Logger do
43
43
  Backup::Logger.silent "This has been logged."
44
44
  end
45
45
  end
46
+
47
+ context 'when quieted' do
48
+ it do
49
+ Backup::Logger.send(:const_set, :QUIET, true)
50
+ Backup::Logger.expects(:puts).never
51
+
52
+ Backup::Logger.message "This has been logged."
53
+ Backup::Logger.error "This has been logged."
54
+ Backup::Logger.warn "This has been logged."
55
+ Backup::Logger.normal "This has been logged."
56
+ end
57
+ end
46
58
  end
@@ -19,11 +19,13 @@ describe Backup::Storage::RSync do
19
19
  end
20
20
 
21
21
  it 'should have defined the configuration properly' do
22
- rsync.username.should == 'my_username'
23
- rsync.password.should == 'my_password'
24
- rsync.ip.should == '123.45.678.90'
25
- rsync.port.should == 22
26
- rsync.path.should == 'backups/'
22
+ rsync.username.should == 'my_username'
23
+ rsync.send(:password).should =~ /backup-rsync-password/
24
+ rsync.ip.should == '123.45.678.90'
25
+ rsync.port.should == 22
26
+ rsync.path.should == 'backups/'
27
+
28
+ File.read(rsync.instance_variable_get('@password_file').path).should == 'my_password'
27
29
  end
28
30
 
29
31
  it 'should use the defaults if a particular attribute has not been defined' do
@@ -38,10 +40,12 @@ describe Backup::Storage::RSync do
38
40
  rsync.ip = '123.45.678.90'
39
41
  end
40
42
 
41
- rsync.username.should == 'my_default_username'
42
- rsync.password.should == 'my_password'
43
- rsync.ip.should == '123.45.678.90'
44
- rsync.port.should == 22
43
+ rsync.username.should == 'my_default_username'
44
+ rsync.send(:password).should =~ /backup-rsync-password/
45
+ rsync.ip.should == '123.45.678.90'
46
+ rsync.port.should == 22
47
+
48
+ File.read(rsync.instance_variable_get('@password_file').path).should == 'my_password'
45
49
  end
46
50
 
47
51
  it 'should have its own defaults' do
@@ -72,7 +76,19 @@ describe Backup::Storage::RSync do
72
76
 
73
77
  rsync.expects(:create_remote_directories!)
74
78
  rsync.expects(:utility).returns('rsync')
75
- rsync.expects(:run).with("rsync -z --port='22' '#{ File.join(Backup::TMP_PATH, "#{ Backup::TIME }.#{ Backup::TRIGGER }.tar") }' 'my_username@123.45.678.90:backups/#{ Backup::TRIGGER }/#{ Backup::TRIGGER }.tar'")
79
+ rsync.expects(:run).with("rsync -z --port='22' --password-file='#{rsync.instance_variable_get('@password_file').path}' '#{ File.join(Backup::TMP_PATH, "#{ Backup::TIME }.#{ Backup::TRIGGER }.tar") }' 'my_username@123.45.678.90:backups/#{ Backup::TRIGGER }/#{ Backup::TRIGGER }.tar'")
80
+
81
+ rsync.send(:transfer!)
82
+ end
83
+
84
+ it 'should not provide the --password-file option' do
85
+ Backup::Model.new('blah', 'blah') {}
86
+ file = mock("Backup::Storage::RSync::File")
87
+
88
+ rsync.password = nil
89
+ rsync.expects(:create_remote_directories!)
90
+ rsync.expects(:utility).returns('rsync')
91
+ rsync.expects(:run).with("rsync -z --port='22' '#{ File.join(Backup::TMP_PATH, "#{ Backup::TIME }.#{ Backup::TRIGGER }.tar") }' 'my_username@123.45.678.90:backups/#{ Backup::TRIGGER }/#{ Backup::TRIGGER }.tar'")
76
92
 
77
93
  rsync.send(:transfer!)
78
94
  end
@@ -28,12 +28,14 @@ describe Backup::Syncer::RSync do
28
28
 
29
29
  it 'should have defined the configuration properly' do
30
30
  rsync.username.should == 'my_username'
31
- rsync.password.should == 'my_password'
31
+ rsync.password.should =~ /backup-rsync-password/
32
32
  rsync.ip.should == '123.45.678.90'
33
33
  rsync.port.should == "--port='22'"
34
34
  rsync.path.should == 'backups/'
35
35
  rsync.mirror.should == "--delete"
36
36
  rsync.compress.should == "--compress"
37
+
38
+ File.read(rsync.instance_variable_get('@password_file').path).should == 'my_password'
37
39
  end
38
40
 
39
41
  it 'should use the defaults if a particular attribute has not been defined' do
@@ -51,11 +53,13 @@ describe Backup::Syncer::RSync do
51
53
  end
52
54
 
53
55
  rsync.username.should == 'my_default_username'
54
- rsync.password.should == 'my_password'
56
+ rsync.password.should =~ /backup-rsync-password/
55
57
  rsync.ip.should == '123.45.678.90'
56
58
  rsync.port.should == "--port='22'"
57
59
  rsync.mirror.should == nil
58
60
  rsync.compress.should == nil
61
+
62
+ File.read(rsync.instance_variable_get('@password_file').path).should == 'my_password'
59
63
  end
60
64
 
61
65
  it 'should have its own defaults' do
@@ -140,7 +144,30 @@ describe Backup::Syncer::RSync do
140
144
 
141
145
  describe '#options' do
142
146
  it do
143
- rsync.options.should == "--archive --delete --compress --port='22'"
147
+ rsync.options.should == "--archive --delete --compress --port='22' " +
148
+ "--password-file='#{rsync.instance_variable_get('@password_file').path}'"
149
+ end
150
+ end
151
+
152
+ describe '#password' do
153
+ before do
154
+ Backup::Logger.stubs(:message)
155
+ rsync.stubs(:utility).with(:rsync).returns(:rsync)
156
+ rsync.stubs(:run)
157
+ end
158
+
159
+ it do
160
+ rsync.password = 'my_password'
161
+ rsync.expects(:remove_password_file!)
162
+
163
+ rsync.perform!
164
+ end
165
+
166
+ it do
167
+ rsync.password = nil
168
+ rsync.expects(:remove_password_file!)
169
+
170
+ rsync.perform!
144
171
  end
145
172
  end
146
173
 
@@ -148,7 +175,19 @@ describe Backup::Syncer::RSync do
148
175
  it 'should invoke the rsync command to transfer the files and directories' do
149
176
  Backup::Logger.expects(:message).with("Backup::Syncer::RSync started syncing '/some/random/directory' '/another/random/directory'.")
150
177
  rsync.expects(:utility).with(:rsync).returns(:rsync)
151
- rsync.expects(:run).with("rsync -vhP --archive --delete --compress --port='22' '/some/random/directory' '/another/random/directory' 'my_username@123.45.678.90:backups/'")
178
+ rsync.expects(:remove_password_file!)
179
+ rsync.expects(:run).with("rsync -vhP --archive --delete --compress --port='22' --password-file='#{rsync.instance_variable_get('@password_file').path}' " +
180
+ "'/some/random/directory' '/another/random/directory' 'my_username@123.45.678.90:backups/'")
181
+ rsync.perform!
182
+ end
183
+
184
+ it 'should not pass in the --password-file option' do
185
+ Backup::Logger.expects(:message).with("Backup::Syncer::RSync started syncing '/some/random/directory' '/another/random/directory'.")
186
+ rsync.password = nil
187
+ rsync.expects(:utility).with(:rsync).returns(:rsync)
188
+ rsync.expects(:remove_password_file!)
189
+ rsync.expects(:run).with("rsync -vhP --archive --delete --compress --port='22' " +
190
+ "'/some/random/directory' '/another/random/directory' 'my_username@123.45.678.90:backups/'")
152
191
  rsync.perform!
153
192
  end
154
193
  end
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: backup
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 3.0.14
5
+ version: 3.0.15
6
6
  platform: ruby
7
7
  authors:
8
8
  - Michael van Rooijen
@@ -10,8 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2011-04-02 00:00:00 +02:00
14
- default_executable:
13
+ date: 2011-05-01 00:00:00 Z
15
14
  dependencies:
16
15
  - !ruby/object:Gem::Dependency
17
16
  name: thor
@@ -176,7 +175,6 @@ files:
176
175
  - spec/syncer/rsync_spec.rb
177
176
  - spec/syncer/s3_spec.rb
178
177
  - spec/version_spec.rb
179
- has_rdoc: true
180
178
  homepage: http://rubygems.org/gems/backup
181
179
  licenses: []
182
180
 
@@ -200,9 +198,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
200
198
  requirements: []
201
199
 
202
200
  rubyforge_project:
203
- rubygems_version: 1.6.1
201
+ rubygems_version: 1.7.2
204
202
  signing_key:
205
203
  specification_version: 3
206
204
  summary: "Backup is a RubyGem (for UNIX-like operating systems: Linux, Mac OSX) that allows you to configure and perform backups in a simple manner using an elegant Ruby DSL. It supports various databases (MySQL, PostgreSQL, MongoDB and Redis), it supports various storage locations (Amazon S3, Rackspace Cloud Files, Dropbox, any remote server through FTP, SFTP, SCP and RSync), it provide Syncers (RSync, S3) for efficient backups, it can archive files and directories, it can cycle backups, it can do incremental backups, it can compress backups, it can encrypt backups (OpenSSL or GPG), it can notify you about successful and/or failed backups (Email, Twitter and Campfire). It is very extensible and easy to add new functionality to. It's easy to use."
207
205
  test_files: []
208
206
 
207
+ has_rdoc: