palobr 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "fog"
4
+ gem "slop"
5
+ gem "ruby-progressbar"
6
+
7
+ group :development do
8
+ gem "bundler", "~> 1.0.0"
9
+ gem "jeweler", "~> 1.6.0"
10
+ gem "rcov", ">= 0"
11
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Timothy Klim
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,19 @@
1
+ = palobr
2
+
3
+ Simple backup system for pushing into cloud.
4
+
5
+ == Contributing to palobr
6
+
7
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
8
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
9
+ * Fork the project
10
+ * Start a feature/bugfix branch
11
+ * Commit and push until you are happy with your contribution
12
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
13
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2011 Timothy Klim. See LICENSE.txt for
18
+ further details.
19
+
data/Rakefile ADDED
@@ -0,0 +1,53 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "palobr"
18
+ gem.homepage = "http://github.com/TimothyKlim/palobr"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{Simple backup system for pushing into cloud}
21
+ gem.description = %Q{Simple backup system for pushing into cloud}
22
+ gem.email = "klimtimothy@gmail.com"
23
+ gem.authors = ["Timothy Klim"]
24
+ # dependencies defined in Gemfile
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
27
+
28
+ require 'rake/testtask'
29
+ Rake::TestTask.new(:test) do |test|
30
+ test.libs << 'lib' << 'test'
31
+ test.pattern = 'test/**/test_*.rb'
32
+ test.verbose = true
33
+ end
34
+
35
+ require 'rcov/rcovtask'
36
+ Rcov::RcovTask.new do |test|
37
+ test.libs << 'test'
38
+ test.pattern = 'test/**/test_*.rb'
39
+ test.verbose = true
40
+ test.rcov_opts << '--exclude "gems/*"'
41
+ end
42
+
43
+ task :default => :test
44
+
45
+ require 'rake/rdoctask'
46
+ Rake::RDocTask.new do |rdoc|
47
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
48
+
49
+ rdoc.rdoc_dir = 'rdoc'
50
+ rdoc.title = "palobr #{version}"
51
+ rdoc.rdoc_files.include('README*')
52
+ rdoc.rdoc_files.include('lib/**/*.rb')
53
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/bin/palobr ADDED
@@ -0,0 +1,209 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH.unshift(File.expand_path("../../lib/", __FILE__))
3
+
4
+ require 'rubygems'
5
+ require 'yaml'
6
+ require 'digest'
7
+ require 'fileutils'
8
+ require 'openssl'
9
+ require 'socket'
10
+ require 'helpers'
11
+
12
+ safe_require do
13
+ require 'slop'
14
+ require 'fog'
15
+ require 'progressbar'
16
+ end
17
+
18
+ require 'backup'
19
+
20
+ opts = Slop.parse :help => true do
21
+ on :a, :add, "Add path to backup", true
22
+ on :b, :bucket, "Set Amazon S3 bucket to backup", true
23
+ on :k, :key, "Set API key to access Amazon S3", true
24
+ on :s, :secret, "Set API secret to access Amazon S3", true
25
+ on :c, :config, "Use config file to upload backup", true #TODO
26
+ on :colorize, "Colorize print to console"
27
+ on :d, :date, "Date for backup restore (default: last)", true
28
+ on :f, :find, "Find file or directory in backups" #TODO
29
+ on :g, :generate, "Generate 4096 bits RSA keys"
30
+ on :h, :hostname, "Set hostname (default: system)", true
31
+ on :i, :increment, "Use increment mode for backup (default: false)"
32
+ on :j, :jar, "Versions of jar (option: hash or path)", true
33
+ # on :k, :keyfile, "Key to encrypt/decrypt backup", true
34
+ on :l, :local, "Backup in local directory", true
35
+ on :list, "List of jars"
36
+ on :r, :rescue, "Return data from backup (option: jar, path or filter)", true
37
+ on :t, :to, "Path to recovery (default: /)", true
38
+ on :v, :verbose, "Verbose mode" #TODO
39
+
40
+ banner "Usage:\n $ palobr [options]\n\nOptions:"
41
+ end
42
+
43
+ if ARGV.empty?
44
+ puts opts.help
45
+
46
+ exit
47
+ end
48
+
49
+ # $VERBOSE = opts.verbose?
50
+ $PRINT_VERBOSE = true
51
+ $COLORIZE = true
52
+
53
+ if opts.generate?
54
+ puts "Generate 4096 bits RSA keys"
55
+ Crypto::create_keys(
56
+ File.join(Dir.getwd, "rsa_key"),
57
+ File.join(Dir.getwd, "rsa_key.pub")
58
+ )
59
+ puts "Done!"
60
+
61
+ exit
62
+ end
63
+
64
+ if opts.local?
65
+ try_create_dir opts[:local]
66
+ @backup = Backup::Instance.new opts[:local]
67
+ else
68
+ [:key, :secret, :bucket].each do |arg|
69
+ puts_fail "Argument '--#{arg}' should not be empty" if opts[arg].nil?
70
+ end
71
+ @backup = Backup::Instance.new(
72
+ "backups",
73
+ true,
74
+ :bucket => opts[:bucket],
75
+ :key => opts[:key],
76
+ :secret => opts[:secret]
77
+ )
78
+ end
79
+
80
+ @backup.hostname = opts[:hostname] if opts.hostname?
81
+
82
+ if opts.list?
83
+ jars_list = @backup.jars
84
+
85
+ unless jars_list.empty?
86
+ puts "List of jars:\n"
87
+ jars_list.keys.sort.each do |key|
88
+ puts " #{key.dark_green}: #{jars_list[key]}"
89
+ end
90
+ else
91
+ puts "Nothing to listing."
92
+ end
93
+
94
+ exit
95
+ end
96
+
97
+ #TODO: AES or RSA
98
+ # @backup.key = opts[:key] if opts.key?
99
+
100
+ if opts.date?
101
+ date = opts[:date].split("-")
102
+
103
+ unless date.length == 1
104
+ @start_date = Backup::Timestamp.parse_timestamp date[0]
105
+ @end_date = Backup::Timestamp.parse_timestamp date[1], true
106
+
107
+ puts_fail "Last date less than start date" if start_date > end_date
108
+ else
109
+ @start_date = Backup::Timestamp.parse_timestamp date[0]
110
+ @end_date = Backup::Timestamp.parse_timestamp date[0], true
111
+ end
112
+ else
113
+ @start_date = nil
114
+ @end_date = Time.now.utc
115
+ end
116
+
117
+ if opts.jar?
118
+ opts[:jar].split(" ").each do |jar|
119
+ versions = @backup.jar_versions(jar)
120
+
121
+ unless versions.empty?
122
+ puts "Versions of backup '#{jar}':"
123
+
124
+ versions.each do |version|
125
+ puts " => #{version.dark_green}: #{Backup::Timestamp.to_str(version)}"
126
+ end
127
+ else
128
+ puts "Versions doesn't exists for jar: #{jar}"
129
+ end
130
+ end
131
+
132
+ exit
133
+ end
134
+
135
+ #TODO: Support rescue option as hash
136
+ if opts.rescue?
137
+ paths = opts[:rescue].split(" ")
138
+ jars_list = @backup.jars
139
+
140
+ include_path = lambda {|path| jars_list.keys.include? path}
141
+
142
+ jars_hashes = paths.map do |path|
143
+ path = File.expand_path path
144
+
145
+ unless include_path[path] or include_path["#{path}/"]
146
+ puts_fail "Jar \"#{path}\" not exists."
147
+ end
148
+
149
+ jars_list[path] || jars_list["#{path}/"]
150
+ end
151
+
152
+ if opts.to?
153
+ @to = File.expand_path opts[:to]
154
+ try_create_dir @to
155
+ else
156
+ @to = "/"
157
+ end
158
+
159
+ #TODO: Confirm flag
160
+ #TODO: Empty destination directory
161
+
162
+ @index = {}
163
+
164
+ jars_hashes.each do |hash|
165
+ versions = @backup.jar_versions(hash)
166
+ # puts "Versions: #{versions}" #FIXME
167
+
168
+ last_version = Backup::Timestamp.last_from(versions, @end_date, @start_date)
169
+
170
+ unless last_version.nil?
171
+ @index[hash] = last_version
172
+ else
173
+ error_path = "#{Backup::Jar.hash_to_path(@backup.root_path, hash)}"
174
+ start_date = Backup::Timestamp.to_s(@start_date)
175
+ end_date = Backup::Timestamp.to_s(@end_date)
176
+
177
+ unless @end_date == @start_date
178
+ puts_fail "Nothing found for #{error_path}, between date: #{start_date} - #{end_date}"
179
+ else
180
+ puts_fail "Nothing found for #{error_path}, for date: #{end_date}"
181
+ end
182
+ end
183
+ end
184
+
185
+ @index.each do |hash, timestamp|
186
+ # puts "#{hash}: #{timestamp}" #FIXME
187
+ @backup.restore_jar_to(hash, timestamp, @to)
188
+ end
189
+
190
+ puts "Done!".green
191
+ exit
192
+ end
193
+
194
+ if opts.add?
195
+ paths = opts[:add].split(" ")
196
+
197
+ paths = paths.map do |path|
198
+ path = File.expand_path path
199
+ puts_fail "Path \"#{path}\" not exists." unless File.exists? path
200
+
201
+ path
202
+ end
203
+
204
+ paths.each do |path|
205
+ @backup.create! path, opts.increment?
206
+ end
207
+
208
+ puts "Done!".green
209
+ end
data/lib/backup.rb ADDED
@@ -0,0 +1,120 @@
1
+ require 'backup/file_item'
2
+ require 'backup/timestamp'
3
+ require 'backup/jar'
4
+ require 'crypto'
5
+
6
+ module Backup
7
+ class Instance
8
+ attr_reader :root_path, :timestamp, :hostname, :file_item
9
+
10
+ def initialize(root_path, cloud = false, *args)
11
+ if cloud
12
+ @file_item = Backup::FileItem.for :cloud, *args
13
+ else
14
+ @file_item = Backup::FileItem.for :local
15
+ end
16
+
17
+ @hostname = Socket.gethostname
18
+ @root_path = "#{root_path}/#{@hostname}"
19
+ @timestamp = Backup::Timestamp.create
20
+ end
21
+
22
+ def hostname=(host)
23
+ @hostname = host
24
+ @root_path = "#{root_path}/#{@hostname}"
25
+ end
26
+
27
+ def key=(path)
28
+ @key = open(path).read
29
+ end
30
+
31
+ def create!(local_path, increment = false)
32
+ jar = Jar.new(@file_item, @root_path, local_path)
33
+ jar.save(increment)
34
+ end
35
+
36
+ def jars
37
+ Jar.all(@file_item, @root_path)
38
+ end
39
+
40
+ def jar_versions(jar)
41
+ Jar.jar_versions(@file_item, @root_path, jar, !!jar[/^[0-9a-z]{32}$/])
42
+ end
43
+
44
+ def restore_jar_to(hash, timestamp, to)
45
+ files = Jar.fetch_index_for(@file_item, @root_path, hash, timestamp)
46
+
47
+ files.keys.sort.each do |file|
48
+ restore_file = File.join(to, file)
49
+ current_file = files[file]
50
+
51
+ if current_file[:checksum].nil?
52
+ try_create_dir restore_file
53
+
54
+ File.chmod current_file[:mode], restore_file
55
+ File.chown current_file[:uid], current_file[:gid], restore_file
56
+
57
+ file_ok = @file_item.stat(restore_file)[restore_file]
58
+
59
+ check_mode(restore_file, file_ok[:mode], current_file[:mode])
60
+ check_rights(
61
+ restore_file,
62
+ file_ok[:uid],
63
+ file_ok[:gid],
64
+ current_file[:uid],
65
+ current_file[:gid]
66
+ )
67
+ else
68
+ try_create_dir(File.dirname restore_file)
69
+
70
+ begin
71
+ File.open(restore_file, "w") do |f|
72
+ f.chmod current_file[:mode]
73
+ f.chown current_file[:uid], current_file[:gid]
74
+
75
+ remote_path = "#{@root_path}/#{hash}/#{current_file[:timestamp]}"
76
+ remote_path += "/#{@file_item.file_hash file}"
77
+
78
+ data = @file_item.read_file remote_path
79
+ f.puts data
80
+ end
81
+
82
+ file_ok = @file_item.stat(restore_file)[restore_file]
83
+
84
+ check_mode(restore_file, file_ok[:mode], current_file[:mode])
85
+ check_rights(
86
+ restore_file,
87
+ file_ok[:uid],
88
+ file_ok[:gid],
89
+ current_file[:uid],
90
+ current_file[:gid]
91
+ )
92
+ rescue Errno::EACCES
93
+ puts_fail "Permission denied for #{restore_file.dark_green}"
94
+ end
95
+ end
96
+ end
97
+
98
+ end
99
+ end
100
+
101
+ def self.fetch_versions_of_backup(path)
102
+ Dir["#{path}/*"].map do |backup|
103
+ backup.match(/[0-9]{12}$/)[0] if backup.match(/[0-9]{12}$/)
104
+ end.compact.sort
105
+ end
106
+
107
+ def self.aes(command, key, data)
108
+ aes = OpenSSL::Cipher::Cipher.new('aes-256-cbc').send(command)
109
+ aes.key = key
110
+ aes.update(data) << aes.final
111
+ end
112
+
113
+ def self.encrypt_data(key, data)
114
+ Backup::aes(:encrypt, key, data) unless data.empty?
115
+ end
116
+
117
+ def self.decrypt_data(key, data)
118
+ Backup::aes(:decrypt, key, data)
119
+ end
120
+ end
@@ -0,0 +1,18 @@
1
+ require 'backup/file_item/local'
2
+ require 'backup/file_item/cloud'
3
+
4
+ module Backup
5
+ module FileItem
6
+ def self.for(type, *args)
7
+ case type
8
+ when :cloud
9
+ Backup::FileItem::Cloud.new *args
10
+ when :local
11
+ Backup::FileItem::Local.new
12
+
13
+ else
14
+ puts_fail "Unknown '#{type}' type for FileItem"
15
+ end
16
+ end
17
+ end
18
+ end