bostonlogic-safe 0.3.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.
Files changed (41) hide show
  1. data/LICENSE +20 -0
  2. data/README.markdown +227 -0
  3. data/Rakefile +54 -0
  4. data/VERSION.yml +4 -0
  5. data/bin/astrails-safe +53 -0
  6. data/examples/example_helper.rb +19 -0
  7. data/examples/integration/archive_integration_example.rb +86 -0
  8. data/examples/integration/cleanup_example.rb +62 -0
  9. data/examples/unit/archive_example.rb +67 -0
  10. data/examples/unit/config_example.rb +184 -0
  11. data/examples/unit/gpg_example.rb +138 -0
  12. data/examples/unit/gzip_example.rb +64 -0
  13. data/examples/unit/local_example.rb +110 -0
  14. data/examples/unit/mysqldump_example.rb +83 -0
  15. data/examples/unit/pgdump_example.rb +45 -0
  16. data/examples/unit/rcloud_example.rb +110 -0
  17. data/examples/unit/s3_example.rb +112 -0
  18. data/examples/unit/svndump_example.rb +39 -0
  19. data/lib/astrails/safe.rb +71 -0
  20. data/lib/astrails/safe/archive.rb +24 -0
  21. data/lib/astrails/safe/backup.rb +20 -0
  22. data/lib/astrails/safe/config/builder.rb +62 -0
  23. data/lib/astrails/safe/config/node.rb +66 -0
  24. data/lib/astrails/safe/gpg.rb +45 -0
  25. data/lib/astrails/safe/gzip.rb +25 -0
  26. data/lib/astrails/safe/local.rb +48 -0
  27. data/lib/astrails/safe/mysqldump.rb +31 -0
  28. data/lib/astrails/safe/notification.rb +66 -0
  29. data/lib/astrails/safe/pgdump.rb +36 -0
  30. data/lib/astrails/safe/pipe.rb +13 -0
  31. data/lib/astrails/safe/rcloud.rb +73 -0
  32. data/lib/astrails/safe/s3.rb +68 -0
  33. data/lib/astrails/safe/sftp.rb +79 -0
  34. data/lib/astrails/safe/sink.rb +33 -0
  35. data/lib/astrails/safe/source.rb +46 -0
  36. data/lib/astrails/safe/stream.rb +19 -0
  37. data/lib/astrails/safe/svndump.rb +13 -0
  38. data/lib/astrails/safe/tmp_file.rb +48 -0
  39. data/lib/extensions/mktmpdir.rb +45 -0
  40. data/templates/script.rb +155 -0
  41. metadata +135 -0
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Astrails Ltd.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,227 @@
1
+ astrails-safe
2
+ =============
3
+
4
+ Simple database and filesystem backups with S3 and Rackspace Cloudfiles support (with optional encryption)
5
+
6
+ Home: github.com/astrails/safe
7
+
8
+ Motivation
9
+ ----------
10
+
11
+ We needed a backup solution that will satisfy the following requirements:
12
+
13
+ * opensource
14
+ * simple to install and configure
15
+ * support for simple ‘tar’ backups of directories (with includes/excludes)
16
+ * support for simple mysqldump of mysql databases
17
+ * support for symmetric or public key encryption
18
+ * support for local filesystem and Amazon S3 for storage and Rackspace Cloudfile storage
19
+ * support for backup rotation. we don’t want backups filling all the diskspace or cost a fortune on S3
20
+ * email notification on backup failure with error and backtrace
21
+
22
+ And since we didn't find any, we wrote our own :)
23
+
24
+ Contributions
25
+ -------------
26
+
27
+ The following functionality was contributed by astrails-safe users:
28
+
29
+ * PostgreSQL dump using pg_dump (by Mark Mansour <mark@stateofflux.com>)
30
+ * Subversion dump using svndump (by Richard Luther <richard.luther@gmail.com>)
31
+ * SFTP remote storage (by Adam <adam@mediadrive.ca>)
32
+ * benchmarking output (By Neer)
33
+
34
+
35
+ Thanks to all :)
36
+
37
+ Reporting problems
38
+ ------------------
39
+
40
+ Please report problems at the [Issues tracker](http://github.com/astrails/safe/issues)
41
+
42
+ Usage
43
+ -----
44
+
45
+ Usage:
46
+ astrails-safe [OPTIONS] CONFIG_FILE
47
+ Options:
48
+ -h, --help This help screen
49
+ -v, --verbose be verbose, duh!
50
+ -n, --dry-run just pretend, don't do anything.
51
+ -L, --local skip S3
52
+
53
+ Note: CONFIG_FILE will be created from template if missing
54
+
55
+ Encryption
56
+ ----------
57
+
58
+ If you want to encrypt your backups you have 2 options:
59
+ * use simple password encryption
60
+ * use GPG public key encryption
61
+
62
+ For simple password, just add password entry in gpg section.
63
+ For public key encryption you will need to create a public/secret keypair.
64
+
65
+ We recommend to create your GPG keys only on your local machine and then
66
+ transfer your public key to the server that will do the backups.
67
+
68
+ This way the server will only know how to encrypt the backups but only you
69
+ will be able to decrypt them using the secret key you have locally. Of course
70
+ you MUST backup your backup encryption key :)
71
+ We recommend also pringing the hard paper copy of your GPG key 'just in case'.
72
+
73
+ The procedure to create and transfer the key is as follows:
74
+
75
+ 1. run 'gpg --gen-key' on your local machine and follow onscreen instructions to create the key
76
+ (you can accept all the defaults).
77
+
78
+ 2. extract your public key into a file (assuming you used test@example.com as your key email):
79
+ gpg -a --export test@example.com > test@example.com.pub
80
+
81
+ 3. transfer public key to the server
82
+ scp test@example.com.pub root@example.com:
83
+
84
+ 4. import public key on the remote system:
85
+ <pre>
86
+ $ gpg --import test@example.com.pub
87
+ gpg: key 45CA9403: public key "Test Backup <test@example.com>" imported
88
+ gpg: Total number processed: 1
89
+ gpg: imported: 1
90
+ </pre>
91
+
92
+ 5. since we don't keep the secret part of the key on the remote server, gpg has
93
+ no way to know its yours and can be trusted.
94
+ To fix that we can sign it with other trusted key, or just directly modify its
95
+ trust level in gpg (use level 5):
96
+ <pre>
97
+ $ gpg --edit-key test@example.com
98
+ ...
99
+ Command> trust
100
+ ...
101
+ 1 = I don't know or won't say
102
+ 2 = I do NOT trust
103
+ 3 = I trust marginally
104
+ 4 = I trust fully
105
+ 5 = I trust ultimately
106
+ m = back to the main menu
107
+
108
+ Your decision? 5
109
+ ...
110
+ Command> quit
111
+ </pre>
112
+
113
+ 6. export your secret key for backup
114
+ (we recommend to print it on paper and burn to a CD/DVD and store in a safe place):
115
+ <pre>
116
+ $ gpg -a --export-secret-key test@example.com > test@example.com.key
117
+ </pre>
118
+
119
+
120
+ Example configuration
121
+ ---------------------
122
+ <pre>
123
+ safe do
124
+ local :path => "/backup/:kind/:id"
125
+
126
+ s3 do
127
+ key "...................."
128
+ secret "........................................"
129
+ bucket "backup.astrails.com"
130
+ path "servers/alpha/:kind/:id"
131
+ end
132
+
133
+ rcloud do
134
+ username "username"
135
+ api_key "key"
136
+ container "backups"
137
+ path ":kind/"
138
+ end
139
+
140
+ sftp do
141
+ host "sftp.astrails.com"
142
+ user "astrails"
143
+ password "ssh password for sftp"
144
+ end
145
+
146
+ gpg do
147
+ # symmetric encryption key
148
+ # password "qwe"
149
+
150
+ # public GPG key (must be known to GPG, i.e. be on the keyring)
151
+ key "backup@astrails.com"
152
+ end
153
+
154
+ keep do
155
+ local 2
156
+ s3 2
157
+ end
158
+
159
+ notification do
160
+ subject "safe backup failure"
161
+ host "mail.example.com"
162
+ domain "example.com"
163
+ username "safe@example.com"
164
+ password "example"
165
+ authentication :login
166
+ port 25
167
+ from "example@example.com"
168
+ recipients "developement_staff@example.com"
169
+ end
170
+
171
+ mysqldump do
172
+ options "-ceKq --single-transaction --create-options"
173
+
174
+ user "root"
175
+ password "............"
176
+ socket "/var/run/mysqld/mysqld.sock"
177
+
178
+ database :blog
179
+ database :servershape
180
+ database :astrails_com
181
+ database :secret_project_com
182
+
183
+ end
184
+
185
+ svndump do
186
+ repo :my_repo do
187
+ repo_path "/home/svn/my_repo"
188
+ end
189
+ end
190
+
191
+ pgdump do
192
+ options "-i -x -O" # -i => ignore version, -x => do not dump privileges (grant/revoke), -O => skip restoration of object ownership in plain text format
193
+
194
+ user "username"
195
+ password "............" # shouldn't be used, instead setup ident. Current functionality exports a password env to the shell which pg_dump uses - untested!
196
+
197
+ database :blog
198
+ database :stateofflux_com
199
+ end
200
+
201
+ tar do
202
+ archive "git-repositories", :files => "/home/git/repositories"
203
+ archive "dot-configs", :files => "/home/*/.[^.]*"
204
+ archive "etc", :files => "/etc", :exclude => "/etc/puppet/other"
205
+
206
+ archive "blog-astrails-com" do
207
+ files "/var/www/blog.astrails.com/"
208
+ exclude ["/var/www/blog.astrails.com/log", "/var/www/blog.astrails.com/tmp"]
209
+ end
210
+
211
+ archive "astrails-com" do
212
+ files "/var/www/astrails.com/"
213
+ exclude ["/var/www/astrails.com/log", "/var/www/astrails.com/tmp"]
214
+ end
215
+ end
216
+ end
217
+ </pre>
218
+
219
+ Reporting problems
220
+ ------------------
221
+
222
+ http://github.com/astrails/safe/issues
223
+
224
+ Copyright
225
+ ---------
226
+
227
+ Copyright (c) 2009 Astrails Ltd. See LICENSE for details.
@@ -0,0 +1,54 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "safe"
8
+ gem.summary = %Q{Backup filesystem and databases (MySQL and PostgreSQL) to Amazon S3 or Rackspace Cloudfiles (with encryption)}
9
+ gem.description = "Simple tool to backup databases (MySQL and PostgreSQL) and filesystem locally or to Amazon S3 or Rackspace Cloudfiles (with optional encryption)"
10
+ gem.email = "we@astrails.com"
11
+ gem.homepage = "http://github.com/astrails/safe"
12
+ gem.authors = ["Astrails Ltd.", "Mark Mansour"]
13
+ gem.files = FileList["[A-Z]*.*", "{bin,examples,generators,lib,rails,spec,test,templates}/**/*", 'Rakefile', 'LICENSE*']
14
+
15
+ gem.add_dependency("aws-s3")
16
+ gem.add_dependency("net-sftp")
17
+ gem.add_dependency("rackspace-cloudfiles")
18
+
19
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
20
+ end
21
+ rescue LoadError
22
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
23
+ end
24
+
25
+ require 'micronaut/rake_task'
26
+ Micronaut::RakeTask.new(:examples) do |examples|
27
+ examples.pattern = 'examples/**/*_example.rb'
28
+ examples.ruby_opts << '-Ilib -Iexamples'
29
+ end
30
+
31
+ Micronaut::RakeTask.new(:rcov) do |examples|
32
+ examples.pattern = 'examples/**/*_example.rb'
33
+ examples.rcov_opts = '-Ilib -Iexamples -iastrails -x\/gems\/'
34
+ examples.rcov = true
35
+ end
36
+
37
+
38
+ task :default => :examples
39
+
40
+ require 'rake/rdoctask'
41
+ Rake::RDocTask.new do |rdoc|
42
+ if File.exist?('VERSION.yml')
43
+ config = YAML.load(File.read('VERSION.yml'))
44
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
45
+ else
46
+ version = ""
47
+ end
48
+
49
+ rdoc.rdoc_dir = 'rdoc'
50
+ rdoc.title = "safe #{version}"
51
+ rdoc.rdoc_files.include('README*')
52
+ rdoc.rdoc_files.include('lib/**/*.rb')
53
+ end
54
+
@@ -0,0 +1,4 @@
1
+ ---
2
+ :minor: 3
3
+ :patch: 0
4
+ :major: 0
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+
5
+ #require 'ruby-debug'
6
+ #$:.unshift File.join(File.dirname(__FILE__), "..", "lib")
7
+
8
+ require File.dirname(__FILE__) + '/../lib/astrails/safe'
9
+ include Astrails::Safe
10
+
11
+ def die(msg)
12
+ puts "ERROR: #{msg}"
13
+ exit 1
14
+ end
15
+
16
+ def usage
17
+ puts <<-END
18
+ Usage: astrails-safe [OPTIONS] CONFIG_FILE
19
+ Options:
20
+ -h, --help This help screen
21
+ -v, --verbose be verbose, duh!
22
+ -n, --dry-run just pretend, don't do anything.
23
+ -L, --local skip S3, skip Rackspace Cloud
24
+
25
+ Note: config file will be created from template if missing
26
+ END
27
+ exit 1
28
+ end
29
+
30
+ def process_options
31
+ usage if ARGV.delete("-h") || ARGV.delete("--help")
32
+ $_VERBOSE = ARGV.delete("-v") || ARGV.delete("--verbose")
33
+ $DRY_RUN = ARGV.delete("-n") || ARGV.delete("--dry-run")
34
+ $LOCAL = ARGV.delete("-L") || ARGV.delete("--local")
35
+ usage unless ARGV.first
36
+ $CONFIG_FILE_NAME = File.expand_path(ARGV.first)
37
+ end
38
+
39
+ def main
40
+ process_options
41
+
42
+ unless File.exists?($CONFIG_FILE_NAME)
43
+ die "Missing configuration file. NOT CREATED! Rerun w/o the -n argument to create a template configuration file." if $DRY_RUN
44
+
45
+ FileUtils.cp File.join(Astrails::Safe::ROOT, "templates", "script.rb"), $CONFIG_FILE_NAME
46
+
47
+ die "Created default #{$CONFIG_FILE_NAME}. Please edit and run again."
48
+ end
49
+
50
+ load($CONFIG_FILE_NAME)
51
+ end
52
+
53
+ main
@@ -0,0 +1,19 @@
1
+ require 'rubygems'
2
+ require 'micronaut'
3
+ require 'ruby-debug'
4
+
5
+ SAFE_ROOT = File.dirname(File.dirname(__FILE__))
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ $LOAD_PATH.unshift(File.join(SAFE_ROOT, 'lib'))
8
+
9
+ require 'astrails/safe'
10
+
11
+ def not_in_editor?
12
+ !(ENV.has_key?('TM_MODE') || ENV.has_key?('EMACS') || ENV.has_key?('VIM'))
13
+ end
14
+
15
+ Micronaut.configure do |c|
16
+ c.color_enabled = not_in_editor?
17
+ c.filter_run :focused => true
18
+ c.mock_with :rr
19
+ end
@@ -0,0 +1,86 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../example_helper')
2
+
3
+ require "fileutils"
4
+ include FileUtils
5
+
6
+ describe "tar backup" do
7
+ before(:all) do
8
+ # need both local and instance vars
9
+ # instance variables are used in tests
10
+ # local variables are used in the backup definition (instance vars can't be seen)
11
+ @root = root = "tmp/archive_backup_example"
12
+
13
+ # clean state
14
+ rm_rf @root
15
+ mkdir_p @root
16
+
17
+ # create source tree
18
+ @src = src = "#{@root}/src"
19
+ mkdir_p "#{@src}/q/w/e"
20
+ mkdir_p "#{@src}/a/s/d"
21
+
22
+ File.open("#{@src}/qwe1", "w") {|f| f.write("qwe") }
23
+ File.open("#{@src}/q/qwe2", "w") {|f| f.write("qwe"*2) }
24
+ File.open("#{@src}/q/w/qwe3", "w") {|f| f.write("qwe"*3) }
25
+ File.open("#{@src}/q/w/e/qwe4", "w") {|f| f.write("qwe"*4) }
26
+
27
+ File.open("#{@src}/asd1", "w") {|f| f.write("asd") }
28
+ File.open("#{@src}/a/asd2", "w") {|f| f.write("asd" * 2) }
29
+ File.open("#{@src}/a/s/asd3", "w") {|f| f.write("asd" * 3) }
30
+
31
+ @dst = dst = "#{@root}/backup"
32
+ mkdir_p @dst
33
+
34
+ @now = Time.now
35
+ @timestamp = @now.strftime("%y%m%d-%H%M")
36
+
37
+ stub(Time).now {@now} # Freeze
38
+
39
+ Astrails::Safe.safe do
40
+ local :path => "#{dst}/:kind"
41
+ tar do
42
+ archive :test1 do
43
+ files src
44
+ exclude "#{src}/q/w"
45
+ end
46
+ end
47
+ end
48
+
49
+ @backup = "#{dst}/archive/archive-test1.#{@timestamp}.tar.gz"
50
+ end
51
+
52
+ it "should create backup file" do
53
+ puts "Expecting: #{@backup}"
54
+ File.exists?(@backup).should be_true
55
+ end
56
+
57
+ describe "after extracting" do
58
+ before(:all) do
59
+ # prepare target dir
60
+ @target = "#{@root}/test"
61
+ mkdir_p @target
62
+ system "tar -zxvf #{@backup} -C #{@target}"
63
+
64
+ @test = "#{@target}/#{@root}/src"
65
+ puts @test
66
+ end
67
+
68
+ it "should include asd1/2/3" do
69
+ File.exists?("#{@test}/asd1").should be_true
70
+ File.exists?("#{@test}/a/asd2").should be_true
71
+ File.exists?("#{@test}/a/s/asd3").should be_true
72
+ end
73
+
74
+ it "should only include qwe 1 and 2 (no 3)" do
75
+ File.exists?("#{@test}/qwe1").should be_true
76
+ File.exists?("#{@test}/q/qwe2").should be_true
77
+ end
78
+
79
+ it "should preserve file content" do
80
+ File.read("#{@test}/qwe1").should == "qwe"
81
+ File.read("#{@test}/q/qwe2").should == "qweqwe"
82
+ File.read("#{@test}/a/s/asd3").should == "asdasdasd"
83
+ end
84
+ end
85
+
86
+ end