astrails-safe 0.0.4 → 0.0.6

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.
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