simple_backup 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 395191b038aacd9796ee4de7eaceeb967cf5d0af
4
+ data.tar.gz: 5e3e87f048a2d0e56cf3736d66fbec5b20feb45c
5
+ SHA512:
6
+ metadata.gz: 0d808a36f27b75acd742c449e45d53f87b8ccb56f447cc15a1268794eebe5258b73a581a88f8f1df2f62adf9643f76ca3d8d702b1550425bda3c8bd1eacf6d69
7
+ data.tar.gz: 5b698daddf93a0356c0c5437b8daf8a51d3df5aca6d5c8b955bd644c98de395ed7d713a98e0b05ff81f6d9ab1d0eba8e4196c2a28576841a5dc721f17748bc7f
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ /pkg/
2
+ /Gemfile.lock
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/backup_example.rb ADDED
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'simple_backup'
4
+
5
+ SimpleBackup.run do
6
+ # log_level :debug
7
+ backup_dir '/backup'
8
+ high_usage_treshold 0.9
9
+ check_disk_path '/'
10
+ check_disk_path '/backup'
11
+ check_disk_path '/home/app'
12
+
13
+ apps do
14
+ keep_last 9
15
+
16
+ app '/home/app/app-1', type: :capistrano
17
+ app '/home/app/app-2', type: :bare
18
+ end
19
+
20
+ mysql do
21
+ keep_last 9
22
+
23
+ host 'localhost'
24
+ port 3306
25
+ user 'backup'
26
+ pass 'backup'
27
+ db 'test1'
28
+ db 'test2', exclude_tables: ['t_test1']
29
+ end
30
+
31
+ mailer do
32
+ subject_prefix '[BACKUP]'
33
+
34
+ from 'backup@localhost'
35
+ to 'root@localhost'
36
+ cc 'root@localhost'
37
+ bcc 'root@localhost'
38
+ end
39
+ end
@@ -0,0 +1,121 @@
1
+ module SimpleBackup
2
+ TIMESTAMP = Time.new.strftime('%Y%m%d%H%M%S')
3
+ @@status = :failed
4
+
5
+ def self.status
6
+ @@status
7
+ end
8
+
9
+ def self.run(&block)
10
+ Logger::info "Backup #{TIMESTAMP} started"
11
+
12
+ dsl = DSL.new
13
+
14
+ Logger::scope_start :info, "Configuration"
15
+ dsl.instance_eval(&block)
16
+ dsl.prepare
17
+ Logger::scope_end
18
+
19
+ dsl.run
20
+ dsl.cleanup
21
+ @@status = :succeed
22
+
23
+ Logger::info "Backup #{TIMESTAMP} finished"
24
+ rescue StandardError => e
25
+ self.handle_exception(e)
26
+ ensure
27
+ dsl.notify
28
+ Logger::info "Notifications for backup #{TIMESTAMP} finished"
29
+ end
30
+
31
+ def self.handle_exception(e)
32
+ Logger::error "#{e.class} => #{e.message}"
33
+ Logger::error "Backup #{TIMESTAMP} failed"
34
+ STDERR.puts "Error @ #{Time.new.strftime('%Y-%m-%dT%H:%M:%S')}"
35
+ STDERR.puts "#{e.inspect}"
36
+ STDERR.puts e.backtrace
37
+ end
38
+
39
+ class DSL
40
+ def initialize
41
+ @storage = Storage.new
42
+ end
43
+
44
+ def prepare
45
+ if @apps_block
46
+ @apps = Engine::Apps.new
47
+ @apps.storage = @storage
48
+ @apps.instance_eval(&@apps_block)
49
+ end
50
+
51
+ if @mysql_block
52
+ @mysql = Engine::MySQL.new
53
+ @mysql.storage = @storage
54
+ @mysql.instance_eval(&@mysql_block)
55
+ end
56
+ end
57
+
58
+ def run
59
+ usage = Utils::Disk::usage
60
+ Logger::error "Disk high usage treshold exceeded #{usage[:high_usage]}" if usage[:high_usage_exceeded]
61
+
62
+ Logger::scope_start :info, "Backup job"
63
+ @apps.backup if @apps
64
+ @mysql.backup if @mysql
65
+ Logger::scope_end
66
+ end
67
+
68
+ def cleanup
69
+ Logger::scope_start :info, "Cleanup job"
70
+ @apps.cleanup if @apps
71
+ @mysql.cleanup if @mysql
72
+ Logger::scope_end
73
+ end
74
+
75
+ def notify
76
+ @mailer.send if @mailer
77
+ rescue StandardError => e
78
+ SimpleBackup.handle_exception(e)
79
+ end
80
+
81
+ def sources
82
+ sources = {}
83
+ sources[:apps] = @apps.sources
84
+ sources[:mysql] = @mysql.sources
85
+
86
+ sources
87
+ end
88
+
89
+ def log_level(level)
90
+ Logger::level = level
91
+ end
92
+
93
+ def backup_dir(dir)
94
+ @storage.dir = dir
95
+ end
96
+
97
+ def high_usage_treshold(value)
98
+ Logger::info "Setting high_usage_treshold to #{value}"
99
+ Utils::Disk.high_usage_treshold = value
100
+ end
101
+
102
+ def check_disk_path(path)
103
+ Logger::info "Adding disk path '#{path}' to usage check"
104
+ Utils::Disk.add_path(path)
105
+ end
106
+
107
+ def apps(&block)
108
+ @apps_block = block
109
+ end
110
+
111
+ def mysql(&block)
112
+ @mysql_block = block
113
+ end
114
+
115
+ def mailer(&block)
116
+ @mailer = Mailer.new(self, @storage)
117
+ @mailer.instance_eval(&block)
118
+ end
119
+ end
120
+ end
121
+
@@ -0,0 +1,42 @@
1
+ module SimpleBackup
2
+ module Engine
3
+ class Abstract
4
+ @keep_last = 5
5
+
6
+ def storage=(storage)
7
+ @storage = storage.space(self.class.name.split('::').last)
8
+ end
9
+
10
+ def keep_last(value)
11
+ @keep_last = value.to_i
12
+ @keep_last = 5 unless @keep_last > 0
13
+ end
14
+
15
+ def cleanup
16
+ path = @storage.dir.path
17
+
18
+ backups = Dir.glob(File.join(path, '**/*.tar.gz')).map do |file|
19
+ file if file.match(/.*\.tar\.gz/)
20
+ end.compact.sort
21
+
22
+ to_persist = backups
23
+ to_persist = backups.slice(@keep_last * -1, @keep_last) if backups.length > @keep_last
24
+ to_remove = backups - to_persist
25
+
26
+ to_remove.each do |file|
27
+ Logger::info "Removing old backup #{file}"
28
+ FileUtils.rm(file)
29
+ end
30
+ end
31
+
32
+ def sources
33
+ raise NotImplementedError
34
+ end
35
+
36
+ def backup
37
+ raise NotImplementedError
38
+ end
39
+ end
40
+ end
41
+ end
42
+
@@ -0,0 +1,13 @@
1
+ module SimpleBackup
2
+ module Engine
3
+ module AppStrategy
4
+ class Abstract
5
+ attr_accessor :storage
6
+
7
+ def backup(name, path, attr)
8
+ raise NotImplementedError
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,29 @@
1
+ module SimpleBackup
2
+ module Engine
3
+ module AppStrategy
4
+ class Bare < Abstract
5
+ def backup(name, path, attr)
6
+ app_elements = get_path_entries(path).map do |p|
7
+ if p.match(/^\.\.?$/)
8
+ nil
9
+ else
10
+ File.join(path, p)
11
+ end
12
+ end.compact
13
+
14
+ @storage.backup do |dir|
15
+ FileUtils.cp_r app_elements, dir
16
+ end
17
+ end
18
+
19
+ private
20
+ def get_path_entries(path)
21
+ Dir.entries(path)
22
+ rescue Errno::ENOENT
23
+ Logger::warning "App path does not exists"
24
+ nil
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,44 @@
1
+ module SimpleBackup
2
+ module Engine
3
+ module AppStrategy
4
+ class Capistrano < Abstract
5
+ def backup(name, path, attr)
6
+ shared = get_shared_path(path, attr)
7
+ current = get_current_path(path, attr)
8
+
9
+ paths = [current, shared].compact
10
+
11
+ if paths.empty?
12
+ Logger::warning "No capistrano paths for application"
13
+ return false
14
+ end
15
+
16
+ @storage.backup do |dir|
17
+ FileUtils.cp_r paths, dir
18
+ end
19
+
20
+ true
21
+ end
22
+
23
+ private
24
+ def get_current_path(path, attr)
25
+ current = Dir.new(File.join(path, attr[:current] || 'current') + '/')
26
+ Logger::debug "Capistrano current path: #{current.path}"
27
+ current
28
+ rescue Errno::ENOENT
29
+ Logger::warning "No capistrano current path for application"
30
+ nil
31
+ end
32
+
33
+ def get_shared_path(path, attr)
34
+ shared = Dir.new(File.join(path, attr[:shared] || 'shared'))
35
+ Logger::debug "Capistrano shared path: #{shared.path}"
36
+ shared
37
+ rescue Errno::ENOENT
38
+ Logger::warning "No capistrano shared path for application"
39
+ nil
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,20 @@
1
+ require 'simple_backup/engine/app_strategy/abstract'
2
+
3
+ module SimpleBackup
4
+ module Engine
5
+ module AppStrategy
6
+ class Factory
7
+ @@types = [:bare, :capistrano]
8
+
9
+ def self.create(type)
10
+ raise Exception::TypeDoesNotExists.new "Strategy type '#{type}' does not exists" unless @@types.include?(type)
11
+ file = "simple_backup/engine/app_strategy/#{type.to_s}"
12
+
13
+ require file
14
+ strategy = Object.const_get("SimpleBackup::Engine::AppStrategy::#{type.to_s.capitalize}")
15
+ strategy.new
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,63 @@
1
+ require 'simple_backup/engine/app_strategy/factory'
2
+
3
+ module SimpleBackup
4
+ module Engine
5
+ class Apps < Abstract
6
+ def initialize
7
+ @apps = {}
8
+ @strategies = {}
9
+ end
10
+
11
+ def app(path, attr = {})
12
+ Logger::debug "Adding application #{path} #{attr}"
13
+ raise Exception::AppAlreadyDefined.new "Application '#{path}' is already defined" if @apps.has_key?(path)
14
+
15
+ @apps[path] = attr
16
+ end
17
+
18
+ def backup
19
+ @apps.each do |path, attr|
20
+ next unless app_exists(path)
21
+ backup_app(path, attr)
22
+ end
23
+ end
24
+
25
+ def sources
26
+ sources = []
27
+ @apps.each do |path, attr|
28
+ sources << path
29
+ end
30
+
31
+ sources
32
+ end
33
+
34
+ private
35
+ def backup_app(path, attr)
36
+ name = path.split('/').last
37
+ Logger::scope_start :info, "Backup of application #{name} started"
38
+ Logger::debug "name: #{name}, attr: #{attr}"
39
+
40
+ strategy = get_strategy(attr[:type])
41
+ strategy.storage = @storage.space(name)
42
+ is_backuped = strategy.backup(name, path, attr)
43
+
44
+ Logger::scope_end :info, "Backup of application #{name} finished" if is_backuped
45
+ Logger::scope_end :info, "Backup of application #{name} skipped" unless is_backuped
46
+ end
47
+
48
+ def app_exists(path)
49
+ Dir.new(path)
50
+ true
51
+ rescue Errno::ENOENT
52
+ Logger::warning "App path '#{path}' does not exists"
53
+ false
54
+ end
55
+
56
+ def get_strategy(type)
57
+ return @strategies[type] if @strategies.has_key?(type)
58
+
59
+ AppStrategy::Factory.create(type)
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,104 @@
1
+ require 'mysql2'
2
+
3
+ module SimpleBackup
4
+ module Engine
5
+ class MySQL < Abstract
6
+ def initialize
7
+ @host = 'localhost'
8
+ @port = 3306
9
+ @user = nil
10
+ @pass = nil
11
+ @dbs = {}
12
+ end
13
+
14
+ def host(host)
15
+ @host = host
16
+ end
17
+
18
+ def port(port)
19
+ @port = port
20
+ end
21
+
22
+ def user(user)
23
+ @user = user
24
+ end
25
+
26
+ def pass(pass)
27
+ @pass = pass
28
+ end
29
+
30
+ def db(name, attr = {})
31
+ Logger::debug "Adding database #{name} #{attr}"
32
+ raise Exception::AppAlreadyDefined.new "Database '#{name}' is already defined" if @dbs.has_key?(name)
33
+
34
+ @dbs[name] = attr
35
+ end
36
+
37
+ def backup
38
+ @conn = Mysql2::Client.new(host: @host, username: @user, password: @pass, port: @port)
39
+
40
+ prepare_tables
41
+ return if @dbs.empty?
42
+
43
+ @storage.backup do |dir|
44
+ @dbs.each do |db, attr|
45
+ dump_db(dir, db, attr)
46
+ end
47
+ end
48
+ ensure
49
+ @conn.close unless @conn.nil?
50
+ end
51
+
52
+ def sources
53
+ sources = []
54
+ @dbs.each do |db, attr|
55
+ sources << db
56
+ end
57
+
58
+ sources
59
+ end
60
+
61
+ private
62
+ def prepare_tables
63
+ @existing_dbs = []
64
+ @conn.query("SHOW DATABASES").each do |row|
65
+ @existing_dbs << row['Database']
66
+ end
67
+
68
+ dbs = {}
69
+ @dbs.each do |db, attr|
70
+ dbs[db] = attr if check_database_exists?(db)
71
+ end
72
+
73
+ @dbs = dbs
74
+ @dbs.each do |db, attr|
75
+ tables = []
76
+ @conn.query("SHOW TABLES FROM #{db}").each do |row|
77
+ tables << row["Tables_in_#{db}"]
78
+ end
79
+ tables = tables - attr[:exclude_tables] if attr[:exclude_tables]
80
+
81
+ @dbs[db][:tables] ||= tables
82
+ end
83
+ end
84
+
85
+ def check_database_exists?(db)
86
+ if @existing_dbs.include?(db)
87
+ return true
88
+ end
89
+ Logger::warning "Database '#{db}' does not exists"
90
+ end
91
+
92
+ def dump_db(dir, db, attr)
93
+ Logger::scope_start :info, "Backup of mysql database #{db} started"
94
+
95
+ file = File.join(dir, db) + '.sql'
96
+ cmd = "mysqldump --flush-logs --flush-privileges --order-by-primary --complete-insert -C -h #{@host} -u #{@user} -p#{@pass} #{db} #{attr[:tables].join(' ')} > #{file}"
97
+ Logger::debug "Running command: #{cmd}"
98
+ `#{cmd}`
99
+
100
+ Logger::scope_end :info, "Backup of mysql database #{db} finished"
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,3 @@
1
+ require 'simple_backup/engine/abstract'
2
+ require 'simple_backup/engine/apps'
3
+ require 'simple_backup/engine/mysql'
@@ -0,0 +1,6 @@
1
+ module SimpleBackup
2
+ module Exception
3
+ class AppAlreadyDefined < Base
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module SimpleBackup
2
+ module Exception
3
+ class AppsDirDoesNotExists < Base
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module SimpleBackup
2
+ module Exception
3
+ class Base < ::RuntimeError
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module SimpleBackup
2
+ module Exception
3
+ class CantCreateDir < Base
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module SimpleBackup
2
+ module Exception
3
+ class TypeDoesNotExists < Base
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ require 'simple_backup/exception/base'
2
+ require 'simple_backup/exception/apps_dir_does_not_exists'
3
+ require 'simple_backup/exception/app_already_defined'
4
+ require 'simple_backup/exception/cant_create_dir'
5
+ require 'simple_backup/exception/type_does_not_exists'
@@ -0,0 +1,84 @@
1
+ require 'colorize'
2
+
3
+ module SimpleBackup
4
+ class Logger
5
+ TIME_FORMAT = '%Y-%m-%dT%H:%M:%S'
6
+
7
+ @@buffer = []
8
+ @@scope = 0
9
+ @@level = :info
10
+ @@levels = {
11
+ debug: {weight: 3, color: :light_cyan},
12
+ info: {weight: 2, color: :green},
13
+ warning: {weight: 1, color: :light_yellow},
14
+ error: {weight: 0, color: :red}
15
+ }
16
+
17
+ banner = "LOG STARTED #{Time.new.strftime('%Y-%m-%dT%H:%M:%S')}"
18
+ banner2 = "SimpleBackup v#{SimpleBackup::Version::get}"
19
+
20
+ banner_length = 0
21
+ banner_length = banner.length if banner.length > banner_length
22
+ banner_length = banner2.length if banner2.length > banner_length
23
+ banner_length = 80 if 80 > banner_length
24
+
25
+ border = '=' * ((banner_length - banner.length) / 2).ceil.to_i
26
+ @@buffer << "#{border}==[ #{banner} ]==#{border}"
27
+ border = '=' * ((banner_length - banner2.length) / 2).ceil.to_i
28
+ @@buffer << "#{border}==[ #{banner2} ]==#{border}"
29
+ puts @@buffer[0].green
30
+ puts @@buffer[1].green
31
+
32
+ def self.level=(level)
33
+ self.check_level(level)
34
+ @@level = level
35
+ end
36
+
37
+ def self.scope_start(level = nil, message = nil)
38
+ self.log level, message unless level.nil? and message.nil?
39
+ @@scope += 1
40
+ end
41
+
42
+ def self.scope_end(level = nil, message = nil)
43
+ self.log level, message unless level.nil? and message.nil?
44
+ @@scope -= 1
45
+ end
46
+
47
+ def self.debug(message)
48
+ self.log(:debug, message)
49
+ end
50
+
51
+ def self.info(message)
52
+ self.log(:info, message)
53
+ end
54
+
55
+ def self.warning(message)
56
+ self.log(:warning, message)
57
+ end
58
+
59
+ def self.error(message)
60
+ self.log(:error, message)
61
+ end
62
+
63
+ def self.log(level, message)
64
+ self.check_level(level)
65
+
66
+ color = @@levels[level][:color]
67
+ should_write = @@levels[level][:weight] <= @@levels[@@level][:weight]
68
+
69
+ scope_prefix = '..' * @@scope
70
+ message = "%s %7s: %s%s" % [Time.new.strftime(TIME_FORMAT), level.to_s.upcase, scope_prefix, message]
71
+ @@buffer << message
72
+
73
+ puts message.colorize(color: color) if should_write
74
+ end
75
+
76
+ def self.check_level(level)
77
+ raise "Unknown logging level #{level}" unless @@levels.has_key?(level)
78
+ end
79
+
80
+ def self.buffer
81
+ @@buffer
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,138 @@
1
+ require 'mail'
2
+ require 'socket'
3
+
4
+ module SimpleBackup
5
+ class Mailer
6
+ def initialize(dsl, storage)
7
+ @dsl = dsl
8
+ @storage = storage
9
+
10
+ @to = []
11
+ @cc = []
12
+ @bcc = []
13
+ @hostname = Socket.gethostname
14
+ end
15
+
16
+ def subject_prefix(prefix)
17
+ @subject_prefix = prefix
18
+ end
19
+
20
+ def from(from)
21
+ @from = from
22
+ end
23
+
24
+ def to(to)
25
+ @to << to
26
+ end
27
+
28
+ def cc(cc)
29
+ @cc << cc
30
+ end
31
+
32
+ def bcc(bcc)
33
+ @bcc << bcc
34
+ end
35
+
36
+ def send
37
+ Logger::scope_start :info, "Sending e-mail notification"
38
+
39
+ Logger::info "Setting sender to: #{@from}"
40
+ from = @from
41
+ Logger::scope_start :info, "Adding recipients:"
42
+ to = @to
43
+ to.each do |mail|
44
+ Logger::info "to: #{mail}"
45
+ end
46
+ cc = @cc
47
+ cc.each do |mail|
48
+ Logger::info "cc: #{mail}"
49
+ end
50
+ bcc = @bcc
51
+ bcc.each do |mail|
52
+ Logger::info "bcc: #{mail}"
53
+ end
54
+ Logger::scope_end
55
+
56
+ @subject_prefix += '[FAILED]' if SimpleBackup.status == :failed
57
+
58
+ subject = "%s Backup %s for hostname %s" % [@subject_prefix, TIMESTAMP, @hostname]
59
+ Logger::debug "Subject: #{subject}"
60
+
61
+ body = get_body
62
+
63
+ mail = Mail.new do
64
+ from from
65
+ to to
66
+ cc cc
67
+ bcc bcc
68
+ subject subject.strip
69
+ body body
70
+ end
71
+
72
+ mail.delivery_method :sendmail
73
+ Logger::debug "Setting delivery method to sendmail"
74
+
75
+ mail.deliver
76
+ Logger::info "Notification sent"
77
+
78
+ Logger::scope_end
79
+ end
80
+
81
+ private
82
+ def get_body
83
+ sources = ''
84
+ @dsl.sources.each do |type, srcs|
85
+ sources += "+ %s:\n" % type.to_s
86
+ srcs.each do |src|
87
+ sources += " - %s\n" % src
88
+ end
89
+ end
90
+
91
+ backup_files = ''
92
+ @storage.created_files.each do |file|
93
+ backup_files += "- %s\n" % file
94
+ end
95
+
96
+ body = <<MAIL
97
+ Hi,
98
+
99
+ Backup #{TIMESTAMP} was created!
100
+
101
+ Backup contains:
102
+ #{sources}
103
+ Created backup files:
104
+ #{backup_files}
105
+ Disk usage after backup:
106
+ #{disk_usage}
107
+ Backup log:
108
+ ------------
109
+ #{Logger::buffer.join("\n")}
110
+ ------------
111
+
112
+ Have a nice day,
113
+ Backuper
114
+
115
+ --
116
+ Mail was send automatically
117
+ Do not respond!
118
+ MAIL
119
+
120
+ body
121
+ end
122
+
123
+ def disk_usage
124
+ content = "%16s %25s %12s %12s %12s %12s\n" % ['Mount', 'Filesystem', 'Size', 'Used', 'Available', 'Percent used']
125
+
126
+ usage = Utils::Disk::usage
127
+ usage[:mounts].each do |m|
128
+ percent_usage = (m[:percent] * 100).to_s
129
+ percent_usage = '(!!) ' + percent_usage if m[:high_usage_exceeded]
130
+ content += "%16s %25s %8s MiB %8s MiB %8s MiB %11s%%\n" % [m[:mount], m[:fs], m[:size], m[:used], m[:available], percent_usage]
131
+ end
132
+
133
+ content += "\nHigh usage treshold exceeded!\nMax usage is #{usage[:high_usage]} where treshold is set to #{Utils::Disk::high_usage_treshold}\n" if usage[:high_usage_exceeded]
134
+
135
+ content
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,96 @@
1
+ require 'rubygems/package'
2
+ require 'zlib'
3
+ require 'fileutils'
4
+
5
+ module SimpleBackup
6
+ class Storage
7
+ attr_accessor :dir
8
+
9
+ @@created_files = []
10
+
11
+ def dir=(dir)
12
+ @dir = get_dir(dir)
13
+
14
+ Logger::info "Backup dir set to '#{dir}'"
15
+ end
16
+
17
+ def space(space)
18
+ Logger::debug "Setting backup_dir for space '#{space}'"
19
+ storage = Storage.new
20
+ storage.dir = File.join(@dir.path, format_for_path(space))
21
+ storage
22
+ end
23
+
24
+ def backup
25
+ dir = get_dir(File.join(@dir, TIMESTAMP))
26
+ yield(dir)
27
+
28
+ targz = targz(dir)
29
+ backup_file = File.join(@dir, TIMESTAMP) + '.tar.gz'
30
+
31
+ File.open(backup_file, 'w') do |f|
32
+ f.write targz.string
33
+ end
34
+
35
+ FileUtils.rm_r dir.path
36
+
37
+ @@created_files.push backup_file
38
+ backup_file
39
+ end
40
+
41
+ def created_files
42
+ @@created_files
43
+ end
44
+
45
+ private
46
+ def get_dir(dir)
47
+ tries ||= 2
48
+ Dir.new(dir)
49
+ rescue Errno::ENOENT
50
+ recreate_dir(dir)
51
+ retry unless (tries -= 1).zero?
52
+ end
53
+
54
+ def recreate_dir(dir)
55
+ Dir.mkdir(dir, 0644)
56
+
57
+ Logger::warning "Recreated non-existing directory '#{dir}'"
58
+ rescue Errno::EACCES => e
59
+ raise Exception::CantCreateDir.new(e.message)
60
+ end
61
+
62
+ def format_for_path(value)
63
+ value.downcase.gsub(/[^a-zA-Z0-9\-\_\.]*/, '').gsub(/\s+/, '_')
64
+ end
65
+
66
+ def targz(dir)
67
+ path = dir.path
68
+ content = StringIO.new('');
69
+ Gem::Package::TarWriter.new(content) do |tar|
70
+ Dir[File.join(path, '**/*')].each do |file|
71
+ mode = File.stat(file).mode
72
+ relative_file = file.sub(/^#{Regexp::escape path}\/?/, '')
73
+
74
+ if File.directory?(file)
75
+ tar.mkdir(relative_file, mode)
76
+ else
77
+ tar.add_file relative_file, mode do |tf|
78
+ File.open(file, 'rb') do |f|
79
+ tf.write f.read
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ content.rewind
87
+
88
+ gz = StringIO.new('')
89
+ zip = Zlib::GzipWriter.new(gz)
90
+ zip.write content.string
91
+ zip.close
92
+
93
+ gz
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,53 @@
1
+ module SimpleBackup
2
+ module Utils
3
+ class Disk
4
+ @@high_usage_treshold = 0.9
5
+ @@paths = []
6
+
7
+ def self.high_usage_treshold=(value)
8
+ @@high_usage_treshold = value.to_f
9
+
10
+ raise ArgumentError.new "Backuper::Utils::Disk::high_usage_treshold must be a float greater than zero" if @@high_usage_treshold <= 0.0
11
+ end
12
+
13
+ def self.high_usage_treshold
14
+ @@high_usage_treshold
15
+ end
16
+
17
+ def self.add_path(path)
18
+ @@paths << path unless @@paths.include?(path)
19
+ end
20
+
21
+ def self.usage
22
+ df = `df -m #{@@paths.join(' ')} 2>/dev/null`.split("\n")
23
+ df.shift
24
+
25
+ max_percent = 0.0;
26
+ df.map! do |row|
27
+ row = row.split(' ')
28
+
29
+ percent_usage = (row[4].gsub('%', '').to_f / 100).round(2)
30
+ row = {
31
+ mount: row[5],
32
+ fs: row[0],
33
+ size: row[1],
34
+ used: row[2],
35
+ available: row[3],
36
+ percent: percent_usage,
37
+ high_usage_exceeded: percent_usage >= @@high_usage_treshold
38
+ }
39
+
40
+ max_percent = row[:percent] if row[:percent] > max_percent
41
+
42
+ row
43
+ end
44
+
45
+ {
46
+ mounts: df.uniq,
47
+ high_usage_exceeded: max_percent >= @@high_usage_treshold,
48
+ high_usage: max_percent
49
+ }
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,19 @@
1
+ module SimpleBackup
2
+ class Version
3
+ MAJOR = 0
4
+ MINOR = 2
5
+ PATCH = 0
6
+ PRE_RELEASE = nil
7
+
8
+ def self.get
9
+ version = "#{MAJOR}.#{MINOR}.#{PATCH}"
10
+ version += "-#{self.format(PRE_RELEASE)}" unless PRE_RELEASE.nil?
11
+
12
+ version
13
+ end
14
+
15
+ def self.format(value)
16
+ value.gsub(/a-zA-Z0-9\_ /, '').gsub(' ', '_').downcase
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,11 @@
1
+ require 'simple_backup/utils'
2
+ require 'simple_backup/version'
3
+ require 'simple_backup/logger'
4
+ require 'simple_backup/dsl'
5
+ require 'simple_backup/storage'
6
+ require 'simple_backup/engine'
7
+ require 'simple_backup/mailer'
8
+ require 'simple_backup/exception'
9
+
10
+ module SimpleBackup
11
+ end
@@ -0,0 +1,21 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'simple_backup/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'simple_backup'
7
+ spec.version = SimpleBackup::Version::get
8
+ spec.authors = ['Tomasz Maczukin']
9
+ spec.email = ['tomasz@maczukin.pl']
10
+ spec.summary = 'Backup tool with simple DSL for configuration'
11
+ spec.homepage = 'https://github.com/tmaczukin/simple_backup'
12
+ spec.license = 'MIT'
13
+
14
+ spec.files = `git ls-files -z`.split("\x0")
15
+ spec.require_paths = ['lib']
16
+
17
+ spec.add_development_dependency 'rake', '~> 10.0'
18
+ spec.add_dependency 'colorize', '~> 0.7.5'
19
+ spec.add_dependency 'mail', '~> 2.6.3'
20
+ spec.add_dependency 'mysql2', '~> 0.3.18'
21
+ end
metadata ADDED
@@ -0,0 +1,126 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: simple_backup
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Tomasz Maczukin
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-04-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '10.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '10.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: colorize
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.7.5
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.7.5
41
+ - !ruby/object:Gem::Dependency
42
+ name: mail
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 2.6.3
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 2.6.3
55
+ - !ruby/object:Gem::Dependency
56
+ name: mysql2
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.3.18
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.3.18
69
+ description:
70
+ email:
71
+ - tomasz@maczukin.pl
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".gitignore"
77
+ - Gemfile
78
+ - Rakefile
79
+ - backup_example.rb
80
+ - lib/simple_backup.rb
81
+ - lib/simple_backup/dsl.rb
82
+ - lib/simple_backup/engine.rb
83
+ - lib/simple_backup/engine/abstract.rb
84
+ - lib/simple_backup/engine/app_strategy/abstract.rb
85
+ - lib/simple_backup/engine/app_strategy/bare.rb
86
+ - lib/simple_backup/engine/app_strategy/capistrano.rb
87
+ - lib/simple_backup/engine/app_strategy/factory.rb
88
+ - lib/simple_backup/engine/apps.rb
89
+ - lib/simple_backup/engine/mysql.rb
90
+ - lib/simple_backup/exception.rb
91
+ - lib/simple_backup/exception/app_already_defined.rb
92
+ - lib/simple_backup/exception/apps_dir_does_not_exists.rb
93
+ - lib/simple_backup/exception/base.rb
94
+ - lib/simple_backup/exception/cant_create_dir.rb
95
+ - lib/simple_backup/exception/type_does_not_exists.rb
96
+ - lib/simple_backup/logger.rb
97
+ - lib/simple_backup/mailer.rb
98
+ - lib/simple_backup/storage.rb
99
+ - lib/simple_backup/utils.rb
100
+ - lib/simple_backup/version.rb
101
+ - simple_backup.gemspec
102
+ homepage: https://github.com/tmaczukin/simple_backup
103
+ licenses:
104
+ - MIT
105
+ metadata: {}
106
+ post_install_message:
107
+ rdoc_options: []
108
+ require_paths:
109
+ - lib
110
+ required_ruby_version: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ version: '0'
115
+ required_rubygems_version: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ version: '0'
120
+ requirements: []
121
+ rubyforge_project:
122
+ rubygems_version: 2.2.2
123
+ signing_key:
124
+ specification_version: 4
125
+ summary: Backup tool with simple DSL for configuration
126
+ test_files: []