pssh 0.2.2
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.
- data/Gemfile +7 -0
- data/Gemfile.lock +32 -0
- data/README.md +38 -0
- data/assets/bootstrap.css +689 -0
- data/assets/style.css +29 -0
- data/assets/term.js +4245 -0
- data/assets/wssh.js +134 -0
- data/bin/pssh +7 -0
- data/lib/pssh.rb +114 -0
- data/lib/pssh/cli.rb +60 -0
- data/lib/pssh/client.rb +30 -0
- data/lib/pssh/console.rb +81 -0
- data/lib/pssh/socket.rb +187 -0
- data/lib/pssh/version.rb +4 -0
- data/lib/pssh/web_console.rb +17 -0
- data/pssh.gemspec +25 -0
- data/views/index.haml +69 -0
- metadata +160 -0
data/assets/wssh.js
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
/*
|
2
|
+
WSSH Javascript Client
|
3
|
+
|
4
|
+
Usage:
|
5
|
+
|
6
|
+
var client = new WSSHClient();
|
7
|
+
|
8
|
+
client.connect({
|
9
|
+
// Connection and authentication parameters
|
10
|
+
username: 'root',
|
11
|
+
hostname: 'localhost',
|
12
|
+
authentication_method: 'password', // can either be password or private_key
|
13
|
+
password: 'secretpassword', // do not provide when using private_key
|
14
|
+
key_passphrase: 'secretpassphrase', // *may* be provided if the private_key is encrypted
|
15
|
+
|
16
|
+
// Callbacks
|
17
|
+
onError: function(error) {
|
18
|
+
// Called upon an error
|
19
|
+
console.error(error);
|
20
|
+
},
|
21
|
+
onConnect: function() {
|
22
|
+
// Called after a successful connection to the server
|
23
|
+
console.debug('Connected!');
|
24
|
+
|
25
|
+
client.send('ls\n'); // You can send data back to the server by using WSSHClient.send()
|
26
|
+
},
|
27
|
+
onClose: function() {
|
28
|
+
// Called when the remote closes the connection
|
29
|
+
console.debug('Connection Reset By Peer');
|
30
|
+
},
|
31
|
+
onData: function(data) {
|
32
|
+
// Called when data is received from the server
|
33
|
+
console.debug('Received: ' + data);
|
34
|
+
}
|
35
|
+
});
|
36
|
+
|
37
|
+
*/
|
38
|
+
|
39
|
+
function WSSHClient(uuid) {
|
40
|
+
this.uuid=uuid;
|
41
|
+
};
|
42
|
+
|
43
|
+
WSSHClient.prototype._generateEndpoint = function(options) {
|
44
|
+
if (window.location.protocol == 'https:') {
|
45
|
+
var protocol = 'wss://';
|
46
|
+
} else {
|
47
|
+
var protocol = 'ws://';
|
48
|
+
}
|
49
|
+
var endpoint = protocol + window.location.host +
|
50
|
+
'/socket?uuid=' + this.uuid; ///' + encodeURIComponent(options.hostname) + '/' +
|
51
|
+
//encodeURIComponent(options.username);
|
52
|
+
/*if (options.authentication_method == 'password') {
|
53
|
+
endpoint += '?password=' + encodeURIComponent(options.password);
|
54
|
+
} else if (options.authentication_method == 'private_key') {
|
55
|
+
endpoint += '?private_key=' + encodeURIComponent(options.private_key);
|
56
|
+
if (options.key_passphrase !== undefined)
|
57
|
+
endpoint += '&key_passphrase=' + encodeURIComponent(
|
58
|
+
options.key_passphrase);
|
59
|
+
}*/
|
60
|
+
return endpoint;
|
61
|
+
};
|
62
|
+
|
63
|
+
WSSHClient.prototype.startSocket = function() {
|
64
|
+
|
65
|
+
var ping;
|
66
|
+
var _self = this;
|
67
|
+
var connected = true;
|
68
|
+
|
69
|
+
if (window.WebSocket) {
|
70
|
+
this._connection = new WebSocket(this.endpoint);
|
71
|
+
}
|
72
|
+
else if (window.MozWebSocket) {
|
73
|
+
this._connection = MozWebSocket(this.endpoint);
|
74
|
+
}
|
75
|
+
else {
|
76
|
+
this.options.onError('WebSocket Not Supported');
|
77
|
+
return ;
|
78
|
+
}
|
79
|
+
|
80
|
+
this._connection.onopen = function() {
|
81
|
+
console.log("connected");
|
82
|
+
ping = window.setInterval(function() {
|
83
|
+
_self._connection.send('p');
|
84
|
+
}, 10000);
|
85
|
+
_self.options.onConnect();
|
86
|
+
};
|
87
|
+
|
88
|
+
this._connection.onmessage = function (evt) {
|
89
|
+
console.log(evt.data);
|
90
|
+
var data = JSON.parse(evt.data.toString());
|
91
|
+
if (data.error !== undefined) {
|
92
|
+
_self.options.onError(data.error);
|
93
|
+
} else if (data.close !== undefined) {
|
94
|
+
connected = false;
|
95
|
+
} else {
|
96
|
+
_self.options.onData(data.data);
|
97
|
+
}
|
98
|
+
};
|
99
|
+
|
100
|
+
this._connection.onclose = function(evt) {
|
101
|
+
if (connected) {
|
102
|
+
window.clearInterval(ping);
|
103
|
+
_self.reconnect();
|
104
|
+
} else {
|
105
|
+
_self.options.onClose();
|
106
|
+
}
|
107
|
+
};
|
108
|
+
|
109
|
+
};
|
110
|
+
|
111
|
+
WSSHClient.prototype.connect = function(options) {
|
112
|
+
|
113
|
+
this.endpoint = this._generateEndpoint(options);
|
114
|
+
this.options = options;
|
115
|
+
|
116
|
+
this.startSocket();
|
117
|
+
|
118
|
+
};
|
119
|
+
|
120
|
+
WSSHClient.prototype.send = function(data) {
|
121
|
+
this._connection.send('d' + data);
|
122
|
+
};
|
123
|
+
|
124
|
+
WSSHClient.prototype.start = function(width, height) {
|
125
|
+
this._connection.send('s' + width + ',' + height);
|
126
|
+
};
|
127
|
+
|
128
|
+
WSSHClient.prototype.resize = function(width, height) {
|
129
|
+
this._connection.send('r' + width + ',' + height);
|
130
|
+
};
|
131
|
+
|
132
|
+
WSSHClient.prototype.reconnect = function() {
|
133
|
+
this.startSocket();
|
134
|
+
};
|
data/bin/pssh
ADDED
data/lib/pssh.rb
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'haml'
|
2
|
+
require 'io/console'
|
3
|
+
require 'json'
|
4
|
+
require 'optparse'
|
5
|
+
require 'pty'
|
6
|
+
require 'readline'
|
7
|
+
require 'tilt/haml'
|
8
|
+
require 'thin'
|
9
|
+
require 'rack/websocket'
|
10
|
+
|
11
|
+
require 'pssh/cli'
|
12
|
+
require 'pssh/client'
|
13
|
+
require 'pssh/console'
|
14
|
+
require 'pssh/socket'
|
15
|
+
require 'pssh/version'
|
16
|
+
require 'pssh/web_console'
|
17
|
+
|
18
|
+
module Pssh
|
19
|
+
|
20
|
+
DEFAULT_IO_MODE = 'rw'
|
21
|
+
DEFAULT_SOCKET_PREFIX = '/tmp/pssh'
|
22
|
+
DEFAULT_PORT = 8022
|
23
|
+
|
24
|
+
class << self
|
25
|
+
|
26
|
+
attr_writer :io_mode
|
27
|
+
attr_writer :socket_prefix
|
28
|
+
attr_writer :command
|
29
|
+
attr_writer :open_sessions
|
30
|
+
attr_writer :port
|
31
|
+
attr_writer :prompt
|
32
|
+
attr_accessor :client
|
33
|
+
attr_accessor :socket
|
34
|
+
attr_accessor :pty
|
35
|
+
attr_accessor :web
|
36
|
+
|
37
|
+
def base_path
|
38
|
+
File.dirname(__FILE__) + "/.."
|
39
|
+
end
|
40
|
+
|
41
|
+
def port
|
42
|
+
@port ||= DEFAULT_PORT
|
43
|
+
end
|
44
|
+
|
45
|
+
def open_sessions
|
46
|
+
@open_sessions ||= {}
|
47
|
+
end
|
48
|
+
|
49
|
+
# Public: This sets whether the connecting user can just view or can
|
50
|
+
# also write to the screen. Values are 'rw, 'r', and 'w'.
|
51
|
+
#
|
52
|
+
# Returns a String.
|
53
|
+
def io_mode
|
54
|
+
@io_mode ||= DEFAULT_IO_MODE
|
55
|
+
end
|
56
|
+
|
57
|
+
# Public: This is the prefix that will be used to set up the socket for tmux or screen.
|
58
|
+
#
|
59
|
+
# Returns a String.
|
60
|
+
def socket_prefix
|
61
|
+
@socket_prefix ||= DEFAULT_SOCKET_PREFIX
|
62
|
+
end
|
63
|
+
|
64
|
+
# Public: This is the default socket path that will be used if one is not
|
65
|
+
# provided in the command line arguments.
|
66
|
+
#
|
67
|
+
# Returns a String.
|
68
|
+
def default_socket_path
|
69
|
+
@socket ||= "#{socket_prefix}-#{SecureRandom.uuid}"
|
70
|
+
end
|
71
|
+
|
72
|
+
# Public: This is the tool which we are going to use for our multiplexing. If
|
73
|
+
# we're currently in a tmux or screen session, that is the first option,
|
74
|
+
# then it checks if tmux or screen is installed, and then it resorts to
|
75
|
+
# a plain old shell.
|
76
|
+
#
|
77
|
+
# Returns a Symbol.
|
78
|
+
def command
|
79
|
+
@command ||=
|
80
|
+
(ENV['TMUX'] && :tmux) ||
|
81
|
+
(ENV['STY'] && :screen) ||
|
82
|
+
(`which tmux` && :tmux) ||
|
83
|
+
(`which screen` && :screen) ||
|
84
|
+
:shell
|
85
|
+
end
|
86
|
+
|
87
|
+
# Public: Allow configuring details of Pssh by making use of a block.
|
88
|
+
#
|
89
|
+
# Returns True.
|
90
|
+
def configure
|
91
|
+
yield self
|
92
|
+
true
|
93
|
+
end
|
94
|
+
|
95
|
+
# Public: This is the prompt character that shows up at the beginning of
|
96
|
+
# Pssh's console.
|
97
|
+
#
|
98
|
+
# Returns a String.
|
99
|
+
def prompt
|
100
|
+
@prompt ||= "\u26a1 "
|
101
|
+
end
|
102
|
+
|
103
|
+
# Public: Generates a random id for a session and stores it to a list.
|
104
|
+
#
|
105
|
+
# Returns a String.
|
106
|
+
def create_session(username=nil)
|
107
|
+
id = SecureRandom.uuid
|
108
|
+
self.open_sessions[id] = username
|
109
|
+
id
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
data/lib/pssh/cli.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
module Pssh
|
2
|
+
class CLI
|
3
|
+
|
4
|
+
BANNER = <<-BANNER
|
5
|
+
Usage: pssh [options]
|
6
|
+
|
7
|
+
Description:
|
8
|
+
|
9
|
+
Remote pair programming made easy by allowing access via a web
|
10
|
+
browser. Supports HTTP Basic Auth to handle users, and can be
|
11
|
+
combined with tmux, screen, or just a plain shell.
|
12
|
+
|
13
|
+
BANNER
|
14
|
+
|
15
|
+
def self.parse_options(args)
|
16
|
+
options = {}
|
17
|
+
|
18
|
+
@opts = OptionParser.new do |opts|
|
19
|
+
opts.banner = BANNER.gsub(/^ {4}/, '')
|
20
|
+
|
21
|
+
opts.separator ''
|
22
|
+
opts.separator 'Options:'
|
23
|
+
|
24
|
+
opts.on('-p PORT', '--port PORT', Integer, 'Set the port that Pssh will run on') do |port|
|
25
|
+
options[:port] = port.to_i
|
26
|
+
end
|
27
|
+
|
28
|
+
opts.on('-c PATH', '--command COMMAND', [:tmux, :screen, :shell], 'Set the tool that will be used to initialize the web session (tmux, screen, or shell)') do |command|
|
29
|
+
options[:command] = command
|
30
|
+
end
|
31
|
+
|
32
|
+
opts.on('-s PATH', '--socket PATH', String, 'Set the socket that will be used for connecting (/path/to/socket)') do |socket|
|
33
|
+
options[:socket] = socket
|
34
|
+
end
|
35
|
+
|
36
|
+
opts.on( '-h', '--help', 'Display this help.' ) do
|
37
|
+
puts opts
|
38
|
+
exit
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
@opts.parse!(args)
|
44
|
+
|
45
|
+
options
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.run(args)
|
49
|
+
opts = parse_options(args)
|
50
|
+
Pssh.configure do |pssh|
|
51
|
+
opts.each do |k,v|
|
52
|
+
pssh.send :"#{k}=", v
|
53
|
+
end
|
54
|
+
end
|
55
|
+
Pssh::Client.start
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
data/lib/pssh/client.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
module Pssh
|
2
|
+
class Client
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
@pty = Pssh.pty = Pssh::Socket.new
|
6
|
+
@web = Pssh.web = Pssh::WebConsole.new
|
7
|
+
@app = Rack::Builder.new do
|
8
|
+
map "/assets/" do
|
9
|
+
run Rack::File.new "#{Pssh.base_path}/assets/"
|
10
|
+
end
|
11
|
+
map "/socket" do
|
12
|
+
run Pssh.pty
|
13
|
+
end
|
14
|
+
map "/" do
|
15
|
+
run Pssh.web
|
16
|
+
end
|
17
|
+
end
|
18
|
+
Thread.new do
|
19
|
+
@console = Console.new(pty: @pty, web: @web)
|
20
|
+
end
|
21
|
+
Thin::Logging.silent = true
|
22
|
+
Rack::Handler::Thin.run @app, Port: Pssh.port
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.start
|
26
|
+
Pssh.client = @client = Client.new
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
data/lib/pssh/console.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
module Pssh
|
2
|
+
class Console
|
3
|
+
|
4
|
+
COMMANDS = %w(list block unblock sessions kill-session exit).sort
|
5
|
+
BANNER = <<-BANNER
|
6
|
+
------------------------------------------------------------------
|
7
|
+
help\t\tDisplays this help menu.
|
8
|
+
info\t\tDisplays current tmux settings and configuration.
|
9
|
+
list[ sessions]\tShow the currently open sessions.
|
10
|
+
kill-session[s]\tKill either all sessions or a specific by id.
|
11
|
+
--all\t\tKills all the open sessions.
|
12
|
+
'session id'\tKills only the session specified by the id.
|
13
|
+
exit\t\tCloses all active sessions and exits.
|
14
|
+
------------------------------------------------------------------
|
15
|
+
Tip: Use tab-completion for commands and session ids.
|
16
|
+
BANNER
|
17
|
+
|
18
|
+
def initialize(opts = {})
|
19
|
+
|
20
|
+
@pty = opts[:pty]
|
21
|
+
@web = opts[:web]
|
22
|
+
|
23
|
+
begin
|
24
|
+
puts "[ pssh terminal ]"
|
25
|
+
puts "Service started on port #{Pssh.port}."
|
26
|
+
puts "Type 'help' for more information."
|
27
|
+
|
28
|
+
Readline.completion_append_character = " "
|
29
|
+
Readline.completion_proc = completion_proc
|
30
|
+
|
31
|
+
while command = Readline.readline(Pssh.prompt, true)
|
32
|
+
command.strip!
|
33
|
+
command.gsub!(/\s+/, ' ')
|
34
|
+
case command
|
35
|
+
when 'help'
|
36
|
+
puts BANNER.gsub(/^ {6}/,'')
|
37
|
+
when 'exit'
|
38
|
+
@pty.kill_all_sessions
|
39
|
+
Kernel.exit!
|
40
|
+
when 'info'
|
41
|
+
puts 'Current Configuration:'
|
42
|
+
if @pty.path
|
43
|
+
puts "Socket: #{@pty.path}"
|
44
|
+
puts "(Attach to this socket with `#{@pty.attach_cmd}`)"
|
45
|
+
else
|
46
|
+
puts 'Connections are made to a vanilla shell.'
|
47
|
+
end
|
48
|
+
when 'list sessions', 'list'
|
49
|
+
@pty.sessions.each do |k,v|
|
50
|
+
puts v[:user_string]
|
51
|
+
end
|
52
|
+
when /^kill-sessions?\s?(.*)$/
|
53
|
+
if $1 == '--all'
|
54
|
+
puts 'disconnecting all clients'
|
55
|
+
@pty.kill_all_sessions
|
56
|
+
else
|
57
|
+
puts "disconnecting #{$1}"
|
58
|
+
if @pty.sessions.keys.include?($1)
|
59
|
+
@pty.sessions[$1][:socket].close!
|
60
|
+
else
|
61
|
+
@pty.sessions.each do |k, sess|
|
62
|
+
if sess[:username] == $1
|
63
|
+
sess[:socket].close!
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
rescue Exception => e
|
71
|
+
puts e.inspect
|
72
|
+
puts e.backtrace
|
73
|
+
retry
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def completion_proc
|
78
|
+
@completion_proc ||= proc { |s| (COMMANDS + Pssh.open_sessions.keys + Pssh.open_sessions.values.uniq).grep( /^#{Regexp.escape(s)}/ ) }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
data/lib/pssh/socket.rb
ADDED
@@ -0,0 +1,187 @@
|
|
1
|
+
module Pssh
|
2
|
+
class Socket < Rack::WebSocket::Application
|
3
|
+
|
4
|
+
attr_accessor :sessions
|
5
|
+
attr_accessor :path
|
6
|
+
attr_accessor :attach_cmd
|
7
|
+
|
8
|
+
def initialize(opts={})
|
9
|
+
super
|
10
|
+
|
11
|
+
# set up empty variables
|
12
|
+
@sessions = {}
|
13
|
+
@killed_sessions = []
|
14
|
+
@existing_socket = false
|
15
|
+
|
16
|
+
self.send Pssh.command.to_sym
|
17
|
+
end
|
18
|
+
|
19
|
+
def tmux
|
20
|
+
if ENV['TMUX']
|
21
|
+
@path = ENV['TMUX'].split(',').first
|
22
|
+
@existing_socket = true
|
23
|
+
@command = "tmux -S #{@path} attach"
|
24
|
+
else
|
25
|
+
@path = Pssh.default_socket_path
|
26
|
+
@command = "tmux -S #{@path} new"
|
27
|
+
end
|
28
|
+
@attach_cmd = "tmux -S #{@path} attach"
|
29
|
+
end
|
30
|
+
|
31
|
+
def screen
|
32
|
+
if ENV['STY']
|
33
|
+
@path = ENV['STY']
|
34
|
+
@existing_socket = true
|
35
|
+
@command = "screen -S #{@path} -X multiuser on && screen -x #{@path}"
|
36
|
+
else
|
37
|
+
@path = Pssh.default_socket_path
|
38
|
+
@command = "screen -m -S #{@path}"
|
39
|
+
end
|
40
|
+
@attach_cmd = "screen -x #{@path}"
|
41
|
+
end
|
42
|
+
|
43
|
+
def shell
|
44
|
+
@path = nil
|
45
|
+
@command = 'sh'
|
46
|
+
end
|
47
|
+
|
48
|
+
def params(env)
|
49
|
+
Hash[env['QUERY_STRING'].split('&').map { |e| e.split '=' }]
|
50
|
+
end
|
51
|
+
|
52
|
+
def on_open(env)
|
53
|
+
uuid = @uuid = params(env)['uuid']
|
54
|
+
if @killed_sessions.include? uuid
|
55
|
+
# this session has been killed from the console and shouldn't be
|
56
|
+
# restarted
|
57
|
+
close_websocket and return
|
58
|
+
end
|
59
|
+
if @sessions[uuid]
|
60
|
+
@sessions[uuid][:active] = true
|
61
|
+
elsif Pssh.open_sessions.keys.include?(uuid)
|
62
|
+
user_string = Pssh.open_sessions[uuid] ? "#{Pssh.open_sessions[uuid]} (#{uuid})" : uuid
|
63
|
+
print "\n#{user_string} attached.\n#{Pssh.prompt}"
|
64
|
+
@sessions[uuid] = {
|
65
|
+
data: [],
|
66
|
+
username: Pssh.open_sessions[uuid],
|
67
|
+
user_string: user_string,
|
68
|
+
active: true,
|
69
|
+
started: false
|
70
|
+
}
|
71
|
+
Pssh.open_sessions.delete uuid
|
72
|
+
else
|
73
|
+
@sessions[uuid] = {socket: self}
|
74
|
+
self.close! and return
|
75
|
+
end
|
76
|
+
@sessions[uuid][:socket] = self
|
77
|
+
end
|
78
|
+
|
79
|
+
def on_close(env)
|
80
|
+
uuid = params(env)['uuid']
|
81
|
+
@sessions[uuid][:active] = false
|
82
|
+
Thread.new do
|
83
|
+
sleep 10
|
84
|
+
if @sessions[uuid][:active] == false
|
85
|
+
@sessions[uuid][:read].close
|
86
|
+
@sessions[uuid][:write].close
|
87
|
+
@sessions.delete uuid
|
88
|
+
print "\n#{user_string} detached.\n#{Pssh.prompt}"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def close_websocket
|
94
|
+
@sessions[@uuid][:socket].send_data({ close: true }.to_json) if @sessions[@uuid][:socket]
|
95
|
+
@sessions[@uuid][:read].close if @sessions[@uuid][:read]
|
96
|
+
@sessions[@uuid][:write].close if @sessions[@uuid][:write]
|
97
|
+
@sessions[@uuid][:thread].exit if @sessions[@uuid][:thread]
|
98
|
+
@sessions.delete @uuid
|
99
|
+
super
|
100
|
+
end
|
101
|
+
|
102
|
+
def kill_all_sessions
|
103
|
+
@sessions.each do |k,v|
|
104
|
+
v[:socket].close!
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def close!
|
109
|
+
self.close_websocket
|
110
|
+
@killed_sessions << @uuid
|
111
|
+
end
|
112
|
+
|
113
|
+
# Internal: Sends a message to the tmux or screen display notifying of a
|
114
|
+
# new user that has connected.
|
115
|
+
#
|
116
|
+
# Returns nothing.
|
117
|
+
def send_display_message(uuid)
|
118
|
+
if @existing_socket
|
119
|
+
case Pssh.command.to_sym
|
120
|
+
when :tmux
|
121
|
+
`tmux -S #{@path} display-message "#{@sessions[uuid][:user_string]} has connected"`
|
122
|
+
when :screen
|
123
|
+
`screen -S #{@path} -X wall "#{@sessions[uuid][:user_string]} has connected"`
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def clear_environment
|
129
|
+
ENV['TMUX'] = nil
|
130
|
+
ENV['STY'] = nil
|
131
|
+
end
|
132
|
+
|
133
|
+
def on_message(env, message)
|
134
|
+
uuid = params(env)['uuid']
|
135
|
+
return unless @sessions[uuid]
|
136
|
+
case message[0]
|
137
|
+
when 's'
|
138
|
+
unless @sessions[uuid][:started]
|
139
|
+
@sessions[uuid][:started] = true
|
140
|
+
@sessions[uuid][:thread] = Thread.new do
|
141
|
+
begin
|
142
|
+
if @sessions[uuid]
|
143
|
+
size = message[1..-1].split ','
|
144
|
+
send_display_message(uuid)
|
145
|
+
clear_environment
|
146
|
+
@sessions[uuid][:read], @sessions[uuid][:write], @sessions[uuid][:pid] = PTY.spawn(@command)
|
147
|
+
@sessions[uuid][:write].winsize = [size[1].to_i, size[0].to_i]
|
148
|
+
|
149
|
+
while(@sessions[uuid] && @sessions[uuid][:active]) do
|
150
|
+
IO.select([@sessions[uuid][:read]])
|
151
|
+
begin
|
152
|
+
while (data = @sessions[uuid][:read].readpartial(2048)) do
|
153
|
+
data.encode!('UTF-16', 'UTF-8', :invalid => :replace, :replace => '')
|
154
|
+
data.encode!('UTF-8', 'UTF-16')
|
155
|
+
if data.valid_encoding?
|
156
|
+
@sessions[uuid][:socket].send_data({ data: data }.to_json)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
rescue Exception => e
|
160
|
+
if @sessions[uuid]
|
161
|
+
if e.is_a?(Errno::EAGAIN)
|
162
|
+
retry
|
163
|
+
else
|
164
|
+
@sessions[uuid][:active] = false
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
rescue Exception => e
|
172
|
+
puts e.inspect
|
173
|
+
puts e.backtrace
|
174
|
+
puts '---'
|
175
|
+
retry
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
when 'd'
|
180
|
+
@sessions[uuid][:write].write_nonblock message[1..-1] if Pssh.io_mode['w']
|
181
|
+
when 'r'
|
182
|
+
size = message[1..-1].split ','
|
183
|
+
@sessions[uuid][:write].winsize= [size[1].to_i, size[0].to_i]
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|