cm-backup 1.0.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 (133) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +20 -0
  3. data/bin/backup +5 -0
  4. data/lib/backup.rb +144 -0
  5. data/lib/backup/archive.rb +170 -0
  6. data/lib/backup/binder.rb +22 -0
  7. data/lib/backup/cleaner.rb +116 -0
  8. data/lib/backup/cli.rb +374 -0
  9. data/lib/backup/cloud_io/base.rb +41 -0
  10. data/lib/backup/cloud_io/cloud_files.rb +298 -0
  11. data/lib/backup/cloud_io/s3.rb +260 -0
  12. data/lib/backup/compressor/base.rb +35 -0
  13. data/lib/backup/compressor/bzip2.rb +39 -0
  14. data/lib/backup/compressor/custom.rb +53 -0
  15. data/lib/backup/compressor/gzip.rb +74 -0
  16. data/lib/backup/config.rb +119 -0
  17. data/lib/backup/config/dsl.rb +103 -0
  18. data/lib/backup/config/helpers.rb +143 -0
  19. data/lib/backup/database/base.rb +85 -0
  20. data/lib/backup/database/mongodb.rb +187 -0
  21. data/lib/backup/database/mysql.rb +192 -0
  22. data/lib/backup/database/openldap.rb +95 -0
  23. data/lib/backup/database/postgresql.rb +133 -0
  24. data/lib/backup/database/redis.rb +179 -0
  25. data/lib/backup/database/riak.rb +82 -0
  26. data/lib/backup/database/sqlite.rb +57 -0
  27. data/lib/backup/encryptor/base.rb +29 -0
  28. data/lib/backup/encryptor/gpg.rb +747 -0
  29. data/lib/backup/encryptor/open_ssl.rb +77 -0
  30. data/lib/backup/errors.rb +58 -0
  31. data/lib/backup/logger.rb +199 -0
  32. data/lib/backup/logger/console.rb +51 -0
  33. data/lib/backup/logger/fog_adapter.rb +29 -0
  34. data/lib/backup/logger/logfile.rb +133 -0
  35. data/lib/backup/logger/syslog.rb +116 -0
  36. data/lib/backup/model.rb +479 -0
  37. data/lib/backup/notifier/base.rb +128 -0
  38. data/lib/backup/notifier/campfire.rb +63 -0
  39. data/lib/backup/notifier/command.rb +102 -0
  40. data/lib/backup/notifier/datadog.rb +107 -0
  41. data/lib/backup/notifier/flowdock.rb +103 -0
  42. data/lib/backup/notifier/hipchat.rb +118 -0
  43. data/lib/backup/notifier/http_post.rb +117 -0
  44. data/lib/backup/notifier/mail.rb +249 -0
  45. data/lib/backup/notifier/nagios.rb +69 -0
  46. data/lib/backup/notifier/pagerduty.rb +81 -0
  47. data/lib/backup/notifier/prowl.rb +68 -0
  48. data/lib/backup/notifier/pushover.rb +74 -0
  49. data/lib/backup/notifier/ses.rb +105 -0
  50. data/lib/backup/notifier/slack.rb +148 -0
  51. data/lib/backup/notifier/twitter.rb +58 -0
  52. data/lib/backup/notifier/zabbix.rb +63 -0
  53. data/lib/backup/package.rb +55 -0
  54. data/lib/backup/packager.rb +107 -0
  55. data/lib/backup/pipeline.rb +124 -0
  56. data/lib/backup/splitter.rb +76 -0
  57. data/lib/backup/storage/base.rb +69 -0
  58. data/lib/backup/storage/cloud_files.rb +158 -0
  59. data/lib/backup/storage/cycler.rb +75 -0
  60. data/lib/backup/storage/dropbox.rb +212 -0
  61. data/lib/backup/storage/ftp.rb +112 -0
  62. data/lib/backup/storage/local.rb +64 -0
  63. data/lib/backup/storage/qiniu.rb +65 -0
  64. data/lib/backup/storage/rsync.rb +248 -0
  65. data/lib/backup/storage/s3.rb +156 -0
  66. data/lib/backup/storage/scp.rb +67 -0
  67. data/lib/backup/storage/sftp.rb +82 -0
  68. data/lib/backup/syncer/base.rb +70 -0
  69. data/lib/backup/syncer/cloud/base.rb +179 -0
  70. data/lib/backup/syncer/cloud/cloud_files.rb +83 -0
  71. data/lib/backup/syncer/cloud/local_file.rb +100 -0
  72. data/lib/backup/syncer/cloud/s3.rb +110 -0
  73. data/lib/backup/syncer/rsync/base.rb +54 -0
  74. data/lib/backup/syncer/rsync/local.rb +31 -0
  75. data/lib/backup/syncer/rsync/pull.rb +51 -0
  76. data/lib/backup/syncer/rsync/push.rb +205 -0
  77. data/lib/backup/template.rb +46 -0
  78. data/lib/backup/utilities.rb +224 -0
  79. data/lib/backup/version.rb +5 -0
  80. data/templates/cli/archive +28 -0
  81. data/templates/cli/compressor/bzip2 +4 -0
  82. data/templates/cli/compressor/custom +7 -0
  83. data/templates/cli/compressor/gzip +4 -0
  84. data/templates/cli/config +123 -0
  85. data/templates/cli/databases/mongodb +15 -0
  86. data/templates/cli/databases/mysql +18 -0
  87. data/templates/cli/databases/openldap +24 -0
  88. data/templates/cli/databases/postgresql +16 -0
  89. data/templates/cli/databases/redis +16 -0
  90. data/templates/cli/databases/riak +17 -0
  91. data/templates/cli/databases/sqlite +11 -0
  92. data/templates/cli/encryptor/gpg +27 -0
  93. data/templates/cli/encryptor/openssl +9 -0
  94. data/templates/cli/model +26 -0
  95. data/templates/cli/notifier/zabbix +15 -0
  96. data/templates/cli/notifiers/campfire +12 -0
  97. data/templates/cli/notifiers/command +32 -0
  98. data/templates/cli/notifiers/datadog +57 -0
  99. data/templates/cli/notifiers/flowdock +16 -0
  100. data/templates/cli/notifiers/hipchat +16 -0
  101. data/templates/cli/notifiers/http_post +32 -0
  102. data/templates/cli/notifiers/mail +24 -0
  103. data/templates/cli/notifiers/nagios +13 -0
  104. data/templates/cli/notifiers/pagerduty +12 -0
  105. data/templates/cli/notifiers/prowl +11 -0
  106. data/templates/cli/notifiers/pushover +11 -0
  107. data/templates/cli/notifiers/ses +15 -0
  108. data/templates/cli/notifiers/slack +22 -0
  109. data/templates/cli/notifiers/twitter +13 -0
  110. data/templates/cli/splitter +7 -0
  111. data/templates/cli/storages/cloud_files +11 -0
  112. data/templates/cli/storages/dropbox +20 -0
  113. data/templates/cli/storages/ftp +13 -0
  114. data/templates/cli/storages/local +8 -0
  115. data/templates/cli/storages/qiniu +12 -0
  116. data/templates/cli/storages/rsync +17 -0
  117. data/templates/cli/storages/s3 +16 -0
  118. data/templates/cli/storages/scp +15 -0
  119. data/templates/cli/storages/sftp +15 -0
  120. data/templates/cli/syncers/cloud_files +22 -0
  121. data/templates/cli/syncers/rsync_local +20 -0
  122. data/templates/cli/syncers/rsync_pull +28 -0
  123. data/templates/cli/syncers/rsync_push +28 -0
  124. data/templates/cli/syncers/s3 +27 -0
  125. data/templates/general/links +3 -0
  126. data/templates/general/version.erb +2 -0
  127. data/templates/notifier/mail/failure.erb +16 -0
  128. data/templates/notifier/mail/success.erb +16 -0
  129. data/templates/notifier/mail/warning.erb +16 -0
  130. data/templates/storage/dropbox/authorization_url.erb +6 -0
  131. data/templates/storage/dropbox/authorized.erb +4 -0
  132. data/templates/storage/dropbox/cache_file_written.erb +10 -0
  133. metadata +1077 -0
@@ -0,0 +1,83 @@
1
+ # encoding: utf-8
2
+ require 'backup/cloud_io/cloud_files'
3
+
4
+ module Backup
5
+ module Syncer
6
+ module Cloud
7
+ class CloudFiles < Base
8
+ class Error < Backup::Error; end
9
+
10
+ ##
11
+ # Rackspace CloudFiles Credentials
12
+ attr_accessor :username, :api_key
13
+
14
+ ##
15
+ # Rackspace CloudFiles Container
16
+ attr_accessor :container
17
+
18
+ ##
19
+ # Rackspace AuthURL (optional)
20
+ attr_accessor :auth_url
21
+
22
+ ##
23
+ # Rackspace Region (optional)
24
+ attr_accessor :region
25
+
26
+ ##
27
+ # Rackspace Service Net
28
+ # (LAN-based transfers to avoid charges and improve performance)
29
+ attr_accessor :servicenet
30
+
31
+ ##
32
+ # Additional options to pass along to fog.
33
+ # e.g. Fog::Storage.new({ :provider => 'Rackspace' }.merge(fog_options))
34
+ attr_accessor :fog_options
35
+
36
+ def initialize(syncer_id = nil)
37
+ super
38
+
39
+ @servicenet ||= false
40
+
41
+ check_configuration
42
+ end
43
+
44
+ private
45
+
46
+ def cloud_io
47
+ @cloud_io ||= CloudIO::CloudFiles.new(
48
+ :username => username,
49
+ :api_key => api_key,
50
+ :auth_url => auth_url,
51
+ :region => region,
52
+ :servicenet => servicenet,
53
+ :container => container,
54
+ :max_retries => max_retries,
55
+ :retry_waitsec => retry_waitsec,
56
+ # Syncer can not use SLOs.
57
+ :segments_container => nil,
58
+ :segment_size => 0,
59
+ :fog_options => fog_options
60
+ )
61
+ end
62
+
63
+ def get_remote_files(remote_base)
64
+ hash = {}
65
+ cloud_io.objects(remote_base).each do |object|
66
+ relative_path = object.name.sub(remote_base + '/', '')
67
+ hash[relative_path] = object.hash
68
+ end
69
+ hash
70
+ end
71
+
72
+ def check_configuration
73
+ required = %w{ username api_key container }
74
+ raise Error, <<-EOS if required.map {|name| send(name) }.any?(&:nil?)
75
+ Configuration Error
76
+ #{ required.map {|name| "##{ name }"}.join(', ') } are all required
77
+ EOS
78
+ end
79
+
80
+ end # class Cloudfiles < Base
81
+ end # module Cloud
82
+ end
83
+ end
@@ -0,0 +1,100 @@
1
+ # encoding: utf-8
2
+ require 'digest/md5'
3
+
4
+ module Backup
5
+ module Syncer
6
+ module Cloud
7
+ class LocalFile
8
+ attr_reader :path
9
+ attr_accessor :md5
10
+
11
+ class << self
12
+
13
+ # Returns a Hash of LocalFile objects for each file within +dir+,
14
+ # except those matching any of the +excludes+.
15
+ # Hash keys are the file's path relative to +dir+.
16
+ def find(dir, excludes = [])
17
+ dir = File.expand_path(dir)
18
+ hash = {}
19
+ find_md5(dir, excludes).each do |file|
20
+ hash[file.path.sub(dir + '/', '')] = file
21
+ end
22
+ hash
23
+ end
24
+
25
+ # Return a new LocalFile object if it's valid.
26
+ # Otherwise, log a warning and return nil.
27
+ def new(*args)
28
+ file = super
29
+ if file.invalid?
30
+ Logger.warn("\s\s[skipping] #{ file.path }\n" +
31
+ "\s\sPath Contains Invalid UTF-8 byte sequences")
32
+ file = nil
33
+ end
34
+ file
35
+ end
36
+
37
+ private
38
+
39
+ # Returns an Array of file paths and their md5 hashes.
40
+ def find_md5(dir, excludes)
41
+ found = []
42
+ (Dir.entries(dir) - %w{. ..}).map {|e| File.join(dir, e) }.each do |path|
43
+ if File.directory?(path)
44
+ unless exclude?(excludes, path)
45
+ found += find_md5(path, excludes)
46
+ end
47
+ elsif File.file?(path)
48
+ if file = new(path)
49
+ unless exclude?(excludes, file.path)
50
+ file.md5 = Digest::MD5.file(file.path).hexdigest
51
+ found << file
52
+ end
53
+ end
54
+ end
55
+ end
56
+ found
57
+ end
58
+
59
+ # Returns true if +path+ matches any of the +excludes+.
60
+ # Note this can not be called if +path+ includes invalid UTF-8.
61
+ def exclude?(excludes, path)
62
+ excludes.any? do |ex|
63
+ if ex.is_a?(String)
64
+ File.fnmatch?(ex, path)
65
+ elsif ex.is_a?(Regexp)
66
+ ex.match(path)
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ # If +path+ contains invalid UTF-8, it will be sanitized
73
+ # and the LocalFile object will be flagged as invalid.
74
+ # This is done so @file.path may be logged.
75
+ def initialize(path)
76
+ @path = sanitize(path)
77
+ end
78
+
79
+ def invalid?
80
+ !!@invalid
81
+ end
82
+
83
+ private
84
+
85
+ def sanitize(str)
86
+ str.each_char.map do |char|
87
+ begin
88
+ char.unpack('U')
89
+ char
90
+ rescue
91
+ @invalid = true
92
+ "\xEF\xBF\xBD" # => "\uFFFD"
93
+ end
94
+ end.join
95
+ end
96
+
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,110 @@
1
+ # encoding: utf-8
2
+ require 'backup/cloud_io/s3'
3
+
4
+ module Backup
5
+ module Syncer
6
+ module Cloud
7
+ class S3 < Base
8
+ class Error < Backup::Error; end
9
+
10
+ ##
11
+ # Amazon Simple Storage Service (S3) Credentials
12
+ attr_accessor :access_key_id, :secret_access_key, :use_iam_profile
13
+
14
+ ##
15
+ # Amazon S3 bucket name
16
+ attr_accessor :bucket
17
+
18
+ ##
19
+ # Region of the specified S3 bucket
20
+ attr_accessor :region
21
+
22
+ ##
23
+ # Encryption algorithm to use for Amazon Server-Side Encryption
24
+ #
25
+ # Supported values:
26
+ #
27
+ # - :aes256
28
+ #
29
+ # Default: nil
30
+ attr_accessor :encryption
31
+
32
+ ##
33
+ # Storage class to use for the S3 objects uploaded
34
+ #
35
+ # Supported values:
36
+ #
37
+ # - :standard (default)
38
+ # - :reduced_redundancy
39
+ #
40
+ # Default: :standard
41
+ attr_accessor :storage_class
42
+
43
+ ##
44
+ # Additional options to pass along to fog.
45
+ # e.g. Fog::Storage.new({ :provider => 'AWS' }.merge(fog_options))
46
+ attr_accessor :fog_options
47
+
48
+ def initialize(syncer_id = nil)
49
+ super
50
+
51
+ @storage_class ||= :standard
52
+
53
+ check_configuration
54
+ end
55
+
56
+ private
57
+
58
+ def cloud_io
59
+ @cloud_io ||= CloudIO::S3.new(
60
+ :access_key_id => access_key_id,
61
+ :secret_access_key => secret_access_key,
62
+ :use_iam_profile => use_iam_profile,
63
+ :bucket => bucket,
64
+ :region => region,
65
+ :encryption => encryption,
66
+ :storage_class => storage_class,
67
+ :max_retries => max_retries,
68
+ :retry_waitsec => retry_waitsec,
69
+ # Syncer can not use multipart upload.
70
+ :chunk_size => 0,
71
+ :fog_options => fog_options
72
+ )
73
+ end
74
+
75
+ def get_remote_files(remote_base)
76
+ hash = {}
77
+ cloud_io.objects(remote_base).each do |object|
78
+ relative_path = object.key.sub(remote_base + '/', '')
79
+ hash[relative_path] = object.etag
80
+ end
81
+ hash
82
+ end
83
+
84
+ def check_configuration
85
+ if use_iam_profile
86
+ required = %w{ bucket }
87
+ else
88
+ required = %w{ access_key_id secret_access_key bucket }
89
+ end
90
+ raise Error, <<-EOS if required.map {|name| send(name) }.any?(&:nil?)
91
+ Configuration Error
92
+ #{ required.map {|name| "##{ name }"}.join(', ') } are all required
93
+ EOS
94
+
95
+ raise Error, <<-EOS if encryption && encryption.to_s.upcase != 'AES256'
96
+ Configuration Error
97
+ #encryption must be :aes256 or nil
98
+ EOS
99
+
100
+ classes = ['STANDARD', 'REDUCED_REDUNDANCY']
101
+ raise Error, <<-EOS unless classes.include?(storage_class.to_s.upcase)
102
+ Configuration Error
103
+ #storage_class must be :standard or :reduced_redundancy
104
+ EOS
105
+ end
106
+
107
+ end # Class S3 < Base
108
+ end # module Cloud
109
+ end
110
+ end
@@ -0,0 +1,54 @@
1
+ # encoding: utf-8
2
+
3
+ module Backup
4
+ module Syncer
5
+ module RSync
6
+ class Base < Syncer::Base
7
+
8
+ ##
9
+ # Additional String or Array of options for the rsync cli
10
+ attr_accessor :additional_rsync_options
11
+ attr_accessor :archive
12
+
13
+ def initialize(syncer_id = nil, &block)
14
+ super
15
+ instance_eval(&block) if block_given?
16
+
17
+ @path ||= '~/backups'
18
+ @archive = @archive.nil? ? true : @archive
19
+ end
20
+
21
+ private
22
+
23
+ ##
24
+ # Common base command for Local/Push/Pull
25
+ def rsync_command
26
+ utility(:rsync) << archive_option << mirror_option << exclude_option <<
27
+ " #{ Array(additional_rsync_options).join(' ') }".rstrip
28
+ end
29
+
30
+ def mirror_option
31
+ mirror ? ' --delete' : ''
32
+ end
33
+
34
+ def archive_option
35
+ archive ? ' --archive' : ''
36
+ end
37
+
38
+ def exclude_option
39
+ excludes.map {|pattern| " --exclude='#{ pattern }'" }.join
40
+ end
41
+
42
+ ##
43
+ # Each path is expanded, since these refer to local paths and are
44
+ # being shell-quoted. This will also remove any trailing `/` from
45
+ # each path, as we don't want rsync's "trailing / on source directories"
46
+ # behavior. This method is used by RSync::Local and RSync::Push.
47
+ def paths_to_push
48
+ directories.map {|dir| "'#{ File.expand_path(dir) }'" }.join(' ')
49
+ end
50
+
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,31 @@
1
+ # encoding: utf-8
2
+
3
+ module Backup
4
+ module Syncer
5
+ module RSync
6
+ class Local < Base
7
+
8
+ def perform!
9
+ log!(:started)
10
+
11
+ create_dest_path!
12
+ run("#{ rsync_command } #{ paths_to_push } '#{ dest_path }'")
13
+
14
+ log!(:finished)
15
+ end
16
+
17
+ private
18
+
19
+ # Expand path, since this is local and shell-quoted.
20
+ def dest_path
21
+ @dest_path ||= File.expand_path(path)
22
+ end
23
+
24
+ def create_dest_path!
25
+ FileUtils.mkdir_p dest_path
26
+ end
27
+
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,51 @@
1
+ # encoding: utf-8
2
+
3
+ module Backup
4
+ module Syncer
5
+ module RSync
6
+ class Pull < Push
7
+
8
+ def perform!
9
+ log!(:started)
10
+ write_password_file!
11
+
12
+ create_dest_path!
13
+ run("#{ rsync_command } #{ host_options }#{ paths_to_pull } " +
14
+ "'#{ dest_path }'")
15
+
16
+ log!(:finished)
17
+ ensure
18
+ remove_password_file!
19
+ end
20
+
21
+ private
22
+
23
+ ##
24
+ # Returns the syntax for pulling multiple paths from the remote host.
25
+ # e.g.
26
+ # rsync -a -e "ssh -p 22" host:'path1' :'path2' '/dest'
27
+ # rsync -a rsync_user@host::'modname/path1' ::'modname/path2' '/dest'
28
+ #
29
+ # Remove any preceeding '~/', since these paths are on the remote.
30
+ # Also remove any trailing `/`, since we don't want rsync's
31
+ # "trailing / on source directories" behavior.
32
+ def paths_to_pull
33
+ sep = mode == :ssh ? ':' : '::'
34
+ directories.map {|dir|
35
+ "#{ sep }'#{ dir.sub(/^~\//, '').sub(/\/$/, '') }'"
36
+ }.join(' ').sub(/^#{ sep }/, '')
37
+ end
38
+
39
+ # Expand path, since this is local and shell-quoted.
40
+ def dest_path
41
+ @dest_path ||= File.expand_path(path)
42
+ end
43
+
44
+ def create_dest_path!
45
+ FileUtils.mkdir_p dest_path
46
+ end
47
+
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,205 @@
1
+ # encoding: utf-8
2
+
3
+ module Backup
4
+ module Syncer
5
+ module RSync
6
+ class Push < Base
7
+
8
+ ##
9
+ # Mode of operation
10
+ #
11
+ # [:ssh (default)]
12
+ # Connects to the remote via SSH.
13
+ # Does not use an rsync daemon on the remote.
14
+ #
15
+ # [:ssh_daemon]
16
+ # Connects to the remote via SSH.
17
+ # Spawns a single-use daemon on the remote, which allows certain
18
+ # daemon features (like modules) to be used.
19
+ #
20
+ # [:rsync_daemon]
21
+ # Connects directly to an rsync daemon via TCP.
22
+ # Data transferred is not encrypted.
23
+ #
24
+ attr_accessor :mode
25
+
26
+ ##
27
+ # Server Address
28
+ attr_accessor :host
29
+
30
+ ##
31
+ # SSH or RSync port
32
+ #
33
+ # For `:ssh` or `:ssh_daemon` mode, this specifies the SSH port to use
34
+ # and defaults to 22.
35
+ #
36
+ # For `:rsync_daemon` mode, this specifies the TCP port to use
37
+ # and defaults to 873.
38
+ attr_accessor :port
39
+
40
+ ##
41
+ # SSH User
42
+ #
43
+ # If the user running the backup is not the same user that needs to
44
+ # authenticate with the remote server, specify the user here.
45
+ #
46
+ # The user must have SSH keys setup for passphrase-less access to the
47
+ # remote. If the SSH User does not have passphrase-less keys, or no
48
+ # default keys in their `~/.ssh` directory, you will need to use the
49
+ # `-i` option in `:additional_ssh_options` to specify the
50
+ # passphrase-less key to use.
51
+ #
52
+ # Used only for `:ssh` and `:ssh_daemon` modes.
53
+ attr_accessor :ssh_user
54
+
55
+ ##
56
+ # Additional SSH Options
57
+ #
58
+ # Used to supply a String or Array of options to be passed to the SSH
59
+ # command in `:ssh` and `:ssh_daemon` modes.
60
+ #
61
+ # For example, if you need to supply a specific SSH key for the `ssh_user`,
62
+ # you would set this to: "-i '/path/to/id_rsa'". Which would produce:
63
+ #
64
+ # rsync -e "ssh -p 22 -i '/path/to/id_rsa'"
65
+ #
66
+ # Arguments may be single-quoted, but should not contain any double-quotes.
67
+ #
68
+ # Used only for `:ssh` and `:ssh_daemon` modes.
69
+ attr_accessor :additional_ssh_options
70
+
71
+ ##
72
+ # RSync User
73
+ #
74
+ # If the user running the backup is not the same user that needs to
75
+ # authenticate with the rsync daemon, specify the user here.
76
+ #
77
+ # Used only for `:ssh_daemon` and `:rsync_daemon` modes.
78
+ attr_accessor :rsync_user
79
+
80
+ ##
81
+ # RSync Password
82
+ #
83
+ # If specified, Backup will write the password to a temporary file and
84
+ # use it with rsync's `--password-file` option for daemon authentication.
85
+ #
86
+ # Note that setting this will override `rsync_password_file`.
87
+ #
88
+ # Used only for `:ssh_daemon` and `:rsync_daemon` modes.
89
+ attr_accessor :rsync_password
90
+
91
+ ##
92
+ # RSync Password File
93
+ #
94
+ # If specified, this path will be passed to rsync's `--password-file`
95
+ # option for daemon authentication.
96
+ #
97
+ # Used only for `:ssh_daemon` and `:rsync_daemon` modes.
98
+ attr_accessor :rsync_password_file
99
+
100
+ ##
101
+ # Flag for compressing (only compresses for the transfer)
102
+ attr_accessor :compress
103
+
104
+ def initialize(syncer_id = nil)
105
+ super
106
+
107
+ @mode ||= :ssh
108
+ @port ||= mode == :rsync_daemon ? 873 : 22
109
+ @compress ||= false
110
+ end
111
+
112
+ def perform!
113
+ log!(:started)
114
+ write_password_file!
115
+
116
+ create_dest_path!
117
+ run("#{ rsync_command } #{ paths_to_push } " +
118
+ "#{ host_options }'#{ dest_path }'")
119
+
120
+ log!(:finished)
121
+ ensure
122
+ remove_password_file!
123
+ end
124
+
125
+ private
126
+
127
+ ##
128
+ # Remove any preceeding '~/', since this is on the remote,
129
+ # and remove any trailing `/`.
130
+ def dest_path
131
+ @dest_path ||= path.sub(/^~\//, '').sub(/\/$/, '')
132
+ end
133
+
134
+ ##
135
+ # Runs a 'mkdir -p' command on the remote to ensure the dest_path exists.
136
+ # This used because rsync will attempt to create the path, but will only
137
+ # call 'mkdir' without the '-p' option. This is only applicable in :ssh
138
+ # mode, and only used if the path would require this.
139
+ def create_dest_path!
140
+ return unless mode == :ssh && dest_path.index('/').to_i > 0
141
+
142
+ run "#{ utility(:ssh) } #{ ssh_transport_args } #{ host } " +
143
+ %Q["mkdir -p '#{ dest_path }'"]
144
+ end
145
+
146
+ ##
147
+ # For Push, this will prepend the #dest_path.
148
+ # For Pull, this will prepend the first path in #paths_to_pull.
149
+ def host_options
150
+ if mode == :ssh
151
+ "#{ host }:"
152
+ else
153
+ user = "#{ rsync_user }@" if rsync_user
154
+ "#{ user }#{ host }::"
155
+ end
156
+ end
157
+
158
+ ##
159
+ # Common base command, plus options for Push/Pull
160
+ def rsync_command
161
+ super << compress_option << password_option << transport_options
162
+ end
163
+
164
+ def compress_option
165
+ compress ? ' --compress' : ''
166
+ end
167
+
168
+ def password_option
169
+ return '' if mode == :ssh
170
+
171
+ path = @password_file ? @password_file.path : rsync_password_file
172
+ path ? " --password-file='#{ File.expand_path(path) }'" : ''
173
+ end
174
+
175
+ def transport_options
176
+ if mode == :rsync_daemon
177
+ " --port #{ port }"
178
+ else
179
+ %Q[ -e "#{ utility(:ssh) } #{ ssh_transport_args }"]
180
+ end
181
+ end
182
+
183
+ def ssh_transport_args
184
+ args = "-p #{ port } "
185
+ args << "-l #{ ssh_user } " if ssh_user
186
+ args << Array(additional_ssh_options).join(' ')
187
+ args.rstrip
188
+ end
189
+
190
+ def write_password_file!
191
+ return unless rsync_password && mode != :ssh
192
+
193
+ @password_file = Tempfile.new('backup-rsync-password')
194
+ @password_file.write(rsync_password)
195
+ @password_file.close
196
+ end
197
+
198
+ def remove_password_file!
199
+ @password_file.delete if @password_file
200
+ end
201
+
202
+ end
203
+ end
204
+ end
205
+ end