mys3ql 0.0.2 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +7 -3
- data/bin/mys3ql +16 -44
- data/lib/mys3ql/conductor.rb +41 -13
- data/lib/mys3ql/mysql.rb +54 -9
- data/lib/mys3ql/s3.rb +51 -32
- data/lib/mys3ql/shell.rb +1 -1
- data/lib/mys3ql/version.rb +1 -1
- data/mys3ql.gemspec +1 -1
- metadata +15 -15
data/README.md
CHANGED
@@ -32,7 +32,7 @@ Second, create your config file:
|
|
32
32
|
|
33
33
|
mysql:
|
34
34
|
# Database to back up
|
35
|
-
database:
|
35
|
+
database:
|
36
36
|
# MySql credentials
|
37
37
|
user:
|
38
38
|
password:
|
@@ -41,7 +41,7 @@ Second, create your config file:
|
|
41
41
|
# If you are using MySql binary logging:
|
42
42
|
# Path to the binary logs, should match the bin_log option in your my.cnf.
|
43
43
|
# Comment out if you are not using mysql binary logging
|
44
|
-
bin_log: /
|
44
|
+
bin_log: /var/lib/mysql/binlog/mysql-bin
|
45
45
|
|
46
46
|
s3:
|
47
47
|
# S3 credentials
|
@@ -63,6 +63,10 @@ The MySQL user needs to have the RELOAD and the SUPER privileges, these can be g
|
|
63
63
|
GRANT RELOAD ON *.* TO 'user_name'@'%' IDENTIFIED BY 'password';
|
64
64
|
GRANT SUPER ON *.* TO 'user_name'@'%' IDENTIFIED BY 'password';
|
65
65
|
|
66
|
+
You may need to run mys3ql's incremental backup with special permissions (sudo), depending on the ownership of the binlogs directory.
|
67
|
+
|
68
|
+
N.B. the binary logs contain updates to all the databases on the server. This means you can only switch on incremental backups for one database per server, because the logs will be purged each time a database is dumped.
|
69
|
+
|
66
70
|
|
67
71
|
## Inspiration
|
68
72
|
|
@@ -72,8 +76,8 @@ Marc-André Cournoyer's [mysql_s3_backup](https://github.com/macournoyer/mysql_s
|
|
72
76
|
## To Do
|
73
77
|
|
74
78
|
- tests ;)
|
75
|
-
- restore (pull latest dump, pull bin files, pipe dump into mysql, pipe binfiles into mysql)
|
76
79
|
- remove old dump files (s3)
|
80
|
+
- restore from non-latest dump
|
77
81
|
|
78
82
|
|
79
83
|
## Questions, Problems, Feedback
|
data/bin/mys3ql
CHANGED
@@ -1,63 +1,35 @@
|
|
1
1
|
#!/usr/bin/env ruby-local-exec
|
2
2
|
|
3
|
-
# Hmm, is there a better way which doesn't force rubygems?
|
4
|
-
require 'rubygems'
|
5
|
-
require 'bundler/setup'
|
6
|
-
|
7
3
|
lib_dir = File.join(File.dirname(__FILE__), '..', 'lib')
|
8
4
|
$LOAD_PATH.unshift lib_dir if File.directory?(lib_dir)
|
9
5
|
|
10
6
|
require 'mys3ql'
|
11
|
-
require '
|
12
|
-
|
13
|
-
Choice.options do
|
14
|
-
|
15
|
-
header ''
|
16
|
-
header 'Specific options:'
|
7
|
+
require 'main'
|
17
8
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
9
|
+
Main do
|
10
|
+
# consider using modes as/when we need command-specific arguments (e.g. restore specific backup)
|
11
|
+
argument 'command' do
|
12
|
+
validate { |command| %w[ full incremental restore ].include? command }
|
13
|
+
description 'full | incremental | restore'
|
22
14
|
end
|
23
15
|
|
24
|
-
option
|
25
|
-
|
26
|
-
|
27
|
-
|
16
|
+
option 'config', 'c' do
|
17
|
+
argument :required
|
18
|
+
description 'load configuration from YAML file'
|
19
|
+
defaults '~/.mys3ql'
|
28
20
|
end
|
29
21
|
|
30
|
-
option
|
31
|
-
|
32
|
-
long '--config=FILE'
|
33
|
-
desc 'Load configuration from YAML file (default: ~/.mys3ql)'
|
34
|
-
default '~/.mys3ql'
|
22
|
+
option 'debug', 'd' do
|
23
|
+
description 'be verbose'
|
35
24
|
end
|
36
25
|
|
37
|
-
|
38
|
-
separator 'General options:'
|
39
|
-
|
40
|
-
option :debug do
|
41
|
-
short '-d'
|
42
|
-
long '--debug'
|
43
|
-
desc 'Turn on debugging mode'
|
44
|
-
end
|
26
|
+
option 'version', 'v'
|
45
27
|
|
46
|
-
|
47
|
-
|
48
|
-
desc 'Show this message'
|
49
|
-
end
|
50
|
-
|
51
|
-
option :version do
|
52
|
-
short '-v'
|
53
|
-
long '--version'
|
54
|
-
desc 'Show version'
|
55
|
-
action do
|
28
|
+
def run
|
29
|
+
if params[:version]
|
56
30
|
puts "mys3ql v#{Mys3ql::VERSION}"
|
57
31
|
exit
|
58
32
|
end
|
33
|
+
Mys3ql::Conductor.run params[:command].value, params[:config].value, params[:debug].value
|
59
34
|
end
|
60
|
-
|
61
35
|
end
|
62
|
-
|
63
|
-
Mys3ql::Conductor.run Choice.choices
|
data/lib/mys3ql/conductor.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'tempfile'
|
1
2
|
require 'mys3ql/config'
|
2
3
|
require 'mys3ql/mysql'
|
3
4
|
require 'mys3ql/s3'
|
@@ -5,15 +6,10 @@ require 'mys3ql/s3'
|
|
5
6
|
module Mys3ql
|
6
7
|
class Conductor
|
7
8
|
|
8
|
-
def self.run(
|
9
|
-
conductor = Conductor.new(
|
10
|
-
conductor.debug =
|
11
|
-
|
12
|
-
if options['full']
|
13
|
-
conductor.full
|
14
|
-
elsif options['incremental']
|
15
|
-
conductor.incremental
|
16
|
-
end
|
9
|
+
def self.run(command, config, debug)
|
10
|
+
conductor = Conductor.new(config)
|
11
|
+
conductor.debug = debug
|
12
|
+
conductor.send command
|
17
13
|
end
|
18
14
|
|
19
15
|
def initialize(config_file = nil)
|
@@ -24,17 +20,49 @@ module Mys3ql
|
|
24
20
|
|
25
21
|
def full
|
26
22
|
@mysql.dump
|
27
|
-
@s3.
|
28
|
-
@mysql.
|
29
|
-
@s3.
|
23
|
+
@s3.store @mysql.dump_file
|
24
|
+
@mysql.delete_dump
|
25
|
+
@s3.delete_bin_logs
|
30
26
|
end
|
31
27
|
|
32
28
|
def incremental
|
33
|
-
@
|
29
|
+
@mysql.each_bin_log do |log|
|
30
|
+
@s3.store log, false
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# for now only restore from latest
|
35
|
+
def restore
|
36
|
+
# get latest dump
|
37
|
+
with_temp_file do |file|
|
38
|
+
@s3.retrieve :latest, file
|
39
|
+
@mysql.restore file
|
40
|
+
end
|
41
|
+
|
42
|
+
# apply subsequent bin logs
|
43
|
+
@s3.each_bin_log do |log|
|
44
|
+
with_temp_file do |file|
|
45
|
+
@s3.retrieve log, file
|
46
|
+
@mysql.apply_bin_log file
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# NOTE: not sure about this:
|
51
|
+
puts "You might want to flush mysql's logs..."
|
34
52
|
end
|
35
53
|
|
36
54
|
def debug=(val)
|
37
55
|
@config.debug = val
|
38
56
|
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def with_temp_file(&block)
|
61
|
+
file = Tempfile.new 'mys3ql-sql'
|
62
|
+
yield file.path
|
63
|
+
nil
|
64
|
+
ensure
|
65
|
+
file.close!
|
66
|
+
end
|
39
67
|
end
|
40
68
|
end
|
data/lib/mys3ql/mysql.rb
CHANGED
@@ -8,22 +8,57 @@ module Mys3ql
|
|
8
8
|
@config = config
|
9
9
|
end
|
10
10
|
|
11
|
+
#
|
12
|
+
# dump
|
13
|
+
#
|
14
|
+
|
11
15
|
def dump
|
12
|
-
cmd = "#{@config.bin_path}mysqldump
|
13
|
-
cmd +=
|
14
|
-
cmd += " --quick --single-transaction --create-options"
|
16
|
+
cmd = "#{@config.bin_path}mysqldump"
|
17
|
+
cmd += ' --quick --single-transaction --create-options'
|
15
18
|
cmd += ' --flush-logs --master-data=2 --delete-master-logs' if binary_logging?
|
16
|
-
cmd +=
|
17
|
-
|
19
|
+
cmd += cli_options
|
20
|
+
cmd += " | gzip > #{dump_file}"
|
21
|
+
run cmd
|
22
|
+
end
|
23
|
+
|
24
|
+
def dump_file
|
25
|
+
@dump_file ||= "#{timestamp}.sql.gz"
|
18
26
|
end
|
19
27
|
|
20
|
-
def
|
28
|
+
def delete_dump
|
21
29
|
File.delete dump_file
|
22
|
-
log "deleted #{dump_file}"
|
30
|
+
log "mysql: deleted #{dump_file}"
|
23
31
|
end
|
24
32
|
|
25
|
-
|
26
|
-
|
33
|
+
#
|
34
|
+
# bin_logs
|
35
|
+
#
|
36
|
+
|
37
|
+
# flushes logs, yields each bar the last to the block
|
38
|
+
def each_bin_log(&block)
|
39
|
+
execute 'flush logs'
|
40
|
+
logs = Dir.glob("#{@config.bin_log}.[0-9]*").sort_by { |f| f[/\d+/].to_i }
|
41
|
+
logs_to_backup = logs[0..-2] # all logs except the last, which is in use
|
42
|
+
logs_to_backup.each do |log_file|
|
43
|
+
yield log_file
|
44
|
+
end
|
45
|
+
# delete binlogs from file system
|
46
|
+
#execute "purge master logs to '#{File.basename(logs[-1])}'"
|
47
|
+
end
|
48
|
+
|
49
|
+
#
|
50
|
+
# restore
|
51
|
+
#
|
52
|
+
|
53
|
+
def restore(file)
|
54
|
+
run "gunzip -c #{file} | #{@config.bin_path}mysql #{cli_options}"
|
55
|
+
end
|
56
|
+
|
57
|
+
def apply_bin_log(file)
|
58
|
+
cmd = "#{@config.bin_path}mysqlbinlog --database=#{@config.database} #{file}"
|
59
|
+
cmd += " | #{@config.bin_path}mysql -u'#{@config.user}'"
|
60
|
+
cmd += " -p'#{@config.password}'" if @config.password
|
61
|
+
run cmd
|
27
62
|
end
|
28
63
|
|
29
64
|
private
|
@@ -36,5 +71,15 @@ module Mys3ql
|
|
36
71
|
@config.bin_log && @config.bin_log.length > 0
|
37
72
|
end
|
38
73
|
|
74
|
+
def cli_options
|
75
|
+
cmd = " -u'#{@config.user}'"
|
76
|
+
cmd += " -p'#{@config.password}'" if @config.password
|
77
|
+
cmd += " #{@config.database}"
|
78
|
+
end
|
79
|
+
|
80
|
+
def execute(sql)
|
81
|
+
run %Q(#{@config.bin_path}mysql -e "#{sql}" #{cli_options})
|
82
|
+
end
|
83
|
+
|
39
84
|
end
|
40
85
|
end
|
data/lib/mys3ql/s3.rb
CHANGED
@@ -9,35 +9,66 @@ module Mys3ql
|
|
9
9
|
@config = config
|
10
10
|
end
|
11
11
|
|
12
|
-
def
|
13
|
-
key
|
14
|
-
|
15
|
-
|
16
|
-
|
12
|
+
def store(file, dump = true)
|
13
|
+
key = key_for(dump ? :dump : :bin_log, file)
|
14
|
+
s3_file = save file, key
|
15
|
+
if dump && s3_file
|
16
|
+
copy_key = key_for :latest
|
17
17
|
s3_file.copy @config.bucket, copy_key
|
18
18
|
log "copied #{key} to #{copy_key}"
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
|
-
def
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
key = "#{bin_logs_prefix}/#{name}"
|
27
|
-
push_to_s3 file, key
|
28
|
-
end
|
22
|
+
def delete_bin_logs
|
23
|
+
each_bin_log do |file|
|
24
|
+
file.destroy
|
25
|
+
log "s3: destroyed #{file.key}"
|
29
26
|
end
|
30
27
|
end
|
31
28
|
|
32
|
-
def
|
33
|
-
bucket.files.all(:prefix => "#{bin_logs_prefix}").each do |file|
|
34
|
-
file
|
35
|
-
log "destroyed #{file.key}"
|
29
|
+
def each_bin_log(&block)
|
30
|
+
bucket.files.all(:prefix => "#{bin_logs_prefix}").sort_by { |file| file.key[/\d+/].to_i }.each do |file|
|
31
|
+
yield file
|
36
32
|
end
|
37
33
|
end
|
38
34
|
|
35
|
+
def retrieve(s3_file, local_file)
|
36
|
+
key = (s3_file == :latest) ? key_for(:latest) : s3_file.key
|
37
|
+
get key, local_file
|
38
|
+
end
|
39
|
+
|
39
40
|
private
|
40
41
|
|
42
|
+
def get(s3_key, local_file_name)
|
43
|
+
s3_file = bucket.files.get s3_key
|
44
|
+
File.open(local_file_name, 'wb') do |file|
|
45
|
+
file.write s3_file.body
|
46
|
+
end
|
47
|
+
log "s3: pulled #{s3_key} to #{local_file_name}"
|
48
|
+
end
|
49
|
+
|
50
|
+
# returns Fog::Storage::AWS::File if we pushed, nil otherwise.
|
51
|
+
def save(local_file_name, s3_key)
|
52
|
+
unless bucket.files.head(s3_key)
|
53
|
+
s3_file = bucket.files.create(
|
54
|
+
:key => s3_key,
|
55
|
+
:body => File.open(local_file_name),
|
56
|
+
:public => false
|
57
|
+
)
|
58
|
+
log "s3: pushed #{local_file_name} to #{s3_key}"
|
59
|
+
s3_file
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def key_for(kind, file = nil)
|
64
|
+
name = File.basename file if file
|
65
|
+
case kind
|
66
|
+
when :dump; "#{dumps_prefix}/#{name}"
|
67
|
+
when :bin_log; "#{bin_logs_prefix}/#{name}"
|
68
|
+
when :latest; "#{dumps_prefix}/latest.sql.gz"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
41
72
|
def s3
|
42
73
|
@s3 ||= begin
|
43
74
|
s = Fog::Storage.new(
|
@@ -45,32 +76,20 @@ module Mys3ql
|
|
45
76
|
:aws_secret_access_key => @config.secret_access_key,
|
46
77
|
:aws_access_key_id => @config.access_key_id
|
47
78
|
)
|
48
|
-
log 'connected
|
79
|
+
log 's3: connected'
|
49
80
|
s
|
50
81
|
end
|
51
82
|
end
|
52
83
|
|
53
84
|
def bucket
|
54
85
|
@directory ||= begin
|
55
|
-
d = s3.directories.get @config.bucket
|
56
|
-
|
86
|
+
d = s3.directories.get @config.bucket
|
87
|
+
raise "S3 bucket #{@config.bucket} not found" unless d # create bucket instead?
|
88
|
+
log "s3: opened bucket #{@config.bucket}"
|
57
89
|
d
|
58
90
|
end
|
59
91
|
end
|
60
92
|
|
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
|
-
log "pushed #{local_file_name} to #{s3_key}"
|
70
|
-
s3_file
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
93
|
def dumps_prefix
|
75
94
|
"#{@config.database}/dumps"
|
76
95
|
end
|
data/lib/mys3ql/shell.rb
CHANGED
data/lib/mys3ql/version.rb
CHANGED
data/mys3ql.gemspec
CHANGED
@@ -18,7 +18,7 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
19
|
s.require_paths = ["lib"]
|
20
20
|
|
21
|
-
s.add_dependency '
|
21
|
+
s.add_dependency 'main', '~> 4.8.0'
|
22
22
|
s.add_dependency 'fog', '~> 1.0.0'
|
23
23
|
s.add_development_dependency 'rake'
|
24
24
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mys3ql
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 23
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
9
|
+
- 4
|
10
|
+
version: 0.0.4
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Andy Stewart
|
@@ -15,29 +15,29 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-
|
18
|
+
date: 2011-11-03 00:00:00 +01:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
22
|
-
name:
|
22
|
+
name: main
|
23
23
|
prerelease: false
|
24
|
-
|
24
|
+
version_requirements: &id001 !ruby/object:Gem::Requirement
|
25
25
|
none: false
|
26
26
|
requirements:
|
27
27
|
- - ~>
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
hash:
|
29
|
+
hash: 31
|
30
30
|
segments:
|
31
|
-
- 0
|
32
|
-
- 1
|
33
31
|
- 4
|
34
|
-
|
32
|
+
- 8
|
33
|
+
- 0
|
34
|
+
version: 4.8.0
|
35
35
|
type: :runtime
|
36
|
-
|
36
|
+
requirement: *id001
|
37
37
|
- !ruby/object:Gem::Dependency
|
38
38
|
name: fog
|
39
39
|
prerelease: false
|
40
|
-
|
40
|
+
version_requirements: &id002 !ruby/object:Gem::Requirement
|
41
41
|
none: false
|
42
42
|
requirements:
|
43
43
|
- - ~>
|
@@ -49,11 +49,11 @@ dependencies:
|
|
49
49
|
- 0
|
50
50
|
version: 1.0.0
|
51
51
|
type: :runtime
|
52
|
-
|
52
|
+
requirement: *id002
|
53
53
|
- !ruby/object:Gem::Dependency
|
54
54
|
name: rake
|
55
55
|
prerelease: false
|
56
|
-
|
56
|
+
version_requirements: &id003 !ruby/object:Gem::Requirement
|
57
57
|
none: false
|
58
58
|
requirements:
|
59
59
|
- - ">="
|
@@ -63,7 +63,7 @@ dependencies:
|
|
63
63
|
- 0
|
64
64
|
version: "0"
|
65
65
|
type: :development
|
66
|
-
|
66
|
+
requirement: *id003
|
67
67
|
description: Simple backup of your MySql database onto Amazon S3.
|
68
68
|
email:
|
69
69
|
- boss@airbladesoftware.com
|