palobr 0.1.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.
@@ -0,0 +1,37 @@
1
+ module Backup
2
+ module FileItem
3
+ class Base
4
+ def semantic_path(path)
5
+ if Dir.exists? path
6
+ path += '/'
7
+ else
8
+ path
9
+ end
10
+ end
11
+
12
+ def stat(file, timestamp = nil)
13
+ files = {}
14
+
15
+ stat = File.new(file).stat
16
+ files[file] = {
17
+ :uid => stat.uid,
18
+ :gid => stat.gid,
19
+ :mode => stat.mode
20
+ }
21
+ files[file][:timestamp] = timestamp if timestamp
22
+
23
+ unless Dir.exists?(file)
24
+ files[file][:checksum] = Digest::MD5.hexdigest(File.open(file).read)
25
+ end
26
+
27
+ files
28
+ rescue Exception => e
29
+ STDERR.puts e
30
+ end
31
+
32
+ def file_hash(file)
33
+ Digest::MD5.hexdigest file
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,91 @@
1
+ require 'backup/file_item/base'
2
+
3
+ module Backup
4
+ module FileItem
5
+ class Cloud < Backup::FileItem::Base
6
+ attr_reader :key, :secret, :backet, :provider
7
+
8
+ def initialize(args = {})
9
+ puts_fail "Empty hash in Cloud initialize method" if args.empty?
10
+
11
+ [:key, :secret, :bucket].each do |arg|
12
+ puts_fail "'#{arg.to_s.green}' should not be empty" if args[arg].nil?
13
+ instance_eval %{@#{arg} = args[:#{arg}]}
14
+ end
15
+
16
+ try_to_connect_with_cloud
17
+ end
18
+
19
+ def create_directory_once(*directories)
20
+ # Nothing happen
21
+ end
22
+
23
+ def create_file_once(file, data)
24
+ try_to_work_with_cloud do
25
+ @directory.files.create(
26
+ :key => delete_slashes(file),
27
+ :body => data
28
+ )
29
+ end
30
+ end
31
+
32
+ def read_file(file)
33
+ try_to_work_with_cloud do
34
+ file = delete_slashes(file)
35
+ remote_file = @directory.files.get(file)
36
+ remote_file.body if remote_file
37
+ end
38
+ end
39
+
40
+ def dir(path, mask = "*")
41
+ path = delete_slashes(path)
42
+ mask = mask.gsub('.', '\.').gsub('*', '[^\/]')
43
+
44
+ try_to_work_with_cloud do
45
+ files = @directory.files.map &:key
46
+ end
47
+
48
+ files.map do |item|
49
+ match = item.match(/^#{path}\/([^\/]+#{mask}).*$/)
50
+ match[1] if match
51
+ end.compact.uniq
52
+ end
53
+
54
+ private
55
+
56
+ def delete_slashes(str)
57
+ str.chop! if str =~ /\/$/
58
+ str = str[1, str.length] if str =~ /^\//
59
+ str
60
+ end
61
+
62
+ def try_to_work_with_cloud(&block)
63
+ begin
64
+ yield
65
+ rescue Exception => e
66
+ try_to_connect_with_cloud
67
+
68
+ yield
69
+ end
70
+ end
71
+
72
+
73
+ def try_to_connect_with_cloud
74
+ begin
75
+ @connection = ::Fog::Storage.new(
76
+ :provider => 'AWS',
77
+ :aws_secret_access_key => @secret,
78
+ :aws_access_key_id => @key
79
+ )
80
+
81
+ @directory = @connection.directories.get(@bucket)
82
+ rescue Exception => e
83
+ puts_verbose e.message
84
+ puts_fail "403 Forbidden"
85
+ end
86
+
87
+ puts_fail "Bucket '#{@bucket}' is not exists." if @directory.nil?
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,26 @@
1
+ require 'backup/file_item/base'
2
+
3
+ module Backup
4
+ module FileItem
5
+ class Local < Backup::FileItem::Base
6
+ def create_directory_once(*directories)
7
+ directories.each do |path|
8
+ FileUtils.mkdir_p(path) unless Dir.exists?(path)
9
+ end
10
+ end
11
+
12
+ def create_file_once(file, data)
13
+ date = date.read if date.is_a? File
14
+ File.open(file, "w").puts(data) unless File.exists?(file)
15
+ end
16
+
17
+ def read_file(file)
18
+ open(file).read if File.exists? file
19
+ end
20
+
21
+ def dir(path, mask = "*")
22
+ Dir["#{path}/#{mask}"]
23
+ end
24
+ end
25
+ end
26
+ end
data/lib/backup/jar.rb ADDED
@@ -0,0 +1,163 @@
1
+ module Backup
2
+ class Jar
3
+ def initialize(file_item, root_path, local_path)
4
+ @root_path = root_path
5
+ @local_path = local_path
6
+ @timestamp = Backup::Timestamp.create
7
+ @file_item = file_item
8
+ end
9
+
10
+ def jar_hash
11
+ Digest::MD5.hexdigest(@local_path)
12
+ end
13
+
14
+ def save(increment = false)
15
+ unless increment
16
+ @local_files = hash_local_files
17
+ else
18
+ @local_files = {}
19
+ current_files = hash_local_files
20
+
21
+ last_timestamp = Jar.jar_versions(@root_path, jar_hash, true).last
22
+
23
+ if last_timestamp.nil?
24
+ puts_fail "First you must create a full backup for #{@local_path.dark_green}"
25
+ end
26
+
27
+ last_index = Jar.fetch_index_for(@root_path, jar_hash, last_timestamp)
28
+
29
+ current_files.keys.each do |file|
30
+ @local_files[file] = current_files[file]
31
+
32
+ #TODO: Cut to a new method {
33
+ current = current_files[file].dup
34
+ current.delete(:timestamp)
35
+
36
+ unless last_index[file].nil?
37
+ backup = last_index[file].dup
38
+ backup.delete(:timestamp)
39
+
40
+ if (current == backup) or
41
+ (!current[:checksum].nil? and current[:checksum] == backup[:checksum])
42
+
43
+ @local_files[file][:timestamp] = last_index[file][:timestamp]
44
+ end
45
+ end
46
+ # }
47
+ end
48
+ end
49
+
50
+ @file_item.create_directory_once meta_jars_path, meta_jar_path, jar_data_path
51
+ @file_item.create_file_once(
52
+ "#{meta_jars_path}/#{jar_hash}",
53
+ @file_item.semantic_path(@local_path)
54
+ )
55
+ @file_item.create_file_once(
56
+ "#{meta_jar_path}/#{@timestamp}.yml",
57
+ @local_files.to_yaml
58
+ )
59
+
60
+ if @file_item.is_a? Backup::FileItem::Cloud
61
+ pbar = ProgressBar.new(
62
+ "Uploading",
63
+ @local_files.keys.count
64
+ )
65
+ else
66
+ pbar = ProgressBar.new(
67
+ "Copying",
68
+ @local_files.keys.count
69
+ )
70
+ end
71
+
72
+ pbar.bar_mark = '*'
73
+
74
+ @local_files.keys.each do |file|
75
+ unless Dir.exists?(file)
76
+ @file_item.create_file_once "#{jar_data_path}/#{@file_item.file_hash file}",
77
+ File.open(file)
78
+ pbar.inc
79
+ end
80
+ end
81
+
82
+ pbar.finish
83
+ end
84
+
85
+ def hash_local_files
86
+ files = {}
87
+
88
+ puts_verbose "Create index for #{@local_path.dark_green}"
89
+
90
+ if Dir.exists? @local_path
91
+ matches = Dir.glob(File.join(@local_path, "/**/*"), File::FNM_DOTMATCH)
92
+
93
+ matches = matches.select do |match|
94
+ match[/\/..$/].nil? and match[/\/.$/].nil?
95
+ end
96
+
97
+ matches << @local_path
98
+
99
+ matches.each do |match|
100
+ files.merge!(@file_item.stat(match, @timestamp))
101
+ end
102
+ else
103
+ files = @file_item.stat(@local_path, @timestamp)
104
+ end
105
+
106
+ files
107
+ end
108
+
109
+ class << self
110
+ def hash_to_path(file_item, root_path, hash)
111
+ file_item.read_file("#{root_path}/meta/jars/#{hash}").chomp
112
+ rescue Errno::ENOENT
113
+ ""
114
+ end
115
+
116
+ def all(file_item, root_path)
117
+ hashes = file_item.dir("#{root_path}/meta/jars").map do |backup|
118
+ backup[/[0-9a-z]{32}$/]
119
+ end.compact.sort
120
+
121
+ result = {}
122
+
123
+ hashes.each do |hash|
124
+ jar_local_path = Jar.hash_to_path(file_item, root_path, hash)
125
+ result[jar_local_path] = hash unless jar_local_path.empty?
126
+ end
127
+
128
+ result
129
+ end
130
+
131
+ def jar_versions(file_item, root_path, jar, hash = false)
132
+ jar = jar.chop if jar =~ /\/$/
133
+ jar = Digest::MD5.hexdigest(jar) unless hash
134
+
135
+ meta_jar_path = "#{root_path}/meta/#{jar}"
136
+
137
+ file_item.dir(meta_jar_path, "*.yml").map do |file|
138
+ match = file.match(/^\/?([0-9]{12}).yml$/)
139
+ match[1] if match
140
+ end.compact.sort
141
+ end
142
+
143
+ def fetch_index_for(file_item, root_path, hash, timestamp)
144
+ index = file_item.read_file "#{root_path}/meta/#{hash}/#{timestamp}.yml"
145
+ YAML::load(index) unless index.nil?
146
+ end
147
+ end
148
+
149
+ private
150
+
151
+ def meta_jars_path
152
+ "#{@root_path}/meta/jars"
153
+ end
154
+
155
+ def meta_jar_path
156
+ "#{@root_path}/meta/#{jar_hash}"
157
+ end
158
+
159
+ def jar_data_path
160
+ "#{@root_path}/#{jar_hash}/#{@timestamp}"
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,48 @@
1
+ module Backup
2
+ class Timestamp
3
+ def self.parse_timestamp(version, last = false)
4
+ version = version.gsub(".", "").gsub(" ", "").gsub(":", "")
5
+
6
+ puts_fail "Invalid date format: #{version}" unless version.match /[0-9]{6,}/
7
+
8
+ year, month, day, hour, min, sec =
9
+ version.split(/([0-9]{2})/).map do |date|
10
+ date.to_i unless date.empty?
11
+ end.compact
12
+
13
+ if last
14
+ hour = 23 if hour.nil?
15
+ min = 59 if min.nil?
16
+ sec = 59 if sec.nil?
17
+ end
18
+
19
+ time = Time.new(year + 2000, month, day, hour, min, sec, 0)
20
+ end
21
+
22
+ def self.last_from(list, end_date, start_date = nil)
23
+ list.sort.reverse.find do |version|
24
+ version = Backup::Timestamp.parse_timestamp version
25
+
26
+ unless start_date.nil?
27
+ version >= start_date and version <= end_date
28
+ else
29
+ version <= end_date
30
+ end
31
+ end
32
+ end
33
+
34
+ def self.create(time = nil)
35
+ time = time.nil? ? Time.now : time
36
+
37
+ time.utc.strftime "%y%m%d%H%M%S"
38
+ end
39
+
40
+ def self.to_str(version)
41
+ to_s parse_timestamp(version)
42
+ end
43
+
44
+ def self.to_s(time)
45
+ time.strftime "%y.%m.%d %H:%M:%S" if time.is_a? Time
46
+ end
47
+ end
48
+ end
data/lib/crypto.rb ADDED
@@ -0,0 +1,43 @@
1
+ require 'openssl'
2
+ require 'base64'
3
+
4
+ module Crypto
5
+
6
+ def self.create_keys(priv = "rsa_key", pub = "#{priv}.pub", bits = 4096)
7
+ private_key = OpenSSL::PKey::RSA.new(bits)
8
+ File.open(priv, "w+") { |fp| fp << private_key.to_s }
9
+ File.open(pub, "w+") { |fp| fp << private_key.public_key.to_s }
10
+ private_key
11
+ end
12
+
13
+ class Key
14
+ def initialize(data)
15
+ @public = (data =~ /^-----BEGIN (RSA|DSA) PRIVATE KEY-----$/).nil?
16
+ @key = OpenSSL::PKey::RSA.new(data)
17
+ end
18
+
19
+ def self.from_file(filename)
20
+ self.new File.read( filename )
21
+ end
22
+
23
+ def encrypt(text)
24
+ @key.send("#{key_type}_encrypt", text)
25
+ end
26
+
27
+ def decrypt(text)
28
+ @key.send("#{key_type}_decrypt", text)
29
+ end
30
+
31
+ def private?
32
+ !@public
33
+ end
34
+
35
+ def public?
36
+ @public
37
+ end
38
+
39
+ def key_type
40
+ @public ? :public : :private
41
+ end
42
+ end
43
+ end
data/lib/helpers.rb ADDED
@@ -0,0 +1,80 @@
1
+ def puts_fail(msg)
2
+ STDERR.puts "#{"Error: ".red}#{msg}"
3
+
4
+ exit msg.length
5
+ end
6
+
7
+ def puts_verbose(msg)
8
+ puts msg if $PRINT_VERBOSE
9
+ end
10
+
11
+ def print_verbose(msg)
12
+ print msg if $PRINT_VERBOSE
13
+ end
14
+
15
+ def safe_require(&block)
16
+ yield
17
+ rescue Exception => e
18
+ puts_fail %Q{This script use these gems: fog, slop.
19
+ Make sure that you have them all.
20
+ If you don't have, you may install them: $ gem install fog slop ruby-progressbar
21
+ }
22
+ end
23
+
24
+ def try_create_dir(dir)
25
+ begin
26
+ FileUtils.mkdir_p dir unless Dir.exists? dir
27
+ rescue Errno::EACCES
28
+ puts_fail "Permission denied for #{dir.dark_green}"
29
+ end
30
+ end
31
+
32
+ def check_mode(file, first, second)
33
+ unless first == second
34
+ puts_fail "Permission wasn't changed for #{file.dark_green}"
35
+ end
36
+ end
37
+
38
+ def check_rights(file, first_uid, first_gid, second_uid, second_gid)
39
+ unless first_uid == second_uid and first_gid == second_gid
40
+ puts_fail "Group and user wasn't change for #{file.dark_green}"
41
+ end
42
+ end
43
+
44
+ class String
45
+ def red
46
+ colorize(self, "\e[1m\e[31m")
47
+ end
48
+
49
+ def green
50
+ colorize(self, "\e[1m\e[32m")
51
+ end
52
+
53
+ def dark_green
54
+ colorize(self, "\e[32m")
55
+ end
56
+
57
+ def yellow
58
+ colorize(self, "\e[1m\e[33m")
59
+ end
60
+
61
+ def blue
62
+ colorize(self, "\e[1m\e[34m")
63
+ end
64
+
65
+ def dark_blue
66
+ colorize(self, "\e[34m")
67
+ end
68
+
69
+ def pur
70
+ colorize(self, "\e[1m\e[35m")
71
+ end
72
+
73
+ def colorize(text, color_code)
74
+ if $COLORIZE
75
+ "#{color_code}#{text}\e[0m"
76
+ else
77
+ text
78
+ end
79
+ end
80
+ end