pssh 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|