pssh 0.2.3 → 0.3.3

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