capsium 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/workflows/rake.yml +18 -0
- data/.github/workflows/release.yml +24 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.rubocop.yml +8 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +12 -0
- data/README.adoc +258 -0
- data/Rakefile +12 -0
- data/bin/console +11 -0
- data/bin/setup +8 -0
- data/capsium.gemspec +54 -0
- data/exe/capsium +6 -0
- data/images/005df7415a331c466ad2d9a42efdd212b6f42fe50b5fd3b3174c86c706f58244.png +0 -0
- data/images/0374025b3af99b8a473282c8cbbf9fcd29573cf41e586982f328f86c0690f43d.png +0 -0
- data/images/0bb4da785be40ef58e219470ebccb979325928b75453dc46bac23c6ee8a7a7cb.png +0 -0
- data/images/6aa294dccc81af594aacbe804e7ddffdc17eacc28357338108aea5d021d831ff.png +0 -0
- data/images/72dd3fbf3f4b475e27a0e7fb8137c475c32c41f8d222bcf62d6a9ccf102d9532.png +0 -0
- data/images/8772b6961d169738d7b0fa0b669b06fc2f40632d4c62586c7634fc17b93182a3.png +0 -0
- data/images/a998d842405933d45723606ff3f70162ec95b4ef30db25464a366184fd08fb9b.png +0 -0
- data/images/aa8980547e8c003d33273ab4d80e62da7f317bd7581b293c06d67f5331f24f31.png +0 -0
- data/images/bb78a872b539e0e9b2d80dee58acbb688f3f2727b324a5bf8bf417a69d94a166.png +0 -0
- data/images/c48fc83b17725d85fbb64d971196ebfccd8c5c757fe6aa5845303f6e315879b6.png +0 -0
- data/images/f08ef07308d08119ac2124bb7428c8bef17ef1ca70045696604d6e83015a9b91.png +0 -0
- data/images/f7514206111b695647eae9adfcf498ba3e0ff83ecfe25f3fc3ed8e9f04c5c726-1.png +0 -0
- data/images/f7514206111b695647eae9adfcf498ba3e0ff83ecfe25f3fc3ed8e9f04c5c726.png +0 -0
- data/lib/capsium/cli.rb +96 -0
- data/lib/capsium/converters/jekyll.rb +59 -0
- data/lib/capsium/package/dataset.rb +86 -0
- data/lib/capsium/package/manifest.rb +97 -0
- data/lib/capsium/package/metadata.rb +48 -0
- data/lib/capsium/package/routes.rb +157 -0
- data/lib/capsium/package/storage.rb +52 -0
- data/lib/capsium/package.rb +142 -0
- data/lib/capsium/packager.rb +100 -0
- data/lib/capsium/protector.rb +95 -0
- data/lib/capsium/reactor.rb +82 -0
- data/lib/capsium/thor_ext.rb +71 -0
- data/lib/capsium/version.rb +5 -0
- data/lib/capsium.rb +15 -0
- data/sig/capsium.rbs +4 -0
- metadata +354 -0
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# lib/capsium/packager.rb
|
4
|
+
require "json"
|
5
|
+
require "fileutils"
|
6
|
+
require "rubygems"
|
7
|
+
require "zip"
|
8
|
+
|
9
|
+
module Capsium
|
10
|
+
class Packager
|
11
|
+
class FileAlreadyExistsError < StandardError; end
|
12
|
+
|
13
|
+
def pack(package, options = {})
|
14
|
+
package_name = package.metadata.name
|
15
|
+
package_version = package.metadata.version
|
16
|
+
cap_file_path = File.expand_path(
|
17
|
+
File.join(package.path, "..", "#{package_name}-#{package_version}.cap")
|
18
|
+
)
|
19
|
+
|
20
|
+
if File.exist?(cap_file_path)
|
21
|
+
unless options[:force]
|
22
|
+
raise FileAlreadyExistsError,
|
23
|
+
"Package target already exists, aborting: `#{relative_path_current(cap_file_path)}`"
|
24
|
+
end
|
25
|
+
|
26
|
+
puts "Package target already exists, overwriting: `#{relative_path_current(cap_file_path)}`"
|
27
|
+
FileUtils.rm_f(cap_file_path)
|
28
|
+
end
|
29
|
+
|
30
|
+
Dir.mktmpdir do |dir|
|
31
|
+
FileUtils.cp_r("#{package.path}/.", dir)
|
32
|
+
new_package = Package.new(dir)
|
33
|
+
new_package.solidify
|
34
|
+
compress_package(new_package, cap_file_path)
|
35
|
+
puts "Package created: #{relative_path_current(cap_file_path)}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def compress_package(package, cap_file)
|
40
|
+
Zip::File.open(cap_file, Zip::File::CREATE) do |zipfile|
|
41
|
+
Dir[File.join(package.path, "**", "**")].each do |file|
|
42
|
+
zipfile.add(file.sub("#{package.path}/", ""), file)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# def package_files
|
48
|
+
# create_metadata_file
|
49
|
+
# create_manifest_file
|
50
|
+
# create_packaging_file
|
51
|
+
|
52
|
+
# compressor = Compressor.new(@package, @package.metadata[:compression])
|
53
|
+
# compressor.compress
|
54
|
+
|
55
|
+
# protector = Protector.new(@package, @package.metadata[:encryption], @package.metadata[:signature])
|
56
|
+
# protector.apply_encryption_and_sign
|
57
|
+
# end
|
58
|
+
|
59
|
+
# private
|
60
|
+
|
61
|
+
# def create_metadata_file
|
62
|
+
# metadata_path = File.join(@package.path, Package::METADATA_FILE)
|
63
|
+
# metadata_content = @package.metadata.to_h
|
64
|
+
# write_json_file(metadata_path, metadata_content)
|
65
|
+
# end
|
66
|
+
|
67
|
+
# def create_manifest_file
|
68
|
+
# manifest_path = File.join(@package.path, Package::MANIFEST_FILE)
|
69
|
+
# manifest_content = {
|
70
|
+
# files: Dir[File.join(@package.path, '**', '*')].reject { |f| File.directory?(f) }
|
71
|
+
# }
|
72
|
+
# write_json_file(manifest_path, manifest_content)
|
73
|
+
# end
|
74
|
+
|
75
|
+
def create_packaging_file
|
76
|
+
packaging_path = File.join(@package.path, Package::PACKAGING_FILE)
|
77
|
+
packaging_content = {
|
78
|
+
name: @package.name,
|
79
|
+
content_path: relative_path_package(@package.content_path),
|
80
|
+
data_path: relative_path_package(@package.data_path),
|
81
|
+
datasets: @package.datasets.map(&:to_h)
|
82
|
+
}
|
83
|
+
write_json_file(packaging_path, packaging_content)
|
84
|
+
end
|
85
|
+
|
86
|
+
def write_json_file(path, content)
|
87
|
+
File.open(path, "w") do |file|
|
88
|
+
file.write(JSON.pretty_generate(content))
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def relative_path_package(absolute_path)
|
93
|
+
Pathname.new(absolute_path).relative_path_from(Pathname.new(@package.path)).to_s
|
94
|
+
end
|
95
|
+
|
96
|
+
def relative_path_current(absolute_path)
|
97
|
+
Pathname.new(absolute_path).relative_path_from(Dir.pwd).to_s
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# lib/capsium/protector.rb
|
4
|
+
require "openssl"
|
5
|
+
require "base64"
|
6
|
+
require "json"
|
7
|
+
|
8
|
+
module Capsium
|
9
|
+
class Protector
|
10
|
+
def initialize(package, encryption_metadata = nil, digital_signature_metadata = nil)
|
11
|
+
@package = package
|
12
|
+
@encryption_metadata = encryption_metadata
|
13
|
+
@digital_signature_metadata = digital_signature_metadata
|
14
|
+
end
|
15
|
+
|
16
|
+
def apply_encryption_and_sign
|
17
|
+
encrypted_file = apply_encryption if @encryption_metadata
|
18
|
+
sign_package(encrypted_file) if @digital_signature_metadata
|
19
|
+
end
|
20
|
+
|
21
|
+
def verify_signature
|
22
|
+
signature_data = JSON.parse(File.read(signature_file_path))
|
23
|
+
public_key = OpenSSL::PKey::RSA.new(File.read(public_key_path))
|
24
|
+
digest = OpenSSL::Digest.new(signature_data["algorithm"])
|
25
|
+
data = combined_data
|
26
|
+
signature = Base64.decode64(signature_data["signature"])
|
27
|
+
|
28
|
+
public_key.verify(digest, signature, data)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def apply_encryption
|
34
|
+
encryption = @encryption_metadata
|
35
|
+
data = File.read(File.join(@package.path, Package::ENCRYPTED_PACKAGING_FILE))
|
36
|
+
cipher = OpenSSL::Cipher.new(encryption[:algorithm])
|
37
|
+
cipher.encrypt
|
38
|
+
key = cipher.random_key
|
39
|
+
iv = cipher.random_iv
|
40
|
+
|
41
|
+
encrypted_data = cipher.update(data) + cipher.final
|
42
|
+
encrypted_file = File.join(@package.path, "#{@package.name}.enc")
|
43
|
+
|
44
|
+
File.open(encrypted_file, "wb") do |f|
|
45
|
+
f.write(iv)
|
46
|
+
f.write(encrypted_data)
|
47
|
+
end
|
48
|
+
|
49
|
+
save_encryption_key(key, encryption[:keyManagement])
|
50
|
+
encrypted_file
|
51
|
+
end
|
52
|
+
|
53
|
+
def save_encryption_key(key, key_management)
|
54
|
+
case key_management
|
55
|
+
when "secure"
|
56
|
+
# Implement secure key storage mechanism
|
57
|
+
File.write(File.join(@package.path, "encryption_key.secure"), Base64.encode64(key))
|
58
|
+
else
|
59
|
+
raise "Unknown key management strategy: #{key_management}"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def sign_package(encrypted_file)
|
64
|
+
key = OpenSSL::PKey::RSA.new(@digital_signature_metadata[:keyLength])
|
65
|
+
data = combined_data(encrypted_file)
|
66
|
+
digest = OpenSSL::Digest.new(@digital_signature_metadata[:algorithm])
|
67
|
+
signature_data = key.sign(digest, data)
|
68
|
+
|
69
|
+
signature_json = {
|
70
|
+
algorithm: @digital_signature_metadata[:algorithm],
|
71
|
+
certificateType: @digital_signature_metadata[:certificateType],
|
72
|
+
signature: Base64.encode64(signature_data)
|
73
|
+
}
|
74
|
+
|
75
|
+
File.write(signature_file_path, JSON.pretty_generate(signature_json))
|
76
|
+
File.write(public_key_path, key.public_key.to_pem)
|
77
|
+
end
|
78
|
+
|
79
|
+
def combined_data(encrypted_file = nil)
|
80
|
+
metadata_content = File.read(File.join(@package.path, Package::METADATA_FILE))
|
81
|
+
signature_content = File.read(signature_file_path).sub(/"signature": ".*"/, '"signature": ""')
|
82
|
+
package_enc_content = File.read(encrypted_file || File.join(@package.path, "#{@package.name}.enc"))
|
83
|
+
|
84
|
+
metadata_content + signature_content + package_enc_content
|
85
|
+
end
|
86
|
+
|
87
|
+
def signature_file_path
|
88
|
+
File.join(@package.path, @digital_signature_metadata[:signatureFile])
|
89
|
+
end
|
90
|
+
|
91
|
+
def public_key_path
|
92
|
+
File.join(@package.path, "public_key.pem")
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# lib/capsium/reactor.rb
|
4
|
+
require "webrick"
|
5
|
+
require "json"
|
6
|
+
require "listen"
|
7
|
+
require "capsium/package"
|
8
|
+
|
9
|
+
module Capsium
|
10
|
+
class Reactor
|
11
|
+
DEFAULT_PORT = 8864
|
12
|
+
attr_accessor :package, :package_path, :routes, :port, :server, :server_thread
|
13
|
+
|
14
|
+
def initialize(package, port = DEFAULT_PORT)
|
15
|
+
@package = package
|
16
|
+
@package_path = package.path
|
17
|
+
@port = port
|
18
|
+
@server = WEBrick::HTTPServer.new(Port: @port)
|
19
|
+
load_routes
|
20
|
+
mount_routes
|
21
|
+
end
|
22
|
+
|
23
|
+
def serve
|
24
|
+
@server_thread = start_server
|
25
|
+
start_listener
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def load_routes
|
31
|
+
@routes = @package.routes.data.routes
|
32
|
+
end
|
33
|
+
|
34
|
+
def mount_routes
|
35
|
+
@routes.each do |route|
|
36
|
+
path = route.path
|
37
|
+
target = route.target
|
38
|
+
content_path = target.fs_path(@package.manifest)
|
39
|
+
puts "mounting route: #{path} => #{content_path}"
|
40
|
+
@server.mount_proc(path.to_s) do |_req, res|
|
41
|
+
res.body = File.read(content_path)
|
42
|
+
res.content_type = target.mime(@package.manifest)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def start_server
|
48
|
+
@server_thread = Thread.new do
|
49
|
+
trap("INT") do
|
50
|
+
puts "\nShutting down server..."
|
51
|
+
@server.shutdown
|
52
|
+
exit
|
53
|
+
end
|
54
|
+
|
55
|
+
puts "Starting server on http://localhost:#{@port}"
|
56
|
+
@server.start
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def restart_server
|
61
|
+
@server.shutdown
|
62
|
+
# @server_thread.kill
|
63
|
+
load_package
|
64
|
+
@server = WEBrick::HTTPServer.new(Port: @port)
|
65
|
+
mount_routes
|
66
|
+
start_server
|
67
|
+
end
|
68
|
+
|
69
|
+
def start_listener
|
70
|
+
listener = Listen.to(@package_path) do |_modified, _added, _removed|
|
71
|
+
puts "Changes detected, reloading..."
|
72
|
+
restart_server
|
73
|
+
end
|
74
|
+
|
75
|
+
listener.start
|
76
|
+
puts "Listening for changes in #{@package_path}..."
|
77
|
+
|
78
|
+
# Wait for the server thread to finish
|
79
|
+
@server_thread.join
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Capsium
|
4
|
+
module ThorExt
|
5
|
+
# Configures Thor to behave more like a typical CLI, with better help and error handling.
|
6
|
+
#
|
7
|
+
# - Passing -h or --help to a command will show help for that command.
|
8
|
+
# - Unrecognized options will be treated as errors (instead of being silently ignored).
|
9
|
+
# - Error messages will be printed in red to stderr, without stack trace.
|
10
|
+
# - Full stack traces can be enabled by setting the VERBOSE environment variable.
|
11
|
+
# - Errors will cause Thor to exit with a non-zero status.
|
12
|
+
#
|
13
|
+
# To take advantage of this behavior, your CLI should subclass Thor and extend this module.
|
14
|
+
#
|
15
|
+
# class CLI < Thor
|
16
|
+
# extend ThorExt::Start
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# Start your CLI with:
|
20
|
+
#
|
21
|
+
# CLI.start
|
22
|
+
#
|
23
|
+
# In tests, prevent Kernel.exit from being called when an error occurs, like this:
|
24
|
+
#
|
25
|
+
# CLI.start(args, exit_on_failure: false)
|
26
|
+
#
|
27
|
+
module Start
|
28
|
+
def self.extended(base)
|
29
|
+
super
|
30
|
+
base.check_unknown_options!
|
31
|
+
end
|
32
|
+
|
33
|
+
def start(given_args = ARGV, config = {})
|
34
|
+
config[:shell] ||= Thor::Base.shell.new
|
35
|
+
handle_help_switches(given_args) do |args|
|
36
|
+
dispatch(nil, args, nil, config)
|
37
|
+
end
|
38
|
+
rescue StandardError => e
|
39
|
+
handle_exception_on_start(e, config)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def handle_help_switches(given_args)
|
45
|
+
yield(given_args.dup)
|
46
|
+
rescue Thor::UnknownArgumentError => e
|
47
|
+
retry_with_args = []
|
48
|
+
|
49
|
+
if given_args.first == "help"
|
50
|
+
retry_with_args = ["help"] if given_args.length > 1
|
51
|
+
elsif e.unknown.intersect?(%w[-h --help])
|
52
|
+
retry_with_args = ["help", (given_args - e.unknown).first]
|
53
|
+
end
|
54
|
+
raise unless retry_with_args.any?
|
55
|
+
|
56
|
+
yield(retry_with_args)
|
57
|
+
end
|
58
|
+
|
59
|
+
def handle_exception_on_start(error, config)
|
60
|
+
return if error.is_a?(Errno::EPIPE)
|
61
|
+
raise if ENV["VERBOSE"] || !config.fetch(:exit_on_failure, true)
|
62
|
+
|
63
|
+
message = error.message.to_s
|
64
|
+
message.prepend("[#{error.class}] ") if message.empty? || !error.is_a?(Thor::Error)
|
65
|
+
|
66
|
+
config[:shell]&.say_error(message, :red)
|
67
|
+
exit(false)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
data/lib/capsium.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "capsium/version"
|
4
|
+
require "shale"
|
5
|
+
require "shale/adapter/nokogiri"
|
6
|
+
Shale.xml_adapter = Shale::Adapter::Nokogiri
|
7
|
+
|
8
|
+
module Capsium
|
9
|
+
class Error < StandardError; end
|
10
|
+
|
11
|
+
# Your code goes here...
|
12
|
+
end
|
13
|
+
|
14
|
+
require_relative "capsium/package"
|
15
|
+
require_relative "capsium/packager"
|
data/sig/capsium.rbs
ADDED