backupii 0.1.0.pre.alpha.1

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 (135) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +19 -0
  3. data/README.md +37 -0
  4. data/bin/backupii +5 -0
  5. data/bin/docker_test +24 -0
  6. data/lib/backup/archive.rb +171 -0
  7. data/lib/backup/binder.rb +23 -0
  8. data/lib/backup/cleaner.rb +114 -0
  9. data/lib/backup/cli.rb +376 -0
  10. data/lib/backup/cloud_io/base.rb +40 -0
  11. data/lib/backup/cloud_io/cloud_files.rb +301 -0
  12. data/lib/backup/cloud_io/s3.rb +256 -0
  13. data/lib/backup/compressor/base.rb +34 -0
  14. data/lib/backup/compressor/bzip2.rb +37 -0
  15. data/lib/backup/compressor/custom.rb +51 -0
  16. data/lib/backup/compressor/gzip.rb +76 -0
  17. data/lib/backup/config/dsl.rb +103 -0
  18. data/lib/backup/config/helpers.rb +139 -0
  19. data/lib/backup/config.rb +122 -0
  20. data/lib/backup/database/base.rb +89 -0
  21. data/lib/backup/database/mongodb.rb +189 -0
  22. data/lib/backup/database/mysql.rb +194 -0
  23. data/lib/backup/database/openldap.rb +97 -0
  24. data/lib/backup/database/postgresql.rb +134 -0
  25. data/lib/backup/database/redis.rb +179 -0
  26. data/lib/backup/database/riak.rb +82 -0
  27. data/lib/backup/database/sqlite.rb +57 -0
  28. data/lib/backup/encryptor/base.rb +29 -0
  29. data/lib/backup/encryptor/gpg.rb +745 -0
  30. data/lib/backup/encryptor/open_ssl.rb +76 -0
  31. data/lib/backup/errors.rb +55 -0
  32. data/lib/backup/logger/console.rb +50 -0
  33. data/lib/backup/logger/fog_adapter.rb +27 -0
  34. data/lib/backup/logger/logfile.rb +134 -0
  35. data/lib/backup/logger/syslog.rb +116 -0
  36. data/lib/backup/logger.rb +199 -0
  37. data/lib/backup/model.rb +478 -0
  38. data/lib/backup/notifier/base.rb +128 -0
  39. data/lib/backup/notifier/campfire.rb +63 -0
  40. data/lib/backup/notifier/command.rb +101 -0
  41. data/lib/backup/notifier/datadog.rb +107 -0
  42. data/lib/backup/notifier/flowdock.rb +101 -0
  43. data/lib/backup/notifier/hipchat.rb +118 -0
  44. data/lib/backup/notifier/http_post.rb +116 -0
  45. data/lib/backup/notifier/mail.rb +235 -0
  46. data/lib/backup/notifier/nagios.rb +67 -0
  47. data/lib/backup/notifier/pagerduty.rb +82 -0
  48. data/lib/backup/notifier/prowl.rb +70 -0
  49. data/lib/backup/notifier/pushover.rb +73 -0
  50. data/lib/backup/notifier/ses.rb +126 -0
  51. data/lib/backup/notifier/slack.rb +149 -0
  52. data/lib/backup/notifier/twitter.rb +57 -0
  53. data/lib/backup/notifier/zabbix.rb +62 -0
  54. data/lib/backup/package.rb +53 -0
  55. data/lib/backup/packager.rb +108 -0
  56. data/lib/backup/pipeline.rb +122 -0
  57. data/lib/backup/splitter.rb +75 -0
  58. data/lib/backup/storage/base.rb +72 -0
  59. data/lib/backup/storage/cloud_files.rb +158 -0
  60. data/lib/backup/storage/cycler.rb +73 -0
  61. data/lib/backup/storage/dropbox.rb +208 -0
  62. data/lib/backup/storage/ftp.rb +118 -0
  63. data/lib/backup/storage/local.rb +63 -0
  64. data/lib/backup/storage/qiniu.rb +68 -0
  65. data/lib/backup/storage/rsync.rb +251 -0
  66. data/lib/backup/storage/s3.rb +157 -0
  67. data/lib/backup/storage/scp.rb +67 -0
  68. data/lib/backup/storage/sftp.rb +82 -0
  69. data/lib/backup/syncer/base.rb +70 -0
  70. data/lib/backup/syncer/cloud/base.rb +180 -0
  71. data/lib/backup/syncer/cloud/cloud_files.rb +83 -0
  72. data/lib/backup/syncer/cloud/local_file.rb +99 -0
  73. data/lib/backup/syncer/cloud/s3.rb +118 -0
  74. data/lib/backup/syncer/rsync/base.rb +55 -0
  75. data/lib/backup/syncer/rsync/local.rb +29 -0
  76. data/lib/backup/syncer/rsync/pull.rb +49 -0
  77. data/lib/backup/syncer/rsync/push.rb +206 -0
  78. data/lib/backup/template.rb +45 -0
  79. data/lib/backup/utilities.rb +235 -0
  80. data/lib/backup/version.rb +5 -0
  81. data/lib/backup.rb +141 -0
  82. data/templates/cli/archive +28 -0
  83. data/templates/cli/compressor/bzip2 +4 -0
  84. data/templates/cli/compressor/custom +7 -0
  85. data/templates/cli/compressor/gzip +4 -0
  86. data/templates/cli/config +123 -0
  87. data/templates/cli/databases/mongodb +15 -0
  88. data/templates/cli/databases/mysql +18 -0
  89. data/templates/cli/databases/openldap +24 -0
  90. data/templates/cli/databases/postgresql +16 -0
  91. data/templates/cli/databases/redis +16 -0
  92. data/templates/cli/databases/riak +17 -0
  93. data/templates/cli/databases/sqlite +11 -0
  94. data/templates/cli/encryptor/gpg +27 -0
  95. data/templates/cli/encryptor/openssl +9 -0
  96. data/templates/cli/model +26 -0
  97. data/templates/cli/notifier/zabbix +15 -0
  98. data/templates/cli/notifiers/campfire +12 -0
  99. data/templates/cli/notifiers/command +32 -0
  100. data/templates/cli/notifiers/datadog +57 -0
  101. data/templates/cli/notifiers/flowdock +16 -0
  102. data/templates/cli/notifiers/hipchat +16 -0
  103. data/templates/cli/notifiers/http_post +32 -0
  104. data/templates/cli/notifiers/mail +24 -0
  105. data/templates/cli/notifiers/nagios +13 -0
  106. data/templates/cli/notifiers/pagerduty +12 -0
  107. data/templates/cli/notifiers/prowl +11 -0
  108. data/templates/cli/notifiers/pushover +11 -0
  109. data/templates/cli/notifiers/ses +15 -0
  110. data/templates/cli/notifiers/slack +22 -0
  111. data/templates/cli/notifiers/twitter +13 -0
  112. data/templates/cli/splitter +7 -0
  113. data/templates/cli/storages/cloud_files +11 -0
  114. data/templates/cli/storages/dropbox +20 -0
  115. data/templates/cli/storages/ftp +13 -0
  116. data/templates/cli/storages/local +8 -0
  117. data/templates/cli/storages/qiniu +12 -0
  118. data/templates/cli/storages/rsync +17 -0
  119. data/templates/cli/storages/s3 +16 -0
  120. data/templates/cli/storages/scp +15 -0
  121. data/templates/cli/storages/sftp +15 -0
  122. data/templates/cli/syncers/cloud_files +22 -0
  123. data/templates/cli/syncers/rsync_local +20 -0
  124. data/templates/cli/syncers/rsync_pull +28 -0
  125. data/templates/cli/syncers/rsync_push +28 -0
  126. data/templates/cli/syncers/s3 +27 -0
  127. data/templates/general/links +3 -0
  128. data/templates/general/version.erb +2 -0
  129. data/templates/notifier/mail/failure.erb +16 -0
  130. data/templates/notifier/mail/success.erb +16 -0
  131. data/templates/notifier/mail/warning.erb +16 -0
  132. data/templates/storage/dropbox/authorization_url.erb +6 -0
  133. data/templates/storage/dropbox/authorized.erb +4 -0
  134. data/templates/storage/dropbox/cache_file_written.erb +10 -0
  135. metadata +507 -0
@@ -0,0 +1,251 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Backup
4
+ module Storage
5
+ class RSync < Base
6
+ include Utilities::Helpers
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
+ #
29
+ # If not specified, the storage operation will be local.
30
+ attr_accessor :host
31
+
32
+ ##
33
+ # SSH or RSync port
34
+ #
35
+ # For `:ssh` or `:ssh_daemon` mode, this specifies the SSH port to use
36
+ # and defaults to 22.
37
+ #
38
+ # For `:rsync_daemon` mode, this specifies the TCP port to use
39
+ # and defaults to 873.
40
+ attr_accessor :port
41
+
42
+ ##
43
+ # SSH User
44
+ #
45
+ # If the user running the backup is not the same user that needs to
46
+ # authenticate with the remote server, specify the user here.
47
+ #
48
+ # The user must have SSH keys setup for passphrase-less access to the
49
+ # remote. If the SSH User does not have passphrase-less keys, or no
50
+ # default keys in their `~/.ssh` directory, you will need to use the
51
+ # `-i` option in `:additional_ssh_options` to specify the
52
+ # passphrase-less key to use.
53
+ #
54
+ # Used only for `:ssh` and `:ssh_daemon` modes.
55
+ attr_accessor :ssh_user
56
+
57
+ ##
58
+ # Additional SSH Options
59
+ #
60
+ # Used to supply a String or Array of options to be passed to the SSH
61
+ # command in `:ssh` and `:ssh_daemon` modes.
62
+ #
63
+ # For example, if you need to supply a specific SSH key for the
64
+ # `ssh_user`, you would set this to: "-i '/path/to/id_rsa'". Which would
65
+ # produce:
66
+ #
67
+ # rsync -e "ssh -p 22 -i '/path/to/id_rsa'"
68
+ #
69
+ # Arguments may be single-quoted, but should not contain any
70
+ # double-quotes.
71
+ #
72
+ # Used only for `:ssh` and `:ssh_daemon` modes.
73
+ attr_accessor :additional_ssh_options
74
+
75
+ ##
76
+ # RSync User
77
+ #
78
+ # If the user running the backup is not the same user that needs to
79
+ # authenticate with the rsync daemon, specify the user here.
80
+ #
81
+ # Used only for `:ssh_daemon` and `:rsync_daemon` modes.
82
+ attr_accessor :rsync_user
83
+
84
+ ##
85
+ # RSync Password
86
+ #
87
+ # If specified, Backup will write the password to a temporary file and
88
+ # use it with rsync's `--password-file` option for daemon authentication.
89
+ #
90
+ # Note that setting this will override `rsync_password_file`.
91
+ #
92
+ # Used only for `:ssh_daemon` and `:rsync_daemon` modes.
93
+ attr_accessor :rsync_password
94
+
95
+ ##
96
+ # RSync Password File
97
+ #
98
+ # If specified, this path will be passed to rsync's `--password-file`
99
+ # option for daemon authentication.
100
+ #
101
+ # Used only for `:ssh_daemon` and `:rsync_daemon` modes.
102
+ attr_accessor :rsync_password_file
103
+
104
+ ##
105
+ # Additional String or Array of options for the rsync cli
106
+ attr_accessor :additional_rsync_options
107
+
108
+ ##
109
+ # Flag for compressing (only compresses for the transfer)
110
+ attr_accessor :compress
111
+
112
+ ##
113
+ # Path to store the synced backup package file(s) to.
114
+ #
115
+ # If no +host+ is specified, then +path+ will be local, and the only
116
+ # other used option would be +additional_rsync_options+.
117
+ # +path+ will be expanded, so '~/my_path' will expand to '$HOME/my_path'.
118
+ #
119
+ # If a +host+ is specified, this will be a path on the host.
120
+ # If +mode+ is `:ssh` (default), then any relative path, or path starting
121
+ # with '~/' will be relative to the directory the ssh_user is logged
122
+ # into. For `:ssh_daemon` or `:rsync_daemon` modes, this would reference
123
+ # an rsync module/path.
124
+ #
125
+ # In :ssh_daemon and :rsync_daemon modes, +path+ (or path defined by
126
+ # your rsync module) must already exist.
127
+ #
128
+ # In :ssh mode or local operation (no +host+ specified), +path+ will
129
+ # be created if needed - either locally, or on the remote for :ssh mode.
130
+ attr_accessor :path
131
+
132
+ def initialize(model, storage_id = nil)
133
+ super
134
+
135
+ @mode ||= :ssh
136
+ @port ||= mode == :rsync_daemon ? 873 : 22
137
+ @compress ||= false
138
+ @path ||= "~/backups"
139
+ end
140
+
141
+ private
142
+
143
+ def transfer!
144
+ write_password_file
145
+ create_remote_path
146
+
147
+ package.filenames.each do |filename|
148
+ src = "'#{File.join(Config.tmp_path, filename)}'"
149
+ dest = "#{host_options}'#{File.join(remote_path, filename)}'"
150
+ Logger.info "Syncing to #{dest}..."
151
+ run("#{rsync_command} #{src} #{dest}")
152
+ end
153
+ ensure
154
+ remove_password_file
155
+ end
156
+
157
+ ##
158
+ # Other storages add an additional timestamp directory to this path.
159
+ # This is not desired here, since we need to transfer the package files
160
+ # to the same location each time.
161
+ def remote_path
162
+ @remote_path ||= begin
163
+ if host
164
+ path.sub(%r{^~/}, "").sub(%r{/$}, "")
165
+ else
166
+ File.expand_path(path)
167
+ end
168
+ end
169
+ end
170
+
171
+ ##
172
+ # Runs a 'mkdir -p' command on the host (or locally) to ensure the
173
+ # dest_path exists. This is used because we're transferring a single
174
+ # file, and rsync won't attempt to create the intermediate directories.
175
+ #
176
+ # This is only applicable locally and in :ssh mode.
177
+ # In :ssh_daemon and :rsync_daemon modes the `path` would include a
178
+ # module name that must define a path on the remote that already exists.
179
+ def create_remote_path
180
+ if host
181
+ return unless mode == :ssh
182
+
183
+ run "#{utility(:ssh)} #{ssh_transport_args} #{host} " +
184
+ %("mkdir -p '#{remote_path}'")
185
+ else
186
+ FileUtils.mkdir_p(remote_path)
187
+ end
188
+ end
189
+
190
+ def host_options
191
+ @host_options ||= begin
192
+ if !host
193
+ ""
194
+ elsif mode == :ssh
195
+ "#{host}:"
196
+ else
197
+ user = "#{rsync_user}@" if rsync_user
198
+ "#{user}#{host}::"
199
+ end
200
+ end
201
+ end
202
+
203
+ def rsync_command
204
+ @rsync_command ||= begin
205
+ cmd = utility(:rsync).dup << " --archive" <<
206
+ " #{Array(additional_rsync_options).join(" ")}".rstrip
207
+ cmd << compress_option << password_option << transport_options if host
208
+ cmd
209
+ end
210
+ end
211
+
212
+ def compress_option
213
+ compress ? " --compress" : ""
214
+ end
215
+
216
+ def password_option
217
+ return "" if mode == :ssh
218
+
219
+ path = @password_file ? @password_file.path : rsync_password_file
220
+ path ? " --password-file='#{File.expand_path(path)}'" : ""
221
+ end
222
+
223
+ def transport_options
224
+ if mode == :rsync_daemon
225
+ " --port #{port}"
226
+ else
227
+ %( -e "#{utility(:ssh)} #{ssh_transport_args}")
228
+ end
229
+ end
230
+
231
+ def ssh_transport_args
232
+ args = "-p #{port} ".dup
233
+ args << "-l #{ssh_user} " if ssh_user
234
+ args << Array(additional_ssh_options).join(" ")
235
+ args.rstrip
236
+ end
237
+
238
+ def write_password_file
239
+ return unless host && rsync_password && mode != :ssh
240
+
241
+ @password_file = Tempfile.new("backup-rsync-password")
242
+ @password_file.write(rsync_password)
243
+ @password_file.close
244
+ end
245
+
246
+ def remove_password_file
247
+ @password_file.delete if @password_file
248
+ end
249
+ end
250
+ end
251
+ end
@@ -0,0 +1,157 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "backup/cloud_io/s3"
4
+
5
+ module Backup
6
+ module Storage
7
+ class S3 < Base
8
+ include Storage::Cycler
9
+ class Error < Backup::Error; end
10
+
11
+ ##
12
+ # Amazon Simple Storage Service (S3) Credentials
13
+ attr_accessor :access_key_id, :secret_access_key, :use_iam_profile
14
+
15
+ ##
16
+ # Amazon S3 bucket name
17
+ attr_accessor :bucket
18
+
19
+ ##
20
+ # Region of the specified S3 bucket
21
+ attr_accessor :region
22
+
23
+ ##
24
+ # Multipart chunk size, specified in MiB.
25
+ #
26
+ # Each package file larger than +chunk_size+
27
+ # will be uploaded using S3 Multipart Upload.
28
+ #
29
+ # Minimum: 5 (but may be disabled with 0)
30
+ # Maximum: 5120
31
+ # Default: 5
32
+ attr_accessor :chunk_size
33
+
34
+ ##
35
+ # Number of times to retry failed operations.
36
+ #
37
+ # Default: 10
38
+ attr_accessor :max_retries
39
+
40
+ ##
41
+ # Time in seconds to pause before each retry.
42
+ #
43
+ # Default: 30
44
+ attr_accessor :retry_waitsec
45
+
46
+ ##
47
+ # Encryption algorithm to use for Amazon Server-Side Encryption
48
+ #
49
+ # Supported values:
50
+ #
51
+ # - :aes256
52
+ #
53
+ # Default: nil
54
+ attr_accessor :encryption
55
+
56
+ ##
57
+ # Storage class to use for the S3 objects uploaded
58
+ #
59
+ # Supported values:
60
+ #
61
+ # - :standard (default)
62
+ # - :standard_ia
63
+ # - :reduced_redundancy
64
+ #
65
+ # Default: :standard
66
+ attr_accessor :storage_class
67
+
68
+ ##
69
+ # Additional options to pass along to fog.
70
+ # e.g. Fog::Storage.new({ :provider => 'AWS' }.merge(fog_options))
71
+ attr_accessor :fog_options
72
+
73
+ def initialize(model, storage_id = nil)
74
+ super
75
+
76
+ @chunk_size ||= 5 # MiB
77
+ @max_retries ||= 10
78
+ @retry_waitsec ||= 30
79
+ @path ||= "backups"
80
+ @storage_class ||= :standard
81
+
82
+ @path = @path.sub(%r{^/}, "")
83
+
84
+ check_configuration
85
+ end
86
+
87
+ private
88
+
89
+ def cloud_io
90
+ @cloud_io ||= CloudIO::S3.new(
91
+ access_key_id: access_key_id,
92
+ secret_access_key: secret_access_key,
93
+ use_iam_profile: use_iam_profile,
94
+ region: region,
95
+ bucket: bucket,
96
+ encryption: encryption,
97
+ storage_class: storage_class,
98
+ max_retries: max_retries,
99
+ retry_waitsec: retry_waitsec,
100
+ chunk_size: chunk_size,
101
+ fog_options: fog_options
102
+ )
103
+ end
104
+
105
+ def transfer!
106
+ package.filenames.each do |filename|
107
+ src = File.join(Config.tmp_path, filename)
108
+ dest = File.join(remote_path, filename)
109
+ Logger.info "Storing '#{bucket}/#{dest}'..."
110
+ cloud_io.upload(src, dest)
111
+ end
112
+ end
113
+
114
+ # Called by the Cycler.
115
+ # Any error raised will be logged as a warning.
116
+ def remove!(package)
117
+ Logger.info "Removing backup package dated #{package.time}..."
118
+
119
+ remote_path = remote_path_for(package)
120
+ objects = cloud_io.objects(remote_path)
121
+
122
+ raise Error, "Package at '#{remote_path}' not found" if objects.empty?
123
+
124
+ cloud_io.delete(objects)
125
+ end
126
+
127
+ def check_configuration
128
+ required =
129
+ if use_iam_profile
130
+ %w[bucket]
131
+ else
132
+ %w[access_key_id secret_access_key bucket]
133
+ end
134
+ raise Error, <<-EOS if required.map { |name| send(name) }.any?(&:nil?)
135
+ Configuration Error
136
+ #{required.map { |name| "##{name}" }.join(", ")} are all required
137
+ EOS
138
+
139
+ raise Error, <<-EOS if chunk_size > 0 && !chunk_size.between?(5, 5120)
140
+ Configuration Error
141
+ #chunk_size must be between 5 and 5120 (or 0 to disable multipart)
142
+ EOS
143
+
144
+ raise Error, <<-EOS if encryption && encryption.to_s.upcase != "AES256"
145
+ Configuration Error
146
+ #encryption must be :aes256 or nil
147
+ EOS
148
+
149
+ classes = %w[STANDARD STANDARD_IA REDUCED_REDUNDANCY]
150
+ raise Error, <<-EOS unless classes.include?(storage_class.to_s.upcase)
151
+ Configuration Error
152
+ #storage_class must be :standard or :standard_ia or :reduced_redundancy
153
+ EOS
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/scp"
4
+
5
+ module Backup
6
+ module Storage
7
+ class SCP < Base
8
+ include Storage::Cycler
9
+ class Error < Backup::Error; end
10
+
11
+ ##
12
+ # Server credentials
13
+ attr_accessor :username, :password, :ssh_options
14
+
15
+ ##
16
+ # Server IP Address and SCP port
17
+ attr_accessor :ip, :port
18
+
19
+ def initialize(model, storage_id = nil)
20
+ super
21
+
22
+ @port ||= 22
23
+ @path ||= "backups".dup
24
+ @ssh_options ||= {}
25
+ path.sub!(%r{^~/}, "")
26
+ end
27
+
28
+ private
29
+
30
+ def connection
31
+ Net::SSH.start(
32
+ ip, username, { password: password, port: port }.merge(ssh_options)
33
+ ) { |ssh| yield ssh }
34
+ end
35
+
36
+ def transfer!
37
+ connection do |ssh|
38
+ ssh.exec!("mkdir -p '#{remote_path}'")
39
+
40
+ package.filenames.each do |filename|
41
+ src = File.join(Config.tmp_path, filename)
42
+ dest = File.join(remote_path, filename)
43
+ Logger.info "Storing '#{ip}:#{dest}'..."
44
+ ssh.scp.upload!(src, dest)
45
+ end
46
+ end
47
+ end
48
+
49
+ # Called by the Cycler.
50
+ # Any error raised will be logged as a warning.
51
+ def remove!(package)
52
+ Logger.info "Removing backup package dated #{package.time}..."
53
+
54
+ errors = []
55
+ connection do |ssh|
56
+ ssh.exec!("rm -r '#{remote_path_for(package)}'") do |_, stream, data|
57
+ errors << data if stream == :stderr
58
+ end
59
+ end
60
+ unless errors.empty?
61
+ raise Error, "Net::SSH reported the following errors:\n" +
62
+ errors.join("\n")
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/sftp"
4
+
5
+ module Backup
6
+ module Storage
7
+ class SFTP < Base
8
+ include Storage::Cycler
9
+
10
+ ##
11
+ # Server credentials
12
+ attr_accessor :username, :password, :ssh_options
13
+
14
+ ##
15
+ # Server IP Address and SFTP port
16
+ attr_accessor :ip, :port
17
+
18
+ def initialize(model, storage_id = nil)
19
+ super
20
+
21
+ @ssh_options ||= {}
22
+ @port ||= 22
23
+ @path ||= "backups".dup
24
+ path.sub!(%r{^~/}, "")
25
+ end
26
+
27
+ private
28
+
29
+ def connection
30
+ Net::SFTP.start(
31
+ ip, username, { password: password, port: port }.merge(ssh_options)
32
+ ) { |sftp| yield sftp }
33
+ end
34
+
35
+ def transfer!
36
+ connection do |sftp|
37
+ create_remote_path(sftp)
38
+
39
+ package.filenames.each do |filename|
40
+ src = File.join(Config.tmp_path, filename)
41
+ dest = File.join(remote_path, filename)
42
+ Logger.info "Storing '#{ip}:#{dest}'..."
43
+ sftp.upload!(src, dest)
44
+ end
45
+ end
46
+ end
47
+
48
+ # Called by the Cycler.
49
+ # Any error raised will be logged as a warning.
50
+ def remove!(package)
51
+ Logger.info "Removing backup package dated #{package.time}..."
52
+
53
+ remote_path = remote_path_for(package)
54
+ connection do |sftp|
55
+ package.filenames.each do |filename|
56
+ sftp.remove!(File.join(remote_path, filename))
57
+ end
58
+
59
+ sftp.rmdir!(remote_path)
60
+ end
61
+ end
62
+
63
+ ##
64
+ # Creates (if they don't exist yet) all the directories on the remote
65
+ # server in order to upload the backup file. Net::SFTP does not support
66
+ # paths to directories that don't yet exist when creating new
67
+ # directories. Instead, we split the parts up in to an array (for each
68
+ # '/') and loop through that to create the directories one by one.
69
+ # Net::SFTP raises an exception when the directory it's trying to create
70
+ # already exists, so we have rescue it
71
+ def create_remote_path(sftp)
72
+ path_parts = []
73
+ remote_path.split("/").each do |path_part|
74
+ path_parts << path_part
75
+ begin
76
+ sftp.mkdir!(path_parts.join("/"))
77
+ rescue Net::SFTP::StatusException; end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Backup
4
+ module Syncer
5
+ class Base
6
+ include Utilities::Helpers
7
+ include Config::Helpers
8
+
9
+ ##
10
+ # Path to store the synced files/directories to
11
+ attr_accessor :path
12
+
13
+ ##
14
+ # Flag for mirroring the files/directories
15
+ attr_accessor :mirror
16
+
17
+ ##
18
+ # Optional user-defined identifier to differentiate multiple syncers
19
+ # defined within a single backup model. Currently this is only used
20
+ # in the log messages.
21
+ attr_reader :syncer_id
22
+
23
+ attr_reader :excludes
24
+
25
+ def initialize(syncer_id = nil)
26
+ @syncer_id = syncer_id
27
+
28
+ load_defaults!
29
+
30
+ @mirror ||= false
31
+ @directories ||= []
32
+ @excludes ||= []
33
+ end
34
+
35
+ ##
36
+ # Syntactical suger for the DSL for adding directories
37
+ def directories(&block)
38
+ return @directories unless block_given?
39
+
40
+ instance_eval(&block)
41
+ end
42
+
43
+ def add(path)
44
+ directories << path
45
+ end
46
+
47
+ # For Cloud Syncers, +pattern+ can be a string (with shell-style
48
+ # wildcards) or a regex.
49
+ # For RSync, each +pattern+ will be passed to rsync's --exclude option.
50
+ def exclude(pattern)
51
+ excludes << pattern
52
+ end
53
+
54
+ private
55
+
56
+ def syncer_name
57
+ @syncer_name ||= self.class.to_s.sub("Backup::", "") +
58
+ (syncer_id ? " (#{syncer_id})" : "")
59
+ end
60
+
61
+ def log!(action)
62
+ msg = case action
63
+ when :started then "Started..."
64
+ when :finished then "Finished!"
65
+ end
66
+ Logger.info "#{syncer_name} #{msg}"
67
+ end
68
+ end
69
+ end
70
+ end