astrails-safe 0.0.4 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/bin/astrails-safe +11 -321
  2. data/safe.gemspec +3 -3
  3. metadata +2 -2
data/bin/astrails-safe CHANGED
@@ -1,12 +1,17 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'tmpdir'
3
+
4
4
  require 'tempfile'
5
5
  require 'rubygems'
6
6
  require 'fileutils'
7
7
  require "aws/s3"
8
8
  require 'yaml'
9
+
9
10
  #require 'ruby-debug'
11
+ #$:.unshift File.join(File.dirname(__FILE__), "..", "lib")
12
+
13
+ require 'astrails/safe'
14
+ include Astrails::Safe
10
15
 
11
16
  def die(msg)
12
17
  puts "ERROR: #{msg}"
@@ -27,343 +32,28 @@ END
27
32
  exit 1
28
33
  end
29
34
 
30
- def now
31
- @now ||= Time.now
32
- end
33
-
34
- def timestamp
35
- @timestamp ||= now.strftime("%y%m%d-%H%M")
36
- end
37
-
38
- # We allow to put configuration keys on any level
39
- class ConfigHash
40
- def initialize(root, *path)
41
- path = [*path] unless path.empty?
42
- # sequence of all the config levels on path given
43
- # if at some point there is no value, @config will contain nil for this element and all the following
44
- @configs = path.inject([root]) { |res, x| res << (res.last && res.last[x]) }.reverse
45
- # if the first element (last before revers) is nil -> the required path does not exist
46
- @configs = [{}] if @configs.first.nil?
47
- end
48
-
49
- def [](key)
50
- conf = @configs.find {|x| x.is_a?(Hash) && x[key]}
51
- conf && conf[key]
52
- end
53
-
54
- def keys
55
- @configs.first.keys
56
- end
57
-
58
- end
59
-
60
- def create_config_file(path)
61
- File.open(path, "w") do |conf|
62
- conf.write <<-CONF
63
- # you can use comments
64
-
65
- # Note: keys defined on a deeper level will override upper level keys
66
- # See :path for example
67
-
68
- # global path
69
- :path: /backup
70
-
71
- ## uncomment to enable uploads to Amazon S3
72
- ## Amazon S3 auth (optional)
73
- # :s3_key: YOUR_S3_KEY
74
- # :s3_secret: YOUR_S3_SECRET
75
- # :s3_bucket: S3_BUCKET
76
-
77
- ## uncomment to enable backup rotation. keep only given number of latest
78
- ## backups. remove the rest
79
- #:keep_local: 50
80
- #:keep_s3: 200
81
-
82
- :mysql:
83
- # local path override for mysql
84
- :path: /backup/mysql
85
- :socket: /var/run/mysqld/mysqld.sock
86
- :mysqldump_options: -ceKq --single-transaction --create-options
87
- :username: MYSQL_USER
88
- :password: MYSQL_PASS
89
-
90
- :databases:
91
-
92
- :production_db:
93
- :skip_tables:
94
- - logged_exceptions
95
- - request_logs
96
- :gpg_password: OPTIONAL_PASSWORD_TO_ENCRYPT
97
-
98
- :tar:
99
- :path: /backup/archives
100
- :archives:
101
- :git_repositories:
102
- :files:
103
- - /home/git/repositories
104
- - /home/mirrors/foo/repositories
105
- :exclude:
106
- - /home/mirrors/foo/repositories/junk
107
- :etc:
108
- :files:
109
- - /etc
110
-
111
- CONF
112
- end
113
- end
114
-
115
35
  def process_options
116
36
  usage if ARGV.delete("-h") || ARGV.delete("--help")
117
- $VERBOSE = ARGV.delete("-v") || ARGV.delete("--verbose")
37
+ $_VERBOSE = ARGV.delete("-v") || ARGV.delete("--verbose")
118
38
  $DRY_RUN = ARGV.delete("-n") || ARGV.delete("--dry-run")
119
39
  $LOCAL = ARGV.delete("-L") || ARGV.delete("--local")
120
40
  usage unless ARGV.first
121
41
  $CONFIG_FILE_NAME = File.expand_path(ARGV.first)
122
42
  end
123
43
 
124
- $KEEP_FILES = []
125
-
126
- def create_temp_file(name)
127
- file = Tempfile.new("mysqldump", $TMPDIR)
128
-
129
- yield file
130
-
131
- file.close
132
- $KEEP_FILES << file # so that it will not get gcollected and removed from filesystem until the end
133
- file.path
134
- end
135
-
136
- def create_mysql_password_file(conf)
137
- create_temp_file("mysqldump") do |file|
138
- username = conf[:username]
139
- password = conf[:password]
140
- socket = conf[:socket]
141
- host = conf[:host]
142
- port = conf[:port]
143
-
144
- file.puts "[mysqldump]"
145
- file.puts "user = #{username}" if username
146
- file.puts "password = #{password}" if password
147
- file.puts "socket = #{socket}" if socket
148
- file.puts "host = #{host}" if host
149
- file.puts "port = #{port}" if port
150
- end
151
- end
152
-
153
- def create_gpg_password_file(pass)
154
- create_temp_file("gpg-pass") { |file| file.write(pass) }
155
- end
156
-
157
-
158
- def mysql_skip_tables(conf, db, cmd)
159
- if skip_tables = conf[:skip_tables]
160
- skip_tables.each do |t|
161
- cmd << "--ignore-table=#{db}.#{t} "
162
- end
163
- end
164
- cmd
165
- end
166
-
167
- def mysqldump_extra_options(conf, cmd)
168
- cmd << conf[:mysqldump_options] << " " if conf[:mysqldump_options]
169
- cmd
170
- end
171
- def mysqldump(conf, db)
172
- cmd = "mysqldump --defaults-extra-file=#{create_mysql_password_file(conf)} "
173
- cmd = mysqldump_extra_options(conf, cmd)
174
- cmd = mysql_skip_tables(conf, db, cmd)
175
- cmd << " #{db} "
176
-
177
- path = conf[:path]
178
- die "missing :path in configuration" unless path
179
- backup_filename = File.join(path, "mysql-#{db}.#{timestamp}.sql")
180
-
181
- [cmd, backup_filename]
182
- end
183
-
184
- def tar_extra_options(conf, cmd)
185
- cmd << conf[:tar_options] << " " if conf[:tar_options]
186
- cmd
187
- end
188
- def tar_exclude_files(conf, cmd)
189
- if exclude = conf[:exclude]
190
- cmd << exclude.map{|x| "--exclude=#{x} "}.join
191
- end
192
- cmd
193
- end
194
-
195
- def tar_files(conf, cmd)
196
- die "missing files for tar" unless conf[:files]
197
- cmd << conf[:files] * " "
198
- end
199
-
200
- def tar_archive(conf, arch)
201
- cmd = "tar -cf - "
202
- cmd = tar_extra_options(conf, cmd)
203
- cmd = tar_exclude_files(conf, cmd)
204
- cmd = tar_files(conf, cmd)
205
-
206
- path = conf[:path]
207
- die "missing :path in configuration" unless path
208
- backup_filename = File.join(path, "archive-#{arch}.#{timestamp}.tar")
209
-
210
- [cmd, backup_filename]
211
- end
212
-
213
- # GPG uses compression too :)
214
- def compress(conf, cmd, backup_filename)
215
-
216
- gpg_pass = conf[:gpg_password]
217
- gpg_key = conf[:gpg_public_key]
218
-
219
- if gpg_key
220
- die "can't sue both password and pubkey" if gpg_pass
221
- cmd << "|gpg -e -r #{gpg_key}"
222
- backup_filename << ".gpg"
223
- elsif gpg_pass
224
- unless $DRY_RUN
225
- cmd << "|gpg -c --passphrase-file #{create_gpg_password_file(gpg_pass)}"
226
- else
227
- cmd << "|gpg -c --passphrase-file TEMP_GENERATED_FILENAME"
228
- end
229
- backup_filename << ".gpg"
230
- else
231
- cmd << "|gzip"
232
- backup_filename << ".gz"
233
- end
234
- [cmd, backup_filename]
235
- end
236
-
237
- def timestamped_path(prefix, filename, date)
238
- File.join(prefix, "%04d" % date.year, "%02d" % date.month, "%02d" % date.day, File.basename(filename))
239
- end
240
-
241
- def cleanup_files(files, limit, &block)
242
- return unless files.size > limit
243
-
244
- to_remove = files[0..(files.size - limit - 1)]
245
- to_remove.each(&block)
246
- end
247
-
248
- def cleanup_local(conf, backup_filename)
249
- return unless keep_local = conf[:keep_local]
250
-
251
- dir = File.dirname(backup_filename)
252
- base = File.basename(backup_filename).split(".").first
253
-
254
- files = Dir[File.join(dir, "#{base}*")].
255
- select{|f| File.file?(f)}.
256
- sort
257
-
258
- cleanup_files(files, keep_local) do |f|
259
- puts "removing local file #{f}" if $DRY_RUN || $VERBOSE
260
- File.unlink(f) unless $DRY_RUN
261
- end
262
- end
263
-
264
- class String
265
- def starts_with?(str)
266
- self[0..(str.length - 1)] == str
267
- end
268
- end
269
-
270
- def cleanup_s3(conf, bucket, prefix, backup_filename)
271
-
272
- return unless keep_s3 = conf[:keep_s3]
273
-
274
- base = File.basename(backup_filename).split(".").first
275
-
276
- puts "listing files in #{bucket}:#{prefix}"
277
- files = AWS::S3::Bucket.objects(bucket, :prefix => prefix, :max_keys => keep_s3 * 2)
278
- puts files.collect(&:key)
279
- files = files.
280
- collect(&:key).
281
- select{|o| File.basename(o).starts_with?(base)}.
282
- sort
283
-
284
- cleanup_files(files, keep_s3) do |f|
285
- puts "removing s3 file #{bucket}:#{f}" if $DRY_RUN || $VERBOSE
286
- AWS::S3::Bucket.find(bucket)[f].delete unless $DRY_RUN
287
- end
288
- end
289
-
290
-
291
- def s3_upload(conf, backup_filename, default_path)
292
- s3_bucket = conf[:s3_bucket]
293
- s3_key = conf[:s3_key]
294
- s3_secret = conf[:s3_secret]
295
- s3_prefix = conf[:s3_path] || default_path
296
- s3_path = timestamped_path(s3_prefix, backup_filename, now)
297
-
298
- return unless s3_bucket && s3_key && s3_secret
299
-
300
- puts "Uploading file #{backup_filename} to #{s3_bucket}/#{s3_path}" if $VERBOSE || $DRY_RUN
301
-
302
- AWS::S3::Base.establish_connection!(:access_key_id => s3_key, :secret_access_key => s3_secret, :use_ssl => true)
303
-
304
- unless $DRY_RUN || $LOCAL
305
- AWS::S3::Bucket.create(s3_bucket)
306
- AWS::S3::S3Object.store(s3_path, open(backup_filename), s3_bucket)
307
- end
308
- puts "...done" if $VERBOSE
309
-
310
- cleanup_s3(conf, s3_bucket, s3_prefix, backup_filename)
311
- end
312
-
313
- def stream_backup(conf, cmd, backup_name, default_path)
314
- # prepare COMPRESS
315
- cmd, backup_filename = compress(conf, cmd, backup_name)
316
-
317
- dir = File.dirname(backup_filename)
318
- FileUtils.mkdir_p(dir) unless File.directory?(dir) || $DRY_RUN
319
-
320
- # EXECUTE
321
- puts "Backup command: #{cmd} > #{backup_filename}" if $DRY_RUN || $VERBOSE
322
- system "#{cmd} > #{backup_filename}" unless $DRY_RUN
323
-
324
- # UPLOAD
325
- s3_upload(conf, backup_filename, default_path)
326
-
327
- # CLEANUP
328
- cleanup_local(conf, backup_filename)
329
- end
330
-
331
- def backup_mysql
332
- ConfigHash.new($CONFIG, :mysql, :databases).keys.each do |db|
333
- puts "Backup database #{db}" if $VERBOSE
334
- conf = ConfigHash.new($CONFIG, :mysql, :databases, db)
335
- cmd, backup_filename = mysqldump(conf, db)
336
- stream_backup(conf, cmd, backup_filename, "mysql/#{db}/")
337
- end
338
- end
339
-
340
- def backup_archives
341
- ConfigHash.new($CONFIG, :tar, :archives).keys.each do |arch|
342
- puts "Backup archive #{arch}" if $VERBOSE
343
- conf = ConfigHash.new($CONFIG, :tar, :archives, arch)
344
-
345
- cmd, backup_filename = tar_archive(conf, arch)
346
- stream_backup(conf, cmd, backup_filename, "archives/#{arch}/")
347
- end
348
- end
349
-
350
44
  def main
351
45
  process_options
352
46
 
353
47
  unless File.exists?($CONFIG_FILE_NAME)
354
48
  die "Missing configuration file. NOT CREATED! Rerun w/o the -n argument to create a template configuration file." if $DRY_RUN
355
- create_config_file($CONFIG_FILE_NAME)
356
- die "Created default #{$CONFIG_FILE_NAME}. Please edit and run again."
357
- end
358
49
 
359
- $CONFIG = YAML.load(File.read($CONFIG_FILE_NAME))
50
+ FileUtils.cp File.join(Astrails::Safe::ROOT, "templates", "script.rb"), $CONFIG_FILE_NAME
360
51
 
361
- # create temp directory
362
- $TMPDIR = Dir.mktmpdir
52
+ die "Created default #{$CONFIG_FILE_NAME}. Please edit and run again."
53
+ end
363
54
 
364
- backup_mysql
365
55
 
366
- backup_archives
56
+ load($CONFIG_FILE_NAME)
367
57
  end
368
58
 
369
59
  main
data/safe.gemspec CHANGED
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "safe"
3
- s.version = "0.0.4"
4
- s.date = "2009-03-03"
3
+ s.version = "0.0.6"
4
+ s.date = "2009-03-15"
5
5
  s.summary = "Astrails Safe"
6
6
  s.email = "we@astrails.com"
7
7
  s.homepage = "http://github.com/astrails/safe"
@@ -11,7 +11,7 @@ Gem::Specification.new do |s|
11
11
  s.files = files = %w(
12
12
  bin/astrails-safe
13
13
  safe.gemspec
14
- )
14
+ ) + Dir['lib/**/*'] + Dir['template/**/*']
15
15
  s.executables = files.grep(/^bin/).map {|x| x.gsub(/^bin\//, "")}
16
16
 
17
17
  s.test_files = []
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: astrails-safe
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Astrails Ltd.
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-03-03 00:00:00 -08:00
12
+ date: 2009-03-15 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency