capsium 0.1.1
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.
- 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