dmitryv-backup 2.4.0

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