terminalwire 0.2.5 → 0.3.0.alpha2
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 +4 -4
- data/Rakefile +67 -5
- data/exe/terminalwire +1 -1
- data/exe/terminalwire-exec +1 -1
- metadata +9 -171
- data/.rspec +0 -3
- data/CHANGELOG.md +0 -5
- data/CODE_OF_CONDUCT.md +0 -132
- data/LICENSE.txt +0 -9
- data/README.md +0 -45
- data/examples/exec/localrails +0 -2
- data/lib/generators/terminalwire/install/USAGE +0 -9
- data/lib/generators/terminalwire/install/install_generator.rb +0 -38
- data/lib/generators/terminalwire/install/templates/application_terminal.rb.tt +0 -21
- data/lib/generators/terminalwire/install/templates/bin/terminalwire +0 -2
- data/lib/generators/terminalwire/install/templates/main_terminal.rb +0 -40
- data/lib/terminalwire/adapter.rb +0 -53
- data/lib/terminalwire/cache.rb +0 -114
- data/lib/terminalwire/client/entitlement/environment_variables.rb +0 -26
- data/lib/terminalwire/client/entitlement/paths.rb +0 -97
- data/lib/terminalwire/client/entitlement/policy.rb +0 -117
- data/lib/terminalwire/client/entitlement/schemes.rb +0 -26
- data/lib/terminalwire/client/entitlement.rb +0 -9
- data/lib/terminalwire/client/exec.rb +0 -44
- data/lib/terminalwire/client/handler.rb +0 -67
- data/lib/terminalwire/client/resource.rb +0 -204
- data/lib/terminalwire/client/server_license_verification.rb +0 -74
- data/lib/terminalwire/client.rb +0 -30
- data/lib/terminalwire/logging.rb +0 -8
- data/lib/terminalwire/rails.rb +0 -69
- data/lib/terminalwire/server/context.rb +0 -80
- data/lib/terminalwire/server/resource.rb +0 -120
- data/lib/terminalwire/server.rb +0 -63
- data/lib/terminalwire/thor.rb +0 -67
- data/lib/terminalwire/transport.rb +0 -58
- data/lib/terminalwire/version.rb +0 -5
- data/lib/terminalwire.rb +0 -56
- data/sig/terminalwire.rbs +0 -4
data/lib/terminalwire/adapter.rb
DELETED
@@ -1,53 +0,0 @@
|
|
1
|
-
require 'msgpack'
|
2
|
-
|
3
|
-
module Terminalwire::Adapter
|
4
|
-
# Works with Test, TCP, Unix, WebSocket, and other socket-like abstractions.
|
5
|
-
class Socket
|
6
|
-
include Terminalwire::Logging
|
7
|
-
|
8
|
-
attr_reader :transport
|
9
|
-
|
10
|
-
def initialize(transport)
|
11
|
-
@transport = transport
|
12
|
-
end
|
13
|
-
|
14
|
-
def write(data)
|
15
|
-
logger.debug "Adapter: Sending #{data.inspect}"
|
16
|
-
packed_data = MessagePack.pack(data, symbolize_keys: true)
|
17
|
-
@transport.write(packed_data)
|
18
|
-
end
|
19
|
-
|
20
|
-
def read
|
21
|
-
logger.debug "Adapter: Reading"
|
22
|
-
packed_data = @transport.read
|
23
|
-
return nil if packed_data.nil?
|
24
|
-
data = MessagePack.unpack(packed_data, symbolize_keys: true)
|
25
|
-
logger.debug "Adapter: Received #{data.inspect}"
|
26
|
-
data
|
27
|
-
end
|
28
|
-
|
29
|
-
def close
|
30
|
-
@transport.close
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
# This is a test adapter that can be used for testing purposes.
|
35
|
-
class Test
|
36
|
-
attr_reader :responses
|
37
|
-
|
38
|
-
def initialize(responses = [])
|
39
|
-
@responses = responses
|
40
|
-
end
|
41
|
-
|
42
|
-
def write(**data)
|
43
|
-
@responses << data
|
44
|
-
end
|
45
|
-
|
46
|
-
def response
|
47
|
-
@responses.pop
|
48
|
-
end
|
49
|
-
|
50
|
-
def close
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
data/lib/terminalwire/cache.rb
DELETED
@@ -1,114 +0,0 @@
|
|
1
|
-
require "pathname"
|
2
|
-
require "msgpack"
|
3
|
-
require "base64"
|
4
|
-
require "time"
|
5
|
-
require "fileutils"
|
6
|
-
|
7
|
-
# Caches used on the client side for licesens, HTTP requests, etc.
|
8
|
-
module Terminalwire::Cache
|
9
|
-
module File
|
10
|
-
# Hoist the File class to avoid conflicts with the standard library.
|
11
|
-
File = ::File
|
12
|
-
|
13
|
-
class Store
|
14
|
-
include Enumerable
|
15
|
-
|
16
|
-
def initialize(path:)
|
17
|
-
@path = Pathname.new(path).expand_path
|
18
|
-
FileUtils.mkdir_p(@path) unless @path.directory?
|
19
|
-
end
|
20
|
-
|
21
|
-
def entry(key)
|
22
|
-
Entry.new(path: @path.join(Entry.key_path(key)))
|
23
|
-
end
|
24
|
-
alias :[] :entry
|
25
|
-
|
26
|
-
def evict
|
27
|
-
each(&:evict)
|
28
|
-
end
|
29
|
-
|
30
|
-
def destroy
|
31
|
-
each(&:destroy)
|
32
|
-
end
|
33
|
-
|
34
|
-
def each
|
35
|
-
@path.each_child do |path|
|
36
|
-
yield Entry.new(path:)
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
class Entry
|
42
|
-
VERSION = "1.0"
|
43
|
-
|
44
|
-
def self.key_path(value)
|
45
|
-
Base64.urlsafe_encode64(value)
|
46
|
-
end
|
47
|
-
|
48
|
-
attr_accessor :value, :expires
|
49
|
-
|
50
|
-
def initialize(path:)
|
51
|
-
@path = path
|
52
|
-
deserialize if persisted?
|
53
|
-
end
|
54
|
-
|
55
|
-
def nil?
|
56
|
-
@value.nil?
|
57
|
-
end
|
58
|
-
|
59
|
-
def present?
|
60
|
-
not nil?
|
61
|
-
end
|
62
|
-
|
63
|
-
def persisted?
|
64
|
-
File.exist? @path
|
65
|
-
end
|
66
|
-
|
67
|
-
def expired?(time: Time.now)
|
68
|
-
@expires && @expires < time.utc
|
69
|
-
end
|
70
|
-
|
71
|
-
def fresh?(...)
|
72
|
-
not expired?(...)
|
73
|
-
end
|
74
|
-
|
75
|
-
def hit?
|
76
|
-
persisted? and fresh?
|
77
|
-
end
|
78
|
-
|
79
|
-
def miss?
|
80
|
-
not hit?
|
81
|
-
end
|
82
|
-
|
83
|
-
def save
|
84
|
-
File.write @path, serialize
|
85
|
-
end
|
86
|
-
|
87
|
-
def evict
|
88
|
-
destroy if expired?
|
89
|
-
end
|
90
|
-
|
91
|
-
def deserialize
|
92
|
-
case MessagePack.unpack(File.read(@path), symbolize_keys: true)
|
93
|
-
in { value:, expires:, version: VERSION }
|
94
|
-
@value = value
|
95
|
-
@expires = Time.parse(expires).utc if expires
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
def destroy
|
100
|
-
File.delete(@path)
|
101
|
-
end
|
102
|
-
|
103
|
-
private
|
104
|
-
|
105
|
-
def serialize
|
106
|
-
MessagePack.pack(
|
107
|
-
value: @value,
|
108
|
-
expires: @expires&.utc&.iso8601,
|
109
|
-
version: VERSION
|
110
|
-
)
|
111
|
-
end
|
112
|
-
end
|
113
|
-
end
|
114
|
-
end
|
@@ -1,26 +0,0 @@
|
|
1
|
-
module Terminalwire::Client::Entitlement
|
2
|
-
# ENV vars that the server can access on the client.
|
3
|
-
class EnvironmentVariables
|
4
|
-
include Enumerable
|
5
|
-
|
6
|
-
def initialize
|
7
|
-
@permitted = Set.new
|
8
|
-
end
|
9
|
-
|
10
|
-
def each(&)
|
11
|
-
@permitted.each(&)
|
12
|
-
end
|
13
|
-
|
14
|
-
def permit(variable)
|
15
|
-
@permitted << variable.to_s
|
16
|
-
end
|
17
|
-
|
18
|
-
def permitted?(key)
|
19
|
-
include? key.to_s
|
20
|
-
end
|
21
|
-
|
22
|
-
def serialize
|
23
|
-
map { |name| { name: } }
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
@@ -1,97 +0,0 @@
|
|
1
|
-
module Terminalwire::Client::Entitlement
|
2
|
-
# A list of paths and permissions that server has to write on the client workstation.
|
3
|
-
class Paths
|
4
|
-
class Permit
|
5
|
-
attr_reader :path, :mode
|
6
|
-
# Ensure the default file mode is read/write for owner only. This ensures
|
7
|
-
# that if the server tries uploading an executable file, it won't be when it
|
8
|
-
# lands on the client.
|
9
|
-
#
|
10
|
-
# Eventually we'll move this into entitlements so the client can set maximum
|
11
|
-
# permissions for files and directories.
|
12
|
-
MODE = 0o600 # rw-------
|
13
|
-
|
14
|
-
# Constants for permission bit masks
|
15
|
-
OWNER_PERMISSIONS = 0o700 # rwx------
|
16
|
-
GROUP_PERMISSIONS = 0o070 # ---rwx---
|
17
|
-
OTHERS_PERMISSIONS = 0o007 # ------rwx
|
18
|
-
|
19
|
-
# We'll validate that modes are within this range.
|
20
|
-
MODE_RANGE = 0o000..0o777
|
21
|
-
|
22
|
-
def initialize(path:, mode: MODE)
|
23
|
-
@path = Pathname.new(path)
|
24
|
-
@mode = convert(mode)
|
25
|
-
end
|
26
|
-
|
27
|
-
def permitted_path?(path)
|
28
|
-
# This MUST be done via File.fnmatch because Pathname#fnmatch does not work. If you
|
29
|
-
# try changing this 🚨 YOU MAY CIRCUMVENT THE SECURITY MEASURES IN PLACE. 🚨
|
30
|
-
File.fnmatch @path.to_s, path.to_s, File::FNM_PATHNAME
|
31
|
-
end
|
32
|
-
|
33
|
-
def permitted_mode?(value)
|
34
|
-
# Ensure the mode is at least as permissive as the permitted mode.
|
35
|
-
mode = convert(value)
|
36
|
-
|
37
|
-
# Extract permission bits for owner, group, and others
|
38
|
-
owner_bits = mode & OWNER_PERMISSIONS
|
39
|
-
group_bits = mode & GROUP_PERMISSIONS
|
40
|
-
others_bits = mode & OTHERS_PERMISSIONS
|
41
|
-
|
42
|
-
# Ensure that the mode doesn't grant more permissions than @mode in any class (owner, group, others)
|
43
|
-
(owner_bits <= @mode & OWNER_PERMISSIONS) &&
|
44
|
-
(group_bits <= @mode & GROUP_PERMISSIONS) &&
|
45
|
-
(others_bits <= @mode & OTHERS_PERMISSIONS)
|
46
|
-
end
|
47
|
-
|
48
|
-
def permitted?(path:, mode: @mode)
|
49
|
-
permitted_path?(path) && permitted_mode?(mode)
|
50
|
-
end
|
51
|
-
|
52
|
-
def serialize
|
53
|
-
{
|
54
|
-
location: @path.to_s,
|
55
|
-
mode: @mode
|
56
|
-
}
|
57
|
-
end
|
58
|
-
|
59
|
-
protected
|
60
|
-
def convert(value)
|
61
|
-
mode = Integer(value)
|
62
|
-
raise ArgumentError, "The mode #{format_octet value} must be an octet value between #{format_octet MODE_RANGE.first} and #{format_octet MODE_RANGE.last}" unless MODE_RANGE.cover?(mode)
|
63
|
-
mode
|
64
|
-
end
|
65
|
-
|
66
|
-
def format_octet(value)
|
67
|
-
format("0o%03o", value)
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
include Enumerable
|
72
|
-
|
73
|
-
def initialize
|
74
|
-
@permitted = []
|
75
|
-
end
|
76
|
-
|
77
|
-
def each(&)
|
78
|
-
@permitted.each(&)
|
79
|
-
end
|
80
|
-
|
81
|
-
def permit(path, **)
|
82
|
-
@permitted.append Permit.new(path:, **)
|
83
|
-
end
|
84
|
-
|
85
|
-
def permitted?(path, mode: nil)
|
86
|
-
if mode
|
87
|
-
find { |it| it.permitted_path?(path) and it.permitted_mode?(mode) }
|
88
|
-
else
|
89
|
-
find { |it| it.permitted_path?(path) }
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
def serialize
|
94
|
-
map(&:serialize)
|
95
|
-
end
|
96
|
-
end
|
97
|
-
end
|
@@ -1,117 +0,0 @@
|
|
1
|
-
module Terminalwire::Client::Entitlement
|
2
|
-
module Policy
|
3
|
-
# A policy has the authority, paths, and schemes that the server is allowed to access.
|
4
|
-
class Base
|
5
|
-
attr_reader :paths, :authority, :schemes, :environment_variables
|
6
|
-
|
7
|
-
def initialize(authority:)
|
8
|
-
@authority = authority
|
9
|
-
@paths = Paths.new
|
10
|
-
|
11
|
-
# Permit the domain directory. This is necessary for basic operation of the client.
|
12
|
-
@paths.permit storage_path
|
13
|
-
@paths.permit storage_pattern
|
14
|
-
|
15
|
-
@schemes = Schemes.new
|
16
|
-
# Permit http & https by default.
|
17
|
-
@schemes.permit "http"
|
18
|
-
@schemes.permit "https"
|
19
|
-
|
20
|
-
@environment_variables = EnvironmentVariables.new
|
21
|
-
# Permit the HOME and TERMINALWIRE_HOME environment variables.
|
22
|
-
@environment_variables.permit "TERMINALWIRE_HOME"
|
23
|
-
end
|
24
|
-
|
25
|
-
def root_path
|
26
|
-
# TODO: This needs to be passed into the Policy so that it can be set by the client.
|
27
|
-
Terminalwire::Client.root_path
|
28
|
-
end
|
29
|
-
|
30
|
-
def authority_path
|
31
|
-
root_path.join("authorities/#{authority}")
|
32
|
-
end
|
33
|
-
|
34
|
-
def storage_path
|
35
|
-
authority_path.join("storage")
|
36
|
-
end
|
37
|
-
|
38
|
-
def storage_pattern
|
39
|
-
storage_path.join("**/*")
|
40
|
-
end
|
41
|
-
|
42
|
-
def serialize
|
43
|
-
{
|
44
|
-
authority: @authority,
|
45
|
-
schemes: @schemes.serialize,
|
46
|
-
paths: @paths.serialize,
|
47
|
-
environment_variables: @environment_variables.serialize
|
48
|
-
}
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
class Root < Base
|
53
|
-
AUTHORITY = "terminalwire.com".freeze
|
54
|
-
|
55
|
-
# Terminalwire checks these to install the binary stubs path.
|
56
|
-
SHELL_INITIALIZATION_FILE_PATHS = %w[
|
57
|
-
~/.bash_profile
|
58
|
-
~/.bashrc
|
59
|
-
~/.zprofile
|
60
|
-
~/.zshrc
|
61
|
-
~/.profile
|
62
|
-
~/.config/fish/config.fish
|
63
|
-
~/.bash_login
|
64
|
-
~/.cshrc
|
65
|
-
~/.tcshrc
|
66
|
-
].freeze
|
67
|
-
|
68
|
-
# Ensure the binary stubs are executable. This increases the
|
69
|
-
# file mode entitlement so that stubs created in ./bin are executable.
|
70
|
-
BINARY_PATH_FILE_MODE = 0o755
|
71
|
-
|
72
|
-
def initialize(*, **, &)
|
73
|
-
# Make damn sure the authority is set to Terminalwire.
|
74
|
-
super(*, authority: AUTHORITY, **, &)
|
75
|
-
|
76
|
-
# Now setup special permitted paths.
|
77
|
-
@paths.permit root_path
|
78
|
-
@paths.permit root_pattern
|
79
|
-
# Permit the dotfiles so terminalwire can install the binary stubs.
|
80
|
-
SHELL_INITIALIZATION_FILE_PATHS.each do |path|
|
81
|
-
@paths.permit path
|
82
|
-
end
|
83
|
-
|
84
|
-
# Permit terminalwire to grant execute permissions to the binary stubs.
|
85
|
-
@paths.permit binary_pattern, mode: BINARY_PATH_FILE_MODE
|
86
|
-
|
87
|
-
# Used to check if terminalwire is setup in the user's PATH environment variable.
|
88
|
-
@environment_variables.permit "PATH"
|
89
|
-
end
|
90
|
-
|
91
|
-
# Grant access to the `~/.terminalwire/**/*` path so users can install
|
92
|
-
# terminalwire apps via `terminalwire install svbtle`, etc.
|
93
|
-
def root_pattern
|
94
|
-
root_path.join("**/*").freeze
|
95
|
-
end
|
96
|
-
|
97
|
-
# Path where the terminalwire binary stubs are stored.
|
98
|
-
def binary_path
|
99
|
-
root_path.join("bin").freeze
|
100
|
-
end
|
101
|
-
|
102
|
-
# Pattern for the binary path.
|
103
|
-
def binary_pattern
|
104
|
-
binary_path.join("*").freeze
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
def self.resolve(*, authority:, **, &)
|
109
|
-
case authority
|
110
|
-
when Policy::Root::AUTHORITY
|
111
|
-
Root.new(*, **, &)
|
112
|
-
else
|
113
|
-
Base.new *, authority:, **, &
|
114
|
-
end
|
115
|
-
end
|
116
|
-
end
|
117
|
-
end
|
@@ -1,26 +0,0 @@
|
|
1
|
-
module Terminalwire::Client::Entitlement
|
2
|
-
# URLs the server can open on the client.
|
3
|
-
class Schemes
|
4
|
-
include Enumerable
|
5
|
-
|
6
|
-
def initialize
|
7
|
-
@permitted = Set.new
|
8
|
-
end
|
9
|
-
|
10
|
-
def each(&)
|
11
|
-
@permitted.each(&)
|
12
|
-
end
|
13
|
-
|
14
|
-
def permit(scheme)
|
15
|
-
@permitted << scheme.to_s
|
16
|
-
end
|
17
|
-
|
18
|
-
def permitted?(url)
|
19
|
-
include? URI(url).scheme
|
20
|
-
end
|
21
|
-
|
22
|
-
def serialize
|
23
|
-
map { |scheme| { scheme: } }
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
@@ -1,9 +0,0 @@
|
|
1
|
-
require "pathname"
|
2
|
-
|
3
|
-
module Terminalwire::Client
|
4
|
-
# Entitlements are the security boundary between the server and the client that lives on the client.
|
5
|
-
# The server might request a file or directory from the client, and the client will check the entitlements
|
6
|
-
# to see if the server is authorized to access the requested resource.
|
7
|
-
module Entitlement
|
8
|
-
end
|
9
|
-
end
|
@@ -1,44 +0,0 @@
|
|
1
|
-
require "pathname"
|
2
|
-
require "yaml"
|
3
|
-
require "uri"
|
4
|
-
|
5
|
-
module Terminalwire::Client
|
6
|
-
# Called by the `terminalwire-exec` shebang in scripts. This makes it easy for people
|
7
|
-
# to create their own scripts that use Terminalwire that look like this:
|
8
|
-
#
|
9
|
-
# ```sh
|
10
|
-
# #!/usr/bin/env terminalwire-exec
|
11
|
-
# url: "https://terminalwire.com/terminal"
|
12
|
-
# ```
|
13
|
-
#
|
14
|
-
# These files are saved, then `chmod + x` is run on them and they become executable.
|
15
|
-
class Exec
|
16
|
-
attr_reader :arguments, :path, :configuration, :url
|
17
|
-
|
18
|
-
def initialize(path:, arguments:)
|
19
|
-
@arguments = arguments
|
20
|
-
@path = Pathname.new(path)
|
21
|
-
@configuration = YAML.safe_load_file(@path)
|
22
|
-
@url = URI(@configuration.fetch("url"))
|
23
|
-
rescue Errno::ENOENT => e
|
24
|
-
raise Terminalwire::Error, "File not found: #{@path}"
|
25
|
-
rescue URI::InvalidURIError => e
|
26
|
-
raise Terminalwire::Error, "Invalid URI: #{@url}"
|
27
|
-
rescue KeyError => e
|
28
|
-
raise Terminalwire::Error, "Missing key in configuration: #{e}"
|
29
|
-
end
|
30
|
-
|
31
|
-
def start
|
32
|
-
Terminalwire::Client.websocket(url:, arguments:)
|
33
|
-
end
|
34
|
-
|
35
|
-
def self.start
|
36
|
-
case ARGV
|
37
|
-
in path, *arguments
|
38
|
-
new(path:, arguments:).start
|
39
|
-
end
|
40
|
-
rescue NoMatchingPatternError => e
|
41
|
-
raise Terminalwire::Error, "Launched with incorrect arguments: #{ARGV}"
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
@@ -1,67 +0,0 @@
|
|
1
|
-
module Terminalwire::Client
|
2
|
-
# The handler is the main class that connects to the Terminalwire server and
|
3
|
-
# dispatches messages to the appropriate resources.
|
4
|
-
class Handler
|
5
|
-
VERSION = "0.1.0".freeze
|
6
|
-
|
7
|
-
include Terminalwire::Logging
|
8
|
-
|
9
|
-
attr_reader :adapter, :resources, :endpoint
|
10
|
-
attr_accessor :entitlement
|
11
|
-
|
12
|
-
def initialize(adapter, arguments: ARGV, program_name: $0, endpoint:)
|
13
|
-
@endpoint = endpoint
|
14
|
-
@adapter = adapter
|
15
|
-
@program_arguments = arguments
|
16
|
-
@program_name = program_name
|
17
|
-
@entitlement = Entitlement::Policy.resolve(authority: @endpoint.authority)
|
18
|
-
|
19
|
-
yield self if block_given?
|
20
|
-
|
21
|
-
@resources = Resource::Handler.new do |it|
|
22
|
-
it << Resource::STDOUT.new("stdout", @adapter, entitlement:)
|
23
|
-
it << Resource::STDIN.new("stdin", @adapter, entitlement:)
|
24
|
-
it << Resource::STDERR.new("stderr", @adapter, entitlement:)
|
25
|
-
it << Resource::Browser.new("browser", @adapter, entitlement:)
|
26
|
-
it << Resource::File.new("file", @adapter, entitlement:)
|
27
|
-
it << Resource::Directory.new("directory", @adapter, entitlement:)
|
28
|
-
it << Resource::EnvironmentVariable.new("environment_variable", @adapter, entitlement:)
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def verify_license
|
33
|
-
# Connect to the Terminalwire license server to verify the URL endpoint
|
34
|
-
# and displays a message to the user, if any are present.
|
35
|
-
$stdout.print ServerLicenseVerification.new(url: @endpoint.to_url).message
|
36
|
-
rescue
|
37
|
-
$stderr.puts "Failed to verify server license."
|
38
|
-
end
|
39
|
-
|
40
|
-
def connect
|
41
|
-
verify_license
|
42
|
-
|
43
|
-
@adapter.write(
|
44
|
-
event: "initialization",
|
45
|
-
protocol: { version: VERSION },
|
46
|
-
entitlement: @entitlement.serialize,
|
47
|
-
program: {
|
48
|
-
name: @program_name,
|
49
|
-
arguments: @program_arguments
|
50
|
-
}
|
51
|
-
)
|
52
|
-
|
53
|
-
loop do
|
54
|
-
handle @adapter.read
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
def handle(message)
|
59
|
-
case message
|
60
|
-
in { event: "resource", action: "command", name:, parameters: }
|
61
|
-
@resources.dispatch(**message)
|
62
|
-
in { event: "exit", status: }
|
63
|
-
exit Integer(status)
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|