backupper 0.1.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.
@@ -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: []