rye 0.3.2 → 0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES.txt +20 -0
- data/README.rdoc +68 -22
- data/bin/rye +131 -0
- data/bin/try +40 -17
- data/lib/rye.rb +150 -13
- data/lib/rye/box.rb +141 -125
- data/lib/rye/cmd.rb +23 -0
- data/lib/rye/key.rb +134 -0
- data/lib/rye/rap.rb +9 -0
- data/lib/rye/set.rb +9 -0
- data/rye.gemspec +14 -3
- data/try/copying.rb +19 -0
- data/try/keys.rb +139 -0
- data/tst/10-key1 +27 -0
- data/tst/10-key1.pub +1 -0
- data/tst/10-key2 +30 -0
- data/tst/10-key2.pub +1 -0
- data/tst/10_keys_test.rb +88 -0
- data/{test/10_rye_test.rb → tst/50_rye_test.rb} +11 -20
- metadata +34 -5
data/CHANGES.txt
CHANGED
@@ -1,5 +1,25 @@
|
|
1
1
|
RYE, CHANGES
|
2
2
|
|
3
|
+
TODO
|
4
|
+
|
5
|
+
* Fingerprints: ssh-keygen -l -f id_rsa_repos.pub
|
6
|
+
|
7
|
+
|
8
|
+
#### 0.4.0 (2009-04-06) #############################
|
9
|
+
|
10
|
+
* ADDED: to_s and inspect methods for cleaner debugging output
|
11
|
+
* ADDED: == method for Rye::Box
|
12
|
+
* ADDED: exit code and exit signal to Rye::Rap objects
|
13
|
+
* ADDED: commands now raise a Rye::CommandError exception
|
14
|
+
when the command returns an exit code greater than 0.
|
15
|
+
* CHANGED: Box.add_command renamed to Box.run_command
|
16
|
+
* FIXED: Box.run_command was parsing arguments incorrectly
|
17
|
+
* FIXED: Box.net_ssh_exec was working on nil stderr
|
18
|
+
* FIXED: bin/try handles the new command exceptions
|
19
|
+
* ADDED: Command switches can now be sent as Symbols (rbox.ls(:h))
|
20
|
+
* ADDED: Rye.host_keys
|
21
|
+
* ADDED: bin/rye
|
22
|
+
|
3
23
|
|
4
24
|
#### 0.3.2 (2009-04-05) #############################
|
5
25
|
|
data/README.rdoc
CHANGED
@@ -1,10 +1,10 @@
|
|
1
|
-
= Rye - v0.
|
1
|
+
= Rye - v0.4
|
2
2
|
|
3
3
|
Safely run remote commands via SSH in Ruby.
|
4
4
|
|
5
5
|
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 are disabled so unless otherwise specified, you can't do this: <tt>rbox.rm('-rf', '/etc/**/*')</tt>.
|
6
6
|
|
7
|
-
See bin/try
|
7
|
+
See the examples below (which are taken from bin/try).
|
8
8
|
|
9
9
|
== Installation
|
10
10
|
|
@@ -20,7 +20,7 @@ One of:
|
|
20
20
|
rbox = Rye::Box.new('localhost')
|
21
21
|
|
22
22
|
# Commands are run as methods on the Rye::Box object
|
23
|
-
puts rbox.uptime
|
23
|
+
puts rbox.uptime # => 11:02 up 16:01, 3 users
|
24
24
|
|
25
25
|
# The response value for all commands is a Rye::Rap object. The rap is a
|
26
26
|
# subclass of Array so you can treat it as an Array, but it can also act
|
@@ -57,12 +57,9 @@ One of:
|
|
57
57
|
# Safe-mode is enabled by default. In safe-mode, all command
|
58
58
|
# arguments are thoroughly escaped. This prevents access to
|
59
59
|
# environment variables and file globs (among other things).
|
60
|
-
p rbox_safe.echo('$HOME')
|
61
|
-
p rbox_safe['/etc'].ls('host*')
|
62
|
-
p rbox_safe.ls('-l | wc -l')
|
63
|
-
p rbox_safe.echo('$HOME > /tmp/rye-home') # => "$HOME > /tmp/home"
|
64
|
-
p rbox_safe.cat('/tmp/rye-home') # =>
|
65
|
-
p rbox_safe.cat('/tmp/rye-home').stderr # => "No such file or directory"
|
60
|
+
p rbox_safe.echo('$HOME') # => "$HOME"
|
61
|
+
p rbox_safe['/etc'].ls('host*') rescue Rye::CommandError # Doesn't exist
|
62
|
+
p rbox_safe.ls('-l | wc -l') rescue Rye::CommandError # => '|' is not a valid ls arg
|
66
63
|
|
67
64
|
# Here's the same commands with safe-mode disabled:
|
68
65
|
p rbox_wild.echo('$HOME') # => "/home/rye"
|
@@ -70,8 +67,6 @@ One of:
|
|
70
67
|
p rbox_wild.ls('-l | wc -l') # => 110
|
71
68
|
p rbox_wild.echo('$HOME > /tmp/rye-home') # =>
|
72
69
|
p rbox_wild.cat('/tmp/rye-home') # => "/home/rye"
|
73
|
-
p rbox_wild.rm('/tmp/rye-home') # =>
|
74
|
-
|
75
70
|
|
76
71
|
|
77
72
|
== EXAMPLE 3 -- Custom Commands
|
@@ -86,10 +81,10 @@ One of:
|
|
86
81
|
# automatically become available to all Rye::Box objects.
|
87
82
|
module Rye::Cmd
|
88
83
|
def rye9000(*args)
|
89
|
-
|
84
|
+
run_command("ls", args)
|
90
85
|
end
|
91
86
|
def somescript(*args)
|
92
|
-
|
87
|
+
run_command("/path/to/my/script", args)
|
93
88
|
end
|
94
89
|
end
|
95
90
|
|
@@ -132,6 +127,52 @@ One of:
|
|
132
127
|
p rset.env.first.select { |env| env =~ /RYE/ } # => ["RYE=Forty Creek"]
|
133
128
|
|
134
129
|
|
130
|
+
== EXAMPLE 5 -- ERROR HANDLING
|
131
|
+
|
132
|
+
rbox = Rye::Box.new('localhost', :safe => false)
|
133
|
+
|
134
|
+
# Rye follows the standard convention of taking exception to a non-zero
|
135
|
+
# exit code by raising a Rye::CommandError. In this case, rye9000.test
|
136
|
+
# is not found by the ls command.
|
137
|
+
begin
|
138
|
+
rbox.ls('rye9000.test')
|
139
|
+
rescue Rye::CommandError => ex
|
140
|
+
puts ex.exit_code # => 1
|
141
|
+
puts ex.stderr # => ls: rye.test: No such file or directory
|
142
|
+
end
|
143
|
+
|
144
|
+
# The Rye:Rap response objects also give you the STDOUT and STDERR
|
145
|
+
# content separately. Here we redirect STDOUT to STDERR, so this
|
146
|
+
# will return nothing:
|
147
|
+
puts rbox.uname('-a 1>&2').stdout # =>
|
148
|
+
|
149
|
+
# It all went to STDERR:
|
150
|
+
puts rbox.uname('-a 1>&2').stderr # => Darwin ryehost 9.6.0 ...
|
151
|
+
|
152
|
+
# There were no actual error so the exit code should be 0.
|
153
|
+
puts rbox.uname('-a 1>&2').exit_code # => 0
|
154
|
+
|
155
|
+
== EXAMPLE 6 -- rye
|
156
|
+
|
157
|
+
Rye comes with a command-line utility called <tt>rye</tt>.
|
158
|
+
|
159
|
+
# Prints the paths to your private keys
|
160
|
+
$ rye keys
|
161
|
+
|
162
|
+
# Prints the server host keys (suitable for ~/.ssh/known_hosts)
|
163
|
+
$ rye hostkey HOST1 HOST2
|
164
|
+
|
165
|
+
# Add your public keys to authorized_keys and authorized_keys2
|
166
|
+
# on a remote machine
|
167
|
+
$ rye authorize HOST1 HOST2
|
168
|
+
|
169
|
+
More info:
|
170
|
+
|
171
|
+
$ rye -h
|
172
|
+
$ rye COMMAND -h
|
173
|
+
$ rye show-commands
|
174
|
+
|
175
|
+
|
135
176
|
== About Safe-Mode
|
136
177
|
|
137
178
|
In safe-mode:
|
@@ -148,21 +189,26 @@ Using a Ruby interface to execute shell commands is pretty awesome, particularly
|
|
148
189
|
|
149
190
|
== Command Whitelist
|
150
191
|
|
151
|
-
Rye permits only a limited number of system commands to be run. This default whitelist is defined in Rye::Cmd but you can add your own commands as you please (see Example
|
152
|
-
|
153
|
-
== Credits
|
192
|
+
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).
|
154
193
|
|
155
|
-
|
194
|
+
== Dependencies
|
156
195
|
|
196
|
+
* OpenSSL[http://www.openssl.org] (The C library)
|
197
|
+
* Ruby Gems:
|
198
|
+
* net-ssh
|
199
|
+
* net-scp
|
200
|
+
* highline
|
201
|
+
* drydock
|
157
202
|
|
158
|
-
==
|
203
|
+
== Known Issues
|
159
204
|
|
160
|
-
|
161
|
-
* The country of Canada for making Rye Whisky.
|
205
|
+
This list will grow. If you find one let me know!
|
162
206
|
|
207
|
+
* Rye doesn't read the ~/.ssh/config file yet
|
163
208
|
|
164
|
-
==
|
209
|
+
== Thanks
|
165
210
|
|
211
|
+
* Solutious Incorporated (http://solutious.com) for all the orange juice.
|
166
212
|
* http://github.com/adamwiggins/rush
|
167
213
|
* http://github.com/jamis/capistrano/blob/master/lib/capistrano/shell.rb
|
168
214
|
* http://www.nofluffjuststuff.com/blog/david_bock/2008/10/ruby_s_closure_cleanup_idiom_and_net_ssh.html
|
@@ -178,7 +224,7 @@ Rye permits only a limited number of system commands to be run. This default whi
|
|
178
224
|
== Credits
|
179
225
|
|
180
226
|
* Delano Mandelbaum (delano@solutious.com)
|
181
|
-
|
227
|
+
* Escape, Copyright (C) 2006,2007 Tanaka Akira <akr@fsij.org>
|
182
228
|
|
183
229
|
== License
|
184
230
|
|
data/bin/rye
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
# Rye -- A CLI for some handy SSH tools
|
4
|
+
#
|
5
|
+
# If your reading this via the rdocs you won't be able to see the code
|
6
|
+
# See: http://github.com/delano/rye/blob/master/bin/rye
|
7
|
+
#
|
8
|
+
# Usage: rye
|
9
|
+
#
|
10
|
+
|
11
|
+
$:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'stringio'
|
15
|
+
require 'yaml'
|
16
|
+
require 'drydock'
|
17
|
+
require 'rye'
|
18
|
+
|
19
|
+
include Drydock
|
20
|
+
|
21
|
+
global :p, :path, String, "A directory containing SSH private keys or the path to a private key"
|
22
|
+
|
23
|
+
before do |obj|
|
24
|
+
STDERR.puts
|
25
|
+
# Load private keys if specified
|
26
|
+
if obj.global.path
|
27
|
+
keys = Rye.find_private_keys(obj.global.path)
|
28
|
+
Rye.add_keys(keys)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
after do
|
33
|
+
STDERR.puts
|
34
|
+
end
|
35
|
+
|
36
|
+
desc "Add your public keys to one or more remote machines"
|
37
|
+
option :u, :user, String, "Username"
|
38
|
+
argv :hostname
|
39
|
+
command :authorize do |obj|
|
40
|
+
raise "You must specify a host" unless obj.argv.hostname
|
41
|
+
|
42
|
+
highline = HighLine.new # Used for password prompt
|
43
|
+
|
44
|
+
opts = { :debug => nil }
|
45
|
+
opts[:user] = obj.option.user if obj.option.user
|
46
|
+
|
47
|
+
obj.argv.each do |hostname|
|
48
|
+
puts "Authorizing #{hostname}"
|
49
|
+
|
50
|
+
retried = 0
|
51
|
+
begin
|
52
|
+
rbox = Rye::Box.new(hostname, opts).connect
|
53
|
+
|
54
|
+
# We know we're already authorized b/c we didn't have to give a password
|
55
|
+
if retried == 0
|
56
|
+
puts "%s is already authorized" % rbox.opts[:user]
|
57
|
+
puts
|
58
|
+
next
|
59
|
+
end
|
60
|
+
|
61
|
+
# An authentication failure means either the requested user doesn't
|
62
|
+
# exist on the remote machine or we need to supply a password.
|
63
|
+
rescue Net::SSH::AuthenticationFailed => ex
|
64
|
+
retried += 1
|
65
|
+
opts[:password] = highline.ask("Password: ") { |q| q.echo = '' }
|
66
|
+
retry unless retried > 3
|
67
|
+
rescue Net::SSH::AuthenticationFailed
|
68
|
+
STDERR.puts "Authentication failed."
|
69
|
+
exit 1
|
70
|
+
end
|
71
|
+
|
72
|
+
puts "Added public keys for: ", rbox.authorize_keys
|
73
|
+
|
74
|
+
puts
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
command_alias :authorize, :authorise
|
79
|
+
|
80
|
+
|
81
|
+
desc "Generate a public key from a private key"
|
82
|
+
|
83
|
+
|
84
|
+
|
85
|
+
desc "Fetch the host keys for remote machines (suitable for your ~/.ssh/known_hosts file)"
|
86
|
+
usage "rye hostkey HOSTNAME [HOSTNAME2 HOSTNAME3 ...]"
|
87
|
+
argv :hostname
|
88
|
+
command :hostkeys do |obj|
|
89
|
+
raise "You must specify a host" unless obj.argv.hostname
|
90
|
+
ret = Rye.remote_host_keys(obj.argv.hostname)
|
91
|
+
STDERR.puts $/, ret.stderr, $/
|
92
|
+
puts ret.stdout
|
93
|
+
end
|
94
|
+
command_alias :hostkeys, :hostkey
|
95
|
+
|
96
|
+
|
97
|
+
desc "Display your private keys"
|
98
|
+
command :keys do |obj|
|
99
|
+
Rye.keys.each do |key|
|
100
|
+
puts key.join(' ')
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
desc "Display your public SSH keys"
|
105
|
+
usage "rye pubkeys [--pem] [KEYPATH]"
|
106
|
+
option :p, :pem, "Output in PEM format"
|
107
|
+
argv :keypath
|
108
|
+
command :pubkeys do |obj|
|
109
|
+
keys = obj.argv.empty? ? Rye.keys.collect { |k| k[2] } : obj.argv
|
110
|
+
keys.each do |path|
|
111
|
+
STDERR.puts "# Public SSH key for #{path}"
|
112
|
+
k = Rye::Key.from_file(path)
|
113
|
+
puts obj.option.pem ? k.public_key.to_pem : k.public_key.to_ssh2
|
114
|
+
end
|
115
|
+
end
|
116
|
+
command_alias :pubkeys, :pubkey
|
117
|
+
|
118
|
+
|
119
|
+
default :keys
|
120
|
+
debug :on
|
121
|
+
|
122
|
+
# We can Drydock specifically otherwise it will run at_exit. Rye also
|
123
|
+
# uses at_exit for shutting down the ssh-agent. Ruby executes at_exit
|
124
|
+
# blocks in reverse order so if Drydock is required first, it's block
|
125
|
+
# will run after Rye shuts down the ssh-agent.
|
126
|
+
begin
|
127
|
+
Drydock.run!(ARGV, STDIN) if Drydock.run? && !Drydock.has_run?
|
128
|
+
rescue => ex
|
129
|
+
STDERR.puts "ERROR (#{ex.class.to_s}): #{ex.message}"
|
130
|
+
STDERR.puts ex.backtrace if Drydock.debug?
|
131
|
+
end
|
data/bin/try
CHANGED
@@ -24,22 +24,18 @@ puts %Q(
|
|
24
24
|
rbox = Rye::Box.new('localhost')
|
25
25
|
|
26
26
|
# Commands are run as methods on the Rye::Box object
|
27
|
-
puts rbox.uptime
|
27
|
+
puts rbox.uptime # => 11:02 up 16:01, 3 users
|
28
28
|
|
29
29
|
# The response value for all commands is a Rye::Rap object. The rap is a
|
30
30
|
# subclass of Array so you can treat it as an Array, but it can also act
|
31
31
|
# like a String if there's only one element.
|
32
|
-
|
33
|
-
puts rbox.ls('rye.test').stderr # => ls: rye.test: No such file or directory
|
32
|
+
p rbox.ls(:a, '/') # => ['.', '..', 'bin', 'etc', ...]
|
34
33
|
|
35
|
-
puts rbox.touch('rye.test') # => ""
|
36
|
-
puts rbox.rm('rye.test') # => ""
|
37
|
-
|
38
34
|
# You can change directories
|
39
35
|
puts rbox.pwd # => /home/rye
|
40
36
|
puts rbox['/usr/bin'].pwd # => /usr/bin
|
41
37
|
puts rbox.pwd # => /usr/bin
|
42
|
-
puts rbox.cd(
|
38
|
+
puts rbox.cd(Rye.sysinfo.home).pwd # => /home/rye
|
43
39
|
|
44
40
|
# You can specify environment variables
|
45
41
|
rbox.add_env(:RYE, "Forty Creek")
|
@@ -64,12 +60,9 @@ rbox_wild = Rye::Box.new('localhost', :safe => false)
|
|
64
60
|
# Safe-mode is enabled by default. In safe-mode, all command
|
65
61
|
# arguments are thoroughly escaped. This prevents access to
|
66
62
|
# environment variables and file globs (among other things).
|
67
|
-
p rbox_safe.echo('$HOME')
|
68
|
-
p rbox_safe['/etc'].ls('host*')
|
69
|
-
p rbox_safe.ls('-l | wc -l')
|
70
|
-
p rbox_safe.echo('$HOME > /tmp/rye-home') # => "$HOME > /tmp/home"
|
71
|
-
p rbox_safe.cat('/tmp/rye-home') # =>
|
72
|
-
p rbox_safe.cat('/tmp/rye-home').stderr # => "No such file or directory"
|
63
|
+
p rbox_safe.echo('$HOME') # => "$HOME"
|
64
|
+
p rbox_safe['/etc'].ls('host*') rescue Rye::CommandError # Doesn't exist
|
65
|
+
p rbox_safe.ls('-l | wc -l') rescue Rye::CommandError # => '|' is not a valid ls arg
|
73
66
|
|
74
67
|
# Here's the same commands with safe-mode disabled:
|
75
68
|
p rbox_wild.echo('$HOME') # => "/home/rye"
|
@@ -77,7 +70,7 @@ p rbox_wild['/etc'].ls('host*') # => ["hostconfig", "hosts"]
|
|
77
70
|
p rbox_wild.ls('-l | wc -l') # => 110
|
78
71
|
p rbox_wild.echo('$HOME > /tmp/rye-home') # =>
|
79
72
|
p rbox_wild.cat('/tmp/rye-home') # => "/home/rye"
|
80
|
-
|
73
|
+
|
81
74
|
|
82
75
|
|
83
76
|
puts %Q(
|
@@ -95,10 +88,10 @@ p rbox.commands.member?('rye9000') # => false
|
|
95
88
|
# automatically become available to all Rye::Box objects.
|
96
89
|
module Rye::Cmd
|
97
90
|
def rye9000(*args)
|
98
|
-
|
91
|
+
run_command("ls", args)
|
99
92
|
end
|
100
93
|
def somescript(*args)
|
101
|
-
|
94
|
+
run_command("/path/to/my/script", args)
|
102
95
|
end
|
103
96
|
end
|
104
97
|
|
@@ -121,7 +114,7 @@ rset.add_boxes(rbox, 'localhost') # Add boxes as hostnames or objects
|
|
121
114
|
# Calling methods on Rye::Set objects is very similar to calling them on
|
122
115
|
# Rye::Box objects. In fact, it's identical:
|
123
116
|
p rset.uptime # => [[14:19:02 up 32 days, 19:35 ...], [14:19:02 up 30 days, 01:35]]
|
124
|
-
p rset['/
|
117
|
+
p rset['/usr'].ls # => [['bin', 'etc', ...], ['bin', 'etc', ...]]
|
125
118
|
|
126
119
|
# Like Rye::Box, the response value is a Rye::Rap object containing the
|
127
120
|
# responses from each box. Each response is itself a Rye::Rap object.
|
@@ -144,5 +137,35 @@ rset.add_env(:RYE, "Forty Creek")
|
|
144
137
|
p rset.env.first.select { |env| env =~ /RYE/ } # => ["RYE=Forty Creek"]
|
145
138
|
|
146
139
|
|
140
|
+
puts %Q(
|
141
|
+
# ------------------------------------------------------------------
|
142
|
+
# EXAMPLE 5 -- ERROR HANDLING
|
143
|
+
#)
|
144
|
+
|
145
|
+
rbox = Rye::Box.new('localhost', :safe => false)
|
146
|
+
|
147
|
+
# Rye follows the standard convention of taking exception to a non-zero
|
148
|
+
# exit code by raising a Rye::CommandError. In this case, rye9000.test
|
149
|
+
# is not found by the ls command.
|
150
|
+
begin
|
151
|
+
rbox.ls('rye.test')
|
152
|
+
rescue Rye::CommandError => ex
|
153
|
+
puts ex.exit_code # => 1
|
154
|
+
puts ex.stderr # => ls: rye.test: No such file or directory
|
155
|
+
end
|
156
|
+
|
157
|
+
# The Rye:Rap response objects also give you the STDOUT and STDERR
|
158
|
+
# content separately. Here we redirect STDOUT to STDERR, so this
|
159
|
+
# will return nothing:
|
160
|
+
puts rbox.uname('-a 1>&2').stdout # =>
|
161
|
+
|
162
|
+
# It all went to STDERR:
|
163
|
+
puts rbox.uname('-a 1>&2').stderr # => Darwin ryehost 9.6.0 ...
|
164
|
+
|
165
|
+
# There were no actual error so the exit code should be 0.
|
166
|
+
puts rbox.uname('-a 1>&2').exit_code # => 0
|
167
|
+
|
168
|
+
|
169
|
+
|
147
170
|
|
148
171
|
|
data/lib/rye.rb
CHANGED
@@ -1,9 +1,14 @@
|
|
1
1
|
|
2
2
|
require 'rubygems' unless defined? Gem
|
3
3
|
|
4
|
+
require 'tempfile'
|
4
5
|
require 'net/ssh'
|
6
|
+
require 'net/scp'
|
5
7
|
require 'thread'
|
6
8
|
require 'highline'
|
9
|
+
require 'openssl'
|
10
|
+
require 'base64'
|
11
|
+
|
7
12
|
require 'esc'
|
8
13
|
require 'sys'
|
9
14
|
|
@@ -24,7 +29,7 @@ module Rye
|
|
24
29
|
extend self
|
25
30
|
|
26
31
|
unless defined?(SYSINFO)
|
27
|
-
VERSION = 0.
|
32
|
+
VERSION = 0.4.freeze
|
28
33
|
SYSINFO = SystemInfo.new.freeze
|
29
34
|
end
|
30
35
|
|
@@ -37,30 +42,68 @@ module Rye
|
|
37
42
|
# Accessor for an instance of SystemInfo
|
38
43
|
def sysinfo; SYSINFO; end
|
39
44
|
|
40
|
-
class CommandNotFound < RuntimeError; end
|
41
45
|
class NoBoxes < RuntimeError; end
|
42
46
|
class NoHost < RuntimeError; end
|
43
47
|
class NotConnected < RuntimeError; end
|
48
|
+
class CommandNotFound < RuntimeError; end
|
49
|
+
class CommandError < RuntimeError
|
50
|
+
# * +rap+ a Rye::Rap object
|
51
|
+
def initialize(rap)
|
52
|
+
@rap = rap
|
53
|
+
end
|
54
|
+
def message
|
55
|
+
"(code: %s) %s" % [@rap.exit_code, @rap.stderr.join($/)]
|
56
|
+
end
|
57
|
+
def stderr; @rap.stderr if @rap; end
|
58
|
+
def stdout; @rap.stdout if @rap; end
|
59
|
+
def exit_code; @rap.exit_code if @rap; end
|
60
|
+
end
|
44
61
|
|
45
62
|
# Reload Rye dynamically. Useful with irb.
|
46
63
|
# NOTE: does not reload rye.rb.
|
47
64
|
def reload
|
48
65
|
pat = File.join(File.dirname(__FILE__), 'rye')
|
49
|
-
%w{rap cmd box set}.each {|lib| load File.join(pat, "#{lib}.rb") }
|
66
|
+
%w{key rap cmd box set}.each {|lib| load File.join(pat, "#{lib}.rb") }
|
50
67
|
end
|
51
68
|
|
52
69
|
def mutex
|
53
70
|
@@mutex
|
54
71
|
end
|
55
72
|
|
73
|
+
# Looks for private keys in +path+ and returns and Array of paths
|
74
|
+
# to the files it fines. Raises an Exception if path does not exist.
|
75
|
+
# If path is a file rather than a directory, it will check whether
|
76
|
+
# that single file is a private key.
|
77
|
+
def find_private_keys(path)
|
78
|
+
raise "#{path} does not exist" unless File.exists?(path || '')
|
79
|
+
if File.directory?(path)
|
80
|
+
files = Dir.entries(path).collect { |file| File.join(path, file) }
|
81
|
+
else
|
82
|
+
files = [path]
|
83
|
+
end
|
84
|
+
|
85
|
+
files = files.select do |file|
|
86
|
+
next if File.directory?(file)
|
87
|
+
pk = nil
|
88
|
+
begin
|
89
|
+
tmp = Rye::Key.from_file(file)
|
90
|
+
pk = tmp if tmp.private?
|
91
|
+
rescue OpenSSL::PKey::PKeyError
|
92
|
+
end
|
93
|
+
!pk.nil?
|
94
|
+
end
|
95
|
+
files || []
|
96
|
+
end
|
56
97
|
|
98
|
+
|
99
|
+
|
100
|
+
|
57
101
|
# Add one or more private keys to the SSH Agent.
|
58
102
|
# * +keys+ one or more file paths to private keys used for passwordless logins.
|
59
103
|
def add_keys(*keys)
|
60
|
-
keys =
|
104
|
+
keys = keys.flatten.compact || []
|
61
105
|
return if keys.empty?
|
62
|
-
Rye
|
63
|
-
Rye::Box.shell("ssh-add") # Add the user's default keys
|
106
|
+
Rye.shell("ssh-add", keys) if keys
|
64
107
|
keys
|
65
108
|
end
|
66
109
|
|
@@ -73,13 +116,105 @@ module Rye
|
|
73
116
|
def keys
|
74
117
|
# 2048 76:cb:d7:82:90:92:ad:75:3d:68:6c:a9:21:ca:7b:7f /Users/rye/.ssh/id_rsa (RSA)
|
75
118
|
# 2048 7b:a6:ba:55:b1:10:1d:91:9f:73:3a:aa:0c:d4:88:0e /Users/rye/.ssh/id_dsa (DSA)
|
76
|
-
keystr = Rye
|
119
|
+
keystr = Rye.shell("ssh-add", '-l')
|
77
120
|
return nil unless keystr
|
78
|
-
keystr.
|
121
|
+
keystr.collect do |key|
|
79
122
|
key.split(/\s+/)
|
80
123
|
end
|
81
124
|
end
|
82
125
|
|
126
|
+
def remote_host_keys(*hostnames)
|
127
|
+
hostnames = hostnames.flatten.compact || []
|
128
|
+
return if hostnames.empty?
|
129
|
+
Rye.shell("ssh-keyscan", hostnames)
|
130
|
+
end
|
131
|
+
|
132
|
+
# Takes a command with arguments and returns it in a
|
133
|
+
# single String with escaped args and some other stuff.
|
134
|
+
#
|
135
|
+
# * +cmd+ The shell command name or absolute path.
|
136
|
+
# * +args+ an Array of command arguments.
|
137
|
+
#
|
138
|
+
# The command is searched for in the local PATH (where
|
139
|
+
# Rye is running). An exception is raised if it's not
|
140
|
+
# found. NOTE: Because this happens locally, you won't
|
141
|
+
# want to use this method if the environment is quite
|
142
|
+
# different from the remote machine it will be executed
|
143
|
+
# on.
|
144
|
+
#
|
145
|
+
# The command arguments are passed through Escape.shell_command
|
146
|
+
# (that means you can't use environment variables or asterisks).
|
147
|
+
#
|
148
|
+
def prepare_command(cmd, *args)
|
149
|
+
args &&= [args].flatten.compact
|
150
|
+
cmd = Rye.which(cmd)
|
151
|
+
raise CommandNotFound.new(cmd || 'nil') unless cmd
|
152
|
+
# Symbols to switches. :l -> -l, :help -> --help
|
153
|
+
args.collect! do |a|
|
154
|
+
a = "-#{a}" if a.is_a?(Symbol) && a.to_s.size == 1
|
155
|
+
a = "--#{a}" if a.is_a?(Symbol)
|
156
|
+
a
|
157
|
+
end
|
158
|
+
Rye.escape(@safe, cmd, *args)
|
159
|
+
end
|
160
|
+
|
161
|
+
# An all ruby implementation of unix "which" command.
|
162
|
+
#
|
163
|
+
# * +executable+ the name of the executable
|
164
|
+
#
|
165
|
+
# Returns the absolute path if found in PATH otherwise nil.
|
166
|
+
def which(executable)
|
167
|
+
return unless executable.is_a?(String)
|
168
|
+
#return executable if File.exists?(executable) # SHOULD WORK, MUST TEST
|
169
|
+
shortname = File.basename(executable)
|
170
|
+
dir = Rye.sysinfo.paths.select do |path| # dir contains all of the
|
171
|
+
next unless File.exists? path # occurrences of shortname
|
172
|
+
Dir.new(path).entries.member?(shortname) # found in the paths.
|
173
|
+
end
|
174
|
+
File.join(dir.first, shortname) unless dir.empty? # Return just the first
|
175
|
+
end
|
176
|
+
|
177
|
+
# Execute a local system command (via the shell, not SSH)
|
178
|
+
#
|
179
|
+
# * +cmd+ the executable path (relative or absolute)
|
180
|
+
# * +args+ Array of arguments to be sent to the command. Each element
|
181
|
+
# is one argument:. i.e. <tt>['-l', 'some/path']</tt>
|
182
|
+
#
|
183
|
+
# NOTE: shell is a bit paranoid so it escapes every argument. This means
|
184
|
+
# you can only use literal values. That means no asterisks too.
|
185
|
+
#
|
186
|
+
# Returns a Rye::Rap object containing the
|
187
|
+
def shell(cmd, *args)
|
188
|
+
args = args.flatten.compact
|
189
|
+
cmd = cmd.to_s if cmd.is_a?(Symbol)
|
190
|
+
# TODO: allow stdin to be sent to the cmd
|
191
|
+
tf = Tempfile.new(cmd)
|
192
|
+
cmd = Rye.prepare_command(cmd, args)
|
193
|
+
cmd << " 2>#{tf.path}" # Redirect STDERR to file. Works in DOS too.
|
194
|
+
# Deal with STDOUT
|
195
|
+
handle = IO.popen(cmd, "r")
|
196
|
+
stdout = handle.read.chomp
|
197
|
+
handle.close
|
198
|
+
# Then STDERR
|
199
|
+
stderr = File.exists?(tf.path) ? File.read(tf.path) : ''
|
200
|
+
tf.delete
|
201
|
+
# Create the response object
|
202
|
+
rap = Rye::Rap.new(self)
|
203
|
+
rap.add_stdout(stdout || '')
|
204
|
+
rap.add_stderr(stderr || '')
|
205
|
+
rap.exit_code = $?
|
206
|
+
rap
|
207
|
+
end
|
208
|
+
|
209
|
+
# Creates a string from +cmd+ and +args+. If +safe+ is true
|
210
|
+
# it will send them through Escape.shell_command otherwise
|
211
|
+
# it will return them joined by a space character.
|
212
|
+
def escape(safe, cmd, *args)
|
213
|
+
args = args.flatten.compact || []
|
214
|
+
safe ? Escape.shell_command(cmd, *args).to_s : [cmd, args].flatten.compact.join(' ')
|
215
|
+
end
|
216
|
+
|
217
|
+
|
83
218
|
private
|
84
219
|
|
85
220
|
# Start the SSH Agent locally. This is important
|
@@ -105,9 +240,8 @@ module Rye
|
|
105
240
|
#
|
106
241
|
def start_sshagent_environment
|
107
242
|
return if @@agent_env["SSH_AGENT_PID"]
|
108
|
-
|
109
|
-
lines
|
110
|
-
lines.split($/).each do |line|
|
243
|
+
lines = Rye.shell("ssh-agent", '-s') || []
|
244
|
+
lines.each do |line|
|
111
245
|
next unless line.index("echo").nil?
|
112
246
|
line = line.slice(0..(line.index(';')-1))
|
113
247
|
key, value = line.chomp.split( /=/ )
|
@@ -115,6 +249,8 @@ module Rye
|
|
115
249
|
end
|
116
250
|
ENV["SSH_AUTH_SOCK"] = @@agent_env["SSH_AUTH_SOCK"]
|
117
251
|
ENV["SSH_AGENT_PID"] = @@agent_env["SSH_AGENT_PID"]
|
252
|
+
|
253
|
+
Rye.shell("ssh-add") # Add the user's default keys
|
118
254
|
nil
|
119
255
|
end
|
120
256
|
|
@@ -129,8 +265,8 @@ module Rye
|
|
129
265
|
#
|
130
266
|
def end_sshagent_environment
|
131
267
|
pid = @@agent_env["SSH_AGENT_PID"]
|
132
|
-
Rye
|
133
|
-
#Rye
|
268
|
+
Rye.shell("ssh-agent", '-k') || []
|
269
|
+
#Rye.shell("kill", ['-9', pid]) if pid
|
134
270
|
@@agent_env.clear
|
135
271
|
nil
|
136
272
|
end
|
@@ -142,6 +278,7 @@ module Rye
|
|
142
278
|
start_sshagent_environment # Run this now
|
143
279
|
at_exit { end_sshagent_environment } # Run this before Ruby exits
|
144
280
|
}
|
281
|
+
|
145
282
|
rescue => ex
|
146
283
|
STDERR.puts "Error initializing the SSH Agent (is OpenSSL installed?):"
|
147
284
|
STDERR.puts ex.message
|