backup 3.0.16 → 3.0.18

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. data/.travis.yml +10 -0
  2. data/Gemfile.lock +50 -47
  3. data/Guardfile +3 -3
  4. data/README.md +136 -81
  5. data/backup.gemspec +3 -2
  6. data/bin/backup +36 -15
  7. data/lib/backup.rb +30 -20
  8. data/lib/backup/cli.rb +30 -2
  9. data/lib/backup/compressor/lzma.rb +63 -0
  10. data/lib/backup/configuration/compressor/lzma.rb +23 -0
  11. data/lib/backup/configuration/helpers.rb +10 -4
  12. data/lib/backup/configuration/notifier/mail.rb +5 -0
  13. data/lib/backup/configuration/storage/dropbox.rb +19 -4
  14. data/lib/backup/configuration/storage/ftp.rb +4 -0
  15. data/lib/backup/configuration/storage/local.rb +17 -0
  16. data/lib/backup/configuration/storage/ninefold.rb +20 -0
  17. data/lib/backup/configuration/storage/rsync.rb +4 -0
  18. data/lib/backup/database/postgresql.rb +12 -3
  19. data/lib/backup/database/redis.rb +5 -1
  20. data/lib/backup/dependency.rb +11 -12
  21. data/lib/backup/encryptor/gpg.rb +2 -0
  22. data/lib/backup/exception/command_failed.rb +8 -0
  23. data/lib/backup/finder.rb +49 -9
  24. data/lib/backup/notifier/mail.rb +7 -1
  25. data/lib/backup/notifier/twitter.rb +1 -1
  26. data/lib/backup/storage/dropbox.rb +93 -16
  27. data/lib/backup/storage/ftp.rb +10 -3
  28. data/lib/backup/storage/local.rb +78 -0
  29. data/lib/backup/storage/ninefold.rb +96 -0
  30. data/lib/backup/storage/rsync.rb +37 -20
  31. data/lib/backup/storage/s3.rb +1 -1
  32. data/lib/backup/storage/scp.rb +1 -1
  33. data/lib/backup/syncer/rsync.rb +1 -1
  34. data/lib/backup/version.rb +1 -1
  35. data/lib/templates/compressor/lzma +7 -0
  36. data/lib/templates/storage/dropbox +2 -2
  37. data/lib/templates/storage/ftp +8 -7
  38. data/lib/templates/storage/local +7 -0
  39. data/lib/templates/storage/ninefold +9 -0
  40. data/lib/templates/storage/rsync +1 -0
  41. data/spec/archive_spec.rb +0 -1
  42. data/spec/compressor/bzip2_spec.rb +0 -1
  43. data/spec/compressor/gzip_spec.rb +0 -1
  44. data/spec/compressor/lzma_spec.rb +58 -0
  45. data/spec/configuration/compressor/bzip2_spec.rb +28 -0
  46. data/spec/configuration/compressor/lzma_spec.rb +28 -0
  47. data/spec/configuration/database/mongodb_spec.rb +16 -0
  48. data/spec/configuration/database/mysql_spec.rb +17 -0
  49. data/spec/configuration/database/postgresql_spec.rb +17 -0
  50. data/spec/configuration/database/redis_spec.rb +16 -0
  51. data/spec/configuration/notifier/campfire_spec.rb +11 -0
  52. data/spec/configuration/notifier/mail_spec.rb +20 -0
  53. data/spec/configuration/notifier/presently_spec.rb +34 -0
  54. data/spec/configuration/notifier/twitter_spec.rb +12 -0
  55. data/spec/configuration/storage/dropbox_spec.rb +0 -6
  56. data/spec/configuration/storage/ftp_spec.rb +15 -12
  57. data/spec/configuration/storage/local_spec.rb +28 -0
  58. data/spec/configuration/storage/ninefold_spec.rb +31 -0
  59. data/spec/configuration/storage/rsync_spec.rb +2 -0
  60. data/spec/database/mongodb_spec.rb +0 -1
  61. data/spec/database/mysql_spec.rb +0 -1
  62. data/spec/database/postgresql_spec.rb +31 -11
  63. data/spec/database/redis_spec.rb +9 -4
  64. data/spec/encryptor/gpg_spec.rb +1 -1
  65. data/spec/encryptor/open_ssl_spec.rb +0 -1
  66. data/spec/logger_spec.rb +32 -24
  67. data/spec/model_spec.rb +15 -15
  68. data/spec/spec_helper.rb +8 -4
  69. data/spec/storage/base_spec.rb +0 -4
  70. data/spec/storage/cloudfiles_spec.rb +0 -1
  71. data/spec/storage/dropbox_spec.rb +44 -14
  72. data/spec/storage/ftp_spec.rb +26 -15
  73. data/spec/storage/local_spec.rb +83 -0
  74. data/spec/storage/ninefold_spec.rb +142 -0
  75. data/spec/storage/object_spec.rb +1 -1
  76. data/spec/storage/rsync_spec.rb +17 -7
  77. data/spec/storage/s3_spec.rb +4 -3
  78. data/spec/storage/scp_spec.rb +0 -1
  79. data/spec/storage/sftp_spec.rb +0 -1
  80. data/spec/syncer/rsync_spec.rb +8 -8
  81. metadata +62 -36
@@ -20,6 +20,10 @@ module Backup
20
20
  # Path to store backups to
21
21
  attr_accessor :path
22
22
 
23
+ ##
24
+ # use passive mode?
25
+ attr_accessor :passive_mode
26
+
23
27
  ##
24
28
  # Creates a new instance of the FTP storage object
25
29
  # First it sets the defaults (if any exist) and then evaluates
@@ -27,8 +31,9 @@ module Backup
27
31
  def initialize(&block)
28
32
  load_defaults!
29
33
 
30
- @port ||= 21
31
- @path ||= 'backups'
34
+ @port ||= 21
35
+ @path ||= 'backups'
36
+ @passive_mode ||= false
32
37
 
33
38
  instance_eval(&block) if block_given?
34
39
 
@@ -66,7 +71,9 @@ module Backup
66
71
  Net::FTP.send(:remove_const, :FTP_PORT)
67
72
  end; Net::FTP.send(:const_set, :FTP_PORT, port)
68
73
 
69
- Net::FTP.new(ip, username, password)
74
+ ftp = Net::FTP.new(ip, username, password)
75
+ ftp.passive = true if passive_mode
76
+ ftp
70
77
  end
71
78
 
72
79
  ##
@@ -0,0 +1,78 @@
1
+ # encoding: utf-8
2
+
3
+ ##
4
+ # Load the Ruby FileUtils library
5
+ require 'fileutils'
6
+
7
+ module Backup
8
+ module Storage
9
+ class Local < Base
10
+
11
+ ##
12
+ # Path to store backups to
13
+ attr_accessor :path
14
+
15
+ ##
16
+ # Creates a new instance of the Local storage object
17
+ # First it sets the defaults (if any exist) and then evaluates
18
+ # the configuration block which may overwrite these defaults
19
+ def initialize(&block)
20
+ load_defaults!
21
+
22
+ @path ||= "#{ENV['HOME']}/backups"
23
+
24
+ instance_eval(&block) if block_given?
25
+
26
+ @time = TIME
27
+ fix_path!
28
+ end
29
+
30
+ ##
31
+ # This is the remote path to where the backup files will be stored.
32
+ # Eventhough it says "remote", it's actually the "local" path, but
33
+ # the naming is necessary for compatibility reasons
34
+ def remote_path
35
+ File.join(path, TRIGGER)
36
+ end
37
+
38
+ ##
39
+ # Performs the backup transfer
40
+ def perform!
41
+ transfer!
42
+ cycle!
43
+ end
44
+
45
+ private
46
+
47
+ ##
48
+ # Transfers the archived file to the specified local path
49
+ def transfer!
50
+ Logger.message("#{ self.class } started transferring \"#{ remote_file }\".")
51
+ create_local_directories!
52
+ FileUtils.cp(
53
+ File.join(local_path, local_file),
54
+ File.join(remote_path, remote_file)
55
+ )
56
+ end
57
+
58
+ ##
59
+ # Removes the transferred archive file from the local path
60
+ def remove!
61
+ FileUtils.rm(File.join(remote_path, remote_file))
62
+ end
63
+
64
+ ##
65
+ # Creates the path to where the backups are stored if it doesn't exist yet
66
+ def create_local_directories!
67
+ FileUtils.mkdir_p(remote_path)
68
+ end
69
+
70
+ ##
71
+ # Replaces ~/ with the full path to the users $HOME directory
72
+ def fix_path!
73
+ @path = path.sub(/^\~\//, "#{ENV['HOME']}/")
74
+ end
75
+
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,96 @@
1
+ # encoding: utf-8
2
+
3
+ ##
4
+ # Only load the Fog gem when the Backup::Storage::Ninefold class is loaded
5
+ Backup::Dependency.load('fog')
6
+
7
+ module Backup
8
+ module Storage
9
+ class Ninefold < Base
10
+
11
+ ##
12
+ # Ninefold Credentials
13
+ attr_accessor :storage_token, :storage_secret
14
+
15
+ ##
16
+ # Ninefold directory path
17
+ attr_accessor :path
18
+
19
+ ##
20
+ # Creates a new instance of the Ninefold storage object
21
+ # First it sets the defaults (if any exist) and then evaluates
22
+ # the configuration block which may overwrite these defaults
23
+ #
24
+ def initialize(&block)
25
+ load_defaults!
26
+
27
+ @path ||= 'backups'
28
+
29
+ instance_eval(&block) if block_given?
30
+
31
+ @time = TIME
32
+ end
33
+
34
+ ##
35
+ # This is the remote path to where the backup files will be stored
36
+ def remote_path
37
+ File.join(path, TRIGGER).sub(/^\//, '')
38
+ end
39
+
40
+ ##
41
+ # This is the provider that Fog uses for the Ninefold storage
42
+ def provider
43
+ 'Ninefold'
44
+ end
45
+
46
+ ##
47
+ # Performs the backup transfer
48
+ def perform!
49
+ transfer!
50
+ cycle!
51
+ end
52
+
53
+ private
54
+
55
+ ##
56
+ # Establishes a connection to Amazon S3 and returns the Fog object.
57
+ # Not doing any instance variable caching because this object gets persisted in YAML
58
+ # format to a file and will issues. This, however has no impact on performance since it only
59
+ # gets invoked once per object for a #transfer! and once for a remove! Backups run in the
60
+ # background anyway so even if it were a bit slower it shouldn't matter.
61
+ def connection
62
+ Fog::Storage.new(
63
+ :provider => provider,
64
+ :ninefold_storage_token => storage_token,
65
+ :ninefold_storage_secret => storage_secret
66
+ )
67
+ end
68
+
69
+ ##
70
+ # Transfers the archived file to the specified directory
71
+ def transfer!
72
+ begin
73
+ Logger.message("#{ self.class } started transferring \"#{ remote_file }\".")
74
+ directory = connection.directories.get remote_path
75
+ directory ||= connection.directories.create(:key => remote_path)
76
+ directory.files.create(
77
+ :key => remote_file,
78
+ :body => File.open(File.join(local_path, local_file))
79
+ )
80
+ rescue Excon::Errors::NotFound
81
+ raise "An error occurred while trying to transfer the file."
82
+ end
83
+ end
84
+
85
+ ##
86
+ # Removes the transferred archive file from the Amazon S3 bucket
87
+ def remove!
88
+ begin
89
+ directory = connection.directories.get remote_path
90
+ directory.files.get(remote_file).destroy
91
+ rescue Excon::Errors::SocketError; end
92
+ end
93
+
94
+ end
95
+ end
96
+ end
@@ -25,6 +25,10 @@ module Backup
25
25
  # Path to store backups to
26
26
  attr_accessor :path
27
27
 
28
+ ##
29
+ # Flag to use local backups
30
+ attr_accessor :local
31
+
28
32
  ##
29
33
  # Creates a new instance of the RSync storage object
30
34
  # First it sets the defaults (if any exist) and then evaluates
@@ -32,8 +36,9 @@ module Backup
32
36
  def initialize(&block)
33
37
  load_defaults!
34
38
 
35
- @port ||= 22
36
- @path ||= 'backups'
39
+ @port ||= 22
40
+ @path ||= 'backups'
41
+ @local ||= false
37
42
 
38
43
  instance_eval(&block) if block_given?
39
44
  write_password_file!
@@ -55,6 +60,25 @@ module Backup
55
60
  remove_password_file!
56
61
  end
57
62
 
63
+ ##
64
+ # Returns Rsync syntax for defining a port to connect to
65
+ def port
66
+ "-e 'ssh -p #{@port}'"
67
+ end
68
+
69
+ ##
70
+ # Returns Rsync syntax for using a password file
71
+ def password
72
+ "--password-file='#{@password_file.path}'" unless @password.nil?
73
+ end
74
+
75
+ ##
76
+ # RSync options
77
+ # -z = Compresses the bytes that will be transferred to reduce bandwidth usage
78
+ def options
79
+ "-z"
80
+ end
81
+
58
82
  private
59
83
 
60
84
  ##
@@ -64,7 +88,7 @@ module Backup
64
88
  # gets invoked once per object for a #transfer! and once for a remove! Backups run in the
65
89
  # background anyway so even if it were a bit slower it shouldn't matter.
66
90
  def connection
67
- Net::SSH.start(ip, username, :password => @password, :port => port)
91
+ Net::SSH.start(ip, username, :password => @password, :port => @port)
68
92
  end
69
93
 
70
94
  ##
@@ -72,7 +96,11 @@ module Backup
72
96
  def transfer!
73
97
  Logger.message("#{ self.class } started transferring \"#{ remote_file }\".")
74
98
  create_remote_directories!
75
- run("#{ utility(:rsync) } #{ options } #{ password } '#{ File.join(local_path, local_file) }' '#{ username }@#{ ip }:#{ File.join(remote_path, remote_file[20..-1]) }'")
99
+ if @local
100
+ run("#{ utility(:rsync) } '#{ File.join(local_path, local_file) }' '#{ File.join(remote_path, TIME+'.'+remote_file[20..-1]) }'")
101
+ else
102
+ run("#{ utility(:rsync) } #{ options } #{ port } #{ password } '#{ File.join(local_path, local_file) }' '#{ username }@#{ ip }:#{ File.join(remote_path, remote_file[20..-1]) }'")
103
+ end
76
104
  end
77
105
 
78
106
  ##
@@ -88,22 +116,11 @@ module Backup
88
116
  # Creates (if they don't exist yet) all the directories on the remote
89
117
  # server in order to upload the backup file.
90
118
  def create_remote_directories!
91
- connection.exec!("mkdir -p '#{ remote_path }'")
92
- end
93
-
94
- ##
95
- # RSync options
96
- # -z = Compresses the bytes that will be transferred to reduce bandwidth usage
97
- # --port = the port to connect to through SSH
98
- # -Phv = debug options
99
- def options
100
- "-z --port='#{ port }'"
101
- end
102
-
103
- ##
104
- # Returns Rsync syntax for using a password file
105
- def password
106
- "--password-file='#{@password_file.path}'" unless @password.nil?
119
+ if @local
120
+ mkdir(remote_path)
121
+ else
122
+ connection.exec!("mkdir -p '#{ remote_path }'")
123
+ end
107
124
  end
108
125
 
109
126
  ##
@@ -77,7 +77,7 @@ module Backup
77
77
  # Transfers the archived file to the specified Amazon S3 bucket
78
78
  def transfer!
79
79
  begin
80
- Logger.message("#{ self.class } started transferring \"#{ remote_file }\".")
80
+ Logger.message("#{ self.class } started transferring \"#{ remote_file }\" to bucket \"#{ bucket }\"")
81
81
  connection.sync_clock
82
82
  connection.put_object(
83
83
  bucket,
@@ -55,7 +55,7 @@ module Backup
55
55
  private
56
56
 
57
57
  ##
58
- # Establishes a connection to the remote server and returns the Net::SCP object.
58
+ # Establishes a connection to the remote server and returns the Net::SSH object.
59
59
  # Not doing any instance variable caching because this object gets persisted in YAML
60
60
  # format to a file and will issues. This, however has no impact on performance since it only
61
61
  # gets invoked once per object for a #transfer! and once for a remove! Backups run in the
@@ -100,7 +100,7 @@ module Backup
100
100
  ##
101
101
  # Returns Rsync syntax for defining a port to connect to
102
102
  def port
103
- "--port='#{@port}'"
103
+ "-e 'ssh -p #{@port}'"
104
104
  end
105
105
 
106
106
  ##
@@ -13,7 +13,7 @@ module Backup
13
13
  # Defines the minor version
14
14
  # PATCH:
15
15
  # Defines the patch version
16
- MAJOR, MINOR, PATCH = 3, 0, 16
16
+ MAJOR, MINOR, PATCH = 3, 0, 18
17
17
 
18
18
  ##
19
19
  # Returns the major version ( big release based off of multiple minor releases )
@@ -0,0 +1,7 @@
1
+ ##
2
+ # Lzma [Compressor]
3
+ #
4
+ compress_with Lzma do |compression|
5
+ compression.best = true
6
+ compression.fast = false
7
+ end
@@ -1,9 +1,9 @@
1
1
  ##
2
2
  # Dropbox File Hosting Service [Storage]
3
+ # Note: Initial backup must be performed manually to authorize
4
+ # this machine with your Dropbox account.
3
5
  #
4
6
  store_with Dropbox do |db|
5
- db.email = 'my@email.com'
6
- db.password = 'my_password'
7
7
  db.api_key = 'my_api_key'
8
8
  db.api_secret = 'my_api_secret'
9
9
  db.timeout = 300
@@ -2,10 +2,11 @@
2
2
  # FTP (File Transfer Protocol) [Storage]
3
3
  #
4
4
  store_with FTP do |server|
5
- server.username = 'my_username'
6
- server.password = 'my_password'
7
- server.ip = '123.45.678.90'
8
- server.port = 21
9
- server.path = '~/backups/'
10
- server.keep = 5
11
- end
5
+ server.username = 'my_username'
6
+ server.password = 'my_password'
7
+ server.ip = '123.45.678.90'
8
+ server.port = 21
9
+ server.path = '~/backups/'
10
+ server.keep = 5
11
+ server.passive_mode = false
12
+ end
@@ -0,0 +1,7 @@
1
+ ##
2
+ # Local (Copy) [Storage]
3
+ #
4
+ store_with Local do |local|
5
+ local.path = '~/backups/'
6
+ local.keep = 5
7
+ end
@@ -0,0 +1,9 @@
1
+ ##
2
+ # Ninefold Cloud Storage [Storage]
3
+ #
4
+ store_with Ninefold do |nf|
5
+ nf.storage_token = 'my_storage_token'
6
+ nf.storage_secret = 'my_storage_secret'
7
+ nf.path = '/path/to/my/backups'
8
+ nf.keep = 10
9
+ end
@@ -7,4 +7,5 @@
7
7
  server.ip = '123.45.678.90'
8
8
  server.port = 22
9
9
  server.path = '~/backups/'
10
+ server.local = false
10
11
  end
@@ -58,7 +58,6 @@ describe Backup::Archive do
58
58
  describe '#perform!' do
59
59
  before do
60
60
  [:mkdir, :run, :utility].each { |method| archive.stubs(method) }
61
- Backup::Logger.stubs(:message)
62
61
  end
63
62
 
64
63
  context 'when both paths were added and paths that should be excluded were added' do
@@ -22,7 +22,6 @@ describe Backup::Compressor::Bzip2 do
22
22
  describe '#perform!' do
23
23
  before do
24
24
  [:run, :utility].each { |method| compressor.stubs(method) }
25
- Backup::Logger.stubs(:message)
26
25
  end
27
26
 
28
27
  it 'should perform the compression' do
@@ -22,7 +22,6 @@ describe Backup::Compressor::Gzip do
22
22
  describe '#perform!' do
23
23
  before do
24
24
  [:run, :utility].each { |method| compressor.stubs(method) }
25
- Backup::Logger.stubs(:message)
26
25
  end
27
26
 
28
27
  it 'should perform the compression' do
@@ -0,0 +1,58 @@
1
+ # encoding: utf-8
2
+
3
+ require File.dirname(__FILE__) + '/../spec_helper'
4
+
5
+ describe Backup::Compressor::Lzma do
6
+ let(:compressor) { Backup::Compressor::Lzma.new }
7
+
8
+ before do
9
+ Backup::Model.extension = 'tar'
10
+ end
11
+
12
+ describe 'the options' do
13
+ it do
14
+ compressor.send(:best).should == []
15
+ end
16
+
17
+ it do
18
+ compressor.send(:fast).should == []
19
+ end
20
+ end
21
+
22
+ describe '#perform!' do
23
+ before do
24
+ [:run, :utility].each { |method| compressor.stubs(method) }
25
+ end
26
+
27
+ it 'should perform the compression' do
28
+ compressor.expects(:utility).with(:lzma).returns(:lzma)
29
+ compressor.expects(:run).with("lzma '#{ File.join(Backup::TMP_PATH, "#{ Backup::TIME }.#{ Backup::TRIGGER }.tar") }'")
30
+ compressor.perform!
31
+ end
32
+
33
+ it 'should perform the compression with the --best and --fast options' do
34
+ compressor = Backup::Compressor::Lzma.new do |c|
35
+ c.best = true
36
+ c.fast = true
37
+ end
38
+
39
+ compressor.stubs(:utility).returns(:lzma)
40
+ compressor.expects(:run).with("lzma --best --fast '#{ File.join(Backup::TMP_PATH, "#{ Backup::TIME }.#{ Backup::TRIGGER }.tar") }'")
41
+ compressor.perform!
42
+ end
43
+
44
+ it 'should set the class variable @extension (Backup::Model.extension) to .lzma' do
45
+ compressor.stubs(:utility).returns(:lzma)
46
+ compressor.expects(:run)
47
+
48
+ Backup::Model.extension.should == 'tar'
49
+ compressor.perform!
50
+ Backup::Model.extension.should == 'tar.lzma'
51
+ end
52
+
53
+ it 'should log' do
54
+ Backup::Logger.expects(:message).with("Backup::Compressor::Lzma started compressing the archive.")
55
+ compressor.perform!
56
+ end
57
+ end
58
+ end