dmitryv-backup 2.4.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 (59) hide show
  1. data/CHANGELOG +125 -0
  2. data/LICENSE +20 -0
  3. data/README.md +180 -0
  4. data/VERSION +1 -0
  5. data/bin/backup +108 -0
  6. data/generators/backup/backup_generator.rb +69 -0
  7. data/generators/backup/templates/backup.rake +56 -0
  8. data/generators/backup/templates/backup.rb +229 -0
  9. data/generators/backup/templates/create_backup_tables.rb +18 -0
  10. data/generators/backup_update/backup_update_generator.rb +50 -0
  11. data/generators/backup_update/templates/migrations/update_backup_tables.rb +27 -0
  12. data/lib/backup.rb +131 -0
  13. data/lib/backup/adapters/archive.rb +34 -0
  14. data/lib/backup/adapters/base.rb +138 -0
  15. data/lib/backup/adapters/custom.rb +41 -0
  16. data/lib/backup/adapters/mysql.rb +60 -0
  17. data/lib/backup/adapters/postgresql.rb +56 -0
  18. data/lib/backup/adapters/sqlite.rb +25 -0
  19. data/lib/backup/command_helper.rb +11 -0
  20. data/lib/backup/compressors/base.rb +7 -0
  21. data/lib/backup/compressors/gzip.rb +9 -0
  22. data/lib/backup/compressors/seven_zip.rb +9 -0
  23. data/lib/backup/configuration/adapter.rb +21 -0
  24. data/lib/backup/configuration/adapter_options.rb +8 -0
  25. data/lib/backup/configuration/attributes.rb +19 -0
  26. data/lib/backup/configuration/base.rb +77 -0
  27. data/lib/backup/configuration/helpers.rb +24 -0
  28. data/lib/backup/configuration/mail.rb +20 -0
  29. data/lib/backup/configuration/smtp.rb +8 -0
  30. data/lib/backup/configuration/storage.rb +8 -0
  31. data/lib/backup/connection/cloudfiles.rb +75 -0
  32. data/lib/backup/connection/s3.rb +85 -0
  33. data/lib/backup/environment/base.rb +12 -0
  34. data/lib/backup/environment/rails_configuration.rb +15 -0
  35. data/lib/backup/environment/unix_configuration.rb +109 -0
  36. data/lib/backup/mail/base.rb +97 -0
  37. data/lib/backup/mail/mail.txt +7 -0
  38. data/lib/backup/record/base.rb +65 -0
  39. data/lib/backup/record/cloudfiles.rb +28 -0
  40. data/lib/backup/record/ftp.rb +39 -0
  41. data/lib/backup/record/local.rb +26 -0
  42. data/lib/backup/record/s3.rb +26 -0
  43. data/lib/backup/record/scp.rb +33 -0
  44. data/lib/backup/record/sftp.rb +38 -0
  45. data/lib/backup/storage/base.rb +10 -0
  46. data/lib/backup/storage/cloudfiles.rb +16 -0
  47. data/lib/backup/storage/ftp.rb +38 -0
  48. data/lib/backup/storage/local.rb +22 -0
  49. data/lib/backup/storage/s3.rb +17 -0
  50. data/lib/backup/storage/scp.rb +30 -0
  51. data/lib/backup/storage/sftp.rb +31 -0
  52. data/lib/generators/backup/USAGE +10 -0
  53. data/lib/generators/backup/backup_generator.rb +47 -0
  54. data/lib/generators/backup/templates/backup.rake +56 -0
  55. data/lib/generators/backup/templates/backup.rb +229 -0
  56. data/lib/generators/backup/templates/create_backup_tables.rb +18 -0
  57. data/setup/backup.rb +231 -0
  58. data/setup/backup.sqlite3 +0 -0
  59. metadata +271 -0
@@ -0,0 +1,34 @@
1
+ module Backup
2
+ module Adapters
3
+ class Archive < Backup::Adapters::Base
4
+
5
+ attr_accessor :files, :exclude
6
+
7
+ private
8
+
9
+ # Archives and Compresses all files
10
+ def perform
11
+ log system_messages[:archiving]; log system_messages[:compressing]
12
+ run "tar -czf #{File.join(tmp_path, compressed_file)} #{exclude_files} #{tar_files}"
13
+ end
14
+
15
+ def load_settings
16
+ self.files = procedure.get_adapter_configuration.attributes['files']
17
+ self.exclude = procedure.get_adapter_configuration.attributes['exclude']
18
+ end
19
+
20
+ def performed_file_extension
21
+ ".tar"
22
+ end
23
+
24
+ def tar_files
25
+ [*files].map{|f| f.gsub(' ', '\ ')}.join(' ')
26
+ end
27
+
28
+ def exclude_files
29
+ [*exclude].compact.map{|x| "--exclude=#{x}"}.join(' ')
30
+ end
31
+
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,138 @@
1
+ module Backup
2
+ module Adapters
3
+ class Base
4
+
5
+ include Backup::CommandHelper
6
+
7
+ attr_accessor :procedure, :timestamp, :options, :tmp_path, :encrypt_with_password, :encrypt_with_gpg_public_key, :keep_backups, :trigger
8
+
9
+ # IMPORTANT
10
+ # final_file must have the value of the final filename result
11
+ # so if a file gets compressed, then the file could look like this:
12
+ # myfile.gz
13
+ #
14
+ # and if a file afterwards gets encrypted, the file will look like:
15
+ # myfile.gz.enc (with a password)
16
+ # myfile.gz.gpg (with a gpg public key)
17
+ #
18
+ #
19
+ # It is important that, whatever the final filename of the file will be, that :final_file will contain it.
20
+ attr_accessor :performed_file, :compressed_file, :encrypted_file, :final_file
21
+
22
+ # Initializes the Backup Process
23
+ #
24
+ # This will first load in any prefixed settings from the Backup::Adapters::Base
25
+ # Then it will add it's own settings.
26
+ #
27
+ # First it will call the 'perform' method. This method is concerned with the backup, and must
28
+ # be implemented by derived classes!
29
+ # Then it will optionally encrypt the backed up file
30
+ # Then it will store it to the specified storage location
31
+ # Then it will record the data to the database
32
+ # Once this is all done, all the temporary files will be removed
33
+ #
34
+ # Wrapped inside of begin/ensure/end block to ensure the deletion of any files in the tmp directory
35
+ def initialize(trigger, procedure)
36
+ self.trigger = trigger
37
+ self.procedure = procedure
38
+ self.timestamp = Time.now.strftime("%Y%m%d%H%M%S")
39
+ self.tmp_path = File.join(BACKUP_PATH.gsub(' ', '\ '), 'tmp', 'backup', trigger)
40
+ self.encrypt_with_password = procedure.attributes['encrypt_with_password']
41
+ self.encrypt_with_gpg_public_key = procedure.attributes['encrypt_with_gpg_public_key']
42
+ self.keep_backups = procedure.attributes['keep_backups']
43
+
44
+ self.performed_file = "#{timestamp}.#{trigger.gsub(' ', '-')}#{performed_file_extension}"
45
+ self.compressed_file = "#{performed_file}.#{procedure.compressor_class::ARCHIVE_EXTENSION}"
46
+ self.final_file = compressed_file
47
+
48
+ begin
49
+ create_tmp_folder
50
+ load_settings # if respond_to?(:load_settings)
51
+ perform
52
+ encrypt
53
+ store
54
+ record
55
+ notify
56
+ ensure
57
+ remove_tmp_files
58
+ end
59
+ end
60
+
61
+ # Creates the temporary folder for the specified adapter
62
+ def create_tmp_folder
63
+ run "mkdir -p #{tmp_path}"
64
+ end
65
+
66
+ # TODO make methods in derived classes public? respond_to cannot identify private methods
67
+ def load_settings
68
+ end
69
+
70
+ # Removes the files inside the temporary folder
71
+ def remove_tmp_files
72
+ run "rm #{File.join(tmp_path, '*')}"
73
+ end
74
+
75
+ # Encrypts the archive file
76
+ def encrypt
77
+ if encrypt_with_gpg_public_key.is_a?(String) && encrypt_with_password.is_a?(String)
78
+ puts "both 'encrypt_with_gpg_public_key' and 'encrypt_with_password' are set. Please choose one or the other. Exiting."
79
+ exit 1
80
+ end
81
+
82
+ if encrypt_with_gpg_public_key.is_a?(String)
83
+ if `which gpg` == ''
84
+ puts "Encrypting with a GPG public key requires that gpg be in your public path. gpg was not found. Exiting"
85
+ exit 1
86
+ end
87
+ log system_messages[:encrypting_w_key]
88
+ self.encrypted_file = "#{self.final_file}.gpg"
89
+
90
+ # tmp_file = Tempfile.new('backup.pub'){ |tmp_file| tmp_file << encrypt_with_gpg_public_key }
91
+ tmp_file = Tempfile.new('backup.pub')
92
+ tmp_file << encrypt_with_gpg_public_key
93
+ tmp_file.close
94
+ # that will either say the key was added OR that it wasn't needed, but either way we need to parse for the uid
95
+ # which will be wrapped in '<' and '>' like <sweetspot-backup2007@6bar8.com>
96
+ encryptionKeyId = `gpg --import #{tmp_file.path} 2>&1`.match(/<(.+)>/)[1]
97
+ run "gpg -e --trust-model always -o #{File.join(tmp_path, encrypted_file)} -r '#{encryptionKeyId}' #{File.join(tmp_path, compressed_file)}"
98
+ elsif encrypt_with_password.is_a?(String)
99
+ log system_messages[:encrypting_w_pass]
100
+ self.encrypted_file = "#{self.final_file}.enc"
101
+ run "openssl enc -des-cbc -in #{File.join(tmp_path, compressed_file)} -out #{File.join(tmp_path, encrypted_file)} -k #{encrypt_with_password}"
102
+ end
103
+ self.final_file = encrypted_file if encrypted_file
104
+ end
105
+
106
+ # Initializes the storing process
107
+ def store
108
+ procedure.initialize_storage(self)
109
+ end
110
+
111
+ # Records data on every individual file to the database
112
+ def record
113
+ record = procedure.initialize_record
114
+ record.load_adapter(self)
115
+ record.save
116
+ end
117
+
118
+ # Delivers a notification by email regarding the successfully stored backup
119
+ def notify
120
+ if Backup::Mail::Base.setup?
121
+ Backup::Mail::Base.notify!(self)
122
+ end
123
+ end
124
+
125
+ def system_messages
126
+ { :compressing => "Compressing backup..",
127
+ :archiving => "Archiving backup..",
128
+ :encrypting_w_pass => "Encrypting backup with password..",
129
+ :encrypting_w_key => "Encrypting backup with gpg public key..",
130
+ :mysqldump => "Creating MySQL dump..",
131
+ :pgdump => "Creating PostgreSQL dump..",
132
+ :sqlite => "Copying and compressing SQLite database..",
133
+ :commands => "Executing commands.." }
134
+ end
135
+
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,41 @@
1
+ module Backup
2
+ module Adapters
3
+ class Custom < Backup::Adapters::Base
4
+
5
+ attr_accessor :commands
6
+
7
+ private
8
+
9
+ # Execute any given commands, then archive and compress every folder/file
10
+ def perform
11
+ execute_commands
12
+ targz
13
+ end
14
+
15
+ # Executes the commands
16
+ def execute_commands
17
+ return unless commands
18
+ log system_messages[:commands]
19
+ [*commands].each do |command|
20
+ run "#{command.gsub(':tmp_path', tmp_path)}"
21
+ end
22
+ end
23
+
24
+ # Archives and Compresses
25
+ def targz
26
+ log system_messages[:archiving]; log system_messages[:compressing]
27
+ run "tar -czf #{File.join(tmp_path, compressed_file)} #{File.join(tmp_path, '*')}"
28
+ end
29
+
30
+ def performed_file_extension
31
+ ".tar"
32
+ end
33
+
34
+ # Loads the initial settings
35
+ def load_settings
36
+ self.commands = procedure.get_adapter_configuration.attributes['commands']
37
+ end
38
+
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,60 @@
1
+ module Backup
2
+ module Adapters
3
+ class MySQL < Backup::Adapters::Base
4
+
5
+ attr_accessor :user, :password, :database, :skip_tables, :host, :port, :socket, :additional_options, :tables
6
+
7
+ private
8
+
9
+ # Dumps and Compresses the MySQL file
10
+ def perform
11
+ log system_messages[:mysqldump]; log system_messages[:compressing]
12
+ run "#{mysqldump} -u #{user} --password='#{password}' #{options} #{additional_options} #{database} #{tables_to_include} #{tables_to_skip} | #{procedure.compressor_class::COMPRESS_COMMAND} #{File.join(tmp_path, compressed_file)}"
13
+ end
14
+
15
+ def mysqldump
16
+ # try to determine the full path, and fall back to myqsldump if not found
17
+ cmd = `which mysqldump`.chomp
18
+ cmd = 'mysqldump' if cmd.empty?
19
+ cmd
20
+ end
21
+
22
+ def performed_file_extension
23
+ ".sql"
24
+ end
25
+
26
+ # Loads the initial settings
27
+ def load_settings
28
+ %w(user password database tables skip_tables additional_options).each do |attribute|
29
+ send(:"#{attribute}=", procedure.get_adapter_configuration.attributes[attribute])
30
+ end
31
+
32
+ %w(host port socket).each do |attribute|
33
+ send(:"#{attribute}=", procedure.get_adapter_configuration.get_options.attributes[attribute])
34
+ end
35
+ end
36
+
37
+ # Returns a list of options in MySQL syntax
38
+ def options
39
+ options = String.new
40
+ options += " --host='#{host}' " unless host.blank?
41
+ options += " --port='#{port}' " unless port.blank?
42
+ options += " --socket='#{socket}' " unless socket.blank?
43
+ options
44
+ end
45
+
46
+ # Returns a list of tables to skip in MySQL syntax
47
+ def tables_to_skip
48
+ return "" unless skip_tables
49
+ [*skip_tables].map {|table| " --ignore-table='#{database}.#{table}' "}
50
+ end
51
+
52
+ # Returns a list of tables to include in MySQL syntax
53
+ def tables_to_include
54
+ return "" unless tables
55
+ [*tables].join(" ")
56
+ end
57
+
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,56 @@
1
+ module Backup
2
+ module Adapters
3
+ class PostgreSQL < Backup::Adapters::Base
4
+
5
+ attr_accessor :user, :password, :database, :skip_tables, :host, :port, :socket, :additional_options
6
+
7
+ private
8
+
9
+ # Dumps and Compresses the PostgreSQL file
10
+ def perform
11
+ log system_messages[:pgdump]; log system_messages[:compressing]
12
+ ENV['PGPASSWORD'] = password
13
+ run "#{pg_dump} -U #{user} #{options} #{additional_options} #{tables_to_skip} #{database} | gzip -f --best > #{File.join(tmp_path, compressed_file)}"
14
+ ENV['PGPASSWORD'] = nil
15
+ end
16
+
17
+ def pg_dump
18
+ # try to determine the full path, and fall back to pg_dump if not found
19
+ cmd = `which pg_dump`.chomp
20
+ cmd = 'pg_dump' if cmd.empty?
21
+ cmd
22
+ end
23
+
24
+ def performed_file_extension
25
+ ".sql"
26
+ end
27
+
28
+ # Loads the initial settings
29
+ def load_settings
30
+ %w(user password database skip_tables additional_options).each do |attribute|
31
+ send(:"#{attribute}=", procedure.get_adapter_configuration.attributes[attribute])
32
+ end
33
+
34
+ %w(host port socket).each do |attribute|
35
+ send(:"#{attribute}=", procedure.get_adapter_configuration.get_options.attributes[attribute])
36
+ end
37
+ end
38
+
39
+ # Returns a list of options in PostgreSQL syntax
40
+ def options
41
+ options = String.new
42
+ options += " --port='#{port}' " unless port.blank?
43
+ options += " --host='#{host}' " unless host.blank?
44
+ options += " --host='#{socket}' " unless socket.blank? unless options.include?('--host=')
45
+ options
46
+ end
47
+
48
+ # Returns a list of tables to skip in PostgreSQL syntax
49
+ def tables_to_skip
50
+ return "" unless skip_tables
51
+ [*skip_tables].map {|table| " -T \"#{table}\" "}
52
+ end
53
+
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,25 @@
1
+ module Backup
2
+ module Adapters
3
+ class SQLite < Base
4
+
5
+ attr_accessor :database
6
+
7
+ private
8
+
9
+ # Compress the sqlite file
10
+ def perform
11
+ log system_messages[:sqlite]
12
+ run "gzip -c --best #{database} > #{File.join(tmp_path, compressed_file)}"
13
+ end
14
+
15
+ def load_settings
16
+ self.database = procedure.get_adapter_configuration.attributes['database']
17
+ end
18
+
19
+ def performed_file_extension
20
+ ""
21
+ end
22
+
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,11 @@
1
+ module Backup
2
+ module CommandHelper
3
+ def run(command)
4
+ Kernel.system command
5
+ end
6
+
7
+ def log(command)
8
+ puts "Backup (#{Time.now.strftime("%Y-%m-%d %H:%M:%S %Z")}) => #{command}"
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,7 @@
1
+ module Backup
2
+ module Compressors
3
+ class Base
4
+
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ module Backup
2
+ module Compressors
3
+ class Gzip < Base
4
+ ARCHIVE_EXTENSION = 'gz'
5
+ COMPRESS_COMMAND = 'gzip -f --best > '
6
+
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Backup
2
+ module Compressors
3
+ class SevenZip < Base
4
+ ARCHIVE_EXTENSION = '7z'
5
+ COMPRESS_COMMAND = '7za a -si -m0=PPMd -t7z '
6
+
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,21 @@
1
+ module Backup
2
+ module Configuration
3
+ class Adapter
4
+ extend Backup::Configuration::Attributes
5
+ generate_attributes %w(files exclude user password database tables skip_tables commands additional_options)
6
+
7
+ def initialize
8
+ @options = Backup::Configuration::AdapterOptions.new
9
+ end
10
+
11
+ def options(&block)
12
+ @options.instance_eval &block
13
+ end
14
+
15
+ def get_options
16
+ @options
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,8 @@
1
+ module Backup
2
+ module Configuration
3
+ class AdapterOptions
4
+ extend Backup::Configuration::Attributes
5
+ generate_attributes %w(host port socket)
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,19 @@
1
+ module Backup
2
+ module Configuration
3
+ module Attributes
4
+
5
+ def generate_attributes(*attrs)
6
+ define_method :attributes do
7
+ @attributes ||= {}
8
+ end
9
+
10
+ attrs.flatten.each do |att|
11
+ define_method att do |value|
12
+ self.attributes[att.to_s] = value
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+