ssh2shell

Wrapper class for ssh2 shell command.
ssh2shell supports the following functionality:
- SSH shell connection to one or more hosts.
- Run multiple commands sequentially within the context of the previous commands result.
- SSH tunnelling to multiple hosts using nested host objects.
- When tunnelling each host has its own connection parameters, commands, command handlers,
event handlers and debug or verbose settings.
- Supports
sudo
, sudo su
and su user
commands. - Supports changing default prompt matching regular expressions for different prompt requirements
- Ability to respond to prompts resulting from a command as it is being run.
- Ability to check the last command and conditions within the response text before the
next command is run.
- Performing actions based on a command run and the response received.
Things like adding or removing commands, responding using stream.write() or processing the command response text.
- See progress messages, messages you define, error messages and debug messages of script process
or verbose output of responses from the host.
- Access to full session response text in the end event triggered when each host connection is closed.
- Run notification commands that are processed either as messages to the full session text only or
messages outputted to your console only.
- Add event handlers either to the class instance or define them within host object.
- Create bash scripts on the fly, run them and remove them.
- Server SSH fingerprint validation.
- Access to SSH2.connect parameters for first host connection.
- Keyboard-interactive authentication.
- Pipe stream.data into another writeable stream using SSH2shell.pipe(writableStream). Pipe commands can be chained.
- Also process raw stream.data through SSH2shell.on('data') or host.onData event handlers as the stream receives each
character.
- Use a callback function to handle final session text. The callback function can be defined in the host object or
passed to SSH2shell.connect(callback). The callback function has access to the
this
keyword.
Code:
The Class is written in coffee script which can be found here: ./src/ssh2shell.coffee
.
It has comments not found in the build output JavaScript file ./lib/ssh2shell.js
.
Installation:
npm install ssh2shell
Minimal Example:
var host = {
server: {
host: "127.0.0.1",
userName: "test",
password: "1234",
},
commands: [ "echo $(pwd)", "ls -l" ]
};
var SSH2Shell = require ('ssh2shell'),
SSH = new SSH2Shell(host),
callback = function(sessionText){
console.log(sessionText)
}
SSH.connect(callback);
Host Configuration:
SSH2Shell expects an object with the following structure to be passed to its constructor:
host = {
server: {
host: "IP Address",
port: "external port number",
userName: "user name",
password: "user password",
passPhrase: "privateKeyPassphrase",
privateKey: require('fs').readFileSync('/path/to/private/key/id_rsa'),
ssh: {
forceProtocolVersion: true/false,
forceAddressType: true/false,
disablePseudoTTY: true/false,
forcePseudoTTY: true/false,
verbose: true/false,
cipherSpec: "",
escape: "",
logFile: "",
configFile: "",
identityFile: "",
loginName: "",
macSpec: "",
Options: {}
}
},
hosts: [host2, host3, host4, host5],
standardPrompt: ">$%#",
passwordPrompt: ":",
passphrasePrompt: ":",
passPromptText: "Password",
showBanner: false,
window: false,
enter: "\n",
streamEncoding: "utf8",
asciiFilter: "[^\r\n\x20-\x7e]",
disableColorFilter: false,
textColorFilter: "(\[{1}[0-9;]+m{1})",
commands: ["cd /var/logs", "ls -al", "msg:Listed dir", "cd /home/user"],
msg: function( message ) {
console.log(message);
}
},
verbose: false,
debug: false,
idleTimeOut: 5000,
dataIdleTimeOut: 500,
connectedMessage: "Connected",
readyMessage: "Ready",
closedMessage: "Closed",
onKeyboardInteractive: function(name, instructions, instructionsLang, prompts, finish){
},
onData: function( data ) {
},
onPipe: function( source ) {
}
onUnpipe: function( source ) {
},
onCommandProcessing: function( command, response, sshObj, stream ) {
},
onCommandComplete: function( command, response, sshObj ) {
},
onCommandTimeout: function( command, response, stream, connection ) {
},
onEnd: function( sessionText, sshObj ) {
},
onError: function( err, type, close = false, callback ) {
},
callback: function( sessionText ){
}
};
- Host.server will accept current SSH2.client.connect parameters.
- Optional host properties or event handlers do not need to be included if you are not changing them.
- See the end of the readme for event handlers available to be added to the instance listeners.
- Host event handlers completely replace the default event handler definitions in the class when defined.
- The instance
this
keyword is available within host event handlers to give access to ssh2shell object api like
this.emit() and other functions. this.sshObj
or sshObj variable passed into a function provides access to all the host config, some instance
variables and the current array of commands for the host. The sshObj.commands array changes with host connection.
ssh2shell API
SSH2Shell extends events.EventEmitter
Methods
-
.connect(callback(sessionText)) Is the main function to establish the connection and handle data events from the server which triggers
the rest of the process. It take in an optional callback function that receives the full session text as its parameter.
-
.emit("eventName", function, parms,... ). Raises the event based on the name in the first string and takes input
parameters based on the handler function definition.
-
.pipe(destination) binds a writable stream to the output of the read stream but only after the connection is established.
-
.unpipe(destination) removes piped streams but can only be called after a connection has been made.
Variables
Test Files:
cp .env-example .env
node text/simple.js
node test/pipe.js
node test/tunneltest.js
node test/timeouttest.js
node test/sudosutest.js
node test/notificationstest.js
node test/keyboard-interactivetest.js
Usage:
Connecting to a single host:
How to:
- Use an .env file for server values loaded by dotenv from the root of the project.
- Connect using a key pair with pass phrase.
- Use sudo su with user password.
- Set commands.
- Test the response of a command and add more commands and notifications in the host.onCommandComplete event handler.
- Use the two notification types in the commands array.
- Use msg: notifications to track progress in the console as the process completes.
- Email the final full session text to yourself.
(will require a package json with ssh2shell, dotenv and email defined as dependencies)
.env
HOST=192.168.0.1
PORT=22
USER_NAME=myuser
PASSWORD=mypassword
PRIV_KEY_PATH=~/.ssh/id_rsa
PASS_PHRASE=myPassPhrase
app.js
var dotenv = require('dotenv').config(),
Email = require('email');
var host = {
server: {
host: process.env.HOST,
port: process.env.PORT,
userName: process.env.USER_NAME,
password: process.env.PASSWORD,
passPhrase: process.env.PASS_PHRASE,
privateKey: require('fs').readFileSync(process.env.PRIV_KEY_PATH)
},
commands: [
"`This is a message that will be added to the full sessionText`",
"msg:This is a message that will be handled by the msg.send code",
"echo $(pwd)",
"sudo su",
"msg:changing directory",
"cd ~/",
"ls -l",
"msg:Confirming the current path",
"echo $(pwd)",
"msg:Getting the directory listing to confirm the extra command was added",
"ls -l",
"`All done!`"
],
onCommandComplete: function( command, response, sshObj ) {
if(sshObj.debug){
this.emit("msg", this.sshObj.server.host + ": host.onCommandComplete event, command: " + command);
}
if (command === "echo $(pwd)" && response.indexOf("/root") != -1 ) {
sshObj.commands.unshift("msg:The command and response check worked. Added another cd command.");
sshObj.commands.unshift("cd .ssh");
}
else if (command === "ls -l"){
this.emit("msg", response);
}
},
onEnd: function( sessionText, sshObj ) {
if(sshObj.debug){this.emit("msg", this.sshObj.server.host + ": host.onEnd event");}
var sessionEmail = new Email({
from: "me@example.com",
to: "me@example.com",
subject: "Automated SSH Session Response",
body: "\nThis is the full session responses for " + sshObj.server.host + ":\n\n" + sessionText
});
this.emit("msg", "Sending session response email");
sessionEmail.send(function(err){ sshObj.msg.send('error', err, 'Email'); });
}
};
var SSH2Shell = require ('ssh2shell'),
SSH = new SSH2Shell(host);
SSH.connect();
or
var callback = function (sessionText){
console.log (sessionText);
}
SSH.connect(callback);
Tunnelling nested host objects:
SSH tunnelling has been incorporated into core of the class process enabling nested host objects.
The new hosts: [ host1, host2]
setting can make multiple host connections possible.
Each host config object has its own server settings, commands, command handlers and
event handlers. Each host object also has its own hosts array that other host objects can be added to.
This provides for different host connection sequences to any depth of recursion.
SSH connections:
Once the primary host connection is made all other connections are made using an ssh command from the
current host to the next. I have given access to a number of ssh command options by adding
an optional host.server.ssh object. All host.server.ssh properties are optional.
host.server.ssh.options allows setting any ssh config option from the command.
var host = {
server: { ...,
ssh: {
forceProtocolVersion: true/false,
forceAddressType: true/false,
disablePseudoTTY: true/false,
forcePseudoTTY: true/false,
verbose: true/false,
cipherSpec: "",
escape: "",
logFile: "",
configFile: "",
identityFile: "",
loginName: "",
macSpec: "",
options: {}
}
}
}
Tunnelling Example:
This example shows two hosts (server2, server3) that are connected to via server1. The two host configs are add
to server1.hosts array.
server1.hosts = [server2, server3]
server2.hosts = []
server3.hosts = []
The following would also be valid:
server1.hosts = [server2]
server2.hosts = [server3]
server3.hosts = []
The nested process:
- The primary host (server1) is connected and all its commands completed.
- Server1.hosts array is checked for other hosts and the next host popped off the array.
- Server1 is stored for use later and server2's host object is loaded as the current host.
- A connection to server2 is made using its server parameters via running the ssh command on the primary.
- Server2's commands are completed and server2.hosts array is checked for other hosts.
- With no hosts found the connection to server2 is closed triggering an end event (calling server2.onEnd function if defined).
- Server1 host object is reloaded as the current host object and server2 host object discarded.
- Server1.hosts array is checked for other hosts and the next host popped off the array.
- Server1's host object is stored again and server3's host object is loaded as the current host.
- Server3 is connected to and it completes its process.
- Server3.hosts is checked and with no hosts found the connection is closed and the end event is triggered.
- Server1 is loaded for the last time.
- The session text for each connection is appended to the session text for the primary host.
- With no further hosts to load the connection is closed triggering an end event for the last time.
- As all sessions are closed the process ends.
Note:
- A host object needs to be defined before it is added to another host.hosts array.
- Only the primary host objects connected, ready and closed messages will be used by ssh2shell.
- Connection events only apply to the primary host. Keyboard-interactive event handler is one of these.
How to:
- Define nested hosts
- Use unique host connection settings for each host
- Defining different commands and command event handlers for each host
- Sharing duplicate functions between host objects
- What host object attributes you can leave out of primary and secondary host objects
- Unique event handlers set in host objects, common event handler set on class instance
Note:
- Change debug to true to see the full process for each host.
var dotenv = require('dotenv');
dotenv.load();
var conParamsHost1 = {
host: process.env.SERVER1_HOST,
port: process.env.SERVER1_PORT,
userName: process.env.SERVER1_USER_NAME,
password: process.env.SERVER1_PASSWORD,
passPhrase: process.env.SERVER1_PASS_PHRASE,
privateKey: require('fs').readFileSync(process.env.SERVER1_PRIV_KEY_PATH)
},
conParamsHost2 = {
host: process.env.SERVER2_HOST,
port: process.env.SERVER2_PORT,
userName: process.env.SERVER2_USER_NAME,
password: process.env.SERVER2_PASSWORD
},
conParamsHost3 = {
host: process.env.SERVER3_HOST,
port: process.env.SERVER3_PORT,
userName: process.env.SERVER3_USER_NAME,
password: process.env.SERVER3_PASSWORD
}
var host1 = {
server: conParamsHost1,
commands: [
"msg:connected to host: passed. Listing dir.",
"ls -l"
],
debug: false,
onCommandComplete: function( command, response, sshObj ) {
if(sshObj.debug){
this.emit("msg", this.sshObj.server.host + ": host.onCommandComplete host1, command: " + command);
}
},
onEnd: function( sessionText, sshObj ) {
if(sshObj.debug){this.emit("msg", this.sshObj.server.host + ": primary host.onEnd all sessiontText");}
this.emit ("msg", "\nAll Hosts SessiontText ---------------------------------------\n");
this.emit ("msg", sshObj.server.host + ":\n" + sessionText);
this.emit ("msg", "\nEnd sessiontText ---------------------------------------------\n");
})
},
host2 = {
server: conParamsHost2,
commands: [
"msg:connected to host: passed",
"msg:Changing to root dir",
"cd ~/",
"msg:Listing dir",
"ls -l"
],
debug: false,
connectedMessage: "Connected to host2",
onCommandComplete: function( command, response, sshObj ) {
if(sshObj.debug){
this.emit("msg", this.sshObj.server.host + ": host.onCommandComplete host2, command: " + command);
}
if (command.indexOf("cd") != -1){
this.emit("msg", this.sshObj.server.host + ": Just ran a cd command:\n");
this.emit("msg", response);
}
}
},
host3 = {
server: conParamsHost3,
commands: [
"msg:connected to host: passed",
"hostname"
],
debug: false,
connectedMessage: "Connected to host3",
onCommandComplete: function( command, response, sshObj) {
if(sshObj.debug){
this.emit("msg", this.sshObj.server.host + ": host.onCommandComplete host3, command: " + command);
}
if (command.indexOf("cd") != -1){
this.emit("msg", this.sshObj.server.host + ": Just ran hostname command:\n");
this.emit("msg", response);
}
}
}
host1.hosts = [ host2, host3 ];
var SSH2Shell = require ('ssh2shell'),
SSH = new SSH2Shell(host1);
SSH.on ('commandComplete', function onCommandComplete( command, response, sshObj ) {
if(sshObj.debug){
this.emit("msg", this.sshObj.server.host + ": instance.onCommandComplete, command: " + command);
}
if (command == "ls -l"){
this.emit("msg", this.sshObj.server.host + ":\n" + response);
}
}
SSH.on ('end', function( sessionText, sshObj ) {
if(sshObj.debug){this.emit("msg", this.sshObj.server.host + ": instanse.onEnd all hosts");}
this.emit ("msg", "\nSessiontText -------------------------------------------------\n");
this.emit ("msg", sshObj.server.host + ":\n" + sessionText);
this.emit ("msg", "\nEnd sessiontText ---------------------------------------------\n");
});
SSH.connect();
Trouble shooting:
- Adding msg command
"msg:Doing something"
to your commands array at key points will help you track the sequence of
what has been done as the process runs. (See examples) Error: Unable to parse private key while generating public key (expected sequence)
is caused by the pass phrase
being incorrect. This confused me because it doesn't indicate the pass phrase was the problem but it does indicate
that it could not decrypt the private key.- Recheck your pass phrase for typos or missing chars.
- Try connecting manually to the host using the exact pass phrase used by the code to confirm it works.
- I did read of people having problems with the pass phrase or password having an \n added when used from an
external file causing it to fail. They had to add .trim() when setting it.
- If your password is incorrect the connection will return an error.
- There is an optional debug setting in the host object that will output process information when set to true and
passwords for failed authentication of sudo commands and tunnelling.
host.debug = true
- The class now has an idle time out timer (default:5000ms) to stop unexpected command prompts from causing the process
hang without error. The default time out can be changed by setting the host.idleTimeOut with a value in milliseconds.
(1000 = 1 sec)
Verbose and Debug:
- When host.verbose is set to true each command complete raises a msg event outputting the response text.
- When host.debug is set to true each process step raises a msg event to help identify what the internal process of
each step was.
Note:
Do not add these to the commandProccessing event which is called every time a character is received from the host
Add your own verbose messages as follows:
if(this.sshObj.verbose){this.emit("msg", this.sshObj.server.host + ": response: " + response);}
//response might need
to be changed to this._buffer
Add your own debug messages as follows:
if(this.sshObj.debug){this.emit("msg", this.sshObj.server.host + ": eventName");}
//where eventName is the text
identifying what happened
Command Time-out Event Handler
When the program doesn't detect a standard prompt and doesn't receive any more data the onCommandTimeout event triggers
after the host.idleTimeOut value (in ms). This is usually because an unexpected prompt on the server is requiring a
response that isn't handled or the host is not responding at all. In either case detection of the standard prompt will
never happen causing the program to hang, perpetually waiting for a response it won’t get. The commandTimeout stops this.
The commandTimeout event can enable you to handle such prompts without having to disconnect by providing the response
the host requires. The host then replies with more text triggering a data received event resetting the timer and
enabling the process to continue. It is recommended to close the connection as a default action if all else fails so you
are not left with a hanging script again. The default action is to add the last response text to the session text and
disconnect. Enabling host.debug would also provide the process path leading up to disconnection which in conjunction with
the session text would clarify what command and output triggered the event.
Note: If you receive garble back before the clear response you may need to save the previous response text to the
sessionText and clear the buffer before using stream.write() in commandTimeout.
this.sshObj.sessionText = response
and this._buffer = ""
host.onCommandTimeout = function( command, response, stream, connection ) {
if(sshObj.debug){this.emit("msg", this.sshObj.server.host + ": host.onCommandTimeout");}
if (command === "atp-get install node"
&& response.indexOf("[Y/n]?") != -1
&& this.sshObj.nodePrompt != true) {
this.sshObj.nodePrompt = true
stream.write('y\n')
}else{
this.emit ("error", this.sshObj.server.host
+ ": Command timed out after #{this._idleTime/1000} seconds. command: "
+ command, "Timeout", true, function(err, type){
this.sshObj.sessionText += response
})
}
}
or
host.onCommandTimeout = function( command, response, stream, connection ) {
if(sshObj.debug){this.emit("msg", this.sshObj.server.host + ": host.onCommandTimeout");}
if (command === "" && response === "Connected to port 22" && this.sshObj.noFirstPrompt != true) {
this.sshObj.noFirstPrompt = true
stream.write('\n')
return true
}
this.emit ("error", this.sshObj.server.host
+ ": Command timed out after #{this._idleTime/1000} seconds. command: "
+ command, "Timeout", true, function(err, type){
this.sshObj.sessionText += response
})
}
or
host.onCommandTimeout = function( command, response, stream, connection ) {};
var SSH2Shell = require ('ssh2shell'),
SSH = new SSH2Shell(host)
SSH.on ('commandTimeout',function( command, response, stream, connection ){
if(sshObj.debug){this.emit("msg", this.sshObj.server.host + ": instance.onCommandTimeout");}
if (command === "atp-get install node"
&& response.indexOf("[Y/n]?") != -1
&& this.sshObj.nodePrompt != true) {
this.sshObj.nodePrompt = true;
stream.write('y\n');
return true;
}
this.sshObj.sessionText += response;
this.emit("error", this.sshObj.server.host + ": Command \`" + command + "\` timed out after "
+ (this._idleTime / 1000) + " seconds. command: " + command, "Command Timeout", true);
});
SSH.on ('end', function( sessionText, sshObj ) {
if(sshObj.debug){this.emit("msg", this.sshObj.server.host + ": instance.onEnd");}
this.emit("msg","\nSession text for " + sshObj.server.host + ":\n" + sessionText);
});
SSH.connect();
Authentication:
- Each host authenticates with its own host.server parameters.
- When using key authentication you may require a valid pass phrase if your key was created with one.
- When using fingerprint validation both host.server.hashMethod property and host.server.hostVerifier function must be
set.
- When using keyboard-interactive authentication both host.server.tryKeyboard and instance.on ("keayboard-interactive",
function...) or host.onKeyboardInteractive() must be defined.
- Set the default cyphers and keys.
Default Cyphers:
Default Cyphers and Keys used in the initial ssh connection can be redefined by setting the ssh2.connect.algorithms through the host.server.algorithms option.
As with this property all ssh2.connect properties are set in the host.server object.
Example:
var host = {
server: {
host: "<host IP>",
port: "22",
userName: "<username>",
password: "<password>",
hashMethod: "md5",
algorithms: {
kex: [
'diffie-hellman-group1-sha1',
'ecdh-sha2-nistp256',
'ecdh-sha2-nistp384',
'ecdh-sha2-nistp521',
'diffie-hellman-group-exchange-sha256',
'diffie-hellman-group14-sha1'],
cipher: [
'aes128-ctr',
'aes192-ctr',
'aes256-ctr',
'aes128-gcm',
'aes128-gcm@openssh.com',
'aes256-gcm',
'aes256-gcm@openssh.com',
'aes256-cbc'
]
}
},
......
}
Fingerprint Validation:
At connection time the hash of the server’s public key can be compared with the hash the client had previously recorded
for that server. This stops "man in the middle" attacks where you are redirected to a different server as you connect
to the server you expected to. This hash only changes with a reinstall of SSH, a key change on the server or a load
balancer is now in place.
Note:
Fingerprint check doesn't work the same way for tunnelling. The first host will validate using this method but the
subsequent connections would have to be handled by your commands. Only the first host uses the SSH2 connection method
that does the validation.
To use fingerprint validation you first need the server hash string which can be obtained using ssh2shell as follows:
- Set host.verbose to true then set host.server.hashKey to any non-empty string (say "1234").
- Validation will be checked and fail causing the connection to terminate.
- A verbose message will return both the server hash and client hash values that failed comparison.
- This is also what will happen if your hash fails the comparison with the server in the normal verification process.
- Turn on verbose in the host object, run your script with hashKey unset and check the very start of the text returned
for the servers hash value.
- The servers hash value can be saved to a variable outside the host or class so you can access it without having to
parse response text.
Example:
var serverHash, host;
var expectedHash
expectedHash = "85:19:8a:fb:60:4b:94:13:5c:ea:fe:3b:99:c7:a5:4e";
host = {
server: {
hashMethod: "md5",
hostVerifier: function(hashedKey) {
var recievedHash;
expectedHash = expectedHash + "".replace(/[:]/g, "").toLowerCase();
recievedHash = hashedKey + "".replace(/[:]/g, "").toLowerCase();
if (expectedHash === "") {
serverHash = hashedKey;
console.log("Server hash: " + serverHash);
return true;
} else if (recievedHash === expectedHash) {
console.log("Hash values matched");
return true;
}
console.log("Hash values: Server = " + recievedHash + " <> Client = " + expectedHash);
return false;
},
},
};
var SSH2Shell = require ('ssh2shell'),
SSH = new SSH2Shell(host);
SSH.connect();
Note:
host.server.hashMethod only supports md5 or sha1 according to the current SSH2 documentation anything else may produce
undesired results.
Keyboard-interactive
Keyboard-interactive authentication is available when both host.server.tryKeyboard is set to true and the event handler
keyboard-interactive is defined as below. The keyboard-interactive event handler can only be used on the first connection.
Also see test/keyboard-interactivetest.js for the full example
Defining the event handler:
host.server.tryKeyboard = true;
var SSH2Shell = require ('../lib/ssh2shell');
var SSH = new SSH2Shell(host);
SSH.on ('keyboard-interactive', function(name, instructions, instructionsLang, prompts, finish){
if (this.sshObj.debug) {this.emit('msg', this.sshObj.server.host + ": Keyboard-interactive");}
if (this.sshObj.verbose){
this.emit('msg', "name: " + name);
this.emit('msg', "instructions: " + instructions);
var str = JSON.stringify(prompts, null, 4);
this.emit('msg', "Prompts object: " + str);
}
finish([this.sshObj.server.password] );
});
SSH.connect();
Or
host = {
...,
onKeyboardInteractive: function(name, instructions, instructionsLang, prompts, finish){
if (this.sshObj.debug) {this.emit('msg', this.sshObj.server.host + ": Keyboard-interactive");}
if (this.sshObj.verbose){
this.emit('msg', "name: " + name);
this.emit('msg', "instructions: " + instructions);
var str = JSON.stringify(prompts, null, 4);
this.emit('msg', "Prompts object: " + str);
}
finish([this.sshObj.server.password] );
},
...
}
Sudo and su Commands:
It is possible to use sudo [command]
, sudo su
, su [username]
and sudo -u [username] -i
. Sudo commands uses the
password for the user that is accessing the server and is handled by SSH2shell. Su on the other hand uses the password
of root or the other user (su seconduser
) and requires you detect the password prompt in onCommandProcessing.
See: su VS sudo su VS sudo -u -i for
clarification about the difference between the commands.
See: test/sudosutest.js for a working code example.
Notification commands:
There are two notification commands that are added to the host.commands array but are not run as a command on the host.
"msg:This is a message intended for monitoring the process as it runs"
. The msg:
command raises a onMsg(message)
event.
- The text after
msg:
is passed to the message property of the onMsg event.
- "`SessionText notification`" will take the text between "` `" and add it to the sessionText.
- The reason for not using echo or printf commands as a normal command is that you see both the command and the message
in the sessionText which is pointless when all you want is the message.
Prompt detection override:
The following properties have been added to the host object making it possible to override prompt string values used
with regular expressions to for prompt detection. Being able to change these values enables you to easily manage all
sorts of prompt options subject to you server prompts.
These are optional settings.
host.standardPrompt = ">$%#";
host.passwordPrompt = ":";
host.passphrasePrompt = ":";
Text regular expression filters:
There are two regular expression filters that remove unwanted text from response data.
The first removes non-standard ascii and the second removes ANSI text formatting codes. Both of these can be modified in
your host object to override defaults. It is also possible to output the ANSI codes by setting disableColorFilter to true.
These are optional settings
host.asciiFilter = "[^\r\n\x20-\x7e]"
host.disableColorFilter = false
host.textColorFilter = "(\[{1}[0-9;]+m{1})"
Responding to non-standard command prompts:
When running commands there are cases that you might need to respond to specific prompt that results from the command
being run. The command response check method is the same as in the example for the host.onCommandComplete event handler
but in this case we use it in the host.onCommandProcessing event handler. The stream object is available in
onCommandProcessing to the prompt directly using strea.write("y\n"), note "\n" might be required to complete the
response.
Host definition that replaces the default handler and runs only for the current host connection
host.onCommandProcessing = function( command, response, sshObj, stream ) {
if (command == "apt-get install nano" && response.indexOf("[y/N]?") != -1 && sshObj.firstRun != true) {
if (sshObj.debug) {this.emit('msg', this.sshObj.server.host + ": Responding to nano install");}
sshObj.firstRun = true
stream.write('y\n');
}
};
\\sshObj.firstRun can be reset to false in onCommandComplete to allow for another non-standard prompt
Instance definition that runs in parallel with every other commandProcessing for every host connection
var SSH2Shell = require ('../lib/ssh2shell');
var SSH = new SSH2Shell(host);
SSH.on ('commandProcessing', function onCommandProcessing( command, response, sshObj, stream ) {
if (command == "apt-get install nano" && response.indexOf("[y/N]?") != -1 && sshObj.firstRun != true ) {
if (sshObj.debug) {this.emit('msg', this.sshObj.server.host + ": Responding to nano install");}
sshObj.firstRun = true
stream.write('y\n');
}
};
Note:
If there is no response from the server the commandTimeout will be triggered after the idleTimeOut period.
Bash scripts on the fly:
If the commands you need to run would be better suited to a bash script as part of the process it is possible to generate
or get the script on the fly. You can echo/printf the script content into a file as a command, ensure it is executable,
run it and then delete it. The other option is to curl or wget the script from a remote location but
this has some risks associated with it. I like to know what is in the script I am running.
Note # and > in the following commands with conflict with the host.standardPrompt definition ">$%#" change it to "$%"
host.commands = [ "some commands here",
"if [ ! -f myscript.sh ]; then printf '#!/bin/bash\n" +
" #\n" +
" current=$(pwd);\n" +
"cd ..;\n" +
"if [ -f myfile ]; then" +
"sed \"/^[ \\t]*$/d\" ${current}/myfile | while read line; do\n" +
"printf \"Doing some stuff\";\n" +
"printf $line;\n" +
"done\n" +
"fi\n' > myscript.sh;" +
"fi",
"sudo chmod 700 myscript.sh",
"./myscript.sh",
"rm myscript.sh"
],
Event Handlers:
There are a number of event handlers that enable you to add your own code to be run when those events are triggered.
Most of these you have already encountered in the host object. You do not have to add event handlers unless you want
to add your own functionality as the class already has default handlers defined.
There are two ways to add event handlers:
- Add handler functions to the host object (See requirements at start of readme).
- These event handlers will only be run for the currently connected host.Important to understand in a multi host setup.
- Within the host event functions
this
is always referencing the ssh2shell instance at run time. - Instance variables and functions are available through
this
including the Emitter functions like
this.emit("myEvent", properties). - Connect, ready, error and close events are not available for definition in the host object.
- Defining a host event replaces the default event handler. Again while that host is connected.
- Add an event handler, as defined below, to the class instance.
- Handlers added to the class instance will be triggered every time the event is raised in parallel with any other
handlers of the same name.
- It will not replace the internal event handler of the class be it set by the class default or a host definition.
An event can be raised using this.emit('eventName', parameters)
.
Further reading:
node.js event emitter
Class Instance Event Definitions:
ssh2shell.on ("connect", function onConnect() {
});
ssh2shell.on ("ready", function onReady() {
});
ssh2shell.on ("msg", function onMsg( message ) {
});
ssh2shell.on ("commandProcessing", function onCommandProcessing( command, response, sshObj, stream ) {
});
ssh2shell.on ("commandComplete", function onCommandComplete( command, response, sshObj ) {
});
ssh2shell.on ("commandTimeout", function onCommandTimeout( command, response, stream ,connection ) {
});
ssh2shell.on ("end", function onEnd( sessionText, sshObj ) {
});
ssh2shell.on ("close", function onClose(had_error = void(0)) {
});
ssh2shell.on ("error", function onError(err, type, close = false, callback(err, type) = undefined) {
});
ssh2shell.on ("keyboard-interactive",
function onKeyboardInteractive(name, instructions, instructionsLang, prompts, finish){
});
ssh2shell.on ("data", function onData(data){
});
ssh2shell.on ("pipe", function onPipe(source){
});
ssh2shell.on ("Unpipe", function onUnpipe(source){
});