symphony-ssh 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/README.rdoc +22 -18
- data/lib/symphony/tasks/ssh.rb +25 -36
- data/lib/symphony/tasks/sshscript.rb +29 -48
- metadata +80 -28
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 5cd00a4a185aeccc71078587b4b770243cef65fdc6e87645e74f33c0d04dda09
|
4
|
+
data.tar.gz: bbe9c1c376fcf177483c51eaba7375a64c3783e1b92ea83b7c3db386c508661f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a98b520937e9d2c3a8b7448d950ae440854a3988460d9d51cb2e271f3c2e3d48ce8eb85776f5a3064099a04f844d57e591e0b3fbf0f665c25127b24f247dd8cf
|
7
|
+
data.tar.gz: be719bf1e5dcb5431a27933335a571b23b2b20dd1265069a3a9b6f72102cc68494375b2ac25b18680ca59b80499c4107fc58984731e4affd7b2e8dc9024478be
|
data/README.rdoc
CHANGED
@@ -22,22 +22,26 @@ Symphony-ssh uses
|
|
22
22
|
Configurability[https://rubygems.org/gems/configurability] to determine
|
23
23
|
behavior. The configuration is a YAML[http://www.yaml.org/] file.
|
24
24
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
25
|
+
symphony:
|
26
|
+
ssh:
|
27
|
+
path: /usr/bin/ssh
|
28
|
+
user: root
|
29
|
+
key: /path/to/a/private_key.rsa
|
30
|
+
opts:
|
31
|
+
- -e
|
32
|
+
- none
|
33
|
+
- -T
|
34
|
+
- -x
|
35
|
+
- -o
|
36
|
+
- CheckHostIP=no'
|
37
|
+
- -o
|
38
|
+
- BatchMode=yes'
|
39
|
+
- -o
|
40
|
+
- StrictHostKeyChecking=no
|
41
|
+
|
42
|
+
**NOTE**: If you've upgrade from a version pre 0.2.0, the
|
43
|
+
Configurability path has changed from `symphony_ssh`, to an `ssh` key
|
44
|
+
under the `symphony` top level.
|
41
45
|
|
42
46
|
|
43
47
|
=== path
|
@@ -47,7 +51,7 @@ The absolute path to the ssh binary.
|
|
47
51
|
=== user
|
48
52
|
|
49
53
|
The default user to connect to remote hosts with. This can be
|
50
|
-
|
54
|
+
changed per connection in the AMQP payload.
|
51
55
|
|
52
56
|
=== key
|
53
57
|
|
@@ -82,7 +86,7 @@ install any required development dependencies.
|
|
82
86
|
|
83
87
|
== License
|
84
88
|
|
85
|
-
Copyright (c) 2014, Mahlon E. Smith and Michael Granger
|
89
|
+
Copyright (c) 2014-2018, Mahlon E. Smith and Michael Granger
|
86
90
|
All rights reserved.
|
87
91
|
|
88
92
|
Redistribution and use in source and binary forms, with or without
|
data/lib/symphony/tasks/ssh.rb
CHANGED
@@ -23,7 +23,7 @@ require 'symphony/task' unless defined?( Symphony::Task )
|
|
23
23
|
### key: (optional) The path to an SSH private key
|
24
24
|
###
|
25
25
|
###
|
26
|
-
### Additionally, this class responds to the '
|
26
|
+
### Additionally, this class responds to the 'symphony.ssh' configurability
|
27
27
|
### key. Currently, you can set the 'path' argument, which is the
|
28
28
|
### full path to the local ssh binary (defaults to '/usr/bin/ssh') and
|
29
29
|
### override the default ssh user, key, and client opts.
|
@@ -47,53 +47,42 @@ require 'symphony/task' unless defined?( Symphony::Task )
|
|
47
47
|
###
|
48
48
|
class Symphony::Task::SSH < Symphony::Task
|
49
49
|
extend Configurability
|
50
|
-
config_key :symphony_ssh
|
51
50
|
|
52
|
-
#
|
51
|
+
# The default set of ssh command line flags.
|
53
52
|
#
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
'-o', 'StrictHostKeyChecking=no'
|
64
|
-
],
|
65
|
-
:user => 'root',
|
66
|
-
:key => nil
|
67
|
-
}
|
53
|
+
DEFAULT_SSH_OPTS = %w[
|
54
|
+
-e none
|
55
|
+
-T
|
56
|
+
-x
|
57
|
+
-q
|
58
|
+
-o CheckHostIP=no
|
59
|
+
-o BatchMode=yes
|
60
|
+
-o StrictHostKeyChecking=no
|
61
|
+
]
|
68
62
|
|
69
63
|
# SSH "informative" stdout output that should be cleaned from the
|
70
64
|
# command output.
|
71
65
|
SSH_CLEANUP = %r/Warning: no access to tty|Thus no job control in this shell/
|
72
66
|
|
73
|
-
|
67
|
+
|
68
|
+
# Configurability API
|
69
|
+
#
|
70
|
+
configurability( :symphony__ssh ) do
|
71
|
+
|
74
72
|
# The full path to the ssh binary.
|
75
|
-
|
73
|
+
setting :path, default: '/usr/bin/ssh'
|
76
74
|
|
77
|
-
#
|
78
|
-
|
79
|
-
attr_reader :opts
|
75
|
+
# The default user to use when connecting.
|
76
|
+
setting :user, default: 'root'
|
80
77
|
|
81
|
-
#
|
82
|
-
|
78
|
+
# A default Array of ssh client options when connecting
|
79
|
+
# to remote hosts.
|
80
|
+
setting :opts, default: DEFAULT_SSH_OPTS do |val|
|
81
|
+
Array( val )
|
82
|
+
end
|
83
83
|
|
84
84
|
# An absolute path to a password-free ssh private key.
|
85
|
-
|
86
|
-
end
|
87
|
-
|
88
|
-
### Configurability API.
|
89
|
-
###
|
90
|
-
def self::configure( config=nil )
|
91
|
-
config = Symphony::Task::SSH.defaults.merge( config || {} )
|
92
|
-
@path = config.delete( :path )
|
93
|
-
@opts = config.delete( :opts )
|
94
|
-
@user = config.delete( :user )
|
95
|
-
@key = config.delete( :key )
|
96
|
-
super
|
85
|
+
setting :key
|
97
86
|
end
|
98
87
|
|
99
88
|
|
@@ -1,12 +1,13 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# vim: set nosta noet ts=4 sw=4:
|
3
3
|
|
4
|
+
require 'securerandom'
|
4
5
|
require 'net/ssh'
|
5
6
|
require 'net/sftp'
|
6
|
-
require 'tmpdir'
|
7
7
|
require 'inversion'
|
8
8
|
require 'symphony'
|
9
9
|
require 'symphony/task'
|
10
|
+
require 'symphony/tasks/ssh'
|
10
11
|
|
11
12
|
|
12
13
|
### A base class for connecting to a remote host, then uploading and
|
@@ -26,9 +27,10 @@ require 'symphony/task'
|
|
26
27
|
### key: (optional) The path to an SSH private key
|
27
28
|
### attributes: (optional) Additional data to attach to the template
|
28
29
|
### nocleanup: (optional) Leave the remote script after execution? (default to false)
|
30
|
+
### tempdir: (optional) The destination temp directory. (defaults to /tmp)
|
29
31
|
###
|
30
32
|
###
|
31
|
-
### Additionally, this class responds to the '
|
33
|
+
### Additionally, this class responds to the 'symphony.ssh' configurability
|
32
34
|
### key. Currently, you can override the default ssh user and private key.
|
33
35
|
###
|
34
36
|
### Textual output of the command is stored in the @output instance variable.
|
@@ -49,53 +51,27 @@ require 'symphony/task'
|
|
49
51
|
### end
|
50
52
|
###
|
51
53
|
class Symphony::Task::SSHScript < Symphony::Task
|
52
|
-
extend Configurability
|
53
|
-
config_key :symphony_ssh
|
54
54
|
|
55
55
|
# Template config
|
56
56
|
#
|
57
57
|
TEMPLATE_OPTS = {
|
58
|
-
:
|
59
|
-
:
|
60
|
-
:
|
58
|
+
ignore_unknown_tags: false,
|
59
|
+
on_render_error: :propagate,
|
60
|
+
strip_tag_lines: true
|
61
61
|
}
|
62
62
|
|
63
63
|
# The defaults to use when connecting via SSH
|
64
64
|
#
|
65
65
|
DEFAULT_SSH_OPTIONS = {
|
66
|
-
:
|
67
|
-
:
|
68
|
-
:
|
69
|
-
:
|
70
|
-
:
|
71
|
-
:
|
72
|
-
:
|
66
|
+
auth_methods: [ 'publickey' ],
|
67
|
+
compression: true,
|
68
|
+
config: false,
|
69
|
+
keys_only: true,
|
70
|
+
verify_host_key: :never,
|
71
|
+
global_known_hosts_file: '/dev/null',
|
72
|
+
user_known_hosts_file: '/dev/null'
|
73
73
|
}
|
74
74
|
|
75
|
-
# SSH default options.
|
76
|
-
#
|
77
|
-
CONFIG_DEFAULTS = {
|
78
|
-
:user => 'root',
|
79
|
-
:key => nil
|
80
|
-
}
|
81
|
-
|
82
|
-
class << self
|
83
|
-
# The default user to use when connecting. If unset, 'root' is used.
|
84
|
-
attr_reader :user
|
85
|
-
|
86
|
-
# An absolute path to a password-free ssh private key.
|
87
|
-
attr_reader :key
|
88
|
-
end
|
89
|
-
|
90
|
-
### Configurability API.
|
91
|
-
###
|
92
|
-
def self::configure( config=nil )
|
93
|
-
config = Symphony::Task::SSHScript.defaults.merge( config || {} )
|
94
|
-
@user = config.delete( :user )
|
95
|
-
@key = config.delete( :key )
|
96
|
-
super
|
97
|
-
end
|
98
|
-
|
99
75
|
|
100
76
|
### Perform the ssh connection, render the template, send it, and
|
101
77
|
### execute it.
|
@@ -104,20 +80,21 @@ class Symphony::Task::SSHScript < Symphony::Task
|
|
104
80
|
template = payload[ 'template' ]
|
105
81
|
attributes = payload[ 'attributes' ] || {}
|
106
82
|
port = payload[ 'port' ] || 22
|
107
|
-
user = payload[ 'user' ] || Symphony::Task::
|
108
|
-
key = payload[ 'key' ] || Symphony::Task::
|
83
|
+
user = payload[ 'user' ] || Symphony::Task::SSH.user
|
84
|
+
key = payload[ 'key' ] || Symphony::Task::SSH.key
|
109
85
|
nocleanup = payload[ 'nocleanup' ]
|
86
|
+
tempdir = payload[ 'tempdir' ] || '/tmp'
|
110
87
|
|
111
88
|
raise ArgumentError, "Missing required option 'template'" unless template
|
112
|
-
raise ArgumentError, "Missing required option 'host'"
|
89
|
+
raise ArgumentError, "Missing required option 'host'" unless payload[ 'host' ]
|
113
90
|
|
114
|
-
remote_filename = self.make_remote_filename( template )
|
91
|
+
remote_filename = self.make_remote_filename( template, tempdir )
|
115
92
|
source = self.generate_script( template, attributes )
|
116
93
|
|
117
|
-
ssh_options = DEFAULT_SSH_OPTIONS.merge( :
|
94
|
+
ssh_options = DEFAULT_SSH_OPTIONS.merge( port: port, keys: Array(key) )
|
118
95
|
ssh_options.merge!(
|
119
|
-
:
|
120
|
-
:
|
96
|
+
logger: Loggability[ Net::SSH ],
|
97
|
+
verbose: :debug
|
121
98
|
) if payload[ 'debug' ]
|
122
99
|
|
123
100
|
Net::SSH.start( payload['host'], user, ssh_options ) do |conn|
|
@@ -141,11 +118,15 @@ class Symphony::Task::SSHScript < Symphony::Task
|
|
141
118
|
### Generate a unique filename for the script on the remote host,
|
142
119
|
### based on +template+ name.
|
143
120
|
###
|
144
|
-
def make_remote_filename( template )
|
121
|
+
def make_remote_filename( template, tempdir="/tmp" )
|
145
122
|
basename = File.basename( template, File.extname(template) )
|
146
|
-
tmpname =
|
123
|
+
tmpname = "%s/%s-%s" % [
|
124
|
+
tempdir,
|
125
|
+
basename,
|
126
|
+
SecureRandom.hex( 6 )
|
127
|
+
]
|
147
128
|
|
148
|
-
return
|
129
|
+
return tmpname
|
149
130
|
end
|
150
131
|
|
151
132
|
|
metadata
CHANGED
@@ -1,93 +1,145 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: symphony-ssh
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
- Mahlon E. Smith
|
8
|
-
- Michael Granger
|
7
|
+
- Mahlon E. Smith
|
8
|
+
- Michael Granger
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
|
-
cert_chain:
|
12
|
-
|
11
|
+
cert_chain:
|
12
|
+
- |
|
13
|
+
-----BEGIN CERTIFICATE-----
|
14
|
+
MIIDbDCCAlSgAwIBAgIBATANBgkqhkiG9w0BAQUFADA+MQ8wDQYDVQQDDAZtYWhs
|
15
|
+
b24xFzAVBgoJkiaJk/IsZAEZFgdtYXJ0aW5pMRIwEAYKCZImiZPyLGQBGRYCbnUw
|
16
|
+
HhcNMTcxMTIyMjIyMTAyWhcNMTgxMTIyMjIyMTAyWjA+MQ8wDQYDVQQDDAZtYWhs
|
17
|
+
b24xFzAVBgoJkiaJk/IsZAEZFgdtYXJ0aW5pMRIwEAYKCZImiZPyLGQBGRYCbnUw
|
18
|
+
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDpXGN0YbMVpYv4EoiCxpQw
|
19
|
+
sxKdyhlkvpvENUkpEhbpnEuMKXgUfRHO4T/vBZf0h8eYgwnrHCRhAeIqesFKfoj9
|
20
|
+
mpEJk5JUuADOAz18aT+v24UqAtJdiwBJLuqhslSNB6CFXZv3OOMny9bjoJegz0hI
|
21
|
+
Fht9ppCuNmxJNd+L3zAX8lD01RUWNRC+8L5QLCjViJtjFDDCFfh9NCirs+XnTCzo
|
22
|
+
AJgFbsZIzFJtSiXUtFgscKr4Ik8ruhRbPbYbmx9rf6W74aTMPxggq/d3gj0Eh32y
|
23
|
+
WsXsQ5giVnmkbsRkBNu3QyZ8Xr5+7mvy5AWyqXKOrcW7lnYaob6Z9x/MGXGNeD6j
|
24
|
+
AgMBAAGjdTBzMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQWBBRY8ea6
|
25
|
+
+6kAaW7ukKph2/4MTAD8/TAcBgNVHREEFTATgRFtYWhsb25AbWFydGluaS5udTAc
|
26
|
+
BgNVHRIEFTATgRFtYWhsb25AbWFydGluaS5udTANBgkqhkiG9w0BAQUFAAOCAQEA
|
27
|
+
00FljTeSNaYUqJ59yLRA+i43wVNiO4ETQQu6fYQCPns12Sm90spOJb3SmTDkJ7CY
|
28
|
+
dixOQg5A3Et1LVS+Z/YfH1TAbb50oTWbZbTW2JknHS0hohq3UF1pvbkk1niZ26er
|
29
|
+
skJ352MUfcyaUkQyMmCjL/BpkDutYH5OCGh+FmK8+mH7SoC9Nr48WwH2prVdHs3y
|
30
|
+
OMWFgB33sXdj1XqOd2Rw1WPgAeMeDqWeIrRMpUhNZOwroaA1MAr60f9NIYxua/vx
|
31
|
+
n0YyneERGuHPSRZFgo72tGOqLpAlWnhPxRNqnayZmsg3hPPI87B6MTUI2UQ7VUdh
|
32
|
+
UrSf3b+cPoC8PNfjp8zsdw==
|
33
|
+
-----END CERTIFICATE-----
|
34
|
+
date: 2018-07-12 00:00:00.000000000 Z
|
13
35
|
dependencies:
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: configurability
|
38
|
+
requirement: !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - "~>"
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '3.2'
|
43
|
+
type: :runtime
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - "~>"
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '3.2'
|
14
50
|
- !ruby/object:Gem::Dependency
|
15
51
|
name: symphony
|
16
52
|
requirement: !ruby/object:Gem::Requirement
|
17
53
|
requirements:
|
18
|
-
- - ~>
|
54
|
+
- - "~>"
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0.11'
|
57
|
+
type: :runtime
|
58
|
+
prerelease: false
|
59
|
+
version_requirements: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - "~>"
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0.11'
|
64
|
+
- !ruby/object:Gem::Dependency
|
65
|
+
name: inversion
|
66
|
+
requirement: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - "~>"
|
19
69
|
- !ruby/object:Gem::Version
|
20
|
-
version: '
|
70
|
+
version: '1.1'
|
21
71
|
type: :runtime
|
22
72
|
prerelease: false
|
23
73
|
version_requirements: !ruby/object:Gem::Requirement
|
24
74
|
requirements:
|
25
|
-
- - ~>
|
75
|
+
- - "~>"
|
26
76
|
- !ruby/object:Gem::Version
|
27
|
-
version: '
|
77
|
+
version: '1.1'
|
28
78
|
- !ruby/object:Gem::Dependency
|
29
79
|
name: net-ssh
|
30
80
|
requirement: !ruby/object:Gem::Requirement
|
31
81
|
requirements:
|
32
|
-
- - ~>
|
82
|
+
- - "~>"
|
33
83
|
- !ruby/object:Gem::Version
|
34
|
-
version: '
|
84
|
+
version: '5.0'
|
35
85
|
type: :runtime
|
36
86
|
prerelease: false
|
37
87
|
version_requirements: !ruby/object:Gem::Requirement
|
38
88
|
requirements:
|
39
|
-
- - ~>
|
89
|
+
- - "~>"
|
40
90
|
- !ruby/object:Gem::Version
|
41
|
-
version: '
|
91
|
+
version: '5.0'
|
42
92
|
- !ruby/object:Gem::Dependency
|
43
93
|
name: net-sftp
|
44
94
|
requirement: !ruby/object:Gem::Requirement
|
45
95
|
requirements:
|
46
|
-
- - ~>
|
96
|
+
- - "~>"
|
47
97
|
- !ruby/object:Gem::Version
|
48
98
|
version: '2.1'
|
49
99
|
type: :runtime
|
50
100
|
prerelease: false
|
51
101
|
version_requirements: !ruby/object:Gem::Requirement
|
52
102
|
requirements:
|
53
|
-
- - ~>
|
103
|
+
- - "~>"
|
54
104
|
- !ruby/object:Gem::Version
|
55
105
|
version: '2.1'
|
56
106
|
- !ruby/object:Gem::Dependency
|
57
107
|
name: rspec
|
58
108
|
requirement: !ruby/object:Gem::Requirement
|
59
109
|
requirements:
|
60
|
-
- - ~>
|
110
|
+
- - "~>"
|
61
111
|
- !ruby/object:Gem::Version
|
62
|
-
version: '3.
|
112
|
+
version: '3.7'
|
63
113
|
type: :development
|
64
114
|
prerelease: false
|
65
115
|
version_requirements: !ruby/object:Gem::Requirement
|
66
116
|
requirements:
|
67
|
-
- - ~>
|
117
|
+
- - "~>"
|
68
118
|
- !ruby/object:Gem::Version
|
69
|
-
version: '3.
|
119
|
+
version: '3.7'
|
70
120
|
- !ruby/object:Gem::Dependency
|
71
121
|
name: simplecov
|
72
122
|
requirement: !ruby/object:Gem::Requirement
|
73
123
|
requirements:
|
74
|
-
- - ~>
|
124
|
+
- - "~>"
|
75
125
|
- !ruby/object:Gem::Version
|
76
|
-
version: '0.
|
126
|
+
version: '0.16'
|
77
127
|
type: :development
|
78
128
|
prerelease: false
|
79
129
|
version_requirements: !ruby/object:Gem::Requirement
|
80
130
|
requirements:
|
81
|
-
- - ~>
|
131
|
+
- - "~>"
|
82
132
|
- !ruby/object:Gem::Version
|
83
|
-
version: '0.
|
133
|
+
version: '0.16'
|
84
134
|
description: |
|
85
135
|
A small collection of base classes used for interacting with remote
|
86
136
|
machines over ssh. With them, you can use AMQP (via Symphony) to
|
87
137
|
run batch commands, execute templates as scripts, and perform any
|
88
138
|
batch/remoting stuff you can think of without the need of separate
|
89
139
|
client agents.
|
90
|
-
email:
|
140
|
+
email:
|
141
|
+
- mahlon@martini.nu
|
142
|
+
- ged@faeriemud.org
|
91
143
|
executables: []
|
92
144
|
extensions: []
|
93
145
|
extra_rdoc_files: []
|
@@ -97,7 +149,7 @@ files:
|
|
97
149
|
- lib/symphony/tasks/sshscript.rb
|
98
150
|
homepage: http://projects.martini.nu/ruby-modules
|
99
151
|
licenses:
|
100
|
-
- BSD
|
152
|
+
- BSD-3-Clause
|
101
153
|
metadata: {}
|
102
154
|
post_install_message:
|
103
155
|
rdoc_options: []
|
@@ -105,17 +157,17 @@ require_paths:
|
|
105
157
|
- lib
|
106
158
|
required_ruby_version: !ruby/object:Gem::Requirement
|
107
159
|
requirements:
|
108
|
-
- -
|
160
|
+
- - ">="
|
109
161
|
- !ruby/object:Gem::Version
|
110
162
|
version: 2.0.0
|
111
163
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
112
164
|
requirements:
|
113
|
-
- -
|
165
|
+
- - ">="
|
114
166
|
- !ruby/object:Gem::Version
|
115
167
|
version: 2.0.3
|
116
168
|
requirements: []
|
117
169
|
rubyforge_project:
|
118
|
-
rubygems_version: 2.
|
170
|
+
rubygems_version: 2.7.6
|
119
171
|
signing_key:
|
120
172
|
specification_version: 4
|
121
173
|
summary: Base classes for using Symphony with ssh.
|