mys3ql 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/README.md +70 -0
- data/Rakefile +1 -0
- data/bin/mys3ql +8 -0
- data/lib/mys3ql.rb +2 -0
- data/lib/mys3ql/conductor.rb +46 -0
- data/lib/mys3ql/config.rb +67 -0
- data/lib/mys3ql/mysql.rb +40 -0
- data/lib/mys3ql/s3.rb +87 -0
- data/lib/mys3ql/shell.rb +22 -0
- data/lib/mys3ql/version.rb +3 -0
- data/mys3ql.gemspec +23 -0
- metadata +109 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# mys3ql = mysql + s3
|
2
|
+
|
3
|
+
Simple backup of your MySql database onto Amazon S3.
|
4
|
+
|
5
|
+
|
6
|
+
## Quick start
|
7
|
+
|
8
|
+
Install and configure as below.
|
9
|
+
|
10
|
+
To perform a full backup:
|
11
|
+
|
12
|
+
$ mys3ql full
|
13
|
+
|
14
|
+
If you are using MySql's binary logging (see below), back up the binary logs like this:
|
15
|
+
|
16
|
+
$ mys3ql incremental
|
17
|
+
|
18
|
+
|
19
|
+
## Installation
|
20
|
+
|
21
|
+
First install the gem:
|
22
|
+
|
23
|
+
$ gem install mys3ql
|
24
|
+
|
25
|
+
Second, create your `~/.mys3ql` config file:
|
26
|
+
|
27
|
+
mysql:
|
28
|
+
# Database to back up
|
29
|
+
database: aircms_production
|
30
|
+
# MySql credentials
|
31
|
+
user: root
|
32
|
+
password:
|
33
|
+
# Path (with trailing slash) to mysql commands e.g. mysqldump
|
34
|
+
bin_path: /usr/local/mysql/bin/
|
35
|
+
# If you are using MySql binary logging:
|
36
|
+
# Path to the binary logs, should match the bin_log option in your my.cnf.
|
37
|
+
# Comment out if you are not using mysql binary logging
|
38
|
+
bin_log: /Users/andy/Desktop/mysql-bin
|
39
|
+
|
40
|
+
s3:
|
41
|
+
# S3 credentials
|
42
|
+
access_key_id: XXXXXXXXXXXXXXXXXXXX
|
43
|
+
secret_access_key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
44
|
+
# Bucket in which to store your backups
|
45
|
+
bucket: db_backups
|
46
|
+
|
47
|
+
|
48
|
+
## Binary logging
|
49
|
+
|
50
|
+
To use incremental backups you need to enable binary logging by making sure that the MySQL config file (my.cnf) has the following line in it:
|
51
|
+
|
52
|
+
log_bin = /var/db/mysql/binlog/mysql-bin
|
53
|
+
|
54
|
+
The MySQL user needs to have the RELOAD and the SUPER privileges, these can be granted with the following SQL commands (which need to be executed as the MySQL root user):
|
55
|
+
|
56
|
+
GRANT RELOAD ON *.* TO 'user_name'@'%' IDENTIFIED BY 'password';
|
57
|
+
GRANT SUPER ON *.* TO 'user_name'@'%' IDENTIFIED BY 'password';
|
58
|
+
|
59
|
+
|
60
|
+
## Inspiration
|
61
|
+
|
62
|
+
Marc-André Cournoyer's [mysql_s3_backup](https://github.com/macournoyer/mysql_s3_backup).
|
63
|
+
|
64
|
+
|
65
|
+
## To Do
|
66
|
+
|
67
|
+
- tests ;)
|
68
|
+
- restore (pull latest dump, pull bin files, pipe dump into mysql, pipe binfiles into mysql)
|
69
|
+
- remove old dump files (s3)
|
70
|
+
- fix verbosity/debugging flag
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/mys3ql
ADDED
data/lib/mys3ql.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'mys3ql/config'
|
2
|
+
require 'mys3ql/mysql'
|
3
|
+
require 'mys3ql/s3'
|
4
|
+
|
5
|
+
module Mys3ql
|
6
|
+
class Conductor
|
7
|
+
|
8
|
+
def self.run(args)
|
9
|
+
abort usage unless args.length == 1 && %w[ full inc ].include?(args.first)
|
10
|
+
conductor = Conductor.new
|
11
|
+
|
12
|
+
command = args.first
|
13
|
+
if command == 'full'
|
14
|
+
conductor.full
|
15
|
+
else
|
16
|
+
conductor.incremental
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize
|
21
|
+
@config = Config.new
|
22
|
+
@mysql = Mysql.new @config
|
23
|
+
@s3 = S3.new @config
|
24
|
+
end
|
25
|
+
|
26
|
+
def full
|
27
|
+
@mysql.dump
|
28
|
+
@s3.push_dump_to_s3 @mysql.dump_file
|
29
|
+
@mysql.clean_up_dump
|
30
|
+
@s3.delete_bin_logs_on_s3
|
31
|
+
end
|
32
|
+
|
33
|
+
def incremental
|
34
|
+
@s3.push_bin_logs_to_s3
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.usage
|
38
|
+
<<END
|
39
|
+
usage:
|
40
|
+
mys3ql full - full backup, push to S3
|
41
|
+
mys3ql incremental - push bin logs to S3
|
42
|
+
END
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Mys3ql
|
4
|
+
class Config
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@config = YAML.load_file config_file
|
8
|
+
rescue Errno::ENOENT
|
9
|
+
$stderr.puts "missing ~/.mys3ql config file"
|
10
|
+
exit 1
|
11
|
+
end
|
12
|
+
|
13
|
+
#
|
14
|
+
# MySQL
|
15
|
+
#
|
16
|
+
|
17
|
+
def user
|
18
|
+
mysql['user']
|
19
|
+
end
|
20
|
+
|
21
|
+
def password
|
22
|
+
mysql['password']
|
23
|
+
end
|
24
|
+
|
25
|
+
def database
|
26
|
+
mysql['database']
|
27
|
+
end
|
28
|
+
|
29
|
+
def bin_path
|
30
|
+
mysql['bin_path']
|
31
|
+
end
|
32
|
+
|
33
|
+
def bin_log
|
34
|
+
mysql['bin_log']
|
35
|
+
end
|
36
|
+
|
37
|
+
#
|
38
|
+
# S3
|
39
|
+
#
|
40
|
+
|
41
|
+
def access_key_id
|
42
|
+
s3['access_key_id']
|
43
|
+
end
|
44
|
+
|
45
|
+
def secret_access_key
|
46
|
+
s3['secret_access_key']
|
47
|
+
end
|
48
|
+
|
49
|
+
def bucket
|
50
|
+
s3['bucket']
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def mysql
|
56
|
+
@config['mysql']
|
57
|
+
end
|
58
|
+
|
59
|
+
def s3
|
60
|
+
@config['s3']
|
61
|
+
end
|
62
|
+
|
63
|
+
def config_file
|
64
|
+
File.join "#{ENV['HOME']}", '.mys3ql'
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/lib/mys3ql/mysql.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'mys3ql/shell'
|
2
|
+
|
3
|
+
module Mys3ql
|
4
|
+
class Mysql
|
5
|
+
include Shell
|
6
|
+
|
7
|
+
def initialize(config)
|
8
|
+
@config = config
|
9
|
+
end
|
10
|
+
|
11
|
+
def dump
|
12
|
+
cmd = "#{@config.bin_path}mysqldump -u'#{@config.user}'"
|
13
|
+
cmd += " -p'#{@config.password}'" if @config.password
|
14
|
+
cmd += " --quick --single-transaction --create-options"
|
15
|
+
cmd += ' --flush-logs --master-data=2 --delete-master-logs' if binary_logging?
|
16
|
+
cmd += " #{@config.database} | gzip > #{dump_file}"
|
17
|
+
execute cmd
|
18
|
+
end
|
19
|
+
|
20
|
+
def clean_up_dump
|
21
|
+
File.delete dump_file
|
22
|
+
debug "deleted #{dump_file}"
|
23
|
+
end
|
24
|
+
|
25
|
+
def dump_file
|
26
|
+
@dump_file ||= "#{timestamp}.sql.gz"
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def timestamp
|
32
|
+
Time.now.utc.strftime "%Y%m%d%H%M"
|
33
|
+
end
|
34
|
+
|
35
|
+
def binary_logging?
|
36
|
+
@config.bin_log && @config.bin_log.length > 0
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
data/lib/mys3ql/s3.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'mys3ql/shell'
|
2
|
+
require 'fog'
|
3
|
+
|
4
|
+
module Mys3ql
|
5
|
+
class S3
|
6
|
+
include Shell
|
7
|
+
|
8
|
+
def initialize(config)
|
9
|
+
@config = config
|
10
|
+
end
|
11
|
+
|
12
|
+
def push_dump_to_s3(dump_file)
|
13
|
+
key = "#{dumps_prefix}/#{dump_file}"
|
14
|
+
copy_key = "#{dumps_prefix}/latest.sql.gz"
|
15
|
+
s3_file = push_to_s3 dump_file, key
|
16
|
+
if s3_file
|
17
|
+
s3_file.copy @config.bucket, copy_key
|
18
|
+
debug "copied #{key} to #{copy_key}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def push_bin_logs_to_s3
|
23
|
+
if bin_logs_exist?
|
24
|
+
Dir["#{@config.bin_log}/*"].each do |file|
|
25
|
+
name = File.basename file
|
26
|
+
key = "#{bin_logs_prefix}/#{name}"
|
27
|
+
push_to_s3 file, key
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def delete_bin_logs_on_s3
|
33
|
+
bucket.files.all(:prefix => "#{bin_logs_prefix}").each do |file|
|
34
|
+
file.destroy
|
35
|
+
debug "destroyed #{file.key}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def s3
|
42
|
+
@s3 ||= begin
|
43
|
+
s = Fog::Storage.new(
|
44
|
+
:provider => 'AWS',
|
45
|
+
:aws_secret_access_key => @config.secret_access_key,
|
46
|
+
:aws_access_key_id => @config.access_key_id
|
47
|
+
)
|
48
|
+
debug 'connected to s3'
|
49
|
+
s
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def bucket
|
54
|
+
@directory ||= begin
|
55
|
+
d = s3.directories.get @config.bucket # assume bucket exists
|
56
|
+
debug "opened bucket #{@config.bucket}"
|
57
|
+
d
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# returns Fog::Storage::AWS::File if we pushed, nil otherwise.
|
62
|
+
def push_to_s3(local_file_name, s3_key)
|
63
|
+
unless bucket.files.head(s3_key)
|
64
|
+
s3_file = bucket.files.create(
|
65
|
+
:key => s3_key,
|
66
|
+
:body => File.open(local_file_name),
|
67
|
+
:public => false
|
68
|
+
)
|
69
|
+
debug "pushed #{local_file_name} to #{s3_key}"
|
70
|
+
s3_file
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def dumps_prefix
|
75
|
+
"#{@config.database}/dumps"
|
76
|
+
end
|
77
|
+
|
78
|
+
def bin_logs_prefix
|
79
|
+
"#{@config.database}/bin_logs"
|
80
|
+
end
|
81
|
+
|
82
|
+
def bin_logs_exist?
|
83
|
+
@config.bin_log && @config.bin_log.length > 0 && File.exist?(@config.bin_log)
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
data/lib/mys3ql/shell.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
module Mys3ql
|
2
|
+
class ShellCommandError < RuntimeError ; end
|
3
|
+
|
4
|
+
module Shell
|
5
|
+
def execute(command)
|
6
|
+
log command
|
7
|
+
result = `#{command}`
|
8
|
+
log "==> #{result}"
|
9
|
+
raise ShellCommandError, "error (exit status #{$?.exitstatus}): #{command} ==> #{result}: #{$?}" unless $?.success?
|
10
|
+
result
|
11
|
+
end
|
12
|
+
|
13
|
+
def log(message)
|
14
|
+
puts message if debugging?
|
15
|
+
end
|
16
|
+
|
17
|
+
def debugging?
|
18
|
+
true
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
data/mys3ql.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "mys3ql/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "mys3ql"
|
7
|
+
s.version = Mys3ql::VERSION
|
8
|
+
s.authors = ["Andy Stewart"]
|
9
|
+
s.email = ["boss@airbladesoftware.com"]
|
10
|
+
s.homepage = 'https://github.com/airblade/mys3ql'
|
11
|
+
s.summary = 'Simple backup of your MySql database onto Amazon S3.'
|
12
|
+
s.description = s.summary
|
13
|
+
|
14
|
+
s.rubyforge_project = "mys3ql"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.add_dependency 'fog', '~> 1.0.0'
|
22
|
+
s.add_development_dependency 'rake'
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mys3ql
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Andy Stewart
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-10-26 00:00:00 +02:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: fog
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 23
|
30
|
+
segments:
|
31
|
+
- 1
|
32
|
+
- 0
|
33
|
+
- 0
|
34
|
+
version: 1.0.0
|
35
|
+
type: :runtime
|
36
|
+
requirement: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: rake
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 3
|
46
|
+
segments:
|
47
|
+
- 0
|
48
|
+
version: "0"
|
49
|
+
type: :development
|
50
|
+
requirement: *id002
|
51
|
+
description: Simple backup of your MySql database onto Amazon S3.
|
52
|
+
email:
|
53
|
+
- boss@airbladesoftware.com
|
54
|
+
executables:
|
55
|
+
- mys3ql
|
56
|
+
extensions: []
|
57
|
+
|
58
|
+
extra_rdoc_files: []
|
59
|
+
|
60
|
+
files:
|
61
|
+
- .gitignore
|
62
|
+
- Gemfile
|
63
|
+
- README.md
|
64
|
+
- Rakefile
|
65
|
+
- bin/mys3ql
|
66
|
+
- lib/mys3ql.rb
|
67
|
+
- lib/mys3ql/conductor.rb
|
68
|
+
- lib/mys3ql/config.rb
|
69
|
+
- lib/mys3ql/mysql.rb
|
70
|
+
- lib/mys3ql/s3.rb
|
71
|
+
- lib/mys3ql/shell.rb
|
72
|
+
- lib/mys3ql/version.rb
|
73
|
+
- mys3ql.gemspec
|
74
|
+
has_rdoc: true
|
75
|
+
homepage: https://github.com/airblade/mys3ql
|
76
|
+
licenses: []
|
77
|
+
|
78
|
+
post_install_message:
|
79
|
+
rdoc_options: []
|
80
|
+
|
81
|
+
require_paths:
|
82
|
+
- lib
|
83
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
84
|
+
none: false
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
hash: 3
|
89
|
+
segments:
|
90
|
+
- 0
|
91
|
+
version: "0"
|
92
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
93
|
+
none: false
|
94
|
+
requirements:
|
95
|
+
- - ">="
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
hash: 3
|
98
|
+
segments:
|
99
|
+
- 0
|
100
|
+
version: "0"
|
101
|
+
requirements: []
|
102
|
+
|
103
|
+
rubyforge_project: mys3ql
|
104
|
+
rubygems_version: 1.6.2
|
105
|
+
signing_key:
|
106
|
+
specification_version: 3
|
107
|
+
summary: Simple backup of your MySql database onto Amazon S3.
|
108
|
+
test_files: []
|
109
|
+
|