This is not a duplicate of How to SSH interactive Session or Net::SSH interactive terminal scripting. How? or Ruby net-ssh interactive response/reply
I'm trying to write an interactive SSH client using Net:SSH, because I'm already using it for run non-interactive commands on the target hosts.
I could just shell out to system "ssh"
but it would require converting the connection settings, proxying, etc to ssh
params.
The problem is streaming the data from STDIN
to the shell channel. The Net::SSH documentation for listen_to
shows how to do it when the input is from a socket instead of STDIN. $stdin
or IO.console
are not sockets and thus not compatible with Net::SSH::BufferedIo
.
Is there a way to create a socket from STDIN
that can be used for this? Or is there a better way to send everything from the STDIN
to the Net::SSH::Channel
until the channel closes?
Here's code that works, but is way too slow to be usable:
require 'net/ssh'
require 'io/console'
require 'io/wait'
Net::SSH.start('localhost', 'root') do |session|
session.open_channel do |channel|
channel.on_data do |_, data|
$stdout.write(data)
end
channel.on_extended_data do |_, data|
$stdout.write(data)
end
channel.request_pty do
channel.send_channel_request "shell"
end
channel.connection.loop do
$stdin.raw do |io|
input = io.readpartial(1024)
channel.send_data(input) unless input.empty?
end
channel.active?
end
end.wait
end
Sockets are really nothing more than file descriptors, and since STDIN is a file descriptor too, it doesn't hurt to try.
What you want however, is to put the TTY into raw mode first to get interactivity.
This code seems to work fine:
begin
system('stty cbreak -echo')
Net::SSH.start(...) do |session|
session.open_channel do |...|
...
session.listen_to(STDIN) { |stdin|
input = stdin.readpartial(1024)
channel.send_data(input) unless input.empty?
}
end.wait
end
ensure
system('stty sane')
end