brbackup 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of brbackup might be problematic. Click here for more details.
- data/MIT-LICENSE +18 -0
- data/README.markdown +20 -0
- data/Rakefile +56 -0
- data/bin/brbackup +5 -0
- data/gemspec.rb +19 -0
- data/lib/brbackup.rb +342 -0
- data/spec/brbackup/brbackup.rb +6 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +18 -0
- metadata +83 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
Copyright (c) 2009 Tung Nguyen
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
4
|
+
this software and associated documentation files (the "Software"), to deal in
|
5
|
+
the Software without restriction, including without limitation the rights to
|
6
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
7
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
8
|
+
subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
11
|
+
copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
15
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
16
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
17
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
18
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/gempackagetask'
|
4
|
+
require 'spec/rake/spectask'
|
5
|
+
require 'gemspec'
|
6
|
+
|
7
|
+
desc "Generate gemspec"
|
8
|
+
task :gemspec do
|
9
|
+
File.open("#{Dir.pwd}/#{GEM_NAME}.gemspec", 'w') do |f|
|
10
|
+
f.write(GEM_SPEC.to_ruby)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
desc "Install gem"
|
15
|
+
task :install do
|
16
|
+
Rake::Task['gem'].invoke
|
17
|
+
`gem install pkg/#{GEM_NAME}*.gem`
|
18
|
+
`rm -Rf pkg`
|
19
|
+
end
|
20
|
+
|
21
|
+
desc "Package gem"
|
22
|
+
Rake::GemPackageTask.new(GEM_SPEC) do |pkg|
|
23
|
+
pkg.gem_spec = GEM_SPEC
|
24
|
+
end
|
25
|
+
|
26
|
+
desc "Run specs"
|
27
|
+
Spec::Rake::SpecTask.new do |t|
|
28
|
+
t.spec_opts = ["--format", "specdoc", "--colour"]
|
29
|
+
t.spec_files = FileList["spec/**/*_spec.rb"]
|
30
|
+
end
|
31
|
+
|
32
|
+
task :default => :spec do
|
33
|
+
end
|
34
|
+
|
35
|
+
# You can delete this after you use it
|
36
|
+
desc "Rename project"
|
37
|
+
task :rename do
|
38
|
+
name = ENV['NAME'] || File.basename(Dir.pwd)
|
39
|
+
class_name = name.split('_').collect{|x| x.capitalize}.join("")
|
40
|
+
begin
|
41
|
+
dir = Dir['**/gem_template*']
|
42
|
+
from = dir.pop
|
43
|
+
if from
|
44
|
+
rb = from.include?('.rb')
|
45
|
+
to = File.dirname(from) + "/#{name}#{'.rb' if rb}"
|
46
|
+
FileUtils.mv(from, to)
|
47
|
+
end
|
48
|
+
end while dir.length > 0
|
49
|
+
Dir["**/*"].each do |path|
|
50
|
+
next if path.include?('Rakefile')
|
51
|
+
if File.file?(path)
|
52
|
+
`sed -i "" 's/gem_template/#{name}/g' #{path}`
|
53
|
+
`sed -i "" 's/GemTemplate/#{class_name}/g' #{path}`
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/bin/brbackup
ADDED
data/gemspec.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
GEM_NAME = 'brbackup'
|
2
|
+
GEM_FILES = FileList['**/*'] - FileList['coverage', 'coverage/**/*', 'pkg', 'pkg/**/*']
|
3
|
+
GEM_SPEC = Gem::Specification.new do |s|
|
4
|
+
# == CONFIGURE ==
|
5
|
+
s.author = "Tung Nguyen"
|
6
|
+
s.email = "tongueroo@gmail.com"
|
7
|
+
s.homepage = "http://github.com/tongueroo/#{GEM_NAME}"
|
8
|
+
s.summary = "brbackup summary"
|
9
|
+
# == CONFIGURE ==
|
10
|
+
s.executables = ["brbackup"]
|
11
|
+
s.add_dependency('builder', '>=2.1.2')
|
12
|
+
s.extra_rdoc_files = [ "README.markdown" ]
|
13
|
+
s.files = GEM_FILES.to_a
|
14
|
+
s.has_rdoc = false
|
15
|
+
s.name = GEM_NAME
|
16
|
+
s.platform = Gem::Platform::RUBY
|
17
|
+
s.require_path = "lib"
|
18
|
+
s.version = "0.1.0"
|
19
|
+
end
|
data/lib/brbackup.rb
ADDED
@@ -0,0 +1,342 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'aws/s3'
|
5
|
+
require 'yaml'
|
6
|
+
require 'pp'
|
7
|
+
require 'optparse'
|
8
|
+
require 'open4'
|
9
|
+
require 'fileutils'
|
10
|
+
require 'logger'
|
11
|
+
|
12
|
+
module AWS::S3
|
13
|
+
class S3Object
|
14
|
+
def <=>(other)
|
15
|
+
DateTime.parse(self.about['last-modified']) <=> DateTime.parse(other.about['last-modified'])
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Usage:
|
21
|
+
# Simple:
|
22
|
+
# restores bleacherreport_production from production to staging db
|
23
|
+
# $ brbackup -f prod_br --clone bleacherreport
|
24
|
+
#
|
25
|
+
# More examples:
|
26
|
+
# Specifying more options
|
27
|
+
# $ brbackup -f prod_ss --clone cmservice --logger /tmp/brbackup.log -c .mysql.backups.yml
|
28
|
+
# $ brbackup -f prod_ss --clone cmservice --logger /tmp/brbackup.log -c .mysql.backups.yml
|
29
|
+
#
|
30
|
+
# Notes:
|
31
|
+
# Format:
|
32
|
+
# <bucket_name>/<env>.<db>/<filename>
|
33
|
+
# Example :
|
34
|
+
# ey-backup-fe5a050b997d.s3.amazonaws.com/...
|
35
|
+
# prod_br.bleacherreport_production/bleacherreport_production.2010-03-07T09-10-01.sql.gz
|
36
|
+
#
|
37
|
+
# The --clone option only needs the prefix of the database name because the amazon searches based
|
38
|
+
# on the prefix of <env>.<db>
|
39
|
+
# The filename of the dumpfile is then taken and then gsubed _production -> _staging
|
40
|
+
#
|
41
|
+
module BR
|
42
|
+
class DatabaseEngine
|
43
|
+
def self.register_as(name)
|
44
|
+
BR::Backups::ENGINES[name] = self
|
45
|
+
end
|
46
|
+
|
47
|
+
attr_accessor :logger
|
48
|
+
|
49
|
+
def initialize(backups)
|
50
|
+
@backups = backups
|
51
|
+
end
|
52
|
+
|
53
|
+
def dump_database(name)
|
54
|
+
raise "Implement #dump_database in #{self.class}"
|
55
|
+
end
|
56
|
+
|
57
|
+
def dbuser
|
58
|
+
@backups.config[:dbuser]
|
59
|
+
end
|
60
|
+
|
61
|
+
def dbpass
|
62
|
+
@backups.config[:dbpass]
|
63
|
+
end
|
64
|
+
|
65
|
+
def log(msg)
|
66
|
+
@logger.info(msg)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class Backups
|
71
|
+
ENGINES = {}
|
72
|
+
|
73
|
+
def self.run(args)
|
74
|
+
options = {}
|
75
|
+
|
76
|
+
# Build a parser for the command line arguments
|
77
|
+
opts = OptionParser.new do |opts|
|
78
|
+
opts.version = VERSION
|
79
|
+
|
80
|
+
opts.banner = "Usage: brbackup [-flag] [argument]"
|
81
|
+
opts.define_head "brbackup: clone db backups across environments"
|
82
|
+
opts.separator '*'*80
|
83
|
+
|
84
|
+
opts.on("-f", "--from ENVIRONMENT", "EY Cloud environment name want to clone from : prod_br, beta, alpha, etc") do |env|
|
85
|
+
options[:env] = env
|
86
|
+
end
|
87
|
+
|
88
|
+
opts.on("-l", "--list-backup DATABASE", "List mysql backups for DATABASE") do |db|
|
89
|
+
options[:db] = (db || 'all')
|
90
|
+
options[:command] = :list
|
91
|
+
end
|
92
|
+
|
93
|
+
# opts.on("-n", "--names DB1,DB2,DB3", "Only restore these databases") do |n|
|
94
|
+
# options[:databases] = n.split(',')
|
95
|
+
# end
|
96
|
+
|
97
|
+
opts.on("-c", "--config CONFIG", "Use config file.") do |config|
|
98
|
+
options[:config] = config
|
99
|
+
end
|
100
|
+
|
101
|
+
opts.on("-d", "--download BACKUP_INDEX", "download the backup specified by index. Run brbackup -l to get the index.") do |index|
|
102
|
+
options[:command] = :download
|
103
|
+
options[:index] = index
|
104
|
+
end
|
105
|
+
|
106
|
+
opts.on("--clone DB_NAME", "Clones production database to staging") do |db_name|
|
107
|
+
options[:command] = :clone
|
108
|
+
options[:db_name] = db_name
|
109
|
+
end
|
110
|
+
|
111
|
+
opts.on("--logger /path/to/logger", "Path to log path, default is stdout") do |logger|
|
112
|
+
options[:logger_path] = logger
|
113
|
+
end
|
114
|
+
|
115
|
+
# opts.on("-r", "--restore BACKUP_INDEX", "Download and apply the backup specified by index WARNING! will overwrite the current db with the backup. Run brbackup -l to get the index.") do |index|
|
116
|
+
# options[:command] = :restore
|
117
|
+
# options[:index] = index
|
118
|
+
# end
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
opts.parse!(args)
|
123
|
+
|
124
|
+
options[:engine] ||= 'mysql'
|
125
|
+
options[:config] ||= "/etc/.#{options[:engine]}.backups.yml"
|
126
|
+
|
127
|
+
brb = new(options)
|
128
|
+
|
129
|
+
case options[:command]
|
130
|
+
when :list
|
131
|
+
brb.list options[:db], true
|
132
|
+
when :download
|
133
|
+
brb.download(options[:index])
|
134
|
+
# when :restore
|
135
|
+
# brb.restore(options[:index])
|
136
|
+
when :clone
|
137
|
+
brb.clone(options[:db_name])
|
138
|
+
end
|
139
|
+
rescue SystemExit
|
140
|
+
exit 1
|
141
|
+
rescue Exception => e
|
142
|
+
$stderr.puts "An unknown exception was raised"
|
143
|
+
$stderr.puts e.inspect
|
144
|
+
$stderr.puts e.backtrace
|
145
|
+
$stderr.puts
|
146
|
+
raise
|
147
|
+
end
|
148
|
+
|
149
|
+
def initialize(options)
|
150
|
+
if options[:logger_path]
|
151
|
+
@logger = Logger.new(options[:logger_path])
|
152
|
+
else
|
153
|
+
@logger = Logger.new($stdout)
|
154
|
+
end
|
155
|
+
|
156
|
+
engine_klass = ENGINES[options[:engine]] || raise("Invalid database engine: #{options[:engine].inspect}")
|
157
|
+
@engine = engine_klass.new(self)
|
158
|
+
@engine.logger = @logger
|
159
|
+
|
160
|
+
load_config(options[:config])
|
161
|
+
|
162
|
+
AWS::S3::Base.establish_connection!(
|
163
|
+
:access_key_id => config[:aws_secret_id],
|
164
|
+
:secret_access_key => config[:aws_secret_key]
|
165
|
+
)
|
166
|
+
@databases = options[:databases] || config[:databases]
|
167
|
+
@keep = config[:keep]
|
168
|
+
@bucket = "ey-backup-#{Digest::SHA1.hexdigest(config[:aws_secret_id])[0..11]}"
|
169
|
+
@tmpname = "#{Time.now.strftime("%Y-%m-%dT%H:%M:%S").gsub(/:/, '-')}.sql.gz"
|
170
|
+
@env = options[:env] || config[:env]
|
171
|
+
FileUtils.mkdir_p '/mnt/backups'
|
172
|
+
FileUtils.mkdir_p '/mnt/tmp'
|
173
|
+
begin
|
174
|
+
AWS::S3::Bucket.find(@bucket)
|
175
|
+
rescue AWS::S3::NoSuchBucket
|
176
|
+
AWS::S3::Bucket.create(@bucket)
|
177
|
+
end
|
178
|
+
|
179
|
+
FileUtils.mkdir_p self.backup_dir
|
180
|
+
end
|
181
|
+
attr_reader :config
|
182
|
+
|
183
|
+
def load_config(filename)
|
184
|
+
if File.exist?(filename)
|
185
|
+
@config = YAML::load(File.read(filename))
|
186
|
+
else
|
187
|
+
log "You need to have a backup file at #{filename}"
|
188
|
+
$stderr.puts "You need to have a backup file at #{filename}"
|
189
|
+
exit 1
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def new_backup
|
194
|
+
@databases.each do |db|
|
195
|
+
backup_database(db)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def backup_database(database)
|
200
|
+
File.open("#{self.backup_dir}/#{database}.#{@tmpname}", "w") do |f|
|
201
|
+
log "doing database: #{database}"
|
202
|
+
@engine.dump_database(database, f)
|
203
|
+
end
|
204
|
+
|
205
|
+
File.open("#{self.backup_dir}/#{database}.#{@tmpname}") do |f|
|
206
|
+
path = "#{@env}.#{database}/#{database}.#{@tmpname}"
|
207
|
+
AWS::S3::S3Object.store(path, f, @bucket, :access => :private)
|
208
|
+
log "successful backup: #{database}.#{@tmpname}"
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
def download(index)
|
213
|
+
idx, db = index.split(":")
|
214
|
+
raise Error, "You didn't specify a database name: e.g. 1:rails_production" unless db
|
215
|
+
|
216
|
+
if obj = list(db)[idx.to_i]
|
217
|
+
filename = normalize_name(obj)
|
218
|
+
log "downloading: #{filename}"
|
219
|
+
File.open(filename, 'wb') do |f|
|
220
|
+
print "."
|
221
|
+
obj.value {|chunk| f.write chunk }
|
222
|
+
end
|
223
|
+
log ""
|
224
|
+
log "finished"
|
225
|
+
[db, filename]
|
226
|
+
else
|
227
|
+
raise BackupNotFound, "No backup found for database #{db.inspect}: requested index: #{idx}"
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
def restore(index)
|
232
|
+
db, filename = download(index)
|
233
|
+
File.open(filename) do |f|
|
234
|
+
@engine.restore_database(db, f)
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def clone(db_name)
|
239
|
+
index = most_recent_index(db_name)
|
240
|
+
db, filename = download(index)
|
241
|
+
log "db #{db.inspect}"
|
242
|
+
log "filename #{filename.inspect}"
|
243
|
+
staging_name = filename.split('.')[0].gsub('_production', '_staging')
|
244
|
+
File.open(filename) do |f|
|
245
|
+
@engine.clone_database(staging_name, f)
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
def cleanup
|
250
|
+
begin
|
251
|
+
list('all',false)[0...-(@keep*@databases.size)].each do |o|
|
252
|
+
log "deleting: #{o.key}"
|
253
|
+
o.delete
|
254
|
+
end
|
255
|
+
rescue AWS::S3::S3Exception, AWS::S3::Error
|
256
|
+
nil # see bucket_minder cleanup note regarding S3 consistency
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
def normalize_name(obj)
|
261
|
+
obj.key.gsub(/^.*?\//, '')
|
262
|
+
end
|
263
|
+
|
264
|
+
def find_obj(name)
|
265
|
+
AWS::S3::S3Object.find name, @bucket
|
266
|
+
end
|
267
|
+
|
268
|
+
def list(database='all', printer = false)
|
269
|
+
puts "Listing database backups for #{database}" if printer
|
270
|
+
backups = []
|
271
|
+
if database == 'all'
|
272
|
+
@databases.each do |db|
|
273
|
+
backups << AWS::S3::Bucket.objects(@bucket, :prefix => "#{@env}.#{db}")
|
274
|
+
end
|
275
|
+
backups = backups.flatten.sort
|
276
|
+
else
|
277
|
+
backups = AWS::S3::Bucket.objects(@bucket, :prefix => "#{@env}.#{database}").sort
|
278
|
+
end
|
279
|
+
if printer
|
280
|
+
puts "#{backups.size} backup(s) found"
|
281
|
+
backups.each_with_index do |b,i|
|
282
|
+
puts "#{i}:#{database} #{normalize_name(b)}"
|
283
|
+
end
|
284
|
+
end
|
285
|
+
backups
|
286
|
+
end
|
287
|
+
|
288
|
+
# dirty way to get most recent index
|
289
|
+
def most_recent_index(db)
|
290
|
+
index = list(db).size - 1
|
291
|
+
"#{index}:#{db}"
|
292
|
+
end
|
293
|
+
|
294
|
+
protected
|
295
|
+
def backup_dir
|
296
|
+
"/mnt/tmp"
|
297
|
+
end
|
298
|
+
|
299
|
+
def log(msg)
|
300
|
+
@logger.info(msg)
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
class MysqlDatabase < DatabaseEngine
|
305
|
+
register_as 'mysql'
|
306
|
+
|
307
|
+
def dump_database(name, io)
|
308
|
+
single_transaction = db_has_myisam?(name) ? '' : '--single-transaction'
|
309
|
+
Open4.spawn ["mysqldump -u#{dbuser} #{password_option} #{single_transaction} #{name} | gzip -c"], :stdout => io
|
310
|
+
end
|
311
|
+
|
312
|
+
def db_has_myisam?(name)
|
313
|
+
query = "SELECT 1 FROM information_schema.tables WHERE table_schema='#{name}' AND engine='MyISAM' LIMIT 1;"
|
314
|
+
%x{mysql -u #{dbuser} #{password_option} -N -e"#{query}"}.strip == '1'
|
315
|
+
end
|
316
|
+
|
317
|
+
def restore_database(name, io)
|
318
|
+
log "mock restoring database..."
|
319
|
+
# Open4.spawn ["gzip -dc | mysql -u#{dbuser} #{password_option} #{name}"], :stdin => io
|
320
|
+
end
|
321
|
+
|
322
|
+
def clone_database(staging_name, io)
|
323
|
+
log "dropping #{staging_name} database"
|
324
|
+
cmd = "mysql -u#{dbuser} #{password_option} -e 'drop database #{staging_name}'"
|
325
|
+
Open4.popen4 cmd do |pid, stdin, stdout, stderr|
|
326
|
+
log stdout.read
|
327
|
+
end
|
328
|
+
log "creating #{staging_name} database"
|
329
|
+
Open4.popen4 "mysql -u#{dbuser} #{password_option} -e 'create database #{staging_name}'" do |pid, stdin, stdout, stderr|
|
330
|
+
log stdout.read
|
331
|
+
end
|
332
|
+
log "loading new dump..."
|
333
|
+
Open4.spawn ["gzip -dc | mysql -u#{dbuser} #{password_option} #{staging_name}"], :stdin => io
|
334
|
+
log "new dump loaded."
|
335
|
+
end
|
336
|
+
|
337
|
+
def password_option
|
338
|
+
dbpass.nil? || dbpass.blank? ? "" : "-p'#{dbpass}'"
|
339
|
+
end
|
340
|
+
|
341
|
+
end
|
342
|
+
end
|
data/spec/spec.opts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
$testing = true
|
2
|
+
SPEC = File.dirname(__FILE__)
|
3
|
+
$:.unshift File.expand_path("#{SPEC}/../lib")
|
4
|
+
|
5
|
+
require 'brbackup'
|
6
|
+
require 'pp'
|
7
|
+
require 'rubygems'
|
8
|
+
|
9
|
+
Spec::Runner.configure do |config|
|
10
|
+
end
|
11
|
+
|
12
|
+
# For use with rspec textmate bundle
|
13
|
+
def debug(object)
|
14
|
+
puts "<pre>"
|
15
|
+
puts object.pretty_inspect.gsub('<', '<').gsub('>', '>')
|
16
|
+
puts "</pre>"
|
17
|
+
end
|
18
|
+
|
metadata
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: brbackup
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
version: 0.1.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Tung Nguyen
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-03-21 00:00:00 -07:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: builder
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 2
|
29
|
+
- 1
|
30
|
+
- 2
|
31
|
+
version: 2.1.2
|
32
|
+
type: :runtime
|
33
|
+
version_requirements: *id001
|
34
|
+
description:
|
35
|
+
email: tongueroo@gmail.com
|
36
|
+
executables:
|
37
|
+
- brbackup
|
38
|
+
extensions: []
|
39
|
+
|
40
|
+
extra_rdoc_files:
|
41
|
+
- README.markdown
|
42
|
+
files:
|
43
|
+
- bin/brbackup
|
44
|
+
- gemspec.rb
|
45
|
+
- lib/brbackup.rb
|
46
|
+
- MIT-LICENSE
|
47
|
+
- Rakefile
|
48
|
+
- README.markdown
|
49
|
+
- spec/brbackup/brbackup.rb
|
50
|
+
- spec/spec.opts
|
51
|
+
- spec/spec_helper.rb
|
52
|
+
has_rdoc: true
|
53
|
+
homepage: http://github.com/tongueroo/brbackup
|
54
|
+
licenses: []
|
55
|
+
|
56
|
+
post_install_message:
|
57
|
+
rdoc_options: []
|
58
|
+
|
59
|
+
require_paths:
|
60
|
+
- lib
|
61
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
segments:
|
66
|
+
- 0
|
67
|
+
version: "0"
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
segments:
|
73
|
+
- 0
|
74
|
+
version: "0"
|
75
|
+
requirements: []
|
76
|
+
|
77
|
+
rubyforge_project:
|
78
|
+
rubygems_version: 1.3.6
|
79
|
+
signing_key:
|
80
|
+
specification_version: 3
|
81
|
+
summary: brbackup summary
|
82
|
+
test_files: []
|
83
|
+
|