backup 1.3.4 → 2.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.
- data/README.rdoc +121 -53
- data/Rakefile +3 -8
- data/VERSION +1 -1
- data/backup.gemspec +20 -34
- data/generators/backup/backup_generator.rb +31 -0
- data/generators/backup/templates/config/backup.rb +60 -0
- data/generators/backup/templates/migrations/create_backup_tables.rb +24 -0
- data/generators/backup/templates/tasks/backup.rake +42 -0
- data/lib/backup.rb +64 -12
- data/lib/backup/adapters/archive.rb +53 -0
- data/lib/backup/adapters/base.rb +65 -0
- data/lib/backup/adapters/mysql.rb +48 -0
- data/lib/backup/configuration/adapter.rb +17 -0
- data/lib/backup/configuration/base.rb +38 -0
- data/lib/backup/configuration/helpers.rb +12 -0
- data/lib/backup/configuration/storage.rb +17 -0
- data/lib/backup/connection/s3.rb +28 -13
- data/lib/backup/record/s3.rb +90 -0
- data/lib/backup/record/scp.rb +92 -0
- data/lib/backup/storage/s3.rb +14 -0
- data/lib/backup/storage/scp.rb +28 -0
- metadata +18 -35
- data/generators/backup_files/backup_files_generator.rb +0 -72
- data/generators/backup_files/templates/backup.sqlite3 +0 -0
- data/generators/backup_files/templates/config.rake +0 -20
- data/generators/backup_files/templates/db.rake +0 -62
- data/generators/backup_files/templates/s3.rake +0 -231
- data/generators/backup_files/templates/s3.yml +0 -120
- data/generators/backup_files/templates/setup.rake +0 -28
- data/generators/backup_files/templates/ssh.rake +0 -226
- data/generators/backup_files/templates/ssh.yml +0 -119
- data/lib/backup/adapter/assets.rb +0 -57
- data/lib/backup/adapter/custom.rb +0 -91
- data/lib/backup/adapter/mysql.rb +0 -65
- data/lib/backup/adapter/sqlite3.rb +0 -49
- data/lib/backup/backup_record/s3.rb +0 -90
- data/lib/backup/backup_record/ssh.rb +0 -90
- data/lib/backup/base.rb +0 -92
- data/lib/backup/connection/base.rb +0 -13
- data/lib/backup/connection/ssh.rb +0 -19
- data/lib/backup/encrypt.rb +0 -18
- data/lib/backup/transfer/base.rb +0 -13
- data/lib/backup/transfer/s3.rb +0 -36
- data/lib/backup/transfer/ssh.rb +0 -30
@@ -0,0 +1,24 @@
|
|
1
|
+
class CreateBackupTables < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :backup_s3 do |t|
|
4
|
+
t.string :trigger
|
5
|
+
t.string :adapter
|
6
|
+
t.string :filename
|
7
|
+
t.string :bucket
|
8
|
+
t.timestamps
|
9
|
+
end
|
10
|
+
|
11
|
+
create_table :backup_scp do |t|
|
12
|
+
t.string :trigger
|
13
|
+
t.string :adapter
|
14
|
+
t.string :filename
|
15
|
+
t.string :path
|
16
|
+
t.timestamps
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.down
|
21
|
+
drop_table :backup_s3
|
22
|
+
drop_table :backup_scp
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
namespace :backup do
|
2
|
+
|
3
|
+
desc "Run Backup Procedure."
|
4
|
+
task :run => :environment do
|
5
|
+
Backup::Setup.new(ENV['trigger'], @backup_procedures).initialize_adapter
|
6
|
+
end
|
7
|
+
|
8
|
+
desc "Truncates all records for the specified \"trigger\", excluding the physical files on s3 or the remote server."
|
9
|
+
task :truncate => :environment do
|
10
|
+
backup = Backup::Setup.new(ENV['trigger'], @backup_procedures)
|
11
|
+
case backup.procedure.storage_name.to_sym
|
12
|
+
when :s3 then Backup::Record::S3.destroy_all(:trigger => ENV['trigger'])
|
13
|
+
when :scp then Backup::Record::SCP.destroy_all(:trigger => ENV['trigger'])
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
desc "Truncates everything."
|
18
|
+
task :truncate_all => :environment do
|
19
|
+
Backup::Record::S3.destroy_all
|
20
|
+
Backup::Record::SCP.destroy_all
|
21
|
+
end
|
22
|
+
|
23
|
+
desc "Destroys all records for the specified \"trigger\", including the physical files on s3 or the remote server."
|
24
|
+
task :destroy => :environment do
|
25
|
+
backup = Backup::Setup.new(ENV['trigger'], @backup_procedures)
|
26
|
+
case backup.procedure.storage_name.to_sym
|
27
|
+
when :s3 then Backup::Record::S3.destroy_all_backups(backup.procedure, ENV['trigger'])
|
28
|
+
when :scp then Backup::Record::SCP.destroy_all_backups(backup.procedure, ENV['trigger'])
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
desc "Destroys all records for the specified \"trigger\", including the physical files on s3 or the remote server."
|
33
|
+
task :destroy_all => :environment do
|
34
|
+
@backup_procedures.each do |backup_procedure|
|
35
|
+
case backup_procedure.storage_name.to_sym
|
36
|
+
when :s3 then Backup::Record::S3.destroy_all_backups(backup_procedure, backup_procedure.trigger)
|
37
|
+
when :scp then Backup::Record::SCP.destroy_all_backups(backup_procedure, backup_procedure.trigger)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
data/lib/backup.rb
CHANGED
@@ -1,16 +1,68 @@
|
|
1
|
-
|
2
|
-
require '
|
3
|
-
require '
|
4
|
-
require '
|
5
|
-
require '
|
6
|
-
|
7
|
-
|
8
|
-
require 'backup/
|
9
|
-
require 'backup/
|
10
|
-
require 'backup/
|
1
|
+
# Connectivity and Record Gems
|
2
|
+
require 'net/ssh'
|
3
|
+
require 'net/scp'
|
4
|
+
require 'aws/s3'
|
5
|
+
require 'sqlite3'
|
6
|
+
|
7
|
+
# Load in Adapters
|
8
|
+
require 'backup/adapters/base'
|
9
|
+
require 'backup/adapters/mysql'
|
10
|
+
require 'backup/adapters/archive'
|
11
|
+
|
12
|
+
# Load in Connectors
|
11
13
|
require 'backup/connection/s3'
|
12
|
-
require 'backup/connection/ssh'
|
13
|
-
require 'backup/backup_record/s3'
|
14
14
|
|
15
|
+
# Load in Storage
|
16
|
+
require 'backup/storage/s3'
|
17
|
+
require 'backup/storage/scp'
|
18
|
+
|
19
|
+
# Load in Backup Recorders
|
20
|
+
require 'backup/record/s3'
|
21
|
+
require 'backup/record/scp'
|
22
|
+
|
23
|
+
# Load in Configuration
|
24
|
+
require 'backup/configuration/base'
|
25
|
+
require 'backup/configuration/adapter'
|
26
|
+
require 'backup/configuration/storage'
|
27
|
+
require 'backup/configuration/helpers'
|
28
|
+
|
29
|
+
# Load Backup Configuration Helpers
|
30
|
+
include Backup::Configuration::Helpers
|
31
|
+
|
32
|
+
# Load in User Configured Backup Procedures if the file exists
|
33
|
+
if File.exist?(File.join(RAILS_ROOT, 'config', 'backup.rb'))
|
34
|
+
require File.join(RAILS_ROOT, 'config', 'backup.rb')
|
35
|
+
end
|
36
|
+
|
37
|
+
# Backup Module
|
15
38
|
module Backup
|
39
|
+
class Setup
|
40
|
+
|
41
|
+
attr_accessor :trigger, :procedures, :procedure
|
42
|
+
|
43
|
+
def initialize(trigger, procedures)
|
44
|
+
self.trigger = trigger
|
45
|
+
self.procedures = procedures
|
46
|
+
self.procedure = find_triggered_procedure
|
47
|
+
end
|
48
|
+
|
49
|
+
def initialize_adapter
|
50
|
+
case procedure.adapter_name.to_sym
|
51
|
+
when :mysql then Backup::Adapters::MySQL.new(trigger, procedure)
|
52
|
+
when :archive then Backup::Adapters::Archive.new(trigger, procedure)
|
53
|
+
else raise "Unknown Adapter: \"#{procedure.adapter_name}\""
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def find_triggered_procedure
|
58
|
+
procedures.each do |procedure|
|
59
|
+
if procedure.trigger.eql?(trigger)
|
60
|
+
return procedure
|
61
|
+
end
|
62
|
+
end
|
63
|
+
available_triggers = procedures.each.map {|procedure| "- #{procedure.trigger}\n" }
|
64
|
+
raise "Could not find a backup procedure with the trigger \"#{trigger}\". \nHere's a list of available triggers:\n#{available_triggers}"
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
16
68
|
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Backup
|
2
|
+
module Adapters
|
3
|
+
class Archive < Backup::Adapters::Base
|
4
|
+
|
5
|
+
attr_accessor :archived_file, :compressed_file, :encrypted_file, :user, :password, :database
|
6
|
+
|
7
|
+
# Initializes the Backup Process
|
8
|
+
def initialize(trigger, procedure)
|
9
|
+
super
|
10
|
+
load_settings
|
11
|
+
|
12
|
+
targz
|
13
|
+
encrypt
|
14
|
+
store
|
15
|
+
record
|
16
|
+
remove_tmp_files
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
# Archives and Compresses all files
|
22
|
+
def targz
|
23
|
+
files = procedure.get_adapter_configuration.attributes['files']
|
24
|
+
if files.is_a?(Array)
|
25
|
+
%x{ tar -czf #{File.join(tmp_path, compressed_file)} #{files.map{|f| f.gsub(' ', '\ ')}.join(' ')} }
|
26
|
+
elsif files.is_a?(String)
|
27
|
+
%x{ tar -czf #{File.join(tmp_path, compressed_file)} #{files.gsub(' ', '\ ')} }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Encrypts the Archive
|
32
|
+
def encrypt
|
33
|
+
if encrypt_with_password.is_a?(String)
|
34
|
+
%x{ openssl enc -des-cbc -in #{File.join(tmp_path, compressed_file)} -out #{File.join(tmp_path, encrypted_file)} -k #{encrypt_with_password} }
|
35
|
+
self.final_file = encrypted_file
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Loads the initial settings
|
40
|
+
def load_settings
|
41
|
+
self.user = procedure.get_adapter_configuration.attributes['user']
|
42
|
+
self.password = procedure.get_adapter_configuration.attributes['password']
|
43
|
+
self.database = procedure.get_adapter_configuration.attributes['database']
|
44
|
+
|
45
|
+
self.archived_file = "#{timestamp}.archive.#{trigger.gsub(' ', '-')}.tar"
|
46
|
+
self.compressed_file = "#{archived_file}.gz"
|
47
|
+
self.encrypted_file = "#{compressed_file}.enc"
|
48
|
+
self.final_file = compressed_file
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Backup
|
2
|
+
module Adapters
|
3
|
+
class Base
|
4
|
+
|
5
|
+
attr_accessor :procedure, :timestamp, :options, :tmp_path, :encrypt_with_password, :keep_backups, :trigger
|
6
|
+
|
7
|
+
# IMPORTANT
|
8
|
+
# final_file must have the value of the final filename result
|
9
|
+
# so if a file gets compressed, then the file could look like this:
|
10
|
+
# myfile.gz
|
11
|
+
#
|
12
|
+
# and if a file afterwards gets encrypted, the file will look like:
|
13
|
+
# myfile.gz.enc
|
14
|
+
#
|
15
|
+
# It is important that, whatever the final filename of the file will be, that :final_file will contain it.
|
16
|
+
attr_accessor :final_file
|
17
|
+
|
18
|
+
def initialize(trigger, procedure)
|
19
|
+
self.trigger = trigger
|
20
|
+
self.procedure = procedure
|
21
|
+
self.timestamp = Time.now.strftime("%Y%m%d%H%M%S")
|
22
|
+
self.tmp_path = "#{RAILS_ROOT.gsub(' ', '\ ')}/tmp/backup/#{procedure.adapter_name}"
|
23
|
+
self.encrypt_with_password = procedure.attributes['encrypt_with_password']
|
24
|
+
self.keep_backups = procedure.attributes['keep_backups']
|
25
|
+
create_tmp_folder
|
26
|
+
end
|
27
|
+
|
28
|
+
# Creates the temporary folder for the specified adapter
|
29
|
+
def create_tmp_folder
|
30
|
+
%x{ mkdir -p #{tmp_path} }
|
31
|
+
end
|
32
|
+
|
33
|
+
# Removes the files inside the temporary folder
|
34
|
+
def remove_tmp_files
|
35
|
+
%x{ rm #{tmp_path}/* }
|
36
|
+
end
|
37
|
+
|
38
|
+
# Initializes the storing process depending on the store settings
|
39
|
+
# Options:
|
40
|
+
# Amazon (S3)
|
41
|
+
# Remote Server (SCP)
|
42
|
+
def store
|
43
|
+
case procedure.storage_name.to_sym
|
44
|
+
when :s3 then Backup::Storage::S3.new(self)
|
45
|
+
when :scp then Backup::Storage::SCP.new(self)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Records data on every individual file to the backup.sqlite3 local database
|
50
|
+
def record
|
51
|
+
case procedure.storage_name.to_sym
|
52
|
+
when :s3
|
53
|
+
record = Backup::Record::S3.new
|
54
|
+
record.load_adapter(self)
|
55
|
+
record.save
|
56
|
+
when :scp
|
57
|
+
record = Backup::Record::SCP.new
|
58
|
+
record.load_adapter(self)
|
59
|
+
record.save
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Backup
|
2
|
+
module Adapters
|
3
|
+
class MySQL < Backup::Adapters::Base
|
4
|
+
|
5
|
+
attr_accessor :dumped_file, :compressed_file, :encrypted_file, :user, :password, :database
|
6
|
+
|
7
|
+
# Initializes the Backup Process
|
8
|
+
def initialize(trigger, procedure)
|
9
|
+
super
|
10
|
+
load_settings
|
11
|
+
|
12
|
+
mysqldump
|
13
|
+
encrypt
|
14
|
+
store
|
15
|
+
record
|
16
|
+
remove_tmp_files
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
# Dumps and Compresses the MySQL file
|
22
|
+
def mysqldump
|
23
|
+
%x{ mysqldump -u #{user} --password='#{password}' #{database} | gzip -f --best > #{File.join(tmp_path, compressed_file)} }
|
24
|
+
end
|
25
|
+
|
26
|
+
# Encrypts the MySQL file
|
27
|
+
def encrypt
|
28
|
+
if encrypt_with_password.is_a?(String)
|
29
|
+
%x{ openssl enc -des-cbc -in #{File.join(tmp_path, compressed_file)} -out #{File.join(tmp_path, encrypted_file)} -k #{encrypt_with_password} }
|
30
|
+
self.final_file = encrypted_file
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Loads the initial settings
|
35
|
+
def load_settings
|
36
|
+
self.user = procedure.get_adapter_configuration.attributes['user']
|
37
|
+
self.password = procedure.get_adapter_configuration.attributes['password']
|
38
|
+
self.database = procedure.get_adapter_configuration.attributes['database']
|
39
|
+
|
40
|
+
self.dumped_file = "#{timestamp}.#{database}.sql"
|
41
|
+
self.compressed_file = "#{dumped_file}.gz"
|
42
|
+
self.encrypted_file = "#{compressed_file}.enc"
|
43
|
+
self.final_file = compressed_file
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Backup
|
2
|
+
module Configuration
|
3
|
+
class Adapter
|
4
|
+
attr_accessor :attributes
|
5
|
+
|
6
|
+
%w(files user password database).each do |method|
|
7
|
+
define_method method do |value|
|
8
|
+
attributes[method] = value
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@attributes = {}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Backup
|
2
|
+
module Configuration
|
3
|
+
class Base
|
4
|
+
attr_accessor :attributes, :trigger, :storage_name, :adapter_name
|
5
|
+
|
6
|
+
%w(encrypt_with_password keep_backups).each do |method|
|
7
|
+
define_method method do |value|
|
8
|
+
attributes[method] = value
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(trigger)
|
13
|
+
@attributes = {}
|
14
|
+
@trigger = trigger
|
15
|
+
@adapter_configuration = Backup::Configuration::Adapter.new
|
16
|
+
@storage_configuration = Backup::Configuration::Storage.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def adapter(adapter, &block)
|
20
|
+
@adapter_name = adapter
|
21
|
+
@adapter_configuration.instance_eval &block
|
22
|
+
end
|
23
|
+
|
24
|
+
def storage(storage, &block)
|
25
|
+
@storage_name = storage
|
26
|
+
@storage_configuration.instance_eval &block
|
27
|
+
end
|
28
|
+
|
29
|
+
def get_adapter_configuration
|
30
|
+
@adapter_configuration
|
31
|
+
end
|
32
|
+
|
33
|
+
def get_storage_configuration
|
34
|
+
@storage_configuration
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Backup
|
2
|
+
module Configuration
|
3
|
+
module Helpers
|
4
|
+
def backup(trigger, &block)
|
5
|
+
backup = Backup::Configuration::Base.new(trigger)
|
6
|
+
backup.instance_eval &block
|
7
|
+
@backup_procedures ||= Array.new
|
8
|
+
@backup_procedures << backup
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Backup
|
2
|
+
module Configuration
|
3
|
+
class Storage
|
4
|
+
attr_accessor :attributes
|
5
|
+
|
6
|
+
%w(ip user password path access_key_id secret_access_key bucket).each do |method|
|
7
|
+
define_method method do |value|
|
8
|
+
attributes[method] = value
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@attributes = {}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/backup/connection/s3.rb
CHANGED
@@ -1,18 +1,32 @@
|
|
1
1
|
module Backup
|
2
2
|
module Connection
|
3
|
-
class S3
|
3
|
+
class S3
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
5
|
+
attr_accessor :adapter, :access_key_id, :secret_access_key, :s3_bucket, :final_file, :tmp_path
|
6
|
+
|
7
|
+
# Initializes the S3 connection, setting the values using the S3 adapter
|
8
|
+
def initialize(adapter = false)
|
9
|
+
if adapter
|
10
|
+
self.final_file = adapter.final_file
|
11
|
+
self.tmp_path = adapter.tmp_path.gsub('\ ', ' ')
|
12
|
+
self.access_key_id = adapter.procedure.get_storage_configuration.attributes['access_key_id']
|
13
|
+
self.secret_access_key = adapter.procedure.get_storage_configuration.attributes['secret_access_key']
|
14
|
+
self.s3_bucket = adapter.procedure.get_storage_configuration.attributes['bucket']
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Sets values from a procedure, rather than from the adapter object
|
19
|
+
def static_initialize(procedure)
|
20
|
+
self.access_key_id = procedure.get_storage_configuration.attributes['access_key_id']
|
21
|
+
self.secret_access_key = procedure.get_storage_configuration.attributes['secret_access_key']
|
22
|
+
self.s3_bucket = procedure.get_storage_configuration.attributes['bucket']
|
9
23
|
end
|
10
24
|
|
11
25
|
# Establishes a connection with Amazon S3 using the credentials provided by the user
|
12
26
|
def connect
|
13
27
|
AWS::S3::Base.establish_connection!(
|
14
|
-
:access_key_id =>
|
15
|
-
:secret_access_key =>
|
28
|
+
:access_key_id => access_key_id,
|
29
|
+
:secret_access_key => secret_access_key
|
16
30
|
)
|
17
31
|
end
|
18
32
|
|
@@ -33,17 +47,18 @@ module Backup
|
|
33
47
|
|
34
48
|
# Initializes the file transfer to Amazon S3
|
35
49
|
# This can only run after a connection has been made using the #connect method
|
36
|
-
def
|
50
|
+
def store
|
51
|
+
puts "Storing \"#{final_file}\" to bucket \"#{s3_bucket}\" on Amazon S3."
|
37
52
|
object.store(
|
38
|
-
|
39
|
-
open(File.join(
|
40
|
-
|
53
|
+
final_file,
|
54
|
+
open(File.join(tmp_path, final_file)),
|
55
|
+
s3_bucket )
|
41
56
|
end
|
42
57
|
|
43
58
|
# Destroys file from a bucket on Amazon S3
|
44
|
-
def destroy(
|
59
|
+
def destroy(file, bucket)
|
45
60
|
object.delete(
|
46
|
-
|
61
|
+
file,
|
47
62
|
bucket )
|
48
63
|
end
|
49
64
|
|