simple_backup 0.2.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.
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: []