capsium 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/rake.yml +18 -0
  3. data/.github/workflows/release.yml +24 -0
  4. data/.gitignore +11 -0
  5. data/.rspec +3 -0
  6. data/.rubocop.yml +8 -0
  7. data/CODE_OF_CONDUCT.md +84 -0
  8. data/Gemfile +12 -0
  9. data/README.adoc +258 -0
  10. data/Rakefile +12 -0
  11. data/bin/console +11 -0
  12. data/bin/setup +8 -0
  13. data/capsium.gemspec +54 -0
  14. data/exe/capsium +6 -0
  15. data/images/005df7415a331c466ad2d9a42efdd212b6f42fe50b5fd3b3174c86c706f58244.png +0 -0
  16. data/images/0374025b3af99b8a473282c8cbbf9fcd29573cf41e586982f328f86c0690f43d.png +0 -0
  17. data/images/0bb4da785be40ef58e219470ebccb979325928b75453dc46bac23c6ee8a7a7cb.png +0 -0
  18. data/images/6aa294dccc81af594aacbe804e7ddffdc17eacc28357338108aea5d021d831ff.png +0 -0
  19. data/images/72dd3fbf3f4b475e27a0e7fb8137c475c32c41f8d222bcf62d6a9ccf102d9532.png +0 -0
  20. data/images/8772b6961d169738d7b0fa0b669b06fc2f40632d4c62586c7634fc17b93182a3.png +0 -0
  21. data/images/a998d842405933d45723606ff3f70162ec95b4ef30db25464a366184fd08fb9b.png +0 -0
  22. data/images/aa8980547e8c003d33273ab4d80e62da7f317bd7581b293c06d67f5331f24f31.png +0 -0
  23. data/images/bb78a872b539e0e9b2d80dee58acbb688f3f2727b324a5bf8bf417a69d94a166.png +0 -0
  24. data/images/c48fc83b17725d85fbb64d971196ebfccd8c5c757fe6aa5845303f6e315879b6.png +0 -0
  25. data/images/f08ef07308d08119ac2124bb7428c8bef17ef1ca70045696604d6e83015a9b91.png +0 -0
  26. data/images/f7514206111b695647eae9adfcf498ba3e0ff83ecfe25f3fc3ed8e9f04c5c726-1.png +0 -0
  27. data/images/f7514206111b695647eae9adfcf498ba3e0ff83ecfe25f3fc3ed8e9f04c5c726.png +0 -0
  28. data/lib/capsium/cli.rb +96 -0
  29. data/lib/capsium/converters/jekyll.rb +59 -0
  30. data/lib/capsium/package/dataset.rb +86 -0
  31. data/lib/capsium/package/manifest.rb +97 -0
  32. data/lib/capsium/package/metadata.rb +48 -0
  33. data/lib/capsium/package/routes.rb +157 -0
  34. data/lib/capsium/package/storage.rb +52 -0
  35. data/lib/capsium/package.rb +142 -0
  36. data/lib/capsium/packager.rb +100 -0
  37. data/lib/capsium/protector.rb +95 -0
  38. data/lib/capsium/reactor.rb +82 -0
  39. data/lib/capsium/thor_ext.rb +71 -0
  40. data/lib/capsium/version.rb +5 -0
  41. data/lib/capsium.rb +15 -0
  42. data/sig/capsium.rbs +4 -0
  43. 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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capsium
4
+ VERSION = "0.1.1"
5
+ 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
@@ -0,0 +1,4 @@
1
+ module Capsium
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end