Files | Admin

Notes:

Release Name: 0.6.0

Notes:
= Rye - v0.6

Safely run SSH commands on a bunch of machines at the same time (from Ruby).

Rye is similar to Rush[http://rush.heroku.com] but everything happens over SSH (no HTTP daemon) and the default settings are less powerful (for safety). For example, file globs and the "rm" command are disabled so unless otherwise specified, you can't do this: <tt>rbox.rm('-rf', '/etc/**/*')</tt>.

See the examples below (which are taken from bin/try).

== Installation

One of:

    $ sudo gem install rye
    $ sudo gem install delano-rye --source http://gems.github.com/
    $ git clone git://github.com/delano/rye.git


== EXAMPLE 1 -- Basic Usage
    
    rbox = Rye::Box.new('localhost')
    
    # Commands are run as methods on the Rye::Box object
    puts rbox.uptime                   # => 11:02  up 16:01, 3 users
    
    # The response value for all commands is a Rye::Rap object. The rap is a
    # subclass of Array so you can treat it as an Array, but it can also act 
    # like a String if there's only one element. 
    puts rbox.ls('rye.test')           # => ""
    puts rbox.ls('rye.test').stderr    # => ls: rye.test: No such file or directory
    
    puts rbox.touch('rye.test')        # => ""
    puts rbox.rm('rye.test')           # => ""
    
    # You can change directories
    puts rbox.pwd                      # => /home/rye
    puts rbox['/usr/bin'].pwd          # => /usr/bin
    puts rbox.pwd                      # => /usr/bin
    puts rbox.cd('/home/rye').pwd      # => /home/rye
    
    # You can specify environment variables
    rbox.setenv(:RYE, "Forty Creek")
    rbox.env                           # => ['HOME=/home/rye', 'RYE=Forty Creek', ...]
    
    # The commands method returns an Array of available commands:                        
    puts rbox.commands.join(', ')      # => pwd, touch, echo, wc, ...
    
    # When you're done you can disconnect explicitly. 
    # (Although Rye does this automatically at exit.)
    rbox.disconnect


== EXAMPLE 2 -- Disabling Safe-Mode

    rbox_safe = Rye::Box.new('localhost')
    rbox_wild = Rye::Box.new('localhost', :safe => false)

    # Safe-mode is enabled by default. In safe-mode, all command 
    # arguments are thoroughly escaped. This prevents access to
    # environment variables and file globs (among other things).
    p rbox_safe.echo('$HOME')                                    # => "$HOME"
    p rbox_safe['/etc'].ls('host*')  rescue Rye::CommandError    # Doesn't exist
    p rbox_safe.ls('-l | wc -l')     rescue Rye::CommandError    # => '|' is not a valid ls arg

    # Here's the same commands with safe-mode disabled:
    p rbox_wild.echo('$HOME')                   # => "/home/rye"
    p rbox_wild['/etc'].ls('host*')             # => ["hostconfig", "hosts"]
    p rbox_wild.ls('-l | wc -l')                # => 110
    p rbox_wild.echo('$HOME > /tmp/rye-home')   # => 
    p rbox_wild.cat('/tmp/rye-home')            # => "/home/rye"


== EXAMPLE 3 -- Custom Commands

    rbox = Rye::Box.new('localhost')
    rbox.add_keys('/private/key/path')   # Specify additional private keys
    
    # There's currently no rye900 command
    p rbox.commands.member?('rye9000')   # => false
    
    # But we can add our own commands to the Rye::Cmd class. They 
    # automatically become available to all Rye::Box objects.
    module Rye::Cmd
      def rye9000(*args)
        run_command("ls", args)
      end
      def somescript(*args)
        run_command("/path/to/my/script", args)
      end
    end
    
    # We can now run rye9000 (with arguments)
    p rbox.rye9000('-a')                 # => [".", "..", ".bashrc", ...]
    p rbox.commands.member?('rye9000')   # => true


== EXAMPLE 4 -- Accessing Multiple Machines

    rset = Rye::Set.new
    rbox = Rye::Box.new
    
    rset.add_keys('/private/key/path')     # For passwordless logins 
    rset.add_boxes(rbox, 'localhost')      # Add boxes as hostnames or objects
    
    # Calling methods on Rye::Set objects is very similar to calling them on
    # Rye::Box objects. In fact, it's identical:
    p rset.uptime        # => [[14:19:02 up 32 days, 19:35 ...], [14:19:02 up 30 days, 01:35]]
    p rset['/etc'].ls    # => [['file1', 'file2', ...], ['life1', 'life2', ...]]
    
    # Like Rye::Box, the response value is a Rye::Rap object containing the
    # responses from each box. Each response is itself a Rye::Rap object.
    unames = rset.uname
    p unames                               # => [["Darwin"], ["Darwin"]]
    puts unames.class                      # => Rye::Rap
    
    # The Rye::Rap object also keeps a reference to the object that called the 
    # command. In this case, it will keep a reference to Rye::Set:
    puts unames.set.class                  # => Rye::Set
    puts unames.set == rset                # => true
    puts unames.size                       # => 2
    puts unames.first                      # => Darwin
    puts unames.first.class                # => Rye::Rap
    puts unames.first.box.class            # => Rye::Box
    puts unames.first.box == rbox          # => true
    
    # Envrionment variables can be set the same way as with Rye::Box
    rset.setenv(:RYE, "Forty Creek")
    p rset.env.first.select { |env| env =~ /RYE/ }  # => ["RYE=Forty Creek"]


== EXAMPLE 5 -- ERROR HANDLING

    rbox = Rye::Box.new('localhost', :safe => false)

    # Rye follows the standard convention of taking exception to a non-zero 
    # exit code by raising a Rye::CommandError. In this case, rye9000.test
    # is not found by the ls command. 
    begin
      rbox.ls('rye9000.test')
    rescue Rye::CommandError => ex
      puts ex.exit_code                # => 1
      puts ex.stderr                   # => ls: rye.test: No such file or directory
    end

    # The Rye:Rap response objects also give you the STDOUT and STDERR
    # content separately. Here we redirect STDOUT to STDERR, so this
    # will return nothing:
    puts rbox.uname('-a 1>&2').stdout    # =>

    # It all went to STDERR:
    puts rbox.uname('-a 1>&2').stderr    # => Darwin ryehost 9.6.0 ...

    # There were no actual error so the exit code should be 0.
    puts rbox.uname('-a 1>&2').exit_code # => 0

    
== EXAMPLE 6 -- FILE TRANSFERS

    rbox = Rye::Box.new("localhost", :info => true)

    dir_upload = "#{Rye.sysinfo.tmpdir}/rye-upload/"
    dir_download = "#{Rye.sysinfo.tmpdir}/rye-download/"
    
    rbox.upload("#{RYE_HOME}/README.rdoc", 
                "#{RYE_HOME}/LICENSE.txt", dir_upload)

    applejack = StringIO.new("Some in-memory content")
    rbox.upload(applejack, "#{dir_upload}/applejack.txt")

    p rbox.ls(dir_upload)            # => [README.rdoc, LICENSE.txt, applejack.txt]
    p rbox.cat("#{dir_upload}/applejack.txt")   # => "Some in-memory content"

    filecontent = StringIO.new
    rbox.download("#{dir_upload}/applejack.txt", filecontent)

    p filecontent.read

== EXAMPLE 7 -- rye

Rye comes with a command-line utility called <tt>rye</tt>.

    # Prints the paths to your private keys
    $ rye keys

    # Prints the server host keys (suitable for ~/.ssh/known_hosts)
    $ rye hostkey HOST1 HOST2

    # Add your public keys to authorized_keys and authorized_keys2 
    # on a remote machine
    $ rye authorize HOST1 HOST2
More info:

    $ rye -h
    $ rye COMMAND -h
    $ rye show-commands


== About Safe-Mode

In safe-mode:

* You can't use file globs. This means you can't do this: <tt>rbox.ls('*.rb')</tt>. <tt>~</tt> also doesn't work!
* Command arguments cannot contain environment variables (however, environment variables are available to the commands you run). This means you can't do this: <tt>rbox.echo('$HOME')</tt>.
* Pipes and operators don't work: <tt>|, &&, >, <, ||, ~</tt>, etc...
* Backticks don't work either: <tt>procs=`ps aux`</tt>

Why? In safe-mode, all command arguments are escaped which turns all arguments into their literal values. 

Using a Ruby interface to execute shell commands is pretty awesome, particularly to run them on several machines simultaneously. That's a lot of power and it's potentially very dangerous. That's why Rye disables this stuff by default. There's probably a way to do it safely but it's not obvious yet (to me). If you have any ideas, I'd love to hear them!


== Command Whitelist

Rye permits only a limited number of system commands to be run. This default whitelist is defined in Rye::Cmd[http://github.com/delano/rye/blob/master/lib/rye/cmd.rb] but you can add your own commands as you please (see Example 3).

== Dependencies

* OpenSSL[http://www.openssl.org] (The C library)
* Ruby Gems:
  * net-ssh
  * net-scp
  * highline
  * drydock

== Known Issues

This list will grow. If you find one let me know!

* Rye doesn't read the ~/.ssh/config file yet
* Highline 1.5 not working in Ruby 1.9 (password prompts hang)
* Rye uses OpenSSL's ssh-agent (if it exists). Rye starts it up as a child process and shuts it down using at_exit. If you have code in an at_exit that rely's on Rye, make sure your code runs before Rye's at_exit block is called. For example, Drydock uses at_exit too which is why in bin/rye you can see that Drydock is called explicitly so that Rye's at_exit is executed after Drydock executes a command. 

== Thanks

* Solutious Incorporated (http://solutious.com) for all the orange juice.
* http://github.com/adamwiggins/rush
* http://github.com/jamis/capistrano/blob/master/lib/capistrano/shell.rb
* http://www.nofluffjuststuff.com/blog/david_bock/2008/10/ruby_s_closure_cleanup_idiom_and_net_ssh.html
* http://groups.google.com/group/ruby-talk-google/browse_thread/thread/674a6f6de15ceb49?pli=1
* http://paste.lisp.org/display/6912

== More Info

* http://github.com/delano/rye
* http://delano.github.com/rye
* http://www.youtube.com/watch?v=_StUVh6ENuw

== Credits

* Delano Mandelbaum (delano@solutious.com)
* Escape, Copyright (C) 2006,2007 Tanaka Akira  <akr@fsij.org>

== License

See: LICENSE.txt


Changes: RYE, CHANGES TODO * Fingerprints: ssh-keygen -l -f id_rsa_repos.pub #### 0.6.0 (2009-04-28) ############################# * FIXED: handling of Process::Status ($?) in Rye.shell * FIXED: Removed reference to Rudy::Error in bin/rye * ADDED: Rye::Box.getenv * ADDED: Rye::Box.can? * ADDED: Rye::Box.umask= (a similar work around as cd / []) * ADDED: Rye::Box.file_exists? * ADDED: Rye::Box.authorize_keys_remote can now authorize for a different user * ADDED: Rye::Box.upload and Rye::Box.download * ADDED: Rye::SystemInfo.tmpdir * CHANGE: Rye::Box.prep_args doesn't treat multicharacter Symbols as command-line options any more (single character ones are still converted) * CHANGE: Renamed Rye::Box.add_env to Rye::Box.setenv * CHANGE: rm and kill are available in Rye::Cmd by default * CHANGE: Rye::Box.authorize_keys renamed Rye::Box.authorize_keys_remote * CHANGE: The Rye::Rap object now contains the exit code as an integer for Rye.shell and Rye::Box.run_command (SSH) commands. * UPDATED: Rdocs and README #### 0.5.4 (2009-04-22) ############################# * FIXED: Sys is now returning environment paths and home path in JRuby. * ADDED: Better Interrupt handling * ADDED: Rings terminal bell when asks to Continue after HostKey error. * CHANGE: Removed require 'rubygems' #### 0.5.3 (2009-04-20) ############################# * FIXED: Rye::Box.connect raises exceptions instead of exits * ADDED: Rye::Box.connect now rescues HostKeyMismatch exceptions and prompts for a response. #### 0.5.2 (2009-04-19) ############################# * FIXED: authorize-local command attempted to connect via SSH before authorizing. #### 0.5.0 (2009-04-18) ############################# * FIXED: Bug in connect which prevented key-based logins for reconnections * FIXED: Method errors in JRuby * FIXED: Bug in Rye::Set.add_boxes pushing nils into the list of boxes * ADDED: Rye::Box.switch_user * ADDED: Several new commands to Rye::Cmd * ADDED: Rye::Box.authorize_keys_local and "rye authorize-local #### 0.4.3 (2009-04-14) ############################# * FIXED: All Rye::Cmd command methods accept *args to make calling consistent. * ADDED: Rye::Box.missing_method to handle non existent commands #### 0.4.2 (2009-04-13) ############################# * ADDED: More helpful debug output * ADDED: hostname command to Rye::Cmd * ADDED: Rye::Box.connect now supports multiple password attempts if STDIN.tty returns true * ADDED: Rye::Box.interactive_ssh for opening an SSH session to the given box. * CHANGE: Using OpenSSL's ssh-agent but also let's Net::SSH handle the ssh keys. #### 0.4.1 (2009-04-06) ############################# * FIXED: Rye::Box.authorize_keys_remote was not disabling safe mode properly * FIXED: Disabled debug mode. * ADDED: "rye authorize" now specifically enforces the auth method order #### 0.4.0 (2009-04-06) ############################# * FIXED: Box.run_command was parsing arguments incorrectly * FIXED: Box.net_ssh_exec was working on nil stderr * FIXED: bin/try handles the new command exceptions * ADDED: to_s and inspect methods for cleaner debugging output * ADDED: == method for Rye::Box * ADDED: exit code and exit signal to Rye::Rap objects * ADDED: Command switches can now be sent as Symbols (rbox.ls(:h)) * ADDED: Rye.host_keys * ADDED: bin/rye * ADDED: commands now raise a Rye::CommandError exception when the command returns an exit code greater than 0. * CHANGE: Box.add_command renamed to Box.run_command #### 0.3.2 (2009-04-05) ############################# * FIXED: Module.instance_methods bug. In Ruby 1.9 it's Symbols (1.8 was Strings). * FIXED: Rye::Set#add_boxes didn't return self. * UPDATED: Lots of docs tweaks. #### 0.3 (2009-04-05) ############################### * FIXED: Rye::Box wasn't properly adding keypairs to SSH Agent * FIXED: Rye::Box.method_missing Symbol/String ambiguity * ADDED: Rye::Set supports executing commands parallel * ADDED: Rye::Rap now contains STDERR output from command * ADDED: Supports all options provided by Net::SSH#start. This includes support for password logins and proxies. * ADDED: Safe mode can now be disabled (to allow file globs and environment variable access). * ADDED: Basic sanity test * ADDED: Mucho more rdocs and examples. * CHANGE: Moved all SSH key stuff to Rye (used to be done per Box) #### 0.2 (2009-04-04) ############################### * FIXED: ssh-agent shutdown wasn't deleting the SSH tmp directory * ADDED: Now with more rdocs! #### 0.1 (2009-04-03) ############################### Initial public release