I am calling a method so:
Net::SSH.start( value, USER, :password => PASS, :keys => ["/keys/id_rsa"] ) do|ssh|
mess = ssh.sudo "password", "apt-get upgrade"
end
The method is:
class Net::SSH::Connection::Session
def sudo password, command
exec %Q%echo "#{password}" | sudo -S #{command}% do |channel, stream, data|
stdout << data if stream == :stdout
end
return stdout
end
end
Later, I send an email with the mess variable and this works fine.
However, if I want to run through an array of hosts and try to append mess to a string like results << mess, all I get is an empty variable. I don't know why this is.
Related
This is what I got so far. This works great, the problem being I can't input a password for the ssh login, I need to have shared ssh keys in order for this to work:
def ssh_conn(user, host, &block)
begin
ping_output = []
timeout(20) do
ping_output = IO.popen("ssh #{user}##{host} 'echo \"success\"'", "w+")
end
ping = ping_output.readlines.join[/success/] ? true : false
rescue Timeout::Error
ping = false
rescue
ping = false
end
ping_output.close
if block_given? && ping
yield
end
return ping
end
The question here is: How can I do something similar to this, but with password input through the arguments passed to the method? Preferably using ruby native Classes/Methods without installing any "external" gems.
By searching a bit in StackOverflow I've found this thread
and I was able to solve my problem doing this:
def ssh_try(user, host, pass)
puts "SSHing #{host} ..."
Net::SSH.start( host.to_s, user.to_s, :password => pass.to_s ) do |ssh|
puts ssh.exec!('date')
puts "Logging out..."
end
end
Anyone who is facing a similar problem can try this method, works great to test/use ssh connection in ruby.
I believe you cannot do that with ssh itself, but that's what sshpass it's for, as you can read in this serverfault answer. In Ubuntu:
$ sudo apt-get install sshpass
And then change your IO call like this:
ping_output = IO.popen("sshpass -p ssh #{user}##{host} 'echo \"success\"'", "w+")
An alternative would be to rewrite your code to use Ruby SSH client, such as net-ssh, instead of using the system command. This is actually my recommendation, since it'll allow you to work at a higher abstraction level and not deal with system issues. Also, the result looks more beautiful! Check this (untested) code:
require 'net/ssh'
def ssh_conn(user, host, password, &block)
authentication_successful = Net::SSH::Authentication::Session.authenticate(host, user, password)
authentication_successful && (yield if block_given?)
authentication_successful
end
I'm trying to write a ruby script that logs into a remote server, switches to another user, executes a script and answers questions to that script. Right now, I can log in but it hangs on the execution of the bash script. I'm not sure if I got the prompt part right but it's not getting to that point yet. It "hangs" on the running of the script or it just isn't printing the output of the script to the screen.
Here's what I got for now:
require 'rubygems'
require 'net/ssh'
require 'net/ssh/telnet'
s = Net::SSH::Telnet.new("Host" => "server1", "Username" => "dev", "Password" => "12345", "Prompt" => /[$%#>] \z/n)
puts s.cmd("sudo -s")
puts s.cmd("su - user1")
puts s.cmd("/opt/develop/develop-bin/start.sh")
puts s.waitfor(/Prompt/)
Here is the snippet that works for me. It is taken from the bigger library, so treat it as an example. Fill up the variables command, username, password, servername and sudoer and give a try
class AuthenticationError < StandardError; end
AUTH_METHODS = ['hostbased', 'password', 'keyboard-interactive']
MYPROMPT = SecureRandom.hex # or whatever you want
ret = ""
stderr_data = ""
Net::SSH.start(servername, username, :password => password, :auth_methods => AUTH_METHODS) do |ssh|
ssh.open_channel do |channel|
channel.on_data do |channel, data|
ret += data
raise AuthenticationError, "SUDO ACCESS DENIED" if data.inspect.include?('Sorry, try again.') || data.inspect.include?('not in sudoers')
channel.send_data(password+"\n") if data.inspect.include? MYPROMPT
sleep 0.1
end
channel.on_extended_data do |ch, type, data|
stderr_data+=data
end
channel.request_pty
channel.exec("sudo -p #{MYPROMPT} su - #{sudoer} -c '#{command}'")
channel.wait
end
end
puts ret.gsub(/^"/,'').gsub(/"$/,'')
I have a problem that I hope you can help me with
I’m trying to use ruby to ssh onto a machine and run a bash script, this part is fairly easy but the bash script requires me to entry a username and password interactively and this is where I’m stuck
So if I run the script manually I see:-
./run_file.sh
Enter username:
Enter password:
So at the enter Username prompt I have to enter the username etc
I’ve got a simple method I use to make the connection and my idea was to pass in an array made up of
[‘command to run’, ‘username’, ‘password’]
but I don’t know how to extend the Net::SSH call to respond to the prompts
# ssh conectivity method
def command_ssh(host, cmd)
require 'net/ssh'
user = LocalConfig::SSH_DETAILS[:user]
pass = LocalConfig::SSH_DETAILS[:pass]
Net::SSH.start(host, user, :password => pass, :paranoid => false, :auth_methods => ['password'], :timeout => 10 )do |ssh |
output = (ssh.exec!(cmd[0]))
return output
end
end
anyone got any ideas
managed to fix this by using the channel function, here's the method I use now
def connect(host,command)
require 'rubygems'
require 'net/ssh'
user = LocalConfig::SSH_DETAILS[:user]
pass = LocalConfig::SSH_DETAILS[:pass
o = ""
Net::SSH.start(host, user, :password => pass, :paranoid => false, :auth_methods => ['password'], :timeout => 30 )do |ssh |
channel = ssh.open_channel do |ch|
ch.exec(command) do |ch2, success|
ch2.send_data "myUserName\n"
ch2.send_data "mPassWord\n"
ch.on_data do |ch2, data|
o += data
end
end
end
channel.wait
return o.to_s
end
end
I have a function
def run_through_ssh(command)
host = $edumate_server
user = 'user'
pass = '******'
output = Array.new
Net::SSH.start( host, user, :password => pass ) do|ssh|
output = ssh.exec(command)
#output = ssh.exec(command+" 2>&1")
end
return output
end
which executes the command I want correctly on the remote server but the output variable doesn't contain what the remotely executed command outputs to the screen. I use this function inside sinatra and strangely I can see the output on the screen where I run sinatra.
How can I capture the output of remotely executed command?
The output variable contains things like
#<Net::SSH::Connection::Channel:0x3fe40c0 #remote_maximum_window_size=2097152, #
eof=false, #on_open_failed=nil, #remote_window_size=2097152, #closing=true, #pro
perties={}, #local_maximum_packet_size=65536, #on_process=nil, #type="session",
#remote_id=0, #on_confirm_open=#<Proc:0x031f64a0#C:/Ruby187/lib/ruby/gems/1.8/ge
ms/net-ssh-2.2.1/lib/net/ssh/connection/session.rb:320>, #on_request={}, #local_
id=0, #on_extended_data=#<Proc:0x031f6560#C:/Ruby187/lib/ruby/gems/1.8/gems/net-
ssh-2.2.1/lib/net/ssh/connection/session.rb:332>, #local_maximum_window_size=131
072, #on_eof=nil, #connection=#<Net::SSH::Connection::Session:0x3fe42a0 #options
={:logger=>#<Logger:0x400cb90 #level=4, #progname=nil, #logdev=#<Logger::LogDevi
using
ruby 1.8.7 (2010-08-16 patchlevel 302) [i386-mingw32]
net-ssh (2.2.1)
Something like this should work:
Net::SSH.start( host, user, :password => pass ) do |ssh|
output = ssh.exec!(command)
end
output
The exec! method captures stdin/stdout if no block is given. See http://net-ssh.github.com/ssh/v2/api/classes/Net/SSH/Connection/Session.html#M000094
I've got a script thats supposed to mimic ffmpeg on my local machine, by sending the command of to a remote machine, running it there and then returning the results.
(see previous stackoverflow question.)
#!/usr/bin/env ruby
require 'rubygems'
require 'net/ssh'
require 'net/sftp'
require 'highline/import'
file = ARGV[ ARGV.index( '-i' ) + 1] if ARGV.include?( '-i' )
puts 'No input file specified' unless file;
host = "10.0.0.10"
user = "user"
prod = "new-#{file}" # product filename (call it <file>-new)
rpath = "/home/#{user}/.rffmpeg" # remote computer operating directory
rfile = "#{rpath}/#{file}" # remote filename
rprod = "#{rpath}/#{prod}" # remote product
cmd = "ffmpeg -i #{rfile} #{rprod}"# remote command, constructed
pass = ask("Password: ") { |q| q.echo = false } # password from stdin
Net::SSH.start(host, user ) do |ssh|
ssh.sftp.connect do |sftp|
# upload local 'file' to remote 'rfile'
sftp.upload!(file, rfile)
# run remote command 'cmd' to produce 'rprod'
ssh.exec!(cmd)
# download remote 'rprod' to local 'prod'
sftp.download!(rprod, prod)
end
end
now my problem is at
ssh.exec!(cmd)
I want to display the cmd's output to the local user in real-time. But making it
puts ssh.exec!(cmd)
I only get the resulting output after the command has finished running. How would I have to change the code to make this work?
On the display side of your question, you can generate an updating progress bar in Ruby using the "\r" string char. This backs you up to the beginning of the current line allowing you to re-write it. For example:
1.upto(100) { |i| sleep 0.05; print "\rPercent Complete #{i}%"}
Or if you just want a progress bar across the screen you can simply do something similar to this:
1.upto(50) { sleep 0.05; print "|"}
Also, relating to stdout, in addition to flushing output per previous example (STDOUT.flush), you can ask Ruby to automatically sync writes to an IO buffer (in this case STDOUT) with associated device writes (basically turns off internal buffering):
STDOUT.sync = true
Also, I find that sometimes flush doesn't work for me, and I use "IO.fsync" instead. For me that's mostly been related to file system work, but it's worth knowing.
From ri Net::SSH::start:
-------------------------------------------------------- Net::SSH::start
Net::SSH::start(host, user, options={}, &block) {|connection| ...}
------------------------------------------------------------------------
The standard means of starting a new SSH connection. When used with
a block, the connection will be closed when the block terminates,
otherwise the connection will just be returned. The yielded (or
returned) value will be an instance of
Net::SSH::Connection::Session (q.v.). (See also
Net::SSH::Connection::Channel and Net::SSH::Service::Forward.)
Net::SSH.start("host", "user") do |ssh|
ssh.exec! "cp /some/file /another/location"
hostname = ssh.exec!("hostname")
ssh.open_channel do |ch|
ch.exec "sudo -p 'sudo password: ' ls" do |ch, success|
abort "could not execute sudo ls" unless success
ch.on_data do |ch, data|
print data
if data =~ /sudo password: /
ch.send_data("password\n")
end
end
end
end
ssh.loop
end
So it looks like you can get more interactive by using #open_channel
Here's some example code:
user#server% cat echo.rb
#! /usr/local/bin/ruby
def putsf s
puts s
STDOUT.flush
end
putsf "hello"
5.times do
putsf gets.chomp
end
putsf "goodbye"
And on your local machine:
user#local% cat client.rb
#! /usr/local/bin/ruby
require 'rubygems'
require 'net/ssh'
words = %w{ earn more sessions by sleaving }
index = 0;
Net::SSH.start('server', 'user') do |ssh|
ssh.open_channel do |ch|
ch.exec './echo.rb' do |ch, success|
abort "could not execute ./echo.rb" unless success
ch.on_data do |ch, data|
p [:data, data]
index %= words.size
ch.send_data( words[index] + "\n" )
index += 1
end
end
end
end
user#local% ./client.rb
[:data, "hello\n"]
[:data, "earn\n"]
[:data, "more\n"]
[:data, "sessions\n"]
[:data, "by\n"]
[:data, "sleaving\n"]
[:data, "goodbye\n"]
So you can interact with a running process this way.
It's important that the running process flush its output before requesting input - otherwise, the program might hang as the channel may not have received the unflushed output.