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.
@@ -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,3 @@
1
+ module StellarCoreBackup
2
+ VERSION = "0.0.4"
3
+ 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: []