backup_restore 0.0.0

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.
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: []