bostonlogic-safe 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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