pssh 0.2.3 → 0.3.3

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 ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ MDVmMzVhYTE2ZjRhMGZiMmE0NWM1ZjdmM2Q3ZTUyZjljYTJiOTc1Nw==
5
+ data.tar.gz: !binary |-
6
+ ZDE1MDQ0MTBkOTA4NzhlY2UwMDBmZmJmZTU5NmQ5NmJjZmYyNzZhZA==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ NWQ4MTAxMTA1OGVjNzNmM2FkMWJjNDYzYjNiNGFhYzMzNzVmMTNlMDhiYjU0
10
+ MjkwN2I2ZmM5OTQ3MTNhMTZlZjVjNjkwZWUwNTEyMWMxM2RkMzNiODI5MDMz
11
+ ZTU2YTdkYjY2NzlhNmJlZmRhNmQ4OTI5YTM4ZjZkZDYwNTBmMTk=
12
+ data.tar.gz: !binary |-
13
+ OGRiZTE1OWQ4M2MyY2RkMWUxOGY2MGQ5ZjVmMzBlOTE3ODVlMzZjOGFmNjM0
14
+ ZDIzZjM2ODMzNGM4ODJiYmI5YjM4MzkyZTJjMWYyY2JkYjY1OTk0ZjdjOWU1
15
+ MDZmNjQzMmI1ZGM0Yzg4MzE2ZmE4NzQ0OWZhYWIxZjJlNDg3Njc=
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Kelly Martin, except where otherwise noted
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -8,26 +8,32 @@ user, figuring out how the heck to get tmux to integrate everything, and then
8
8
  opening up port 22 on your firewall to let everyone know you're ready and willing
9
9
  for the all the brute force attacks your network can handle.
10
10
 
11
- What if it was as simple as opening up a new tab in tmux or screen, typing four
12
- letters, then sharing a URL with a friend?
11
+ What if it was as simple as opening up a new shell, or a tab in tmux or screen,
12
+ typing four letters, then sharing a URL with a friend?
13
13
 
14
14
  Pssh. Is that even possible?
15
15
 
16
16
  Yup.
17
17
 
18
18
  ```ruby
19
- gem install pssh
19
+ > gem install pssh
20
+ # to get started, just run it from the command line
21
+ > pssh
20
22
  ```
21
23
 
22
24
  It defaults to running on port 8022, but throw in a `-p PORT` flag and you're
23
25
  up and running on whatever port. If you're in a tmux or screen session, it'll
24
- share that. If you're not, just pass in another flag to specify what you want.
26
+ share that. If you're not, no worries. **Pssh has its own session sharing built in.**
27
+ And if you want to use tmux or screen, go right ahead and use the `-c tmux` flag.
25
28
 
26
- When it starts, you'll have a pseudo-terminal that lets you see who's connected,
27
- kick people off, and connect locally to the running session if you need access.
29
+ If you're already in a tmux or screen session, you'll have a pseudo-terminal
30
+ that lets you see who's connected, kick people off, and a few other handy things.
31
+
32
+ If you started `pssh` in a plain shell, you'll be immediately transported into
33
+ that shell. Just type `exit` to quit and kick everyone else off too.
28
34
 
29
35
  Check out all the options with the `-h` flag, or list the console commands by
30
- typing `help` when you start up Pssh.
36
+ typing `help` when you start up Pssh inside tmux/screen.
31
37
 
32
38
  There's also support in there for HTTP basic authentication if you're running
33
39
  behind that layer. But that adds to the complexities.
@@ -36,3 +42,4 @@ You know what makes all this even easier? <a href="https://getportly.com">Portly
36
42
  Install Portly in seconds, add `localhost:8022` to your connections, and you can
37
43
  add authentication and share a classy URL right there on the spot.
38
44
 
45
+ Thanks to those who are working on <a href="https://github.com/chjj/term.js/">term.js</a> and <a href="https://github.com/aluzzardi/wssh">WSSH</a> for making the job easier.
data/assets/pssh.js ADDED
@@ -0,0 +1,129 @@
1
+ /*
2
+ PSSH Javascript Client
3
+ Modeled after WSSH: https://github.com/aluzzardi/wssh
4
+
5
+ Usage:
6
+
7
+ var client = new PsshClient();
8
+
9
+ client.connect({
10
+ // Connection and authentication parameters
11
+ username: 'root',
12
+ hostname: 'localhost',
13
+ authentication_method: 'password', // can either be password or private_key
14
+ password: 'secretpassword', // do not provide when using private_key
15
+ key_passphrase: 'secretpassphrase', // *may* be provided if the private_key is encrypted
16
+
17
+ // Callbacks
18
+ onError: function(error) {
19
+ // Called upon an error
20
+ console.error(error);
21
+ },
22
+ onConnect: function() {
23
+ // Called after a successful connection to the server
24
+ console.debug('Connected!');
25
+
26
+ client.send('ls\n'); // You can send data back to the server by using PsshClient.send()
27
+ },
28
+ onClose: function() {
29
+ // Called when the remote closes the connection
30
+ console.debug('Connection Reset By Peer');
31
+ },
32
+ onData: function(data) {
33
+ // Called when data is received from the server
34
+ console.debug('Received: ' + data);
35
+ }
36
+ });
37
+
38
+ */
39
+
40
+ function PsshClient(uuid) {
41
+ this.uuid=uuid;
42
+ };
43
+
44
+ PsshClient.prototype._generateEndpoint = function(options) {
45
+ if (window.location.protocol == 'https:') {
46
+ var protocol = 'wss://';
47
+ } else {
48
+ var protocol = 'ws://';
49
+ }
50
+ var endpoint = protocol + window.location.host +
51
+ '/socket?uuid=' + this.uuid + '&username=' + 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(options.key_passphrase);
58
+ }
59
+ }
60
+ return endpoint;
61
+ };
62
+
63
+ PsshClient.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
+ ping = window.setInterval(function() {
82
+ _self._connection.send('p');
83
+ }, 10000);
84
+ _self.options.onConnect();
85
+ };
86
+
87
+ this._connection.onmessage = function (evt) {
88
+ var data = JSON.parse(evt.data.toString());
89
+ if (data.error !== undefined) {
90
+ _self.options.onError(data.error);
91
+ } else if (data.close !== undefined) {
92
+ connected = false;
93
+ } else {
94
+ _self.options.onData(data.data);
95
+ }
96
+ };
97
+
98
+ this._connection.onclose = function(evt) {
99
+ if (connected) {
100
+ window.clearInterval(ping);
101
+ _self.reconnect();
102
+ } else {
103
+ _self.options.onClose();
104
+ }
105
+ };
106
+
107
+ };
108
+
109
+ PsshClient.prototype.connect = function(options) {
110
+ this.endpoint = this._generateEndpoint(options);
111
+ this.options = options;
112
+ this.startSocket();
113
+ };
114
+
115
+ PsshClient.prototype.send = function(data) {
116
+ this._connection.send('d' + data);
117
+ };
118
+
119
+ PsshClient.prototype.start = function(width, height) {
120
+ this._connection.send('s' + height + ',' + width);
121
+ };
122
+
123
+ PsshClient.prototype.resize = function(width, height) {
124
+ this._connection.send('r' + height + ',' + width);
125
+ };
126
+
127
+ PsshClient.prototype.reconnect = function() {
128
+ this.startSocket();
129
+ };
data/assets/style.css CHANGED
@@ -11,7 +11,8 @@ body {
11
11
  font-family: "DejaVu Sans Mono", "Liberation Mono", monospace;
12
12
  font-size: 11px;
13
13
  padding: 0;
14
- }
14
+ }
15
+
15
16
  .terminal {
16
17
  font-family: "DejaVu Sans Mono", "Liberation Mono", monospace;
17
18
  font-size: 11px;
data/assets/term.js CHANGED
@@ -1579,9 +1579,10 @@ Terminal.prototype.write = function(data) {
1579
1579
  // Send Device Attributes (Primary DA).
1580
1580
  // CSI > P s c
1581
1581
  // Send Device Attributes (Secondary DA)
1582
- case 'c':
1583
- this.sendDeviceAttributes(this.params);
1584
- break;
1582
+ // We don't need this because we are controlling the device elsewhere.
1583
+ // case 'c':
1584
+ // this.sendDeviceAttributes(this.params);
1585
+ // break;
1585
1586
 
1586
1587
  // CSI Pm d
1587
1588
  // Line Position Absolute [row] (default = [1,column]) (VPA).
data/lib/pssh.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require 'haml'
2
2
  require 'io/console'
3
3
  require 'json'
4
+ require 'open-uri'
4
5
  require 'optparse'
5
6
  require 'pty'
6
7
  require 'readline'
@@ -11,15 +12,18 @@ require 'rack/websocket'
11
12
  require 'pssh/cli'
12
13
  require 'pssh/client'
13
14
  require 'pssh/console'
15
+ require 'pssh/pty'
14
16
  require 'pssh/socket'
15
17
  require 'pssh/version'
16
- require 'pssh/web_console'
18
+ require 'pssh/web'
17
19
 
18
20
  module Pssh
19
21
 
20
22
  DEFAULT_IO_MODE = 'rw'
21
- DEFAULT_SOCKET_PREFIX = '/tmp/pssh'
23
+ DEFAULT_SOCKET_PREFIX = 'pssh'
22
24
  DEFAULT_PORT = 8022
25
+ DEFAULT_CACHE_LENGTH = 4096
26
+ PSSH_DOMAIN = 'pssh.herokuapp.com'
23
27
 
24
28
  class << self
25
29
 
@@ -29,6 +33,8 @@ module Pssh
29
33
  attr_writer :open_sessions
30
34
  attr_writer :port
31
35
  attr_writer :prompt
36
+ attr_accessor :socket_path
37
+ attr_accessor :cache_length
32
38
  attr_accessor :client
33
39
  attr_accessor :socket
34
40
  attr_accessor :pty
@@ -54,6 +60,23 @@ module Pssh
54
60
  @io_mode ||= DEFAULT_IO_MODE
55
61
  end
56
62
 
63
+ # Public: This method retrieves the current IP address and compresses
64
+ # it into a short url that can be shared to redirect to your site.
65
+ def share_url
66
+ return @share_url if @share_url
67
+ ip = open("http://#{PSSH_DOMAIN}/ip").read
68
+ .split('.')
69
+ .map { |x| x.to_i.to_s(36).rjust(2,'0') }
70
+ .join('')
71
+ @share_url = "http://#{PSSH_DOMAIN}/#{ip}#{port.to_i.to_s(36)}"
72
+ end
73
+
74
+ # Public: This sets the amount of data that will be stored to show
75
+ # new connections when they join.
76
+ def cache_length
77
+ @cache_length ||= DEFAULT_CACHE_LENGTH
78
+ end
79
+
57
80
  # Public: This is the prefix that will be used to set up the socket for tmux or screen.
58
81
  #
59
82
  # Returns a String.
@@ -66,7 +89,7 @@ module Pssh
66
89
  #
67
90
  # Returns a String.
68
91
  def default_socket_path
69
- @socket ||= "#{socket_prefix}-#{SecureRandom.uuid}"
92
+ @socket_path ||= "#{socket_prefix}-#{SecureRandom.uuid}"
70
93
  end
71
94
 
72
95
  # Public: This is the tool which we are going to use for our multiplexing. If
@@ -79,9 +102,9 @@ module Pssh
79
102
  @command ||=
80
103
  (ENV['TMUX'] && :tmux) ||
81
104
  (ENV['STY'] && :screen) ||
82
- (`which tmux` && :tmux) ||
83
- (`which screen` && :screen) ||
84
105
  :shell
106
+ #(`which tmux` && :tmux) ||
107
+ #(`which screen` && :screen) ||
85
108
  end
86
109
 
87
110
  # Public: Allow configuring details of Pssh by making use of a block.
data/lib/pssh/cli.rb CHANGED
@@ -33,8 +33,8 @@ module Pssh
33
33
  options[:command] = command
34
34
  end
35
35
 
36
- opts.on('-s PATH', '--socket PATH', String, 'Set the socket that will be used for connecting (/path/to/socket)') do |socket|
37
- options[:socket] = socket
36
+ opts.on('-s NAME', '--socket NAME', String, 'Set the socket that will be used for connecting (socket-name)') do |socket|
37
+ options[:socket_path] = socket
38
38
  end
39
39
 
40
40
  opts.on( '-h', '--help', 'Display this help.' ) do
data/lib/pssh/client.rb CHANGED
@@ -2,23 +2,26 @@ module Pssh
2
2
  class Client
3
3
 
4
4
  def initialize
5
- @pty = Pssh.pty = Pssh::Socket.new
6
- @web = Pssh.web = Pssh::WebConsole.new
5
+ @pty = Pssh.pty = Pssh::Pty.new
6
+ @socket = Pssh.socket = Pssh::Socket.new
7
+ @web = Pssh.web = Pssh::Web.new
7
8
  @app = Rack::Builder.new do
8
9
  map "/assets/" do
9
10
  run Rack::File.new "#{Pssh.base_path}/assets/"
10
11
  end
11
12
  map "/socket" do
12
- run Pssh.pty
13
+ run Pssh.socket
13
14
  end
14
15
  map "/" do
15
16
  run Pssh.web
16
17
  end
17
18
  end
18
- Thread.new do
19
- @console = Console.new(pty: @pty, web: @web)
20
- end
21
19
  Thin::Logging.silent = true
20
+ if Pssh.pty.existing?
21
+ Thread.new do
22
+ @console = Console.new
23
+ end
24
+ end
22
25
  Rack::Handler::Thin.run @app, Port: Pssh.port
23
26
  end
24
27
 
data/lib/pssh/console.rb CHANGED
@@ -17,17 +17,14 @@ module Pssh
17
17
 
18
18
  def initialize(opts = {})
19
19
 
20
- @pty = opts[:pty]
21
- @web = opts[:web]
22
-
23
20
  begin
24
21
  puts "[ pssh terminal ]"
25
22
  puts "Service started on port #{Pssh.port}."
23
+ puts "Share this url for access: #{Pssh.share_url}"
26
24
  puts "Type 'help' for more information."
27
25
 
28
26
  Readline.completion_append_character = " "
29
27
  Readline.completion_proc = completion_proc
30
-
31
28
  while command = Readline.readline(Pssh.prompt, true)
32
29
  command.strip!
33
30
  command.gsub!(/\s+/, ' ')
@@ -35,30 +32,30 @@ module Pssh
35
32
  when 'help'
36
33
  puts BANNER.gsub(/^ {6}/,'')
37
34
  when 'exit'
38
- @pty.kill_all_sessions
35
+ Pssh.socket.kill_all_sessions
39
36
  Kernel.exit!
40
37
  when 'info'
41
38
  puts 'Current Configuration:'
42
- if @pty.path
43
- puts "Socket: #{@pty.path}"
44
- puts "(Attach to this socket with `#{@pty.attach_cmd}`)"
39
+ if Pssh.pty.path
40
+ puts "Socket: #{Pssh.pty.path}"
41
+ puts "(Attach to this socket with `#{Pssh.pty.attach_cmd}`)"
45
42
  else
46
43
  puts 'Connections are made to a vanilla shell.'
47
44
  end
48
45
  when 'list sessions', 'list'
49
- @pty.sessions.each do |k,v|
46
+ Pssh.socket.sessions.each do |k,v|
50
47
  puts v[:user_string]
51
48
  end
52
49
  when /^kill-sessions?\s?(.*)$/
53
50
  if $1 == '--all'
54
51
  puts 'disconnecting all clients'
55
- @pty.kill_all_sessions
52
+ Pssh.socket.kill_all_sessions
56
53
  else
57
54
  puts "disconnecting #{$1}"
58
- if @pty.sessions.keys.include?($1)
59
- @pty.sessions[$1][:socket].close!
55
+ if Pssh.socket.sessions.keys.include?($1)
56
+ Pssh.socket.sessions[$1][:socket].close!
60
57
  else
61
- @pty.sessions.each do |k, sess|
58
+ Pssh.socket.sessions.each do |k, sess|
62
59
  if sess[:username] == $1
63
60
  sess[:socket].close!
64
61
  end
data/lib/pssh/pty.rb ADDED
@@ -0,0 +1,155 @@
1
+ module Pssh
2
+ class Pty
3
+
4
+ attr_reader :read
5
+ #attr_reader :write
6
+ attr_reader :stream
7
+ attr_reader :pid
8
+
9
+ attr_reader :path
10
+ attr_reader :attach_cmd
11
+
12
+ def initialize
13
+ @stream = ''
14
+ set_command
15
+ clear_environment
16
+ Thread.new do
17
+ begin
18
+ @read, @write, @pid = PTY.spawn(@command)
19
+ @write.winsize = $stdout.winsize
20
+ if new?
21
+ system("clear")
22
+ pssh = <<-BANNER
23
+ # [ pssh terminal ]
24
+ # Type `exit` to terminate this terminal.
25
+ BANNER
26
+ $stdout.puts pssh
27
+ Signal.trap(:WINCH) do
28
+ resize!
29
+ end
30
+ system("stty raw -echo")
31
+ end
32
+ @active = true
33
+ while @active do
34
+ begin
35
+ io = [@read]
36
+ io << $stdin if new?
37
+ rs, ws = IO.select(io)
38
+ r = rs[0]
39
+ while (data = r.read_nonblock(2048)) do
40
+ if new? && r == $stdin
41
+ @write.write_nonblock data
42
+ else
43
+ $stdout.write_nonblock data if new?
44
+ data.encode!('UTF-16', 'UTF-8', :invalid => :replace, :replace => '')
45
+ data.encode!('UTF-8', 'UTF-16')
46
+ if data.valid_encoding?
47
+ store data
48
+ Pssh.socket.write data
49
+ end
50
+ end
51
+ end
52
+ rescue Exception => e
53
+ if @active
54
+ if e.is_a?(Errno::EAGAIN)
55
+ retry
56
+ else
57
+ system("stty -raw echo") if new?
58
+ puts 'Terminating Pssh.'
59
+ Kernel.exit!
60
+ @active = false
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+
69
+ def clear_environment
70
+ ENV['TMUX'] = nil
71
+ ENV['STY'] = nil
72
+ end
73
+
74
+ def new?
75
+ !existing?
76
+ end
77
+
78
+ def existing?
79
+ @existing_socket
80
+ end
81
+
82
+ def set_command
83
+ case Pssh.command.to_sym
84
+ when :tmux
85
+ if ENV['TMUX']
86
+ @path = ENV['TMUX'].split(',').first
87
+ @existing_socket = true
88
+ @command = "tmux -S #{@path} attach"
89
+ else
90
+ @path = "/tmp/#{Pssh.default_socket_path}"
91
+ @command = "tmux -S #{@path} new"
92
+ end
93
+ @attach_cmd = "tmux -S #{@path} attach"
94
+ when :screen
95
+ if ENV['STY']
96
+ @path = ENV['STY']
97
+ @existing_socket = true
98
+ @command = "screen -S #{@path} -X multiuser on && screen -x #{@path}"
99
+ else
100
+ @path = Pssh.default_socket_path
101
+ @command = "screen -S #{@path}"
102
+ puts @command
103
+ end
104
+ @attach_cmd = "screen -x #{@path}"
105
+ else
106
+ @path = nil
107
+ @command = ENV['SHELL'] || (`which zsh` && 'zsh') || (`which sh` && 'sh') || 'bash'
108
+ end
109
+ end
110
+
111
+ # Public: Writes to the open stream if they have access.
112
+ #
113
+ # Returns nothing.
114
+ def write(data)
115
+ @write.write_nonblock data if Pssh.io_mode['w']
116
+ end
117
+
118
+ # Public: Resizes the PTY session based on all the open
119
+ # windows.
120
+ #
121
+ # Returns nothing.
122
+ def resize!
123
+ winsizes = Pssh.socket.sessions.values.map { |sess| sess[:winsize] }
124
+ winsizes << $stdout.winsize if new?
125
+ y = winsizes.map { |w| w[0] }.min
126
+ x = winsizes.map { |w| w[1] }.min
127
+ @write.winsize = [ y, x ]
128
+ end
129
+
130
+ # Public: Sends a message to the tmux or screen display notifying of a
131
+ # new user that has connected.
132
+ #
133
+ # Returns nothing.
134
+ def send_display_message(user)
135
+ if @existing_socket
136
+ case Pssh.command.to_sym
137
+ when :tmux
138
+ `tmux -S #{@path} display-message "#{user} has connected"`
139
+ when :screen
140
+ `screen -S #{@path} -X wall "#{user} has connected"`
141
+ end
142
+ end
143
+ end
144
+
145
+ # Internal: Store data to the stream so that when a new connection
146
+ # is started we can send all that data and give them the visual.
147
+ #
148
+ # Returns nothing.
149
+ def store(data)
150
+ @stream << data
151
+ @stream = @stream[-Pssh.cache_length..-1] if @stream.length > Pssh.cache_length
152
+ end
153
+
154
+ end
155
+ end
data/lib/pssh/socket.rb CHANGED
@@ -13,36 +13,6 @@ module Pssh
13
13
  @killed_sessions = []
14
14
  @existing_socket = false
15
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
16
  end
47
17
 
48
18
  def params(env)
@@ -60,13 +30,14 @@ module Pssh
60
30
  @sessions[uuid][:active] = true
61
31
  elsif Pssh.open_sessions.keys.include?(uuid)
62
32
  user_string = Pssh.open_sessions[uuid] ? "#{Pssh.open_sessions[uuid]} (#{uuid})" : uuid
63
- print "\n#{user_string} attached.\n#{Pssh.prompt}"
33
+ Pssh.pty.send_display_message user_string
64
34
  @sessions[uuid] = {
65
35
  data: [],
66
36
  username: Pssh.open_sessions[uuid],
67
37
  user_string: user_string,
68
38
  active: true,
69
- started: false
39
+ started: false,
40
+ winsize: []
70
41
  }
71
42
  Pssh.open_sessions.delete uuid
72
43
  else
@@ -82,19 +53,23 @@ module Pssh
82
53
  Thread.new do
83
54
  sleep 10
84
55
  if @sessions[uuid][:active] == false
85
- @sessions[uuid][:read].close
86
- @sessions[uuid][:write].close
87
56
  @sessions.delete uuid
88
57
  print "\n#{user_string} detached.\n#{Pssh.prompt}"
89
58
  end
90
59
  end
91
60
  end
92
61
 
62
+ # Public: Writes the same data to all the open sessions.
63
+ #
64
+ # Returns nothing.
65
+ def write(data)
66
+ @sessions.each do |k,v|
67
+ v[:socket].send_data({ data: data }.to_json)
68
+ end
69
+ end
70
+
93
71
  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]
72
+ self.send_data({ close: true }.to_json)
98
73
  @sessions.delete @uuid
99
74
  super
100
75
  end
@@ -110,77 +85,22 @@ module Pssh
110
85
  @killed_sessions << @uuid
111
86
  end
112
87
 
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
88
  def on_message(env, message)
134
89
  uuid = params(env)['uuid']
135
90
  return unless @sessions[uuid]
136
91
  case message[0]
137
92
  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
93
+ @sessions[uuid][:started] = true
94
+ size = message[1..-1].split ','
95
+ @sessions[uuid][:winsize] = [size[0].to_i, size[1].to_i]
96
+ send_data({ data: Pssh.pty.stream }.to_json)
97
+ Pssh.pty.resize!
179
98
  when 'd'
180
- @sessions[uuid][:write].write_nonblock message[1..-1] if Pssh.io_mode['w']
99
+ Pssh.pty.write message[1..-1]
181
100
  when 'r'
182
101
  size = message[1..-1].split ','
183
- @sessions[uuid][:write].winsize= [size[1].to_i, size[0].to_i]
102
+ @sessions[uuid][:winsize] = [size[0].to_i, size[1].to_i]
103
+ Pssh.pty.resize!
184
104
  end
185
105
  end
186
106
  end
data/lib/pssh/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  module Pssh
2
- VERSION = "0.2.3"
2
+ VERSION = "0.3.3"
3
3
  end
4
4
 
@@ -1,5 +1,5 @@
1
1
  module Pssh
2
- class WebConsole
2
+ class Web
3
3
  def render(view, opts = {})
4
4
  [200, { 'Content-Type' => 'text/html' }, Tilt::HamlTemplate.new("#{Pssh.base_path}/views/#{view}.haml").render(self, opts)]
5
5
  end
data/views/index.haml CHANGED
@@ -1,35 +1,39 @@
1
1
  %html
2
2
  %head
3
3
  %script{:src => "https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js", :type => "application/javascript"}
4
- %script{:src => "assets/wssh.js", :type => "application/javascript"}
4
+ %script{:src => "assets/pssh.js", :type => "application/javascript"}
5
5
  %script{:src => "assets/term.js", :type => "application/javascript"}
6
6
  :javascript
7
- function init() {
7
+ jQuery(function() {
8
+
9
+ // Calculate the width of a character
8
10
  var k = document.createElement('span.monospace');
9
11
  k.innerHTML = '_';
10
12
  k.style.position = 'absolute';
11
13
  document.body.appendChild(k);
12
-
13
- var size =
14
- [
15
- k.offsetWidth,
16
- 13 // k.offsetHeight
14
+ var size = [
15
+ k.offsetWidth,
16
+ 13
17
17
  ];
18
-
19
-
20
18
  document.body.removeChild(k);
21
- console.log(size[0]+' '+size[1]);
19
+
20
+ // Determine the browser width
22
21
  width = parseInt($(window).width() / size[0]);
23
22
  height = parseInt($(window).height() / size[1]);
23
+
24
+ // Set up the socket and the terminal
24
25
  first_time = true;
25
- var client = new WSSHClient('#{unique_id}');
26
+ var client = new PsshClient('#{unique_id}');
26
27
  var term = new Terminal(width, height, function(key) {
27
28
  client.send(key);
28
29
  });
29
30
  term.open();
30
31
  $('.terminal').detach().appendTo('#term');
31
- //term.resize(200, 40);
32
- term.write('Connecting...');
32
+
33
+ // Pretty sloppy usage that when connected sends over
34
+ // the browser size so we can request the right size window.
35
+ // It also handles reconnecting, which could more than likely
36
+ // be baked into the PsshClient.
33
37
  client.connect($.extend({}, {
34
38
  onError: function(error) {
35
39
  term.write('Error: ' + error + "\\r\\n");
@@ -44,26 +48,24 @@
44
48
  onClose: function() {
45
49
  term.destroy();
46
50
  $('.terminal').remove();
47
- //term.write('Connection Reset By Peer');
48
51
  },
49
52
  onTerminate: function() {
50
53
  },
51
54
  onData: function(data) {
52
- console.log(data);
53
55
  term.write(data);
54
56
  }
55
57
  }));
56
58
 
57
- window.onresize = function() {
59
+ // When we resize the window, make sure the terminal changes too.
60
+ $(window).resize( function() {
58
61
  width = parseInt($(window).width() / size[0]);
59
62
  height = parseInt($(window).height() / size[1]);
60
63
  client.resize(width, height);
61
64
  term.resize(width, height);
62
- };
65
+ });
63
66
 
64
- function log(msg) { document.getElementById('log').innerHTML += msg + '<br>'; }
65
- }
67
+ });
66
68
  %link{:href => "assets/bootstrap.css", :rel => "stylesheet"}/
67
69
  %link{:href => "assets/style.css", :rel => "stylesheet"}/
68
- %body{:onload => "init();"}
70
+ %body
69
71
  #term
metadata CHANGED
@@ -1,20 +1,18 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pssh
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
5
- prerelease:
4
+ version: 0.3.3
6
5
  platform: ruby
7
6
  authors:
8
7
  - Kelly Martin
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2013-08-14 00:00:00.000000000 Z
11
+ date: 2013-08-16 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: json
16
15
  requirement: !ruby/object:Gem::Requirement
17
- none: false
18
16
  requirements:
19
17
  - - ~>
20
18
  - !ruby/object:Gem::Version
@@ -22,7 +20,6 @@ dependencies:
22
20
  type: :runtime
23
21
  prerelease: false
24
22
  version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
23
  requirements:
27
24
  - - ~>
28
25
  - !ruby/object:Gem::Version
@@ -30,7 +27,6 @@ dependencies:
30
27
  - !ruby/object:Gem::Dependency
31
28
  name: rack
32
29
  requirement: !ruby/object:Gem::Requirement
33
- none: false
34
30
  requirements:
35
31
  - - ~>
36
32
  - !ruby/object:Gem::Version
@@ -38,7 +34,6 @@ dependencies:
38
34
  type: :runtime
39
35
  prerelease: false
40
36
  version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
37
  requirements:
43
38
  - - ~>
44
39
  - !ruby/object:Gem::Version
@@ -46,7 +41,6 @@ dependencies:
46
41
  - !ruby/object:Gem::Dependency
47
42
  name: thin
48
43
  requirement: !ruby/object:Gem::Requirement
49
- none: false
50
44
  requirements:
51
45
  - - ~>
52
46
  - !ruby/object:Gem::Version
@@ -54,7 +48,6 @@ dependencies:
54
48
  type: :runtime
55
49
  prerelease: false
56
50
  version_requirements: !ruby/object:Gem::Requirement
57
- none: false
58
51
  requirements:
59
52
  - - ~>
60
53
  - !ruby/object:Gem::Version
@@ -62,7 +55,6 @@ dependencies:
62
55
  - !ruby/object:Gem::Dependency
63
56
  name: websocket-rack
64
57
  requirement: !ruby/object:Gem::Requirement
65
- none: false
66
58
  requirements:
67
59
  - - ~>
68
60
  - !ruby/object:Gem::Version
@@ -70,7 +62,6 @@ dependencies:
70
62
  type: :runtime
71
63
  prerelease: false
72
64
  version_requirements: !ruby/object:Gem::Requirement
73
- none: false
74
65
  requirements:
75
66
  - - ~>
76
67
  - !ruby/object:Gem::Version
@@ -78,7 +69,6 @@ dependencies:
78
69
  - !ruby/object:Gem::Dependency
79
70
  name: haml
80
71
  requirement: !ruby/object:Gem::Requirement
81
- none: false
82
72
  requirements:
83
73
  - - ~>
84
74
  - !ruby/object:Gem::Version
@@ -86,7 +76,6 @@ dependencies:
86
76
  type: :runtime
87
77
  prerelease: false
88
78
  version_requirements: !ruby/object:Gem::Requirement
89
- none: false
90
79
  requirements:
91
80
  - - ~>
92
81
  - !ruby/object:Gem::Version
@@ -94,7 +83,6 @@ dependencies:
94
83
  - !ruby/object:Gem::Dependency
95
84
  name: tilt
96
85
  requirement: !ruby/object:Gem::Requirement
97
- none: false
98
86
  requirements:
99
87
  - - ~>
100
88
  - !ruby/object:Gem::Version
@@ -102,7 +90,6 @@ dependencies:
102
90
  type: :runtime
103
91
  prerelease: false
104
92
  version_requirements: !ruby/object:Gem::Requirement
105
- none: false
106
93
  requirements:
107
94
  - - ~>
108
95
  - !ruby/object:Gem::Version
@@ -117,44 +104,45 @@ extra_rdoc_files: []
117
104
  files:
118
105
  - Gemfile
119
106
  - Gemfile.lock
107
+ - LICENSE
120
108
  - README.md
121
109
  - assets/bootstrap.css
110
+ - assets/pssh.js
122
111
  - assets/style.css
123
112
  - assets/term.js
124
- - assets/wssh.js
125
113
  - bin/pssh
126
114
  - lib/pssh.rb
127
115
  - lib/pssh/cli.rb
128
116
  - lib/pssh/client.rb
129
117
  - lib/pssh/console.rb
118
+ - lib/pssh/pty.rb
130
119
  - lib/pssh/socket.rb
131
120
  - lib/pssh/version.rb
132
- - lib/pssh/web_console.rb
121
+ - lib/pssh/web.rb
133
122
  - pssh.gemspec
134
123
  - views/index.haml
135
124
  homepage: http://pssh.me
136
125
  licenses: []
126
+ metadata: {}
137
127
  post_install_message:
138
128
  rdoc_options: []
139
129
  require_paths:
140
130
  - lib
141
131
  required_ruby_version: !ruby/object:Gem::Requirement
142
- none: false
143
132
  requirements:
144
133
  - - ! '>='
145
134
  - !ruby/object:Gem::Version
146
135
  version: '0'
147
136
  required_rubygems_version: !ruby/object:Gem::Requirement
148
- none: false
149
137
  requirements:
150
138
  - - ! '>='
151
139
  - !ruby/object:Gem::Version
152
140
  version: '0'
153
141
  requirements: []
154
142
  rubyforge_project:
155
- rubygems_version: 1.8.25
143
+ rubygems_version: 2.0.7
156
144
  signing_key:
157
- specification_version: 3
145
+ specification_version: 4
158
146
  summary: Pair Programming made user-friendly with your web browser (and Portly helps).
159
147
  test_files: []
160
148
  has_rdoc:
data/assets/wssh.js DELETED
@@ -1,134 +0,0 @@
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
- };