Ruby SSH (Windows to Linux) | Net SSH

Get Ruby on Windows to SSH into ( and run commands on) plus SCP files (and folders) to and from a Linux server and you’ll have another string on your DevOps bow.

You’ll be able to provision big local servers without resorting to Vagrant and VirtualBox. With just OpenSSH and Docker installed on Linux, you can provision entire service stacks for developers and testers.

Ruby SSH and SCP | How to

The Linux Configuration

Let’s do the Linux config first – it is quick and easy. If you are happy with a password accessible to Ruby then you need only do the first step.

  • install openssh-server if necessary
  • create a key-pair with ssh-keygen -t rsa -C "Key Description"
  • opt for no passphrase as you want to script the connection
  • copy the public key into .ssh/authorized_keys
  • create mandatory permissions chmod 400 ./ssh/authorized_keys
  • copy the private key into a windows local accessible (cloud or flash) drive
  • delete the original keys

The above steps allow you to connect with a key pair which tends to be more robust than the plaintext password.

Ruby Net::SSH | Net::SCP | Windows Config

For Ruby to use public/private key authentication you’ll need to download the public certificate from the makers of the Ruby net-ssh gem (step 2 below).

  • at Windows prompt » gem install net-ssh
  • at Windows prompt » gem install net-scp
  • download https://raw.githubusercontent.com/net-ssh/net-ssh/master/net-ssh-public_cert.pem
  • save the certificate to C:/Users/../net-ssh-public_cert.pem
  • run command » gem cert --add C:/Users/../net-ssh-public_cert.pem

The response from the gem cert --add should be

Added '/CN=netssh/DC=solutious/DC=com'

Ruby Using Net::SSH and Net::SCP

Have you installed both the net-ssh and net-scp gems? The Ruby software below assumes

  • hostname (ip address) » 192.168.0.14
  • linux username apollo
  • linux password p455w0rd


  require 'net/ssh'
  require 'net/scp'

  Net::SSH.start( '192.168.0.14', 'apollo', :password => "p455w0rd") do |ssh|

      # -- -------------------------------------------- --- #
      # -- We have captured the output of this command. --- #
      # -- -------------------------------------------- --- #
      capturedOutput = ssh.exec!("df -h")

      puts
      puts "# --- ----------------------------------------- --- #"
      puts "# --- Disk Report from Server (Captured Output) --- #"
      puts "# --- ----------------------------------------- --- #"
      puts capturedOutput
      puts "# --- ----------------------------------------- --- #"
      puts

      # -- --------------------------------------------------- --- #
      # -- Parallel execution means the last may finish first. --- #
      # -- --------------------------------------------------- --- #
      ssh.exec "for i in {1..5}; do free -g; sleep 2; done"
      ssh.exec "du -shc * | sort -hr"
      ssh.exec "echo; echo @@@@@ Did I Finish Last? @@@@@; echo;"

      ssh.loop

  end



Now note the ssh.exec! command with the exclamation mark. It cpatures the output into your string.

Ruby SSH Parallel Command Execution

The 3 ssh.exec commands are triggered without waiting for each to finish. Look at the output below.

The last command did not finish last!


# --- ----------------------------------------- --- #
# --- Disk Report from Server (Captured Output) --- #
# --- ----------------------------------------- --- #
Filesystem                      Size  Used Avail Use% Mounted on
udev                            5.9G     0  5.9G   0% /dev
tmpfs                           1.2G  9.1M  1.2G   1% /run
/dev/mapper/warehouse--vg-root  905G  8.2G  851G   1% /
tmpfs                           5.9G  4.0K  5.9G   1% /dev/shm
tmpfs                           5.0M     0  5.0M   0% /run/lock
tmpfs                           5.9G     0  5.9G   0% /sys/fs/cgroup
/dev/sda1                       472M  196M  253M  44% /boot
tmpfs                           1.2G     0  1.2G   0% /run/user/1000
# --- ----------------------------------------- --- #


@@@@@ Did I Finish Last? @@@@@

              total        used        free      shared  buff/cache   available
Mem:             11           2           7           0           1           8
Swap:            23           0          23
233M	total
172M	docker.jenkins.plugins
61M	mirror.laundry4j
12K	git-ssh-howto.html
12K	docker-howto.html
8.0K	jenkins-password.html
8.0K	jenkins-howto.html
              total        used        free      shared  buff/cache   available
Mem:             11           2           7           0           1           8
Swap:            23           0          23
              total        used        free      shared  buff/cache   available
Mem:             11           2           7           0           1           8
Swap:            23           0          23
              total        used        free      shared  buff/cache   available
Mem:             11           2           7           0           1           8
Swap:            23           0          23
              total        used        free      shared  buff/cache   available
Mem:             11           2           7           0           1           8
Swap:            23           0          23

Hey – this does not look good – let’s look at what most of us (usually) want.

What We Usually Want | Net::SSH

Most of us just want to run a command and see the output flowing along nicely.

Bad » ssh.exec! gives no output until the end when you can print the captured output – not ideal!
Bad » ssh.exec runs commands in parallel giving us output that is a nightmare!

The ideal strategy is to use just one ssh.exec command along with a ssh.loop command that will wait until your command finishes.


require 'net/ssh'
require 'net/scp'

Net::SSH.start( '192.168.0.14', 'apollo', :password => "p455w0rd") do |ssh|

  # -- ---------------------------------------------------------- --- #
  # -- Only one command printing as it goes with a blocking wait. --- #
  # -- ---------------------------------------------------------- --- #
  ssh.exec "for i in {1..5}; do free -g; sleep 2; done"
  ssh.loop

end

That’s better – it goes in, runs the command, prints the output in real time – then your Ruby program continues when then Net::SSH command is done.

Ruby Net::SCP | Copying Files and Folders

Ruby goes into a server – runs a backup – then downloads the zip. Or Ruby uploads a folder first, then kicks of a command.

Most SSH use cases require SCP-ing (secure copying).


    require 'net/ssh'
    require 'net/scp'

    Net::SCP.start( '192.168.0.14', 'apollo', :password => "p455w0rd") do |scp|

        scp.upload!("c:/Users/../copy.me.inclusive", "/home/apollo/copy.into.here",  :recursive => true)

    end


The :recursive => true makes Net::SCP upload (or download) folder trees – not just files.

Net::SCP | Inclusive and Exclusive Copy

No trailing slash in c:/Users/../copy.me.inclusive will end up with a tree like this at the destination.

     /home/apollo/copy.into.here/copy.me.inclusive/...

A trailing slash at source will produce an “exclusive” copy.

Net::SCP | Blocking Wait

The upload! and download! commands block until the copying is finished.

To fire and forget use the upload and download commands (without the exclamation mark).

Private / Public Key | Ruby Net::SSH

To use public / private key pairs instead of password authentication just change the main Net::SSH line to this.

Net::SSH.start( '192.168.0.14', 'apollo', :keys => [ "c:/Users/../server.private.key.pem" ] ) do |ssh|

The same applies to the Net::SCP command.

Ruby Net::SSH Summary | From Windows to Linux

You’ve used Ruby via SSH to run commands on a Linux server – you can now provision a docker container based continuous integration stack on a local Linux server. No more using Vagrant to create a slow VirtualBox VM loaded with Ansible just to talk SSH.

Cut out the middlemen and use Ruby to talk SSH directly.

Well done – Ruby SSH from Windows to Linux is now part of your ballooning skillset. Lights out and away you go!

Leave a Reply

Your email address will not be published. Required fields are marked *