backupper 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 723c61f682f76c26391c958a439f44cb449c1cd6
4
+ data.tar.gz: 83e680f50cc6f12c401d3def411e6348708a0fa3
5
+ SHA512:
6
+ metadata.gz: 68da65e4bb05372820267af1415ba43d9d8a37d9fd19f99f13ace62b4da820549f3496ca95ad2402e05e1a0a83f97e77b7702798fc14d9ab057fa5781ba6d38b
7
+ data.tar.gz: 823297e467198e1feb532d205186e097a58d7eb97a18cefb13b39d3d07709b8cc388cd3cc79769841cb4bd2343ed5d1cba6d668ec19b4abec70b7bbb4482a799
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ conf.yml
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in backupper.gemspec
6
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 pioz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,67 @@
1
+ # Backupper
2
+
3
+ Backupper is a useful tool to backup all your databases spreaded in the world!
4
+
5
+ ## Installation
6
+
7
+ $ gem install backupper
8
+
9
+ ## Usage
10
+
11
+ $ backupper path/to/config.yml
12
+
13
+ ## Configuration file
14
+
15
+ A common config file for backupper looks like this:
16
+
17
+ ```yaml
18
+ mailer:
19
+ from: support@email.com # Gmail account used to send report email
20
+ to: pioz@email.com # Send report email to this address
21
+ password: Pa$$w0rD # Gmail account password
22
+
23
+ db1:
24
+ disabled: false # true to disable this backup
25
+ dump: '/home/backup/db1' # path where to save the dump of the database
26
+ extra_copy: '/mnt/backup-disk/backups/db1' # path where to save a extra copy of the dump
27
+ username: user # server ssh username
28
+ host: '1.2.3.4' # server ssh ip
29
+ port: 22 # server ssh port
30
+ password: Pa$$w0rD # server ssh password
31
+ adapter: mysql # database to backup (supported are mysql or postgresql)
32
+ database: db_name # database name
33
+ db_username: db_user # database username
34
+ db_password: db_Pa$$w0rD # database password
35
+
36
+ db2:
37
+ disabled: false
38
+ dump: '/home/backup/db2
39
+ extra_copy: '/mnt/backup-disk/backups/db2'
40
+ username: user
41
+ host: '1.2.3.4'
42
+ port: 22
43
+ password: Pa$$w0rD
44
+ adapter: postgresql
45
+ database: db_name
46
+ db_username: db_user
47
+ db_password: db_Pa$$w0rD
48
+ ```
49
+
50
+ After backups a report email is sent to you using gmail smtp service.
51
+
52
+ ## Contributing
53
+
54
+ Bug reports and pull requests are welcome on GitHub at https://github.com/uqido/backupper.
55
+
56
+ ## License
57
+
58
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
59
+
60
+ ## About Uqido
61
+
62
+ [![uqido](https://www.uqido.com/assets/uqido/logo_Uqido@2x-07d759d29607a31dcc13d18b5c67585377eee4fceff1ea733340113ca4f574c0.png)](http://uqido.com)
63
+
64
+ Backupper is maintained and funded by [Uqido](https://uqido.com).
65
+ The names and logos for Uqido are trademarks of Uqido s.r.l.
66
+
67
+ The [Uqido team](https://www.uqido.com/#team).
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
@@ -0,0 +1,36 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "backupper/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "backupper"
8
+ spec.version = Backupper::VERSION
9
+ spec.authors = ["pioz"]
10
+ spec.email = ["epilotto@gmx.com"]
11
+
12
+ spec.summary = %q{Tool to backup databases}
13
+ spec.description = %q{Tool to backup databases}
14
+ spec.homepage = "https://github.com/uqido/backupper"
15
+ spec.license = "MIT"
16
+
17
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
18
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
19
+ if spec.respond_to?(:metadata)
20
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
21
+ else
22
+ raise "RubyGems 2.0 or newer is required to protect against " \
23
+ "public gem pushes."
24
+ end
25
+
26
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
27
+ f.match(%r{^(test|spec|features)/})
28
+ end
29
+ spec.bindir = "bin"
30
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
31
+ spec.require_paths = ["lib"]
32
+
33
+ spec.add_runtime_dependency "sshkit", "~> 1.15"
34
+ spec.add_development_dependency "bundler", "~> 1.16"
35
+ spec.add_development_dependency "rake", "~> 10.0"
36
+ end
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH << './lib'
4
+
5
+ require 'backupper'
6
+
7
+ if ARGV.size != 1
8
+ puts "usage: #{$0} path/to/config.yml"
9
+ exit 1
10
+ end
11
+
12
+ b = Backupper.new(ARGV.first)
13
+ b.backup!
@@ -0,0 +1,3 @@
1
+ require 'backupper/backupper'
2
+ require 'backupper/mailer'
3
+ require 'backupper/version'
@@ -0,0 +1,139 @@
1
+ require 'yaml'
2
+ require 'fileutils'
3
+ require 'sshkit'
4
+ require 'sshkit/dsl'
5
+ include SSHKit::DSL
6
+
7
+ class Backupper
8
+
9
+ SSHKit::Backend::Netssh.configure do |ssh|
10
+ ssh.connection_timeout = 30
11
+ ssh.ssh_options = {
12
+ auth_methods: %w(publickey password)
13
+ }
14
+ end
15
+
16
+ def initialize(conf_file_path)
17
+ conf = YAML.load_file(conf_file_path)
18
+ @default = conf['default'] || {}
19
+ @mailer = conf['mailer'] || {}
20
+ @conf = conf.select{|k, v| !%w(default mailer).include?(k) && (v['disabled'].nil? || v['disabled'] == false)}
21
+ @report = {}
22
+ end
23
+
24
+ def backup!
25
+ @conf.each do |k, options|
26
+ o = @default.merge(options)
27
+ puts "🗄 run backup of #{k}..."
28
+ outdir = check_dir(o['dump'].to_s)
29
+ unless outdir
30
+ err = 'Invalid directory where to save database dump'
31
+ error(k, err)
32
+ puts err
33
+ next
34
+ end
35
+ host = o['host']
36
+ host = "#{o['username']}@#{host}" if o['username']
37
+ host = "#{host}:#{o['port']}" if o['port']
38
+ adapter = o['adapter'] || 'mysql'
39
+ unless self.respond_to?("#{adapter}_dump_command")
40
+ err = "Cannot handle adapter '#{adapter}'"
41
+ error(k, err)
42
+ puts err
43
+ next
44
+ end
45
+ unless o['database']
46
+ err = 'Please specify database name!'
47
+ error(k, err)
48
+ puts err
49
+ next
50
+ end
51
+ begin
52
+ download_dump(
53
+ key: k,
54
+ adapter: adapter,
55
+ host: host,
56
+ password: o['password'],
57
+ database: o['database'],
58
+ db_username: o['db_username'],
59
+ db_password: o['db_password'],
60
+ outdir: outdir,
61
+ extra_copy: o['extra_copy']
62
+ )
63
+ rescue SSHKit::Runner::ExecuteError => e
64
+ error(k, e.to_s)
65
+ puts e
66
+ end
67
+ end
68
+ if @report.any? && @mailer['from'] && @mailer['to'] && @mailer['password']
69
+ begin
70
+ Mailer.send(from: @mailer['from'], to: @mailer['to'], password: @mailer['password'], report: @report)
71
+ rescue Net::SMTPAuthenticationError => e
72
+ puts e
73
+ end
74
+ end
75
+ end
76
+
77
+ def mysql_dump_command(database:, username: 'root', password: nil, outfile:)
78
+ params = []
79
+ params << "--databases '#{database}'"
80
+ params << "-u#{username}"
81
+ params << "-p#{password}" if password
82
+ return "mysqldump #{params.join(' ')} | bzip2 > '#{outfile}'"
83
+ end
84
+
85
+ def postgresql_dump_command(database:, username: 'root', password: nil, outfile:)
86
+ params = []
87
+ params << "-U #{username}"
88
+ params << "-W #{password}" if password
89
+ params << "'#{database}'"
90
+ return "pg_dump #{params.join(' ')} | bzip2 > '#{outfile}'"
91
+ end
92
+
93
+ def download_dump(key:, adapter: 'mysql', host:, password: nil, database:, db_username: 'root', db_password: nil, outdir:, extra_copy: nil)
94
+ if self.respond_to?("#{adapter}_dump_command")
95
+ t = Time.now
96
+ path = nil
97
+ dump_name = nil
98
+ filename = "#{key}__#{database}.sql.bz2"
99
+ tempfile = File.join('/tmp', filename)
100
+ dumpname = "#{Time.now.strftime('%Y-%M-%d_%H-%M-%S')}__#{filename}"
101
+ path = File.join(outdir, dumpname)
102
+ backupper = self
103
+ on(host) do |host|
104
+ host.password = password
105
+ execute backupper.send("#{adapter}_dump_command", database: database, username: db_username, password: db_password, outfile: tempfile)
106
+ download! tempfile, path
107
+ execute :rm, tempfile
108
+ end
109
+ extra_copy = check_dir(extra_copy)
110
+ FileUtils.cp(path, extra_copy) if extra_copy
111
+ @report[key] = {
112
+ path: File.absolute_path(path),
113
+ size: (File.size(path).to_f / 2**20).round(2),
114
+ time: (Time.now - t).round(2)
115
+ }
116
+ @report[key].merge!({extra_copy: File.absolute_path(File.join(extra_copy, dumpname))}) if extra_copy
117
+ end
118
+ end
119
+
120
+ private
121
+
122
+ def error(key, error)
123
+ @report[key] = {error: error}
124
+ end
125
+
126
+ def check_dir(dirpath)
127
+ return nil if dirpath.nil?
128
+ unless File.exists?(dirpath)
129
+ begin
130
+ FileUtils::mkdir_p(dirpath)
131
+ rescue => e
132
+ return nil
133
+ end
134
+ end
135
+ return File.dirname(dirpath) unless File.directory?(dirpath)
136
+ return dirpath
137
+ end
138
+
139
+ end
@@ -0,0 +1,63 @@
1
+ require 'mail'
2
+
3
+ class Mailer
4
+
5
+ def self.send(from:, to:, password:, report:)
6
+ options = {
7
+ address: 'smtp.gmail.com',
8
+ port: 587,
9
+ user_name: from,
10
+ password: password,
11
+ authentication: 'plain',
12
+ enable_starttls_auto: true
13
+ }
14
+ Mail.defaults do
15
+ delivery_method :smtp, options
16
+ end
17
+ Mail.deliver do
18
+ to to
19
+ from from
20
+ subject Mailer.subject(report)
21
+ body Mailer.body(report)
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def self.body(report)
28
+ b = []
29
+ report.each do |k, data|
30
+ s = ''
31
+ if data[:error]
32
+ s << "❌ #{k}\n"
33
+ s << '=' * 80 << "\n"
34
+ s << "Backup FAILED!\n"
35
+ s << " error: #{data[:error]}\n"
36
+ b << s
37
+ else
38
+ s << "️✅ #{k}\n"
39
+ s << '=' * 80 << "\n"
40
+ s << "Backup SUCCESS!\n"
41
+ s << " dump path: #{data[:path]}\n"
42
+ s << " dump size: #{data[:size]} MB\n"
43
+ s << " time: #{data[:time]} seconds\n"
44
+ if data[:extra_copy]
45
+ s << " extra copy in: #{data[:extra_copy]}\n"
46
+ else
47
+ s << " no extra copy has been made\n"
48
+ end
49
+ b << s
50
+ end
51
+ end
52
+ return "Report for backups (#{Time.now})\n\n#{b.join("\n\n")}"
53
+ end
54
+
55
+ def self.subject(report)
56
+ errors = report.select{|k, v| v[:error]}.size
57
+ icon = '✅'
58
+ icon = '⚠️' if errors > 0
59
+ icon = '❌' if errors == report.size
60
+ return "[Backupper] #{report.size-errors}/#{report.size} backups successfully completed #{icon}"
61
+ end
62
+
63
+ end
@@ -0,0 +1,3 @@
1
+ class Backupper
2
+ VERSION = "0.1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,99 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: backupper
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - pioz
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-01-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: sshkit
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.15'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.15'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.16'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.16'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ description: Tool to backup databases
56
+ email:
57
+ - epilotto@gmx.com
58
+ executables:
59
+ - backupper
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - ".gitignore"
64
+ - Gemfile
65
+ - LICENSE.txt
66
+ - README.md
67
+ - Rakefile
68
+ - backupper.gemspec
69
+ - bin/backupper
70
+ - lib/backupper.rb
71
+ - lib/backupper/backupper.rb
72
+ - lib/backupper/mailer.rb
73
+ - lib/backupper/version.rb
74
+ homepage: https://github.com/uqido/backupper
75
+ licenses:
76
+ - MIT
77
+ metadata:
78
+ allowed_push_host: https://rubygems.org
79
+ post_install_message:
80
+ rdoc_options: []
81
+ require_paths:
82
+ - lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ required_rubygems_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ requirements: []
94
+ rubyforge_project:
95
+ rubygems_version: 2.6.13
96
+ signing_key:
97
+ specification_version: 4
98
+ summary: Tool to backup databases
99
+ test_files: []