<commit_before>= Rye - v0.8

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

Inspired by Rush[http://rush.heroku.com] and compatible with Ruby 1.8, 1.9, and JRuby 1.3+!


== Overview

Rye is a Ruby abstraction for executing shell commands via SSH. By default, Rye errs on the side of caution by running in "safe-mode" which specifies a default whitelist of commands and aggressively escapes all command arguments. For example, file globs and the "rm" command are not available in safe-mode, so you can't do this: <tt>rbox.rm('-rf', '/etc/**/*')</tt>.

Rye does not require anything to be installed on the server side (other than an SSH daemon) so it can be run from any machine with Ruby, OpenSSL, and OpenSSH.

=== SSH keys

You need SSH keys to use Rye. After installing Rye, you can check if you have any by running:

    $ rye

If you get the message <i>"The agent has no identities"</i> you will need to generate a keypair.

    $ ssh-keygen


=== Passwordless SSH authorization

The easiest way to work with Rye is to authorize your remote accounts for passwordless logins (otherwise you'll be prompted for a password for every connection). Rye comes with an executable called <tt>rye</tt> to help with this.

Enable passwordless logins to remote HOST1 and HOST2:

    $ rye authorize HOST1 HOST2    

This will copy your public SSH keys to the <tt>~/.ssh/authorized_keys</tt> and <tt>~/.ssh/authorized_keys2</tt> files on the remote machine(s). 

Enable passwordless logins to the local machine:

    $ rye authorize-local

See <tt>rye -h</tt> for more info.


== Example 1 -- Execute commands on a remote machine

Shell commands are executed by calling methods on a Rye::Box object. 
    
    rbox = Rye::Box.new('hostname')
    rbox.pwd                               # => "/<PASSWORD>"
    rbox.uname :a                          # => "Darwin rye-stage 9.7.0 ..."

Method arguments are sent directly as arguments to the shell command. Single-character Symbols are assumed to be single-character switches. <em>e.g. <tt>rbox.uname :a</tt> becomes <tt>uname -a</tt>.</em>
    
The return value for a command is a modified Array containing the contents of STDOUT split by line. It also gives access to STDERR and the exit code

    ret = rbox.uptime                      # => "11:02  up 16:01, 3 users"
    ret.stderr                             # => []
    ret.exit_code                          # => 0
    ret.stdout                             # => "11:02  up 16:01, 3 users"
    ret.stdout.class                       # => Array
    ret.class                              # => Rye::Rap


== Example 2 -- Paths and environment variables

You can change directories.

    rbox.cd '/tmp'
    rbox.pwd                               # => '/tmp'
    rbox['/etc'].ls                        # => ['apache', 'init.d', ...]
    rbox.pwd                               # => '/etc'
    rbox.cd                                # => '/home/rye'
    
You can specify environment variables.

    rbox.setenv('TIPPLE', "Forty Creek")
    rbox.getenv 'TIPPLE'                   # => "Forty Creek"    
    

== Example 3 -- Adding and removing commands

You can add and remove commands to the whitelist.

    rbox.add_command :anything, '/path/2/anything'
    rbox.anything                       
    rbox.remove_command :anything
    rbox.anything                          # => Rye::CommandNotFound exception


== Example 4 -- Disabling Safe Mode

Safe mode can be disabled on one of the following ways.

    rbox = Rye::Box.new 'HOST', :safe => false
      OR
    rbox.disable_safe_mode
  
When safe-mode is disabled, you can run any command (regardless of what is defined in the whitelist) with any valid arguments (fileglobs, tildas, etc...).

    rbox.kill '-SIGHUP', 1928111
    rbox.rm 'path/2/*'
    
You can also execute any valid shell command.

    rbox.execute 'ps aux | grep ruby > /tmp/ruby-process-list'

See the "About Safe Mode" section below for more information.
 

== Example 5a -- Accessing Multiple Machines

Shell commands can be executed on multiple machines using a Rye::Set object. Create a "set" of machines.

    rbox = Rye::Box.new 'HOST1
    rset = Rye::Set.new
    rset.add_boxes(rbox, 'HOST2')          # Add boxes as hostnames or objects
    
Then call methods just like with Rye::Box, except now the return value is an Array of Arrays. The order of return values matches the order the machines were added to the set.
    
    rset.hostname                          # => [["HOST1"], ["HOST2"]]
    rset.uname                             # => [["Darwin"], ["Linux"]]
    rset.
    
== Example 5b -- Accessing Multiple Machines in Parallel
   
By default, Rye::Set connects to each machine sequentially in the order they were added to the set. Commands can also be run in parallel.

    rset = Rye::Set.new "SETNAME", :parallel => true
      OR
    rset.parallel = true
   
   
== Example 6 -- File Transfers

    rbox = Rye::Box.new "localhost"

    rbox.file_upload "README.rdoc", "/tmp" 

    applejack = StringIO.new "Some in-memory content"
    rbox.file_upload applejack, "/tmp/applejack.txt"

    rbox.ls "/tmp/"                 # => [README.rdoc, applejack.txt]
    rbox.cat "/tmp/applejack.txt"   # => "Some in-memory content"

    filecontent = StringIO.new
    rbox.file_download "/tmp/applejack.txt", filecontent

    filecontent.read                # => "Some in-memory content"


== Example 7 -- Local processes

For local processes, you can bypass <tt>Rye::Box</tt> and execute commands directly with <tt>Rye.shell</tt>:

    Rye.shell :uptime    # => 11:02  up 16:01, 3 users


The first argument must be the command name and the remaining arguments are sent directly as arguments to the command. They're not escaped like with <tt>Rye::Box</tt> so you can use the asterisk, environment variables, pipes, and redirects etc. Also note that you can specify single character switches as symbols and you can separate arguments or put them into a single String.

    Rye.shell :ls, '*'
    Rye.shell :ls, '-l $HOME'
    Rye.shell :ls, :l, '$HOME > $TMPDIR/crazy.txt'

The return value is a Rye::Rap object (just like with Rye::Box) so you have access to the exit code and STDERR output:

    ret = Rye.shell :ls, 'nofile'
    ret.exit_code        # => 1
    ret.stderr           # => "sh: nofile: No such file or directory"
    ret.class            # => Rye::Rap



== 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!
* You can't use environment variables as arguments. This means you can't do this: <tt>rbox.echo('$HOME')</tt>. However, environment variables are available to the commands you run.
* 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

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


== Installation

Via Rubygems, one of:

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

or via download:

* rye-latest.tar.gz[http://github.com/delano/rye/tarball/latest]
* rye-latest.zip[http://github.com/delano/rye/zipball/latest]



== Known Issues

* Rye doesn't read the ~/.ssh/config file yet
* Rye uses OpenSSH'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. 
* No support for STDIN for commands.
* Limited support for interactive shells. 

If you find one let me know!


== Thanks

* <NAME> (http://rely.ca)
* Rush[http://github.com/adamwiggins/rush] and Capistrano[http://github.com/jamis/capistrano/blob/master/lib/capistrano/shell.rb] for the inspiration.
* Mike Cline for giving the okay to use the Rye name. 


== More Info

* Codes[http://github.com/delano/rye]
* Rdocs[http://delano.github.com/rye]
* Inspiration[http://www.youtube.com/watch?v=_StUVh6ENuw]


== Related Projects

* Rush[http://github.com/adamwiggins/rush]


== Credits

* Delano <EMAIL>)
* Escape, Copyright (C) 2006,2007 <NAME> <<EMAIL>>
* Rye::Box#instance_exec (for Ruby 1.8) <NAME>


== License

See: LICENSE.txt<commit_msg>Fixed syntax error in README<commit_after>= Rye - v0.8

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

Inspired by Rush[http://rush.heroku.com] and compatible with Ruby 1.8, 1.9, and JRuby 1.3+!


== Overview

Rye is a Ruby abstraction for executing shell commands via SSH. By default, Rye errs on the side of caution by running in "safe-mode" which specifies a default whitelist of commands and aggressively escapes all command arguments. For example, file globs and the "rm" command are not available in safe-mode, so you can't do this: <tt>rbox.rm('-rf', '/etc/**/*')</tt>.

Rye does not require anything to be installed on the server side (other than an SSH daemon) so it can be run from any machine with Ruby, OpenSSL, and OpenSSH.

=== SSH keys

You need SSH keys to use Rye. After installing Rye, you can check if you have any by running:

    $ rye

If you get the message <i>"The agent has no identities"</i> you will need to generate a keypair.

    $ ssh-keygen


=== Passwordless SSH authorization

The easiest way to work with Rye is to authorize your remote accounts for passwordless logins (otherwise you'll be prompted for a password for every connection). 

Enable passwordless logins to remote HOST1 and HOST2:

    $ rye authorize HOST1 HOST2    

This will copy your public SSH keys to the <tt>~/.ssh/authorized_keys</tt> and <tt>~/.ssh/authorized_keys2</tt> files on the remote machine(s). 

Enable passwordless logins to the local machine:

    $ rye authorize-local

See <tt>rye -h</tt> for more info.


== Example 1 -- Execute commands on a remote machine

Shell commands are executed by calling methods on a Rye::Box object. 
    
    rbox = Rye::Box.new('hostname')
    rbox.pwd                               # => <PASSWORD>"
    rbox.uname :a                          # => "Darwin rye-stage 9.7.0 ..."

Method arguments are sent directly as arguments to the shell command. Single-character Symbols are assumed to be single-character switches. <em>e.g. <tt>rbox.uname :a</tt> becomes <tt>uname -a</tt>.</em>
    
The return value for a command is a modified Array containing the contents of STDOUT split by line. It also gives access to STDERR and the exit code

    ret = rbox.uptime                      # => "11:02  up 16:01, 3 users"
    ret.stderr                             # => []
    ret.exit_code                          # => 0
    ret.stdout                             # => "11:02  up 16:01, 3 users"
    ret.stdout.class                       # => Array
    ret.class                              # => Rye::Rap


== Example 2 -- Paths and environment variables

You can change directories.

    rbox.cd '/tmp'
    rbox.pwd                               # => '/tmp'
    rbox['/etc'].ls                        # => ['apache', 'init.d', ...]
    rbox.pwd                               # => '/etc'
    rbox.cd                                # => '/home/rye'
    
You can specify environment variables.

    rbox.setenv('TIPPLE', "Forty Creek")
    rbox.getenv 'TIPPLE'                   # => "Forty Creek"    
    

== Example 3 -- Adding and removing commands

You can add and remove commands to the whitelist.

    rbox.add_command :anything, '/path/2/anything'
    rbox.anything                       
    rbox.remove_command :anything
    rbox.anything                          # => Rye::CommandNotFound exception


== Example 4 -- Disabling Safe Mode

Safe mode can be disabled on one of the following ways.

    rbox = Rye::Box.new 'HOST', :safe => false
      OR
    rbox.disable_safe_mode
  
When safe-mode is disabled, you can run any command (regardless of what is defined in the whitelist) with any valid arguments (fileglobs, tildas, etc...).

    rbox.kill '-SIGHUP', 1928111
    rbox.rm 'path/2/*'
    
You can also execute any valid shell command.

    rbox.execute 'ps aux | grep ruby > /tmp/ruby-process-list'

See the "About Safe Mode" section below for more information.
 

== Example 5a -- Accessing Multiple Machines

Shell commands can be executed on multiple machines using a Rye::Set object. Create a "set" of machines.

    rbox = Rye::Box.new 'HOST1'
    rset = Rye::Set.new
    rset.add_boxes rbox, 'HOST2'           # Add boxes as hostnames or objects
    
Then call methods just like with Rye::Box, except now the return value is an Array of Arrays. The order of return values matches the order the machines were added to the set.
    
    rset.hostname                          # => [["HOST1"], ["HOST2"]]
    rset.uname                             # => [["Darwin"], ["Linux"]]
    
== Example 5b -- Accessing Multiple Machines in Parallel
   
By default, Rye::Set connects to each machine sequentially in the order they were added to the set. Commands can also be run in parallel.

    rset = Rye::Set.new "SETNAME", :parallel => true
      OR
    rset.parallel = true
   
   
== Example 6 -- File Transfers

    rbox = Rye::Box.new "localhost"

    rbox.file_upload "README.rdoc", "/tmp" 

    applejack = StringIO.new "Some in-memory content"
    rbox.file_upload applejack, "/tmp/applejack.txt"

    rbox.ls "/tmp/"                 # => [README.rdoc, applejack.txt]
    rbox.cat "/tmp/applejack.txt"   # => "Some in-memory content"

    filecontent = StringIO.new
    rbox.file_download "/tmp/applejack.txt", filecontent

    filecontent.read                # => "Some in-memory content"


== Example 7 -- Local processes

For local processes, you can bypass <tt>Rye::Box</tt> and execute commands directly with <tt>Rye.shell</tt>:

    Rye.shell :uptime    # => 11:02  up 16:01, 3 users


The first argument must be the command name and the remaining arguments are sent directly as arguments to the command. They're not escaped like with <tt>Rye::Box</tt> so you can use the asterisk, environment variables, pipes, and redirects etc. Also note that you can specify single character switches as symbols and you can separate arguments or put them into a single String.

    Rye.shell :ls, '*'
    Rye.shell :ls, '-l $HOME'
    Rye.shell :ls, :l, '$HOME > $TMPDIR/crazy.txt'

The return value is a Rye::Rap object (just like with Rye::Box) so you have access to the exit code and STDERR output:

    ret = Rye.shell :ls, 'nofile'
    ret.exit_code        # => 1
    ret.stderr           # => "sh: nofile: No such file or directory"
    ret.class            # => Rye::Rap



== 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!
* You can't use environment variables as arguments. This means you can't do this: <tt>rbox.echo('$HOME')</tt>. However, environment variables are available to the commands you run.
* 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

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


== Installation

Via Rubygems, one of:

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

or via download:

* rye-latest.tar.gz[http://github.com/delano/rye/tarball/latest]
* rye-latest.zip[http://github.com/delano/rye/zipball/latest]



== Known Issues

* Rye doesn't read the ~/.ssh/config file yet
* Rye uses OpenSSH'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. 
* No support for STDIN for commands.
* Limited support for interactive shells. 

If you find one let me know!


== Thanks

* <NAME> (http://rely.ca)
* Rush[http://github.com/adamwiggins/rush] and Capistrano[http://github.com/jamis/capistrano/blob/master/lib/capistrano/shell.rb] for the inspiration.
* Mike Cline for giving the okay to use the Rye name. 


== More Info

* Codes[http://github.com/delano/rye]
* Rdocs[http://delano.github.com/rye]
* Inspiration[http://www.youtube.com/watch?v=_StUVh6ENuw]


== Related Projects

* Rush[http://github.com/adamwiggins/rush]


== Credits

* Delano (@solutious.com)
* Escape, Copyright (C) 2006,2007 <NAME> <<EMAIL>>
* Rye::Box#instance_exec (for Ruby 1.8) <NAME>


== License

See: LICENSE.txt
{'fraction_non_alphanumeric': 0.10399222084166171, 'fraction_numerical': 0.008427421533142456, 'mean_word_length': 2.710563239126077, 'pattern_counts': {'":': 0, '<': 88, '<?xml version=': 0, '>': 142, 'https://': 0, 'lorem ipsum': 0, 'www.': 6, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 12, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1005458', 'n_tokens_mistral': 5947, 'n_tokens_neox': 5528, 'n_words': 2204}