pytty 0.3.0 → 0.4.0
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/Gemfile.lock +2 -4
- data/README.md +2 -0
- data/build/linux-debian-deps.sh +7 -0
- data/build/linux.sh +7 -0
- data/build/macos-deps.sh +11 -0
- data/build/macos.sh +8 -0
- data/lib/pytty/client.rb +7 -0
- data/lib/pytty/client/api.rb +2 -0
- data/lib/pytty/client/api/attach.rb +23 -12
- data/lib/pytty/client/api/ps.rb +1 -1
- data/lib/pytty/client/api/signal.rb +20 -0
- data/lib/pytty/client/api/spawn.rb +2 -2
- data/lib/pytty/client/api/stdout.rb +19 -0
- data/lib/pytty/client/api/yield.rb +3 -2
- data/lib/pytty/client/cli.rb +1 -0
- data/lib/pytty/client/cli/attach_command.rb +2 -1
- data/lib/pytty/client/cli/kill_command.rb +4 -2
- data/lib/pytty/client/cli/ps_command.rb +5 -4
- data/lib/pytty/client/cli/rm_command.rb +7 -2
- data/lib/pytty/client/cli/root_command.rb +1 -0
- data/lib/pytty/client/cli/run_command.rb +9 -4
- data/lib/pytty/client/cli/signal_command.rb +6 -9
- data/lib/pytty/client/cli/spawn_command.rb +7 -1
- data/lib/pytty/client/cli/stdout_command.rb +22 -0
- data/lib/pytty/client/cli/yield_command.rb +3 -7
- data/lib/pytty/client/process_yield.rb +10 -4
- data/lib/pytty/daemon.rb +5 -1
- data/lib/pytty/daemon/api/router.rb +28 -17
- data/lib/pytty/daemon/cli/root_command.rb +5 -1
- data/lib/pytty/daemon/cli/serve_command.rb +8 -7
- data/lib/pytty/daemon/components.rb +1 -5
- data/lib/pytty/daemon/components/handler.rb +4 -42
- data/lib/pytty/daemon/components/yield_handler.rb +46 -0
- data/lib/pytty/daemon/process_yield.rb +62 -36
- data/lib/pytty/version.rb +1 -1
- metadata +10 -7
- data/bin/build +0 -15
- data/lib/pytty/daemon/components/http_handler.rb +0 -54
- data/lib/pytty/daemon/components/run.rb +0 -19
- data/lib/pytty/daemon/components/stream.rb +0 -44
- data/lib/pytty/daemon/components/web_socket_handler.rb +0 -37
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 449a17a0c2d92d72ceddac8091af43e21e16d730d063458b58411385cd659fa2
|
4
|
+
data.tar.gz: db5e5b5446c3d18b16d886fcd5409380fcd49ed1f6d120bdc7f29f3e6740146a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cc1f6302b922e313c85327d3348d116905d2f889260695f680c946eedcc769c84bf510a3b0439ed499e3f8528f2c8c8bde89cce0a5bb2e29a600ab04987a7631
|
7
|
+
data.tar.gz: 59553a444c16d811403ce3e288aaf44d454ed1bb100896b4b6a30645664554b6004dfc8e748ee66d81227ce7792a1a43c4c58d6d63a549f43a2ea45b879e2811
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
pytty (0.
|
4
|
+
pytty (0.4.0)
|
5
5
|
async-websocket
|
6
6
|
clamp (~> 1.3)
|
7
7
|
falcon
|
@@ -10,7 +10,7 @@ PATH
|
|
10
10
|
GEM
|
11
11
|
remote: https://rubygems.org/
|
12
12
|
specs:
|
13
|
-
async (1.
|
13
|
+
async (1.13.0)
|
14
14
|
nio4r (~> 2.3)
|
15
15
|
timers (~> 4.1)
|
16
16
|
async-container (0.8.1)
|
@@ -62,7 +62,6 @@ GEM
|
|
62
62
|
http-hpack (0.1.1)
|
63
63
|
http-protocol (0.10.1)
|
64
64
|
http-hpack (~> 0.1.0)
|
65
|
-
kommando (0.1.2)
|
66
65
|
listen (3.1.5)
|
67
66
|
rb-fsevent (~> 0.9, >= 0.9.4)
|
68
67
|
rb-inotify (~> 0.9, >= 0.9.7)
|
@@ -121,7 +120,6 @@ DEPENDENCIES
|
|
121
120
|
guard-bundler
|
122
121
|
guard-process
|
123
122
|
guard-rspec
|
124
|
-
kommando
|
125
123
|
pytty!
|
126
124
|
rake (~> 10.0)
|
127
125
|
rspec (~> 3.0)
|
data/README.md
CHANGED
@@ -0,0 +1,7 @@
|
|
1
|
+
#!/usr/bin/env sh
|
2
|
+
set -e
|
3
|
+
|
4
|
+
apt-get update && apt-get install -y squashfs-tools build-essential bison curl
|
5
|
+
|
6
|
+
curl -L https://github.com/kontena/ruby-packer/releases/download/0.5.0%2Bextra7/rubyc-0.5.0+extra7-linux-amd64.gz | gunzip > /usr/local/bin/rubyc
|
7
|
+
chmod +x /usr/local/bin/rubyc
|
data/build/linux.sh
ADDED
data/build/macos-deps.sh
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
#!/usr/bin/env sh
|
2
|
+
set -e
|
3
|
+
|
4
|
+
export HOMEBREW_NO_AUTO_UPDATE=1
|
5
|
+
brew install squashfs || brew upgrade squashfs || true
|
6
|
+
brew install openssl || brew upgrade openssl || true
|
7
|
+
|
8
|
+
curl -sL https://curl.haxx.se/ca/cacert.pem > /usr/local/etc/openssl/cacert.pem
|
9
|
+
|
10
|
+
curl -sL https://dl.bintray.com/kontena/ruby-packer/0.5.0-dev/rubyc-0.5.0-extra2-darwin-amd64.gz | gunzip > /usr/local/bin/rubyc
|
11
|
+
chmod +x /usr/local/bin/rubyc
|
data/build/macos.sh
ADDED
data/lib/pytty/client.rb
CHANGED
data/lib/pytty/client/api.rb
CHANGED
@@ -2,8 +2,8 @@ module Pytty
|
|
2
2
|
module Client
|
3
3
|
module Api
|
4
4
|
class Attach
|
5
|
-
def self.run(id:)
|
6
|
-
Async::Task.current.async do
|
5
|
+
def self.run(id:, interactive:)
|
6
|
+
stdin_task = Async::Task.current.async do
|
7
7
|
internet = Async::HTTP::Internet.new
|
8
8
|
headers = [['accept', 'application/json']]
|
9
9
|
body = {}.to_json
|
@@ -17,27 +17,35 @@ module Pytty
|
|
17
17
|
stdin_body = {
|
18
18
|
c: "\f"
|
19
19
|
}.to_json
|
20
|
-
response = internet.post("
|
20
|
+
response = internet.post("#{Pytty::Client.host_url}/v1/stdin/#{id}", headers, [stdin_body])
|
21
21
|
|
22
22
|
detach_sequence_started = false
|
23
23
|
while c = async_stdin.read(1) do
|
24
|
+
detach = false
|
24
25
|
case c
|
25
26
|
when "\x10"
|
26
27
|
detach_sequence_started = true
|
27
28
|
next
|
28
29
|
when "\x11"
|
29
|
-
if detach_sequence_started
|
30
|
-
|
31
|
-
|
32
|
-
|
30
|
+
detach = true if detach_sequence_started
|
31
|
+
when "\x03"
|
32
|
+
detach = true unless interactive
|
33
|
+
end
|
34
|
+
|
35
|
+
if detach
|
36
|
+
puts ""
|
37
|
+
puts "detached.\r"
|
38
|
+
exit 0
|
33
39
|
end
|
34
40
|
|
35
41
|
detach_sequence_started = false
|
36
42
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
43
|
+
if interactive
|
44
|
+
stdin_body = {
|
45
|
+
c: c
|
46
|
+
}.to_json
|
47
|
+
response = internet.post("#{Pytty::Client.host_url}/v1/stdin/#{id}", headers, [stdin_body])
|
48
|
+
end
|
41
49
|
end
|
42
50
|
end
|
43
51
|
|
@@ -46,12 +54,15 @@ module Pytty
|
|
46
54
|
headers = [['accept', 'application/json']]
|
47
55
|
body = {}.to_json
|
48
56
|
|
49
|
-
response = internet.post("
|
57
|
+
response = internet.post("#{Pytty::Client.host_url}/v1/attach/#{id}", headers, [body])
|
50
58
|
response.body.each do |c|
|
51
59
|
print c
|
52
60
|
end
|
53
61
|
rescue Async::Wrapper::Cancelled => ex
|
62
|
+
p ["rescued", ex]
|
54
63
|
ensure
|
64
|
+
stdin_task.stop
|
65
|
+
|
55
66
|
internet.close
|
56
67
|
end
|
57
68
|
end
|
data/lib/pytty/client/api/ps.rb
CHANGED
@@ -7,7 +7,7 @@ module Pytty
|
|
7
7
|
headers = [['accept', 'application/json']]
|
8
8
|
body = {}.to_json
|
9
9
|
|
10
|
-
response = internet.post("
|
10
|
+
response = internet.post("#{Pytty::Client.host_url}/v1/ps", headers, [body])
|
11
11
|
JSON.parse(response.body.read)
|
12
12
|
ensure
|
13
13
|
internet.close
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Pytty
|
2
|
+
module Client
|
3
|
+
module Api
|
4
|
+
class Signal
|
5
|
+
def self.run(id:, signal:)
|
6
|
+
internet = Async::HTTP::Internet.new
|
7
|
+
headers = [['accept', 'application/json']]
|
8
|
+
body = {
|
9
|
+
signal: signal
|
10
|
+
}.to_json
|
11
|
+
|
12
|
+
response = internet.post("#{Pytty::Client.host_url}/v1/signal/#{id}", headers, [body])
|
13
|
+
[response, JSON.parse(response.read)]
|
14
|
+
ensure
|
15
|
+
internet.close
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -10,8 +10,8 @@ module Pytty
|
|
10
10
|
interactive: interactive
|
11
11
|
}.to_json
|
12
12
|
|
13
|
-
response = internet.post("
|
14
|
-
JSON.parse(response.read)
|
13
|
+
response = internet.post("#{Pytty::Client.host_url}/v1/spawn/#{id}", headers, [body])
|
14
|
+
[response, JSON.parse(response.read)]
|
15
15
|
ensure
|
16
16
|
internet.close
|
17
17
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Pytty
|
2
|
+
module Client
|
3
|
+
module Api
|
4
|
+
class Stdout
|
5
|
+
def self.run(id:)
|
6
|
+
internet = Async::HTTP::Internet.new
|
7
|
+
headers = [['accept', 'application/json']]
|
8
|
+
body = {
|
9
|
+
}.to_json
|
10
|
+
|
11
|
+
response = internet.post("#{Pytty::Client.host_url}/v1/stdout/#{id}", headers, [body])
|
12
|
+
puts response.read
|
13
|
+
ensure
|
14
|
+
internet.close
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -2,7 +2,7 @@ module Pytty
|
|
2
2
|
module Client
|
3
3
|
module Api
|
4
4
|
class Yield
|
5
|
-
def self.run(cmd:, env:)
|
5
|
+
def self.run(cmd:, id:, env:)
|
6
6
|
internet = Async::HTTP::Internet.new
|
7
7
|
headers = [['accept', 'application/json']]
|
8
8
|
|
@@ -12,11 +12,12 @@ module Pytty
|
|
12
12
|
}.merge env
|
13
13
|
|
14
14
|
body = {
|
15
|
+
id: id,
|
15
16
|
cmd: cmd,
|
16
17
|
env: term_env
|
17
18
|
}.to_json
|
18
19
|
|
19
|
-
response = internet.post("
|
20
|
+
response = internet.post("#{Pytty::Client.host_url}/v1/yield", headers, [body])
|
20
21
|
JSON.parse(response.body.read)
|
21
22
|
end
|
22
23
|
end
|
data/lib/pytty/client/cli.rb
CHANGED
@@ -9,10 +9,11 @@ module Pytty
|
|
9
9
|
module Cli
|
10
10
|
class AttachCommand < Clamp::Command
|
11
11
|
parameter "ID", "id"
|
12
|
+
option ["-i","--interactive"], :flag, "interactive"
|
12
13
|
|
13
14
|
def execute
|
14
15
|
Async.run do
|
15
|
-
Pytty::Client::Api::Attach.run id: id
|
16
|
+
Pytty::Client::Api::Attach.run id: id, interactive: interactive?
|
16
17
|
end
|
17
18
|
end
|
18
19
|
end
|
@@ -14,10 +14,12 @@ module Pytty
|
|
14
14
|
Async.run do
|
15
15
|
internet = Async::HTTP::Internet.new
|
16
16
|
headers = [['accept', 'application/json']]
|
17
|
-
body = {
|
17
|
+
body = {
|
18
|
+
signal: "KILL"
|
19
|
+
}.to_json
|
18
20
|
|
19
21
|
for id in id_list do
|
20
|
-
response = internet.post("
|
22
|
+
response = internet.post("#{Pytty::Client.host_url}/v1/signal/#{id}", headers, [body])
|
21
23
|
puts response.read
|
22
24
|
end
|
23
25
|
ensure
|
@@ -11,17 +11,18 @@ module Pytty
|
|
11
11
|
option ["-q","--quiet"], :flag, "quiet"
|
12
12
|
|
13
13
|
def execute
|
14
|
-
|
14
|
+
process_yield_jsons = Async.run do
|
15
15
|
Pytty::Client::Api::Ps.run
|
16
16
|
end.wait
|
17
17
|
|
18
18
|
unless quiet?
|
19
|
-
puts "id
|
19
|
+
puts "id\trunning\tcmd"
|
20
20
|
puts "-"*40
|
21
21
|
end
|
22
|
-
for
|
22
|
+
for process_yield_json in process_yield_jsons do
|
23
|
+
process_yield = Pytty::Client::ProcessYield.from_json process_yield_json
|
23
24
|
if quiet?
|
24
|
-
puts process_yield.
|
25
|
+
puts process_yield.id
|
25
26
|
else
|
26
27
|
puts process_yield
|
27
28
|
end
|
@@ -29,8 +29,13 @@ module Pytty
|
|
29
29
|
body = {}.to_json
|
30
30
|
|
31
31
|
for id in ids do
|
32
|
-
response = internet.post("
|
33
|
-
|
32
|
+
response = internet.post("#{Pytty::Client.host_url}/v1/rm/#{id}", headers, [body])
|
33
|
+
if response.status == 200
|
34
|
+
puts id
|
35
|
+
else
|
36
|
+
puts response.read
|
37
|
+
exit 1
|
38
|
+
end
|
34
39
|
end
|
35
40
|
ensure
|
36
41
|
internet.close
|
@@ -12,17 +12,22 @@ module Pytty
|
|
12
12
|
option ["-i","--interactive"], :flag, "interactive"
|
13
13
|
option ["-t","--tty"], :flag, "tty"
|
14
14
|
option ["-d","--detach"], :flag, "detach"
|
15
|
+
option ["--name"], "name", "name"
|
15
16
|
|
16
17
|
def execute
|
17
|
-
Async.run do
|
18
|
-
json = Pytty::Client::Api::Yield.run cmd: cmd_list, env: {}
|
18
|
+
Async.run do |task|
|
19
|
+
json = Pytty::Client::Api::Yield.run id: name, cmd: cmd_list, env: {}
|
19
20
|
process_yield = Pytty::Client::ProcessYield.from_json json
|
21
|
+
unless detach?
|
22
|
+
task.async do
|
23
|
+
process_yield.attach interactive: interactive?
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
20
27
|
process_yield.spawn tty: tty?, interactive: interactive?
|
21
28
|
|
22
29
|
if detach?
|
23
30
|
puts process_yield.id
|
24
|
-
else
|
25
|
-
process_yield.attach
|
26
31
|
end
|
27
32
|
end
|
28
33
|
end
|
@@ -13,17 +13,14 @@ module Pytty
|
|
13
13
|
|
14
14
|
def execute
|
15
15
|
Async.run do
|
16
|
-
internet = Async::HTTP::Internet.new
|
17
|
-
headers = [['accept', 'application/json']]
|
18
|
-
body = {}.to_json
|
19
|
-
|
20
16
|
for id in id_list do
|
21
|
-
|
22
|
-
response
|
23
|
-
|
17
|
+
response, body = Pytty::Client::Api::Signal.run id: id, signal: signal
|
18
|
+
if response.status == 200
|
19
|
+
puts id
|
20
|
+
else
|
21
|
+
puts body
|
22
|
+
end
|
24
23
|
end
|
25
|
-
ensure
|
26
|
-
internet.close
|
27
24
|
end
|
28
25
|
end
|
29
26
|
end
|
@@ -13,7 +13,13 @@ module Pytty
|
|
13
13
|
def execute
|
14
14
|
Async.run do
|
15
15
|
for id in id_list
|
16
|
-
Pytty::Client::Api::Spawn.run id: id
|
16
|
+
response, body = Pytty::Client::Api::Spawn.run id: id, tty:true, interactive:true
|
17
|
+
unless response.status == 200
|
18
|
+
puts body
|
19
|
+
exit 1
|
20
|
+
else
|
21
|
+
puts id
|
22
|
+
end
|
17
23
|
end
|
18
24
|
end
|
19
25
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'async'
|
3
|
+
require 'async/http'
|
4
|
+
require 'async/http/internet'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
module Pytty
|
8
|
+
module Client
|
9
|
+
module Cli
|
10
|
+
class StdoutCommand < Clamp::Command
|
11
|
+
parameter "ID", "id"
|
12
|
+
|
13
|
+
def execute
|
14
|
+
Async.run do
|
15
|
+
Pytty::Client::Api::Stdout.run id: id
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
@@ -9,19 +9,15 @@ module Pytty
|
|
9
9
|
module Cli
|
10
10
|
class YieldCommand < Clamp::Command
|
11
11
|
parameter "CMD ...", "command"
|
12
|
-
option ["
|
12
|
+
option ["--name"], "NAME", "name"
|
13
13
|
|
14
14
|
def execute
|
15
15
|
process_yield = Async.run do
|
16
|
-
json = Pytty::Client::Api::Yield.run cmd: cmd_list, env: {}
|
16
|
+
json = Pytty::Client::Api::Yield.run cmd: cmd_list, id: name, env: {}
|
17
17
|
::Pytty::Client::ProcessYield.from_json json
|
18
18
|
end.wait
|
19
19
|
|
20
|
-
|
21
|
-
puts process_yield.id
|
22
|
-
else
|
23
|
-
puts process_yield
|
24
|
-
end
|
20
|
+
puts process_yield.id
|
25
21
|
end
|
26
22
|
end
|
27
23
|
end
|
@@ -18,17 +18,23 @@ module Pytty
|
|
18
18
|
pid: json.fetch("pid")
|
19
19
|
})
|
20
20
|
end
|
21
|
-
|
21
|
+
def running?
|
22
|
+
!@pid.nil?
|
23
|
+
end
|
22
24
|
def to_s
|
23
|
-
|
25
|
+
fields = []
|
26
|
+
fields << @id
|
27
|
+
fields << running?
|
28
|
+
fields << @cmd.join(" ")
|
29
|
+
fields.join("\t")
|
24
30
|
end
|
25
31
|
|
26
32
|
def spawn(tty:, interactive:)
|
27
33
|
Pytty::Client::Api::Spawn.run id: @id, tty: tty, interactive: interactive
|
28
34
|
end
|
29
35
|
|
30
|
-
def attach
|
31
|
-
Pytty::Client::Api::Attach.run id: @id
|
36
|
+
def attach(interactive:)
|
37
|
+
Pytty::Client::Api::Attach.run id: @id, interactive: interactive
|
32
38
|
end
|
33
39
|
end
|
34
40
|
end
|
data/lib/pytty/daemon.rb
CHANGED
@@ -15,26 +15,34 @@ module Pytty
|
|
15
15
|
end
|
16
16
|
|
17
17
|
resp = case params["component"]
|
18
|
-
when "
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
18
|
+
when "stdout"
|
19
|
+
process_yield = Pytty::Daemon.yields[params["id"]]
|
20
|
+
return [404, {'content-type' => 'text/html; charset=utf-8'}, ["does not exist"]] unless process_yield
|
21
|
+
|
22
|
+
body = Async::HTTP::Body::Writable.new
|
23
|
+
|
24
|
+
begin
|
25
|
+
our_stdout = process_yield.stdout.dup
|
26
|
+
while c = our_stdout.read
|
27
|
+
body.write c
|
28
|
+
end
|
29
|
+
rescue Exception => ex
|
30
|
+
p ex
|
31
|
+
ensure
|
32
|
+
puts "closing body"
|
33
|
+
body.close
|
26
34
|
end
|
27
35
|
|
28
|
-
[
|
36
|
+
[200, {'content-type' => 'text/html; charset=utf-8'}, body]
|
29
37
|
when "attach"
|
30
|
-
|
38
|
+
process_yield = Pytty::Daemon.yields[params["id"]]
|
31
39
|
body = Async::HTTP::Body::Writable.new
|
32
40
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
41
|
+
Async::Task.current.async do |task|
|
42
|
+
notification = process_yield.add_stdout body
|
43
|
+
p ["blocking", notification]
|
44
|
+
notification.wait
|
45
|
+
p "got notification"
|
38
46
|
rescue Exception => ex
|
39
47
|
p ex
|
40
48
|
ensure
|
@@ -43,8 +51,11 @@ module Pytty
|
|
43
51
|
end
|
44
52
|
|
45
53
|
[200, {'content-type' => 'text/html; charset=utf-8'}, body]
|
46
|
-
when "
|
47
|
-
status, output = Pytty::Daemon::Components::Handler.handle component: params["component"],
|
54
|
+
when "ps","yield"
|
55
|
+
status, output = Pytty::Daemon::Components::Handler.handle component: params["component"], params: body
|
56
|
+
[status, {"Content-Type" => "application/json"}, [output.to_json]]
|
57
|
+
when "spawn","rm","signal","stdin"
|
58
|
+
status, output = Pytty::Daemon::Components::YieldHandler.handle component: params["component"], id: params["id"], params: body
|
48
59
|
[status, {"Content-Type" => "application/json"}, [output.to_json]]
|
49
60
|
when "ws"
|
50
61
|
if env["HTTP_UPGRADE"] == "websocket"
|
@@ -5,23 +5,24 @@ module Pytty
|
|
5
5
|
module Daemon
|
6
6
|
module Cli
|
7
7
|
class ServeCommand < Clamp::Command
|
8
|
-
option ["--url"], "URL", "url"
|
9
|
-
|
10
8
|
def execute
|
11
9
|
puts "🚽 pyttyd #{Pytty::VERSION}"
|
12
10
|
|
13
11
|
url_parts = ["http://"]
|
14
|
-
url_parts << if
|
15
|
-
|
12
|
+
url_parts << if ENV["PYTTY_BIND"]
|
13
|
+
ENV["PYTTY_BIND"]
|
16
14
|
else
|
17
15
|
"127.0.0.1"
|
18
16
|
end
|
19
|
-
url_parts <<
|
20
|
-
|
17
|
+
url_parts << ":"
|
18
|
+
url_parts << if ENV["PYTTY_PORT"]
|
19
|
+
if ENV["PYTTY_PORT"] == "PORT"
|
21
20
|
ENV.fetch "PORT"
|
22
21
|
else
|
23
|
-
"
|
22
|
+
ENV["PYTTY_PORT"]
|
24
23
|
end
|
24
|
+
else
|
25
|
+
"1234"
|
25
26
|
end
|
26
27
|
|
27
28
|
Async::Reactor.run do
|
@@ -1,28 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
require "rack/request"
|
3
|
-
require "mustermann"
|
4
2
|
|
5
3
|
module Pytty
|
6
4
|
module Daemon
|
7
5
|
module Components
|
8
6
|
module Handler
|
9
|
-
def self.handle(component:,
|
7
|
+
def self.handle(component:, params:)
|
10
8
|
return case component
|
11
|
-
when "signal"
|
12
|
-
process_yield = Pytty::Daemon.yields[id]
|
13
|
-
return [404, "not found"] unless process_yield
|
14
|
-
return [500, "not running"] unless process_yield.running?
|
15
|
-
p params
|
16
|
-
process_yield.signal params["signal"]
|
17
|
-
[200, "ok"]
|
18
|
-
when "spawn"
|
19
|
-
process_yield = Pytty::Daemon.yields[id]
|
20
|
-
return [404, "not found"] unless process_yield
|
21
|
-
|
22
|
-
process_yield.spawn
|
23
|
-
Pytty::Daemon.dump
|
24
|
-
|
25
|
-
[200, "ok"]
|
26
9
|
when "ps"
|
27
10
|
output = []
|
28
11
|
Pytty::Daemon.yields.each do |id, process_yield|
|
@@ -32,33 +15,12 @@ module Pytty
|
|
32
15
|
when "yield"
|
33
16
|
cmd = params.dig "cmd"
|
34
17
|
env = params.dig "env"
|
35
|
-
|
36
|
-
Pytty::Daemon.yields[process_yield.id] = process_yield
|
37
|
-
Pytty::Daemon.dump
|
18
|
+
id = params.dig "id"
|
38
19
|
|
39
|
-
|
40
|
-
|
41
|
-
process_yield = Pytty::Daemon.yields[id]
|
42
|
-
p Pytty::Daemon.yields
|
43
|
-
p id
|
44
|
-
return [404, "not found"] unless process_yield
|
45
|
-
if process_yield.running?
|
46
|
-
process_yield.kill
|
47
|
-
end
|
48
|
-
Pytty::Daemon.yields.delete process_yield.id
|
20
|
+
process_yield = Pytty::Daemon::ProcessYield.new cmd, id: id, env: env
|
21
|
+
Pytty::Daemon.yields[process_yield.id] = process_yield
|
49
22
|
Pytty::Daemon.dump
|
50
23
|
|
51
|
-
[200, nil]
|
52
|
-
when "spawn"
|
53
|
-
process_yield = Pytty::Daemon.yields[id]
|
54
|
-
return [404, "not found"] unless process_yield
|
55
|
-
|
56
|
-
pipe = IO.pipe
|
57
|
-
stderr_reader = Async::IO::Generic.new(pipe.first)
|
58
|
-
stderr_writer = Async::IO::Generic.new(pipe.last)
|
59
|
-
|
60
|
-
process_yield.spawn stdout: $stdout, stderr: stderr_writer, stdin: $stdin
|
61
|
-
stderr_reader.close
|
62
24
|
[200, process_yield]
|
63
25
|
else
|
64
26
|
raise "unknown: #{component}"
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pytty
|
4
|
+
module Daemon
|
5
|
+
module Components
|
6
|
+
module YieldHandler
|
7
|
+
def self.handle(component:, id:, params:)
|
8
|
+
process_yield = Pytty::Daemon.yields[id]
|
9
|
+
return [404, "not found"] unless process_yield
|
10
|
+
|
11
|
+
return case component
|
12
|
+
when "stdin"
|
13
|
+
process_yield.stdin.enqueue params["c"]
|
14
|
+
|
15
|
+
[200, "ok"]
|
16
|
+
when "signal"
|
17
|
+
return [500, "not running"] unless process_yield.running?
|
18
|
+
|
19
|
+
process_yield.signal params["signal"]
|
20
|
+
|
21
|
+
[200, "ok"]
|
22
|
+
when "spawn"
|
23
|
+
return [500, "already running"] if process_yield.running?
|
24
|
+
|
25
|
+
if process_yield.spawn
|
26
|
+
Pytty::Daemon.dump
|
27
|
+
else
|
28
|
+
return [500, "could not spawn"]
|
29
|
+
end
|
30
|
+
|
31
|
+
[200, "ok"]
|
32
|
+
when "rm"
|
33
|
+
process_yield.signal("KILL") if process_yield.running?
|
34
|
+
Pytty::Daemon.yields.delete process_yield.id
|
35
|
+
Pytty::Daemon.dump
|
36
|
+
|
37
|
+
[200, id]
|
38
|
+
else
|
39
|
+
raise "unknown: #{component} with id: #{id}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require "securerandom"
|
3
|
+
require "pty"
|
3
4
|
|
4
5
|
module Pytty
|
5
6
|
module Daemon
|
@@ -9,14 +10,22 @@ module Pytty
|
|
9
10
|
@env = env
|
10
11
|
|
11
12
|
@pid = nil
|
13
|
+
@status = nil
|
12
14
|
@id = id || SecureRandom.uuid
|
13
15
|
|
14
|
-
@
|
16
|
+
@stdout = nil
|
17
|
+
@stdouts = {}
|
15
18
|
@stdin = Async::Queue.new
|
16
19
|
end
|
17
20
|
|
18
|
-
attr_reader :id, :cmd, :pid
|
19
|
-
attr_accessor :
|
21
|
+
attr_reader :id, :cmd, :pid, :status, :stdout
|
22
|
+
attr_accessor :stdin
|
23
|
+
|
24
|
+
def add_stdout(stdout)
|
25
|
+
notification = Async::Notification.new
|
26
|
+
@stdouts[notification] = stdout
|
27
|
+
notification
|
28
|
+
end
|
20
29
|
|
21
30
|
def running?
|
22
31
|
!@pid.nil?
|
@@ -26,6 +35,7 @@ module Pytty
|
|
26
35
|
{
|
27
36
|
id: @id,
|
28
37
|
pid: @pid,
|
38
|
+
status: @status,
|
29
39
|
cmd: @cmd,
|
30
40
|
env: @env,
|
31
41
|
running: running?
|
@@ -33,11 +43,21 @@ module Pytty
|
|
33
43
|
end
|
34
44
|
|
35
45
|
def spawn
|
36
|
-
|
37
|
-
@env.merge!({
|
38
|
-
"TERM" => "vt100"
|
39
|
-
})
|
46
|
+
return false if running?
|
40
47
|
|
48
|
+
executable, args = @cmd
|
49
|
+
# @env.merge!({
|
50
|
+
# "TERM" => "xterm"
|
51
|
+
# })
|
52
|
+
|
53
|
+
stdout_path = File.join(Pytty::Daemon.pytty_path, @id)
|
54
|
+
File.unlink stdout_path if File.exist? stdout_path
|
55
|
+
stdout_appender = Async::IO::Stream.new(
|
56
|
+
File.open stdout_path, "a"
|
57
|
+
)
|
58
|
+
@stdout = Async::IO::Stream.new(
|
59
|
+
File.open stdout_path, "r"
|
60
|
+
)
|
41
61
|
Async::Task.current.async do |task|
|
42
62
|
p ["spawn", executable, args, @env]
|
43
63
|
|
@@ -46,46 +66,52 @@ module Pytty
|
|
46
66
|
async_stdout = Async::IO::Generic.new real_stdout
|
47
67
|
async_stdin = Async::IO::Generic.new real_stdin
|
48
68
|
|
49
|
-
task.async do |subtask|
|
69
|
+
task_stdin_writer = task.async do |subtask|
|
50
70
|
while c = @stdin.dequeue do
|
51
|
-
p c
|
52
71
|
async_stdin.write c
|
53
72
|
end
|
73
|
+
rescue Exception => ex
|
74
|
+
puts "async_stdin.write: #{ex.inspect}"
|
54
75
|
end
|
55
76
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
77
|
+
task_stdout_writer = task.async do |subtask|
|
78
|
+
while c = async_stdout.read(1)
|
79
|
+
stdout_appender.write c
|
80
|
+
stdout_appender.flush
|
81
|
+
@stdouts.each do |notification, stdout|
|
82
|
+
begin
|
83
|
+
stdout.write c
|
84
|
+
rescue Errno::EPIPE => ex
|
85
|
+
notification.signal
|
86
|
+
@stdouts.delete notification
|
87
|
+
end
|
63
88
|
end
|
64
89
|
end
|
90
|
+
ensure
|
91
|
+
task_stdin_writer.stop
|
92
|
+
Process.wait(@pid)
|
93
|
+
@status = $?.exitstatus
|
94
|
+
@pid = nil
|
95
|
+
stdout_appender.close
|
96
|
+
|
97
|
+
@stdouts.each do |notification, stdout|
|
98
|
+
p ["notifying: #{notification}"]
|
99
|
+
notification.signal
|
100
|
+
@stdouts.delete notification
|
101
|
+
end
|
102
|
+
p ["exited", @cmd, "status", @status]
|
65
103
|
end
|
66
|
-
|
67
|
-
#stdout.close
|
68
|
-
end
|
69
|
-
|
70
|
-
end
|
71
|
-
def signal(sig)
|
72
|
-
Process.kill(sig, @pid)
|
73
|
-
end
|
74
|
-
|
75
|
-
def tstp
|
76
|
-
Process.kill("TSTP", @pid)
|
77
|
-
end
|
104
|
+
end.wait
|
78
105
|
|
79
|
-
|
80
|
-
|
106
|
+
puts "spawned"
|
107
|
+
p ["@stdouts", @stdouts]
|
108
|
+
return true
|
81
109
|
end
|
82
110
|
|
83
|
-
def
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
def term
|
88
|
-
Process.kill("TERM", @pid)
|
111
|
+
def signal(sig)
|
112
|
+
sig_upcased = sig.upcase
|
113
|
+
p ["signaling", sig_upcased, "to", @pid]
|
114
|
+
Process.kill(sig_upcased, @pid)
|
89
115
|
end
|
90
116
|
end
|
91
117
|
end
|
data/lib/pytty/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pytty
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matti Paksula
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-01-
|
11
|
+
date: 2019-01-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: clamp
|
@@ -178,9 +178,12 @@ files:
|
|
178
178
|
- LICENSE.txt
|
179
179
|
- README.md
|
180
180
|
- Rakefile
|
181
|
-
- bin/build
|
182
181
|
- bin/console
|
183
182
|
- bin/setup
|
183
|
+
- build/linux-debian-deps.sh
|
184
|
+
- build/linux.sh
|
185
|
+
- build/macos-deps.sh
|
186
|
+
- build/macos.sh
|
184
187
|
- exe/pytty
|
185
188
|
- exe/pyttyd
|
186
189
|
- lib/pytty.rb
|
@@ -188,7 +191,9 @@ files:
|
|
188
191
|
- lib/pytty/client/api.rb
|
189
192
|
- lib/pytty/client/api/attach.rb
|
190
193
|
- lib/pytty/client/api/ps.rb
|
194
|
+
- lib/pytty/client/api/signal.rb
|
191
195
|
- lib/pytty/client/api/spawn.rb
|
196
|
+
- lib/pytty/client/api/stdout.rb
|
192
197
|
- lib/pytty/client/api/yield.rb
|
193
198
|
- lib/pytty/client/cli.rb
|
194
199
|
- lib/pytty/client/cli/attach_command.rb
|
@@ -199,6 +204,7 @@ files:
|
|
199
204
|
- lib/pytty/client/cli/run_command.rb
|
200
205
|
- lib/pytty/client/cli/signal_command.rb
|
201
206
|
- lib/pytty/client/cli/spawn_command.rb
|
207
|
+
- lib/pytty/client/cli/stdout_command.rb
|
202
208
|
- lib/pytty/client/cli/stream_command.rb
|
203
209
|
- lib/pytty/client/cli/yield_command.rb
|
204
210
|
- lib/pytty/client/process_yield.rb
|
@@ -212,10 +218,7 @@ files:
|
|
212
218
|
- lib/pytty/daemon/cli/serve_command.rb
|
213
219
|
- lib/pytty/daemon/components.rb
|
214
220
|
- lib/pytty/daemon/components/handler.rb
|
215
|
-
- lib/pytty/daemon/components/
|
216
|
-
- lib/pytty/daemon/components/run.rb
|
217
|
-
- lib/pytty/daemon/components/stream.rb
|
218
|
-
- lib/pytty/daemon/components/web_socket_handler.rb
|
221
|
+
- lib/pytty/daemon/components/yield_handler.rb
|
219
222
|
- lib/pytty/daemon/process_yield.rb
|
220
223
|
- lib/pytty/version.rb
|
221
224
|
- pytty.gemspec
|
data/bin/build
DELETED
@@ -1,54 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
require "rack/request"
|
3
|
-
require "mustermann"
|
4
|
-
|
5
|
-
module Pytty
|
6
|
-
module Daemon
|
7
|
-
module Components
|
8
|
-
class HttpHandler
|
9
|
-
def initialize(env)
|
10
|
-
@env = env
|
11
|
-
end
|
12
|
-
|
13
|
-
def handle
|
14
|
-
req = Rack::Request.new(@env)
|
15
|
-
body = begin
|
16
|
-
JSON.parse(req.body.read)
|
17
|
-
rescue
|
18
|
-
end
|
19
|
-
|
20
|
-
params = Mustermann.new('/:component(/?:id)?').params(req.path_info)
|
21
|
-
case params.fetch("component")
|
22
|
-
when "rm"
|
23
|
-
id = params["id"]
|
24
|
-
|
25
|
-
process_yield = Pytty::Daemon.yields[id]
|
26
|
-
unless process_yield
|
27
|
-
return [404, nil]
|
28
|
-
else
|
29
|
-
return [200, process_yield]
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
case req.path_info
|
34
|
-
when "/kill"
|
35
|
-
p req
|
36
|
-
return [200, {}]
|
37
|
-
end
|
38
|
-
|
39
|
-
obj = case req.path_info
|
40
|
-
when "/run"
|
41
|
-
Run.new cmd: body.dig("cmd")
|
42
|
-
when "/stream"
|
43
|
-
Stream.new cmd: body.dig("cmd")
|
44
|
-
else
|
45
|
-
raise "Unknown: #{req.path_info}"
|
46
|
-
end
|
47
|
-
|
48
|
-
obj.run
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
@@ -1,44 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
require 'pty'
|
3
|
-
require 'io/console'
|
4
|
-
|
5
|
-
module Pytty
|
6
|
-
module Daemon
|
7
|
-
module Components
|
8
|
-
class Stream
|
9
|
-
def initialize(cmd:, env:)
|
10
|
-
@cmd = cmd
|
11
|
-
@env = env
|
12
|
-
end
|
13
|
-
|
14
|
-
def run(stream:)
|
15
|
-
pipe = IO.pipe
|
16
|
-
stderr = Async::IO::Generic.new(pipe.first)
|
17
|
-
stderr_writer = Async::IO::Generic.new(pipe.last)
|
18
|
-
|
19
|
-
|
20
|
-
cmd, args = @cmd
|
21
|
-
process_stdout, process_stdin, pid = PTY.spawn(@env, cmd, *args, err: stderr_writer.fileno)
|
22
|
-
stderr_writer.close
|
23
|
-
|
24
|
-
stdout = Async::IO::Generic.new process_stdout
|
25
|
-
# stdin = Async::IO::Generic.new process_stdin
|
26
|
-
Async::Task.current.async do |task|
|
27
|
-
while c = stdout.read(1)
|
28
|
-
stream.write c
|
29
|
-
end
|
30
|
-
stream.close
|
31
|
-
end
|
32
|
-
# Async::Task.current.async do |task|
|
33
|
-
# loop do
|
34
|
-
# p "o"
|
35
|
-
# task.sleep 1
|
36
|
-
# end
|
37
|
-
# end
|
38
|
-
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
@@ -1,37 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
require "rack/request"
|
3
|
-
|
4
|
-
module Pytty
|
5
|
-
module Daemon
|
6
|
-
module Components
|
7
|
-
class WebSocketHandler
|
8
|
-
def initialize(env)
|
9
|
-
@env = env
|
10
|
-
end
|
11
|
-
|
12
|
-
def handle
|
13
|
-
req = Rack::Request.new(@env)
|
14
|
-
ws = Pytty::Daemon::Api::WebSockets.new(@env)
|
15
|
-
ws.handle
|
16
|
-
|
17
|
-
klass = case req.path_info
|
18
|
-
when "/stream"
|
19
|
-
Stream
|
20
|
-
else
|
21
|
-
raise "Unknown: #{req.path_info}"
|
22
|
-
end
|
23
|
-
params = ws.read
|
24
|
-
body = begin
|
25
|
-
JSON.parse(params)
|
26
|
-
rescue Exception => ex
|
27
|
-
p ex
|
28
|
-
end
|
29
|
-
|
30
|
-
obj = klass.new cmd: body.dig("cmd"), env: body.dig("env")
|
31
|
-
obj.run stream: ws
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|