stellar-core-backup 0.0.4
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 +7 -0
- data/.gitignore +18 -0
- data/CONTRIBUTING.md +58 -0
- data/Gemfile +2 -0
- data/LICENSE.txt +202 -0
- data/README.md +92 -0
- data/Rakefile +9 -0
- data/bin/stellar-core-backup +46 -0
- data/config/sample.yaml +7 -0
- data/lib/stellar-core-backup.rb +21 -0
- data/lib/stellar-core-backup/cmd.rb +28 -0
- data/lib/stellar-core-backup/cmd_result.rb +14 -0
- data/lib/stellar-core-backup/config.rb +37 -0
- data/lib/stellar-core-backup/database.rb +67 -0
- data/lib/stellar-core-backup/filesystem.rb +62 -0
- data/lib/stellar-core-backup/job.rb +190 -0
- data/lib/stellar-core-backup/restore/database.rb +36 -0
- data/lib/stellar-core-backup/restore/filesystem.rb +36 -0
- data/lib/stellar-core-backup/s3.rb +64 -0
- data/lib/stellar-core-backup/tar.rb +37 -0
- data/lib/stellar-core-backup/utils.rb +130 -0
- data/lib/stellar-core-backup/version.rb +3 -0
- data/stellar-core-backup.gemspec +26 -0
- metadata +137 -0
@@ -0,0 +1,36 @@
|
|
1
|
+
module StellarCoreBackup::Restore
|
2
|
+
class Filesystem < ::StellarCoreBackup::Filesystem
|
3
|
+
include Contracts
|
4
|
+
|
5
|
+
class DataDirNotEmpty < StandardError ; end
|
6
|
+
|
7
|
+
attr_reader :core_data_dir
|
8
|
+
|
9
|
+
Contract StellarCoreBackup::Config => Contracts::Any
|
10
|
+
def initialize(config)
|
11
|
+
@config = config
|
12
|
+
@working_dir = StellarCoreBackup::Utils.create_working_dir(@config.get('working_dir'))
|
13
|
+
@core_data_dir = get_core_data_dir(@config.get('core_config'))
|
14
|
+
end
|
15
|
+
|
16
|
+
public
|
17
|
+
|
18
|
+
Contract String => nil
|
19
|
+
def restore(backup_archive)
|
20
|
+
# unpack the filesystem backup
|
21
|
+
puts "info: stellar-core buckets restored" if StellarCoreBackup::Tar.unpack("#{@working_dir}/core-fs.tar", @core_data_dir)
|
22
|
+
end
|
23
|
+
|
24
|
+
Contract nil => Bool
|
25
|
+
def core_data_dir_empty?()
|
26
|
+
# checks fs and asks user to remove fs manually if fs is already in place.
|
27
|
+
if (Dir.entries(@core_data_dir) - %w{ . .. }).empty? then
|
28
|
+
return true
|
29
|
+
else
|
30
|
+
puts "error: #{@core_data_dir} is not empty, you can only restore to an empty data directory"
|
31
|
+
raise DataDirNotEmpty
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'aws-sdk-s3'
|
2
|
+
|
3
|
+
module StellarCoreBackup
|
4
|
+
class S3
|
5
|
+
include Contracts
|
6
|
+
|
7
|
+
Contract StellarCoreBackup::Config => Contracts::Any
|
8
|
+
def initialize(config)
|
9
|
+
@config = config
|
10
|
+
@working_dir = StellarCoreBackup::Utils.create_working_dir(@config.get('working_dir'))
|
11
|
+
@s3_region = @config.get('s3_region')
|
12
|
+
@s3_bucket = @config.get('s3_bucket')
|
13
|
+
@s3_path = @config.get('s3_path')
|
14
|
+
begin
|
15
|
+
@s3_client = Aws::S3::Client.new(region: @s3_region)
|
16
|
+
@s3_resource = Aws::S3::Resource.new(client: @s3_client)
|
17
|
+
rescue Aws::S3::Errors::ServiceError => e
|
18
|
+
puts "info: error connecting to s3"
|
19
|
+
puts e
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def push(file)
|
24
|
+
begin
|
25
|
+
upload = @s3_resource.bucket(@s3_bucket).object("#{@s3_path}/#{File.basename(file)}")
|
26
|
+
if upload.upload_file(file) then
|
27
|
+
puts "info: pushed #{file} to s3 (#{@s3_bucket})"
|
28
|
+
else
|
29
|
+
puts "error: upload to s3 failed"
|
30
|
+
end
|
31
|
+
rescue Aws::S3::Errors::ServiceError => e
|
32
|
+
puts "info: error pushing #{file} to s3 (#{@s3_bucket})"
|
33
|
+
puts e
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# fetches a backup tar file from s3, places in working dir
|
38
|
+
def get(file)
|
39
|
+
local_copy = "#{@working_dir}/#{File.basename(file)}"
|
40
|
+
begin
|
41
|
+
download = @s3_resource.bucket(@s3_bucket).object(file)
|
42
|
+
if download.download_file(local_copy) then
|
43
|
+
puts "info: fetched #{file} from s3 (#{@s3_bucket})"
|
44
|
+
return local_copy
|
45
|
+
else
|
46
|
+
puts "error: download from s3 failed"
|
47
|
+
end
|
48
|
+
rescue Aws::S3::Errors::ServiceError => e
|
49
|
+
puts "info: error downloading #{file} from s3 (#{@s3_bucket})"
|
50
|
+
puts e
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def latest(listlen)
|
55
|
+
begin
|
56
|
+
@s3_client.list_objects_v2({bucket: @s3_bucket, prefix: @s3_path+'/core-backup-'}).contents.map{|o| o.key}.sort{|a,b| a.gsub(/(\d+)/,'\1') <=> b.gsub(/(\d+)/,'\1')}.last(listlen)
|
57
|
+
rescue Aws::S3::Errors::ServiceError => e
|
58
|
+
puts "info: error listing s3 (#{@s3_bucket})"
|
59
|
+
puts e
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module StellarCoreBackup
|
4
|
+
class Tar
|
5
|
+
include Contracts
|
6
|
+
|
7
|
+
Contract String, String => String
|
8
|
+
def self.pack(archive, directory)
|
9
|
+
if StellarCoreBackup::Utils.readable?(directory) then
|
10
|
+
# archive directory
|
11
|
+
puts "info: packing #{directory} in #{archive}"
|
12
|
+
%x{/bin/tar --create --file=#{archive} #{directory}}
|
13
|
+
if $?.exitstatus == 0 then
|
14
|
+
puts "info: #{archive} created"
|
15
|
+
return archive
|
16
|
+
else
|
17
|
+
raise StandardError
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
Contract String, String => String
|
23
|
+
def self.unpack(archive, destination)
|
24
|
+
if StellarCoreBackup::Utils.writable?(destination) then
|
25
|
+
# extract archive in destination directory
|
26
|
+
puts "info: unpacking #{archive} in #{destination}"
|
27
|
+
%x{/bin/tar --extract --file=#{archive} --directory=#{destination}}
|
28
|
+
if $?.exitstatus == 0 then
|
29
|
+
puts "info: #{archive} unpacked in #{destination}"
|
30
|
+
return destination
|
31
|
+
else
|
32
|
+
raise StandardError
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module StellarCoreBackup
|
4
|
+
class Utils
|
5
|
+
include Contracts
|
6
|
+
|
7
|
+
Contract StellarCoreBackup::Config => Contracts::Any
|
8
|
+
def initialize(config)
|
9
|
+
@config = config
|
10
|
+
@working_dir = StellarCoreBackup::Utils.create_working_dir(@config.get('working_dir'))
|
11
|
+
@db_restore = StellarCoreBackup::Restore::Database.new(@config)
|
12
|
+
@fs_restore = StellarCoreBackup::Restore::Filesystem.new(@config)
|
13
|
+
end
|
14
|
+
|
15
|
+
Contract String => String
|
16
|
+
def self.create_working_dir(dir)
|
17
|
+
working_dir = dir + "/#{Process.pid}"
|
18
|
+
unless Dir.exists?(working_dir) then
|
19
|
+
Dir.mkdir working_dir
|
20
|
+
end
|
21
|
+
return working_dir
|
22
|
+
end
|
23
|
+
|
24
|
+
Contract String => nil
|
25
|
+
def self.remove_working_dir(working_dir)
|
26
|
+
if Dir.exists?(working_dir) then
|
27
|
+
Dir.rmdir working_dir + "/#{Process.pid}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
Contract String => String
|
32
|
+
def self.create_backup_dir(dir)
|
33
|
+
unless Dir.exists?(dir) then
|
34
|
+
Dir.mkdir dir
|
35
|
+
end
|
36
|
+
return dir
|
37
|
+
end
|
38
|
+
|
39
|
+
Contract String, String => String
|
40
|
+
def self.create_backup_tar(working_dir, backup_dir)
|
41
|
+
puts 'info: creating backup tarball'
|
42
|
+
tar_file = "#{backup_dir}/core-backup-#{Time.now.to_i}.tar"
|
43
|
+
Dir.chdir(working_dir)
|
44
|
+
# archive the working directory
|
45
|
+
StellarCoreBackup::Tar.pack(tar_file, '.')
|
46
|
+
return tar_file
|
47
|
+
end
|
48
|
+
|
49
|
+
Contract String => nil
|
50
|
+
def extract_backup(backup_archive)
|
51
|
+
# extract the backup archive into the working directory
|
52
|
+
StellarCoreBackup::Tar.unpack(backup_archive, @working_dir)
|
53
|
+
return
|
54
|
+
end
|
55
|
+
|
56
|
+
# Contract String => nil
|
57
|
+
# def restore(backup_archive)
|
58
|
+
# @fs_restore.restore(backup_archive)
|
59
|
+
# @db_restore.restore()
|
60
|
+
# end
|
61
|
+
|
62
|
+
Contract String => Bool
|
63
|
+
def self.cleanbucket(bucket_dir)
|
64
|
+
if FileUtils.remove(Dir.glob(bucket_dir+'/*')) then
|
65
|
+
puts 'info: cleaning up workspace'
|
66
|
+
return true
|
67
|
+
else
|
68
|
+
return false
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
Contract String => Bool
|
73
|
+
def self.cleanup(working_dir)
|
74
|
+
if FileUtils.remove_dir(working_dir) then
|
75
|
+
puts 'info: cleaning up workspace'
|
76
|
+
return true
|
77
|
+
else
|
78
|
+
return false
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
Contract String, String => Bool
|
83
|
+
def self.confirm_shasums_definitive(working_dir, backup_archive)
|
84
|
+
|
85
|
+
# create an array of filesunpacked into the working_dir
|
86
|
+
Dir.chdir(working_dir)
|
87
|
+
files_present=Dir.glob('./**/*')
|
88
|
+
|
89
|
+
# remove directories and shasum details from file array
|
90
|
+
files_present.delete('./'+File.basename(backup_archive))
|
91
|
+
files_present.delete('./core-db')
|
92
|
+
files_present.delete('./SHA256SUMS')
|
93
|
+
files_present.delete('./SHA256SUMS.sig')
|
94
|
+
|
95
|
+
# now delete the file names in the shasums file from the array
|
96
|
+
# we are expecting an array of zero length after this process
|
97
|
+
File.open("SHA256SUMS").each { |sha_file| files_present.delete(sha_file.split(' ')[1].chomp) }
|
98
|
+
if files_present.none? then
|
99
|
+
return true
|
100
|
+
else
|
101
|
+
return false
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# check we have read permissions
|
106
|
+
Contract String => Bool
|
107
|
+
def self.readable?(file)
|
108
|
+
if File.readable?(file) then
|
109
|
+
puts "info: #{file} readable"
|
110
|
+
return true
|
111
|
+
else
|
112
|
+
puts "error: cannot read #{file}"
|
113
|
+
raise Errno::EACCES
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# check we have write permissions
|
118
|
+
Contract String => Bool
|
119
|
+
def self.writable?(file)
|
120
|
+
if File.writable?(file) then
|
121
|
+
puts "info: #{file} writeable"
|
122
|
+
return true
|
123
|
+
else
|
124
|
+
puts "error: cannot write to #{file}"
|
125
|
+
raise Errno::EACCES
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'stellar-core-backup/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "stellar-core-backup"
|
8
|
+
spec.version = StellarCoreBackup::VERSION
|
9
|
+
spec.authors = ["Tom Llewellyn-Smith"]
|
10
|
+
spec.email = ["tom@stellar.org"]
|
11
|
+
spec.summary = %q{A helper script to backup a stellar-core node}
|
12
|
+
spec.homepage = "https://github.com/stellar/stellar-core-backup"
|
13
|
+
spec.license = "Apache-2.0"
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0")
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
spec.add_dependency "contracts", "~> 0.9"
|
21
|
+
spec.add_dependency "pg", "~> 0.18.1"
|
22
|
+
spec.add_dependency "aws-sdk-s3", "~> 1"
|
23
|
+
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
25
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: stellar-core-backup
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.4
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tom Llewellyn-Smith
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-07-01 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: contracts
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.9'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.9'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: pg
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.18.1
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.18.1
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: aws-sdk-s3
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.7'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.7'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '10.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '10.0'
|
83
|
+
description:
|
84
|
+
email:
|
85
|
+
- tom@stellar.org
|
86
|
+
executables:
|
87
|
+
- stellar-core-backup
|
88
|
+
extensions: []
|
89
|
+
extra_rdoc_files: []
|
90
|
+
files:
|
91
|
+
- ".gitignore"
|
92
|
+
- CONTRIBUTING.md
|
93
|
+
- Gemfile
|
94
|
+
- LICENSE.txt
|
95
|
+
- README.md
|
96
|
+
- Rakefile
|
97
|
+
- bin/stellar-core-backup
|
98
|
+
- config/sample.yaml
|
99
|
+
- lib/stellar-core-backup.rb
|
100
|
+
- lib/stellar-core-backup/cmd.rb
|
101
|
+
- lib/stellar-core-backup/cmd_result.rb
|
102
|
+
- lib/stellar-core-backup/config.rb
|
103
|
+
- lib/stellar-core-backup/database.rb
|
104
|
+
- lib/stellar-core-backup/filesystem.rb
|
105
|
+
- lib/stellar-core-backup/job.rb
|
106
|
+
- lib/stellar-core-backup/restore/database.rb
|
107
|
+
- lib/stellar-core-backup/restore/filesystem.rb
|
108
|
+
- lib/stellar-core-backup/s3.rb
|
109
|
+
- lib/stellar-core-backup/tar.rb
|
110
|
+
- lib/stellar-core-backup/utils.rb
|
111
|
+
- lib/stellar-core-backup/version.rb
|
112
|
+
- stellar-core-backup.gemspec
|
113
|
+
homepage: https://github.com/stellar/stellar-core-backup
|
114
|
+
licenses:
|
115
|
+
- Apache-2.0
|
116
|
+
metadata: {}
|
117
|
+
post_install_message:
|
118
|
+
rdoc_options: []
|
119
|
+
require_paths:
|
120
|
+
- lib
|
121
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - ">="
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - ">="
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '0'
|
131
|
+
requirements: []
|
132
|
+
rubyforge_project:
|
133
|
+
rubygems_version: 2.5.2.1
|
134
|
+
signing_key:
|
135
|
+
specification_version: 4
|
136
|
+
summary: A helper script to backup a stellar-core node
|
137
|
+
test_files: []
|