mogbak 0.1.2 → 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/CHANGES +10 -0
- data/Gemfile.lock +31 -7
- data/bin/mogbak +33 -2
- data/lib/backup.rb +70 -55
- data/lib/forkinator.rb +0 -7
- data/lib/list.rb +2 -1
- data/lib/log.rb +17 -0
- data/lib/mogbak_version.rb +1 -1
- data/lib/restore.rb +4 -2
- data/lib/signal_handler.rb +19 -0
- data/lib/validations.rb +1 -1
- data/mogbak.gemspec +0 -3
- metadata +5 -18
data/CHANGES
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
2012-04-03: Release version 0.2.0
|
2
|
+
|
3
|
+
* Graceful handling of SIGINT and SIGTERM have been added. Upon receiving, mogbak will finish
|
4
|
+
the files being transfered and will exit. (Jesse Angell <jesse.angell@firespring.com>)
|
5
|
+
|
6
|
+
* Backup now has an optional --non-stop switch. This will cause mogbak to perform backup after backup
|
7
|
+
without exiting until SIGTERM or SIGINT is received. (Jesse Angell <jesse.angell@firespring.com>)
|
8
|
+
|
9
|
+
* --log-file option added to Restore, List, and Backup. Optional parameter that will redirect
|
10
|
+
output to a log file. Works well in conjunction with --non-stop. (Jesse Angell <jesse.angell@firespring.com>)
|
data/Gemfile.lock
CHANGED
@@ -1,20 +1,44 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
mogbak (0.
|
4
|
+
mogbak (0.1.3)
|
5
|
+
activerecord-import (>= 0.2.9)
|
6
|
+
gli (>= 1.5.1)
|
7
|
+
mogilefs-client (>= 3.1.1)
|
8
|
+
mysql2 (>= 0.3.11)
|
9
|
+
sqlite3 (>= 1.3.5)
|
5
10
|
|
6
11
|
GEM
|
7
12
|
remote: http://rubygems.org/
|
8
13
|
specs:
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
14
|
+
activemodel (3.2.2)
|
15
|
+
activesupport (= 3.2.2)
|
16
|
+
builder (~> 3.0.0)
|
17
|
+
activerecord (3.2.2)
|
18
|
+
activemodel (= 3.2.2)
|
19
|
+
activesupport (= 3.2.2)
|
20
|
+
arel (~> 3.0.2)
|
21
|
+
tzinfo (~> 0.3.29)
|
22
|
+
activerecord-import (0.2.9)
|
23
|
+
activerecord (~> 3.0)
|
24
|
+
activerecord (~> 3.0)
|
25
|
+
activesupport (3.2.2)
|
26
|
+
i18n (~> 0.6)
|
27
|
+
multi_json (~> 1.0)
|
28
|
+
arel (3.0.2)
|
29
|
+
awesome_print (1.0.2)
|
30
|
+
builder (3.0.0)
|
31
|
+
gli (1.5.1)
|
32
|
+
i18n (0.6.0)
|
33
|
+
mogilefs-client (3.1.1)
|
34
|
+
multi_json (1.2.0)
|
35
|
+
mysql2 (0.3.11)
|
36
|
+
sqlite3 (1.3.5)
|
37
|
+
tzinfo (0.3.32)
|
13
38
|
|
14
39
|
PLATFORMS
|
15
40
|
ruby
|
16
41
|
|
17
42
|
DEPENDENCIES
|
43
|
+
awesome_print
|
18
44
|
mogbak!
|
19
|
-
rake
|
20
|
-
rdoc
|
data/bin/mogbak
CHANGED
@@ -26,6 +26,11 @@ require 'sqlite3'
|
|
26
26
|
require 'yaml'
|
27
27
|
require 'forkinator'
|
28
28
|
require 'path_helper'
|
29
|
+
require 'signal_handler'
|
30
|
+
require 'log'
|
31
|
+
|
32
|
+
#activate signal handler
|
33
|
+
SignalHandler.instance
|
29
34
|
|
30
35
|
|
31
36
|
#Set master process name
|
@@ -102,15 +107,25 @@ command :backup do |c|
|
|
102
107
|
c.desc 'do not remove deleted files from the backup (faster)'
|
103
108
|
c.switch ['no-delete']
|
104
109
|
|
110
|
+
c.desc 'after backup is complete, start another backup. This causes mogbak to run non-stop. Send SIGINT or SIGTERM to exit cleanly'
|
111
|
+
c.switch ['non-stop']
|
112
|
+
|
105
113
|
c.desc 'Number of worker processes'
|
106
114
|
c.default_value 1
|
107
115
|
c.flag :workers
|
108
116
|
|
117
|
+
c.desc 'send backup output to log file'
|
118
|
+
c.flag ['log-file']
|
119
|
+
|
109
120
|
c.action do |global_options, options, args|
|
110
121
|
raise '[backup_path] is required - see: mogbak help backup' unless args[0]
|
111
122
|
$backup_path = args[0]
|
123
|
+
|
124
|
+
#setup a logger for output
|
125
|
+
Log.instance(options[:'log-file'])
|
126
|
+
|
112
127
|
mog = Backup.new(:backup_path => args[0], :workers => options[:workers].to_i)
|
113
|
-
mog.backup(:no_delete => options[:"no-delete"])
|
128
|
+
mog.backup(:no_delete => options[:"no-delete"], :non_stop => options[:"non-stop"])
|
114
129
|
end
|
115
130
|
|
116
131
|
end
|
@@ -142,11 +157,17 @@ command :restore do |c|
|
|
142
157
|
c.desc 'restore a single file by dkey'
|
143
158
|
c.flag :"single-file"
|
144
159
|
|
160
|
+
c.desc 'send restore output to log file'
|
161
|
+
c.flag ['log-file']
|
162
|
+
|
145
163
|
c.action do |global_options, options, args|
|
146
164
|
raise 'domain parameter is required - see: mogbak help restore' unless options[:domain]
|
147
165
|
raise '[backup_path] is required - see: mogbak help restore' unless args[0]
|
148
166
|
$backup_path = args[0]
|
149
167
|
|
168
|
+
#setup a logger for output
|
169
|
+
Log.instance(options[:'log-file'])
|
170
|
+
|
150
171
|
restore = Restore.new(:tracker_ip => options[:trackerip],
|
151
172
|
:tracker_port => options[:trackerport],
|
152
173
|
:domain => options[:domain],
|
@@ -162,18 +183,28 @@ Output is: fid,dkey,length,classname
|
|
162
183
|
EOS
|
163
184
|
arg_name '[backup_path]'
|
164
185
|
command :list do |c|
|
186
|
+
|
187
|
+
c.desc 'send list output to log file'
|
188
|
+
c.flag ['log-file']
|
189
|
+
|
165
190
|
c.action do |global_options, options, args|
|
166
191
|
raise '[backup_path] is required - see: mogbak help list' unless args[0]
|
167
192
|
$backup_path = args[0]
|
168
193
|
|
194
|
+
#setup a logger for output
|
195
|
+
Log.instance(options[:'log-file'])
|
196
|
+
|
169
197
|
list = List.new(:backup_path => args[0])
|
170
198
|
list.list
|
171
199
|
end
|
172
200
|
end
|
173
201
|
|
174
|
-
#
|
202
|
+
#Handle --debug
|
175
203
|
pre do |global_options, command, options, args|
|
204
|
+
|
205
|
+
#This is used to enable some more verbose output
|
176
206
|
$debug = global_options[:debug]
|
207
|
+
|
177
208
|
true
|
178
209
|
end
|
179
210
|
|
data/lib/backup.rb
CHANGED
@@ -22,7 +22,7 @@ class Backup
|
|
22
22
|
|
23
23
|
|
24
24
|
#run validations and setup
|
25
|
-
raise
|
25
|
+
raise unless check_backup_path
|
26
26
|
create_sqlite_db
|
27
27
|
connect_sqlite
|
28
28
|
migrate_sqlite
|
@@ -43,9 +43,9 @@ class Backup
|
|
43
43
|
def bak_file(file)
|
44
44
|
saved = file.bak_it
|
45
45
|
if saved
|
46
|
-
|
46
|
+
Log.instance.info("Backed up: FID #{file.fid}")
|
47
47
|
else
|
48
|
-
|
48
|
+
Log.instance.info("Error - will try again on next run: FID #{file.fid}")
|
49
49
|
end
|
50
50
|
|
51
51
|
return saved
|
@@ -72,11 +72,13 @@ class Backup
|
|
72
72
|
SqliteActiveRecord.clear_active_connections!
|
73
73
|
}
|
74
74
|
|
75
|
-
#This proc receives an array of BakFiles, proccesses them, and returns a result array to the parent proc
|
75
|
+
#This proc receives an array of BakFiles, proccesses them, and returns a result array to the parent proc. We will break
|
76
|
+
#from the files if the signal handler says so.
|
76
77
|
child = Proc.new { |files|
|
77
78
|
result = []
|
78
79
|
files.each do |file|
|
79
80
|
break if file.nil?
|
81
|
+
break if SignalHandler.instance.should_quit
|
80
82
|
saved = bak_file(file)
|
81
83
|
result << {:saved => saved, :file => file}
|
82
84
|
end
|
@@ -91,16 +93,18 @@ class Backup
|
|
91
93
|
#param [Array] files must be an array of BakFiles that need to be deleted
|
92
94
|
def launch_delete_workers(fids)
|
93
95
|
|
94
|
-
#This proc receives an array of BakFiles, handles them, and spits them back to the parent
|
96
|
+
#This proc receives an array of BakFiles, handles them, and spits them back to the parent, break from the fids if
|
97
|
+
#the signal handler says so.
|
95
98
|
child = Proc.new { |fids|
|
96
99
|
result = []
|
97
100
|
fids.each do |fid|
|
98
101
|
break if fid.nil?
|
102
|
+
break if SignalHandler.instance.should_quit
|
99
103
|
deleted = BakFile.delete_from_fs(fid)
|
100
104
|
if deleted
|
101
|
-
|
105
|
+
Log.instance.info("Deleting from backup: FID #{fid}")
|
102
106
|
else
|
103
|
-
|
107
|
+
Log.instance.info("Failed to delete from backup: FID #{fid}")
|
104
108
|
end
|
105
109
|
|
106
110
|
result << fid
|
@@ -133,64 +137,75 @@ class Backup
|
|
133
137
|
#@param [Hash] o if :no_delete then don't remove deleted files from the backup (intensive process)
|
134
138
|
def backup(o = {})
|
135
139
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
files
|
140
|
-
|
140
|
+
#Loop over the main backup logic. We'll break out at the end unless o[:non_stop] is set
|
141
|
+
loop do
|
142
|
+
files = []
|
143
|
+
#first we retry files that we haven't been able to backup successfully, if any.
|
144
|
+
BakFile.find_each(:conditions => ['saved = ?', false]) do |bak_file|
|
145
|
+
files << bak_file
|
146
|
+
end
|
141
147
|
|
142
|
-
|
148
|
+
launch_backup_workers(files)
|
143
149
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
150
|
+
#now back up any new files. if they fail to be backed up we'll retry them the next time the backup
|
151
|
+
#command is ran.
|
152
|
+
dmid = Domain.find_by_namespace(self.domain)
|
153
|
+
results = Fid.find_in_batches(:conditions => ['dmid = ? AND fid > ?', dmid, BakFile.max_fid], :batch_size => 500 * self.workers.to_i, :include => [:domain, :fileclass]) do |batch|
|
154
|
+
|
155
|
+
#Insert all the files into our bak db with :saved false so that we don't think we backed up something that crashed
|
156
|
+
files = []
|
157
|
+
batch.each do |file|
|
158
|
+
files << BakFile.new(:fid => file.fid,
|
159
|
+
:domain => file.domain.namespace,
|
160
|
+
:dkey => file.dkey,
|
161
|
+
:length => file.length,
|
162
|
+
:classname => file.classname,
|
163
|
+
:saved => false)
|
164
|
+
end
|
148
165
|
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
:dkey => file.dkey,
|
155
|
-
:length => file.length,
|
156
|
-
:classname => file.classname,
|
157
|
-
:saved => false)
|
158
|
-
end
|
166
|
+
#There is no way to do a bulk insert in sqlite so this generates a lot of inserts. wrapping all of the inserts
|
167
|
+
#inside a single transaction makes it much much faster.
|
168
|
+
BakFile.transaction do
|
169
|
+
BakFile.import files, :validate => false
|
170
|
+
end
|
159
171
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
172
|
+
#Fire up the workers now that we have work for them to do
|
173
|
+
launch_backup_workers(files)
|
174
|
+
|
175
|
+
#Terminate program if the signal handler says so and this is a clean place to do it
|
176
|
+
return true if SignalHandler.instance.should_quit
|
164
177
|
end
|
165
178
|
|
166
|
-
#
|
167
|
-
|
179
|
+
#Delete files from the backup that no longer exist in the mogilefs domain. Unfortunently there is no easy way to detect
|
180
|
+
#which files have been deleted from the MogileFS domain. Our only option is to brute force our way through. This is a bulk
|
181
|
+
#query that checks a thousand files in each query against the MogileFS database server. The query is kind of tricky because
|
182
|
+
#I wanted to do this with nothing but SELECT privileges which meant I couldn't create a temporary table (which would require,
|
183
|
+
#create temporary table and insert privleges). You might want to only run this operation every once and awhile if you have a
|
184
|
+
#very large domain. In my testing, it is able to get through domains with millions of files in a matter of a second. So
|
185
|
+
#all in all it's not so bad
|
186
|
+
if !o[:no_delete]
|
168
187
|
|
169
|
-
|
188
|
+
BakFile.find_in_batches { |bak_files|
|
189
|
+
union = "SELECT #{bak_files.first.fid} as fid"
|
190
|
+
bak_files.shift
|
191
|
+
bak_files.each do |bakfile|
|
192
|
+
union = "#{union} UNION SELECT #{bakfile.fid}"
|
193
|
+
end
|
194
|
+
connection = ActiveRecord::Base.connection
|
195
|
+
files = connection.select_values("SELECT t1.fid FROM (#{union}) as t1 LEFT JOIN file on t1.fid = file.fid WHERE file.fid IS NULL")
|
196
|
+
launch_delete_workers(files)
|
170
197
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
#I wanted to do this with nothing but SELECT privileges which meant I couldn't create a temporary table (which would require,
|
175
|
-
#create temporary table and insert privleges). You might want to only run this operation every once and awhile if you have a
|
176
|
-
#very large domain. In my testing, it is able to get through domains with millions of files in a matter of a second. So
|
177
|
-
#all in all it's not so bad
|
178
|
-
if !o[:no_delete]
|
179
|
-
files_to_delete = Array.new
|
180
|
-
BakFile.find_in_batches { |bak_files|
|
181
|
-
|
182
|
-
union = "SELECT #{bak_files.first.fid} as fid"
|
183
|
-
bak_files.shift
|
184
|
-
bak_files.each do |bakfile|
|
185
|
-
union = "#{union} UNION SELECT #{bakfile.fid}"
|
186
|
-
end
|
187
|
-
connection = ActiveRecord::Base.connection
|
188
|
-
files = connection.select_values("SELECT t1.fid FROM (#{union}) as t1 LEFT JOIN file on t1.fid = file.fid WHERE file.fid IS NULL")
|
189
|
-
files_to_delete += files
|
190
|
-
}
|
198
|
+
#Terminate program if the signal handler says so and this is a clean place to do it
|
199
|
+
return true if SignalHandler.instance.should_quit
|
200
|
+
}
|
191
201
|
|
192
|
-
launch_delete_workers(files_to_delete)
|
193
202
|
|
203
|
+
|
204
|
+
end
|
205
|
+
|
206
|
+
#Break out of infinite loop unless o[:non_stop] is set
|
207
|
+
break unless o[:non_stop]
|
208
|
+
sleep 1
|
194
209
|
end
|
195
210
|
|
196
211
|
end
|
data/lib/forkinator.rb
CHANGED
@@ -88,13 +88,6 @@ class Forkinator
|
|
88
88
|
children = []
|
89
89
|
qty.times { children << make_child(child_proc)}
|
90
90
|
|
91
|
-
#register signal handler so that children kill if program receives a SIGINT
|
92
|
-
#which will happen if the user ctrl c's the parent process
|
93
|
-
Signal.trap :SIGINT do
|
94
|
-
children.each { |child| Process.kill(:KILL, child[:pid]) if child[:pid]}
|
95
|
-
exit 1
|
96
|
-
end
|
97
|
-
|
98
91
|
#For each worker
|
99
92
|
qty.times do |i|
|
100
93
|
|
data/lib/list.rb
CHANGED
@@ -22,7 +22,8 @@ class List
|
|
22
22
|
#fid,key,length,class
|
23
23
|
def list
|
24
24
|
files = BakFile.find_each(:conditions => ['saved = ?', true]) do |file|
|
25
|
-
|
25
|
+
Log.instance.info("#{file.fid},#{file.dkey},#{file.length},#{file.classname}")
|
26
|
+
break if SignalHandler.instance.should_quit
|
26
27
|
end
|
27
28
|
end
|
28
29
|
end
|
data/lib/log.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'logger'
|
2
|
+
#Create a Logger that is a singleton provided by the instance method
|
3
|
+
class Log
|
4
|
+
def self.instance(log_file = nil)
|
5
|
+
@@instance ||= create_logger(log_file)
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.create_logger(log_file)
|
9
|
+
log_file = STDOUT if log_file == nil
|
10
|
+
logger = Logger.new(log_file)
|
11
|
+
logger.datetime_format = "%Y-%m-%d %H:%M:%S"
|
12
|
+
logger.formatter = proc do |severity, datetime, progname, msg|
|
13
|
+
"#{datetime}: #{msg}\n"
|
14
|
+
end
|
15
|
+
logger
|
16
|
+
end
|
17
|
+
end
|
data/lib/mogbak_version.rb
CHANGED
data/lib/restore.rb
CHANGED
@@ -28,9 +28,9 @@ class Restore
|
|
28
28
|
|
29
29
|
def output_save(save, fid)
|
30
30
|
if save
|
31
|
-
|
31
|
+
Log.instance.info("Restored: FID #{fid}")
|
32
32
|
else
|
33
|
-
|
33
|
+
Log.instance.info("Error: FID #{fid}")
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
@@ -39,6 +39,7 @@ class Restore
|
|
39
39
|
results = []
|
40
40
|
files.each do |file|
|
41
41
|
break if file.nil?
|
42
|
+
break if SignalHandler.instance.should_quit
|
42
43
|
save = file.restore
|
43
44
|
output_save(save, file.fid)
|
44
45
|
results << {:restored => save, :fid => file.fid}
|
@@ -64,6 +65,7 @@ class Restore
|
|
64
65
|
|
65
66
|
BakFile.find_in_batches(:conditions => ['saved = ?', true], :batch_size => 2000) do |batch|
|
66
67
|
launch_restore_workers(batch)
|
68
|
+
break if SignalHandler.instance.should_quit
|
67
69
|
end
|
68
70
|
|
69
71
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
|
3
|
+
#Singleton class used to intercept signals. If a SIGINT or SIGTERM is received a message is outputted and @should_quit
|
4
|
+
#is set to true
|
5
|
+
class SignalHandler
|
6
|
+
include Singleton
|
7
|
+
attr_reader :should_quit
|
8
|
+
|
9
|
+
def handle_signal
|
10
|
+
puts "PID #{Process.pid} is gracefully shutting down..."
|
11
|
+
@should_quit = true
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
@should_quit = false
|
16
|
+
Signal.trap("SIGINT") { handle_signal }
|
17
|
+
Signal.trap("SIGTERM") { handle_signal }
|
18
|
+
end
|
19
|
+
end
|
data/lib/validations.rb
CHANGED
@@ -45,7 +45,7 @@ module Validations
|
|
45
45
|
#@return [Bool]
|
46
46
|
def connect_sqlite(raise_msg = nil)
|
47
47
|
begin
|
48
|
-
ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => "#{$backup_path}/db.sqlite", :timeout =>
|
48
|
+
ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => "#{$backup_path}/db.sqlite", :timeout => 10000)
|
49
49
|
rescue Exception => e
|
50
50
|
raise raise_msg if raise_msg
|
51
51
|
raise e if $debug
|
data/mogbak.gemspec
CHANGED
@@ -19,9 +19,6 @@ spec = Gem::Specification.new do |s|
|
|
19
19
|
s.add_runtime_dependency('gli', '>= 1.5.1')
|
20
20
|
s.add_runtime_dependency('mysql2', '>= 0.3.11')
|
21
21
|
s.add_runtime_dependency('mogilefs-client','>= 3.1.1')
|
22
|
-
s.add_runtime_dependency('json','>= 1.6.5')
|
23
22
|
s.add_runtime_dependency('sqlite3','>=1.3.5')
|
24
23
|
s.add_runtime_dependency('activerecord-import','>=0.2.9')
|
25
|
-
|
26
|
-
|
27
24
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mogbak
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-03
|
12
|
+
date: 2012-04-03 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: awesome_print
|
@@ -75,22 +75,6 @@ dependencies:
|
|
75
75
|
- - ! '>='
|
76
76
|
- !ruby/object:Gem::Version
|
77
77
|
version: 3.1.1
|
78
|
-
- !ruby/object:Gem::Dependency
|
79
|
-
name: json
|
80
|
-
requirement: !ruby/object:Gem::Requirement
|
81
|
-
none: false
|
82
|
-
requirements:
|
83
|
-
- - ! '>='
|
84
|
-
- !ruby/object:Gem::Version
|
85
|
-
version: 1.6.5
|
86
|
-
type: :runtime
|
87
|
-
prerelease: false
|
88
|
-
version_requirements: !ruby/object:Gem::Requirement
|
89
|
-
none: false
|
90
|
-
requirements:
|
91
|
-
- - ! '>='
|
92
|
-
- !ruby/object:Gem::Version
|
93
|
-
version: 1.6.5
|
94
78
|
- !ruby/object:Gem::Dependency
|
95
79
|
name: sqlite3
|
96
80
|
requirement: !ruby/object:Gem::Requirement
|
@@ -131,6 +115,7 @@ extensions: []
|
|
131
115
|
extra_rdoc_files: []
|
132
116
|
files:
|
133
117
|
- .gitignore
|
118
|
+
- CHANGES
|
134
119
|
- Gemfile
|
135
120
|
- Gemfile.lock
|
136
121
|
- LICENSE
|
@@ -145,10 +130,12 @@ files:
|
|
145
130
|
- lib/fileclass.rb
|
146
131
|
- lib/forkinator.rb
|
147
132
|
- lib/list.rb
|
133
|
+
- lib/log.rb
|
148
134
|
- lib/mogbak_version.rb
|
149
135
|
- lib/monkey_patch.rb
|
150
136
|
- lib/path_helper.rb
|
151
137
|
- lib/restore.rb
|
138
|
+
- lib/signal_handler.rb
|
152
139
|
- lib/validations.rb
|
153
140
|
- mogbak.gemspec
|
154
141
|
homepage: http://www.github.com/firespring/mogbak
|