backup_restore 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/backup_restore.rb +232 -0
  3. metadata +45 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4cc90b331805ae3173b46a005168fab2362fc65d
4
+ data.tar.gz: 241e2d48b20b02af82146729602ecc7d24f3d7a5
5
+ SHA512:
6
+ metadata.gz: cbe1a94d29b229713833c2dd915d3e1a38189e7474dc0e74a32e0f969c94b4e8a3970bc98657bb13481f71855efe3e3bbfac5863ee1c38235b3b47a7e1b05f75
7
+ data.tar.gz: e47f474d1cba749be8406f982448172ee31d8058eb8cb532bea9aa7b5ef9760374f642578dad3d5d8f4aa4f0b7b0197edd444b4dfc07348f753be26955bccfdf
@@ -0,0 +1,232 @@
1
+ # Encoding: utf-8
2
+
3
+ # Copyright (C) 2016 - GPLv3 - Mateusz Konieczny
4
+
5
+ require 'io/console'
6
+ require 'fileutils'
7
+
8
+ # some assumptions:
9
+ # this tool runs on Ubuntu
10
+ # archive is valid archive generated with backup gem
11
+ # files are encrypted and archived with tar
12
+ # archives may be split or not
13
+
14
+ class BackupRestore
15
+ def self.debug(message, priority = :medium)
16
+ return if priority == :low
17
+ return if priority == :medium
18
+ puts message
19
+ end
20
+
21
+ def self.change_directory(target)
22
+ Dir.chdir(target)
23
+ return if File.identical?(target, Dir.getwd)
24
+ raise "failed to change working directory to #{target} (it is #{Dir.getwd})"
25
+ end
26
+
27
+ def self.get_storage_folder(archive_storage_root, archive_name)
28
+ debug("joining <#{archive_storage_root}> and <#{archive_name}>", :low)
29
+ target = archive_storage_root + '/' + archive_name
30
+ debug("looking for date folder in <#{target}>", :low)
31
+ change_directory(target)
32
+ directory = Dir.glob('*').select { |f| File.directory? f }
33
+ if directory.length != 1
34
+ puts "unexpected multiple backups at once in #{target}, or backup not found"
35
+ puts "not supposed to happen in my workflow"
36
+ puts "listing #{directory.length} directories, expected exactly 1:"
37
+ directory.each do |file|
38
+ puts file
39
+ end
40
+ raise "unhandled workflow"
41
+ end
42
+ target += '/' + directory[0] + '/'
43
+ return target
44
+ end
45
+
46
+ # alternatives - see http://stackoverflow.com/questions/3159945/running-command-line-commands-within-ruby-script
47
+ def self.execute_command(command, unstandard_error_free_exit_codes = [])
48
+ output = `#{command}`
49
+ if $?.success? || unstandard_error_free_exit_codes.include?($?.exitstatus)
50
+ debug('all done', :low)
51
+ else
52
+ raise "<#{command}> command had problem (<#{$?}> with output <#{output}>). Working directory path was <#{Dir.getwd}>"
53
+ end
54
+ return output
55
+ end
56
+
57
+ def self.extract_tar_file(file, target_folder = nil)
58
+ command = "tar --extract --file=#{file}"
59
+ unless target_folder.nil?
60
+ command += " --preserve-permissions -C #{target_folder}"
61
+ # -C
62
+ # tar will change its current directory to dir before performing any operations
63
+ # https://www.gnu.org/software/tar/manual/html_node/Option-Summary.html
64
+ end
65
+ # base test:
66
+ # echo "tar: Removing leading \`/' from member names" | grep -v "tar: Removing leading \`/' from member names"
67
+ # shell should get:
68
+ # grep -v "tar: Removing leading \`/' from member names"
69
+ # command += ' | grep -v "tar: Removing leading \`/\' from member names"'
70
+ # the code above is not proper way to solve this, it will mess up errorcode (hide errors, grep will return error on lack of match)
71
+ execute_command(command)
72
+ end
73
+
74
+ def self.get_the_only_expected_file(filter = '*')
75
+ files = Dir.glob(filter)
76
+ puts files
77
+ if files.length != 1
78
+ if files.empty?
79
+ puts 'no files found'
80
+ else
81
+ puts "files:"
82
+ end
83
+ for file in files
84
+ puts file
85
+ end
86
+ raise "expected exactly one file, not handled!"
87
+ end
88
+ return files[0]
89
+ end
90
+
91
+ def self.uncrypt_archive(archive_storage_root, archive_name, password)
92
+ storage = get_storage_folder(archive_storage_root, archive_name)
93
+ output_archive = archive_name + '.tar'
94
+ change_directory(storage)
95
+ command = "openssl aes-256-cbc -d -in #{archive_name}.tar.enc -k #{password} -out #{output_archive}"
96
+ execute_command(command)
97
+ end
98
+
99
+ def self.extract_archive(archive_storage_root, archive_name, unpack_root)
100
+ debug("unpacking <#{archive_name}>", :high)
101
+
102
+ storage = get_storage_folder(archive_storage_root, archive_name)
103
+ change_directory(storage)
104
+ debug("archive is stored at <#{storage}>")
105
+
106
+ file = get_the_only_expected_file('*.tar')
107
+ debug("extracting #{file}")
108
+ extract_tar_file(file)
109
+ folder_with_unpacked_archive = storage + archive_name
110
+ debug("unpacked archive with second layer of archive is stored at <#{folder_with_unpacked_archive}>")
111
+
112
+ change_directory(folder_with_unpacked_archive + '/archives/')
113
+ file = get_the_only_expected_file('*.tar.gz')
114
+ debug("extracting #{file}")
115
+ extract_tar_file(file, unpack_root)
116
+
117
+ change_directory(storage)
118
+ FileUtils.rm_rf(folder_with_unpacked_archive)
119
+ end
120
+
121
+ def self.is_unsplitting_necessary(archive_storage_root, archive_name)
122
+ storage = get_storage_folder(archive_storage_root, archive_name)
123
+ return File.exist?(storage + "#{archive_name}.tar.enc-aaa")
124
+ end
125
+
126
+ def self.unsplit_archive(archive_storage_root, archive_name)
127
+ storage = get_storage_folder(archive_storage_root, archive_name)
128
+ change_directory(storage)
129
+ execute_command("cat #{archive_name}.tar.enc-* > #{archive_name}.tar.enc")
130
+ end
131
+
132
+ def self.process_given_archive(archive_storage_root, archive_name, unpack_root, password)
133
+ debug("processsing #{archive_name} in #{archive_storage_root} - extracting to #{unpack_root}", :high)
134
+ if is_unsplitting_necessary(archive_storage_root, archive_name)
135
+ unsplit_archive(archive_storage_root, archive_name)
136
+ end
137
+ uncrypt_archive(archive_storage_root, archive_name, password)
138
+ extract_archive(archive_storage_root, archive_name, unpack_root)
139
+ storage = get_storage_folder(archive_storage_root, archive_name)
140
+ if is_unsplitting_necessary(archive_storage_root, archive_name)
141
+ FileUtils.rm_rf(storage + archive_name + ".tar.enc")
142
+ end
143
+ FileUtils.rm_rf(storage + archive_name + ".tar")
144
+ end
145
+
146
+ def self.compare(compared_path, unpack_root)
147
+ text = compare_paths(compared_path, unpack_root)
148
+ return text
149
+ end
150
+
151
+ class UnexpectedData < StandardError
152
+ def self.initialize(message)
153
+ super(message)
154
+ end
155
+ end
156
+
157
+ def self.discard_unimportant(text, unimportant_paths_array, possible_prefix = [])
158
+ possible_prefix << ""
159
+ output = ""
160
+ text.split("\n").each do |line|
161
+ line.strip!
162
+ unimportant = false
163
+ unimportant_paths_array.each do |filter|
164
+ possible_prefix.each do |prefix|
165
+ r_filter = (Regexp.escape filter).gsub('/', '\/')
166
+ r_prefix = (Regexp.escape prefix).gsub('/', '\/')
167
+ if line =~ /\AOnly in (.+): (.+)\z/
168
+ filepath_without_file, file = /\AOnly in (.+): (.+)\z/.match(line).captures
169
+ filepath_without_file += '/' if filepath_without_file[-1] != '/'
170
+ filepath = filepath_without_file + file
171
+ unimportant = true if filepath =~ /\A#{r_prefix}#{r_filter}.*\z/
172
+ elsif line =~ /\AFiles (.+) and (.+) differ\z/
173
+ filepath_a, filepath_b = /\AFiles (.+) and (.+) differ\z/.match(line).captures
174
+ unimportant = true if filepath_a =~ /\A#{r_prefix}#{r_filter}.*\z/
175
+ unimportant = true if filepath_b =~ /\A#{r_prefix}#{r_filter}.*\z/
176
+ elsif line =~ /\AFile (.+) is a fifo while file (.+) is a fifo\z/
177
+ unimportant = true
178
+ elsif line =~ /\AFile (.+) is a character special file while file (.+) is a character special file\z/
179
+ unimportant = true
180
+ elsif line == everything_is_fine_message.strip
181
+ next
182
+ elsif line == ""
183
+ next
184
+ else
185
+ raise UnexpectedData, "unexpected line <#{line}>"
186
+ end
187
+ end
188
+ end
189
+ next if unimportant
190
+ output += line + "\n"
191
+ end
192
+ puts
193
+ return nil if output == ""
194
+ return output.to_s
195
+ end
196
+
197
+ def self.everything_is_fine_or_unimportant_message
198
+ "no important differences"
199
+ end
200
+
201
+ def self.compare_paths(path_to_backuped, backup_location)
202
+ original = path_to_backuped
203
+ restored = backup_location + path_to_backuped
204
+ raise "missing folder for comparison: #{original}" unless Dir.exist?(original)
205
+ raise "missing folder for comparison: #{restored}" unless Dir.exist?(restored)
206
+ command = "diff --brief -r --no-dereference '#{original}' '#{restored}'"
207
+ puts
208
+ puts command
209
+ puts
210
+ returned = execute_command(command, [1])
211
+ if returned == ""
212
+ return everything_is_fine_message
213
+ else
214
+ return returned
215
+ end
216
+ end
217
+
218
+ def self.everything_is_fine_message
219
+ return "everything is fine!" + "\n"
220
+ end
221
+
222
+ def self.directory_size(path)
223
+ size = 0
224
+ Dir.glob(File.join(path, '**', '*')) { |file| size += File.size(file) }
225
+ return size
226
+ end
227
+
228
+ def self.is_it_at_least_this_size_in_mb(path, mb)
229
+ size = directory_size(path)
230
+ return size > mb * 1024 * 1024
231
+ end
232
+ end
metadata ADDED
@@ -0,0 +1,45 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: backup_restore
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Mateusz Konieczny
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-05-21 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: 'Script for unpacking backups produced by the backup gem. See https://github.com/backup/backup-features/issues/28
14
+ for discussion about this feature in backup gem itself. '
15
+ email: matkoniecz@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - lib/backup_restore.rb
21
+ homepage: https://github.com/matkoniecz/backup-gem-extractor-crutch
22
+ licenses:
23
+ - GPL-3.0
24
+ metadata: {}
25
+ post_install_message:
26
+ rdoc_options: []
27
+ require_paths:
28
+ - lib
29
+ required_ruby_version: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ required_rubygems_version: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ requirements: []
40
+ rubyforge_project:
41
+ rubygems_version: 2.6.4
42
+ signing_key:
43
+ specification_version: 4
44
+ summary: Script for unpacking backups produced by the backup gem.
45
+ test_files: []