dmitryv-backup 2.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +125 -0
- data/LICENSE +20 -0
- data/README.md +180 -0
- data/VERSION +1 -0
- data/bin/backup +108 -0
- data/generators/backup/backup_generator.rb +69 -0
- data/generators/backup/templates/backup.rake +56 -0
- data/generators/backup/templates/backup.rb +229 -0
- data/generators/backup/templates/create_backup_tables.rb +18 -0
- data/generators/backup_update/backup_update_generator.rb +50 -0
- data/generators/backup_update/templates/migrations/update_backup_tables.rb +27 -0
- data/lib/backup.rb +131 -0
- data/lib/backup/adapters/archive.rb +34 -0
- data/lib/backup/adapters/base.rb +138 -0
- data/lib/backup/adapters/custom.rb +41 -0
- data/lib/backup/adapters/mysql.rb +60 -0
- data/lib/backup/adapters/postgresql.rb +56 -0
- data/lib/backup/adapters/sqlite.rb +25 -0
- data/lib/backup/command_helper.rb +11 -0
- data/lib/backup/compressors/base.rb +7 -0
- data/lib/backup/compressors/gzip.rb +9 -0
- data/lib/backup/compressors/seven_zip.rb +9 -0
- data/lib/backup/configuration/adapter.rb +21 -0
- data/lib/backup/configuration/adapter_options.rb +8 -0
- data/lib/backup/configuration/attributes.rb +19 -0
- data/lib/backup/configuration/base.rb +77 -0
- data/lib/backup/configuration/helpers.rb +24 -0
- data/lib/backup/configuration/mail.rb +20 -0
- data/lib/backup/configuration/smtp.rb +8 -0
- data/lib/backup/configuration/storage.rb +8 -0
- data/lib/backup/connection/cloudfiles.rb +75 -0
- data/lib/backup/connection/s3.rb +85 -0
- data/lib/backup/environment/base.rb +12 -0
- data/lib/backup/environment/rails_configuration.rb +15 -0
- data/lib/backup/environment/unix_configuration.rb +109 -0
- data/lib/backup/mail/base.rb +97 -0
- data/lib/backup/mail/mail.txt +7 -0
- data/lib/backup/record/base.rb +65 -0
- data/lib/backup/record/cloudfiles.rb +28 -0
- data/lib/backup/record/ftp.rb +39 -0
- data/lib/backup/record/local.rb +26 -0
- data/lib/backup/record/s3.rb +26 -0
- data/lib/backup/record/scp.rb +33 -0
- data/lib/backup/record/sftp.rb +38 -0
- data/lib/backup/storage/base.rb +10 -0
- data/lib/backup/storage/cloudfiles.rb +16 -0
- data/lib/backup/storage/ftp.rb +38 -0
- data/lib/backup/storage/local.rb +22 -0
- data/lib/backup/storage/s3.rb +17 -0
- data/lib/backup/storage/scp.rb +30 -0
- data/lib/backup/storage/sftp.rb +31 -0
- data/lib/generators/backup/USAGE +10 -0
- data/lib/generators/backup/backup_generator.rb +47 -0
- data/lib/generators/backup/templates/backup.rake +56 -0
- data/lib/generators/backup/templates/backup.rb +229 -0
- data/lib/generators/backup/templates/create_backup_tables.rb +18 -0
- data/setup/backup.rb +231 -0
- data/setup/backup.sqlite3 +0 -0
- 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,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,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
|
+
|