Node-SSH is an extremely lightweight Promise wrapper for ssh2.
$ npm install node-ssh # If you're using npm
$ yarn add node-ssh # If you're using Yarnconstfs=require('fs')constpath=require('path')const{NodeSSH}=require('node-ssh')constssh=newNodeSSH()ssh.connect({host: 'localhost',username: 'steel',privateKeyPath: '/home/steel/.ssh/id_rsa'})// or with inline privateKeyssh.connect({host: 'localhost',username: 'steel',privateKey: Buffer.from('...')}).then(function(){// Local, Remotessh.putFile('/home/steel/Lab/localPath/fileName','/home/steel/Lab/remotePath/fileName').then(function(){console.log("The File thing is done")},function(error){console.log("Something's wrong")console.log(error)})// Array<Shape('local' => string, 'remote' => string)>ssh.putFiles([{local: '/home/steel/Lab/localPath/fileName',remote: '/home/steel/Lab/remotePath/fileName'}]).then(function(){console.log("The File thing is done")},function(error){console.log("Something's wrong")console.log(error)})// Local, Remotessh.getFile('/home/steel/Lab/localPath','/home/steel/Lab/remotePath').then(function(Contents){console.log("The File's contents were successfully downloaded")},function(error){console.log("Something's wrong")console.log(error)})// Putting entire directoriesconstfailed=[]constsuccessful=[]ssh.putDirectory('/home/steel/Lab','/home/steel/Lab',{recursive: true,concurrency: 10,// ^ WARNING: Not all servers support high concurrency// try a bunch of values and see what works on your servervalidate: function(itemPath){constbaseName=path.basename(itemPath)returnbaseName.substr(0,1)!=='.'&&// do not allow dot filesbaseName!=='node_modules'// do not allow node_modules},tick: function(localPath,remotePath,error){if(error){failed.push(localPath)}else{successful.push(localPath)}}}).then(function(status){console.log('the directory transfer was',status ? 'successful' : 'unsuccessful')console.log('failed transfers',failed.join(', '))console.log('successful transfers',successful.join(', '))})// Commandssh.execCommand('hh_client --json',{cwd:'/var/www'}).then(function(result){console.log('STDOUT: '+result.stdout)console.log('STDERR: '+result.stderr)})// Command output is trimmed by default. Pass noTrim when leading or trailing// whitespace is meaningful, such as reading exact file contents.ssh.execCommand('git show --textconv HEAD:README.md',{noTrim: true}).then(function(result){console.log('STDOUT: '+result.stdout)})// Command with escaped paramsssh.exec('hh_client',['--json'],{cwd: '/var/www',stream: 'stdout',options: {pty: true}}).then(function(result){console.log('STDOUT: '+result)})// With streaming stdout/stderr callbacksssh.exec('hh_client',['--json'],{cwd: '/var/www',onStdout(chunk){console.log('stdoutChunk',chunk.toString('utf8'))},onStderr(chunk){console.log('stderrChunk',chunk.toString('utf8'))},})})Some terminal programs, such as screen or tmux, require a pseudo-TTY. Pass
pty: true through execOptions when running those commands:
constresult=awaitssh.execCommand('screen -r',{execOptions: {pty: true}})console.log('STDOUT: '+result.stdout)console.log('STDERR: '+result.stderr)// API reference in Typescript typing format:importSSH2,{AcceptConnection,Channel,ClientChannel,ConnectConfig,ExecOptions,Prompt,PseudoTtyOptions,RejectConnection,SFTPWrapper,ShellOptions,TcpConnectionDetails,TransferOptions,UNIXConnectionDetails,}from'ssh2'importstreamfrom'stream'// ^ You do NOT need to import these package, these are here for reference of where the// types are coming from.exporttypeConfig=ConnectConfig&{password?: stringprivateKey?: stringprivateKeyPath?: stringtryKeyboard?: booleanonKeyboardInteractive?: (name: string,instructions: string,lang: string,prompts: Prompt[],finish: (responses: string[])=>void,)=>void}exportinterfaceSSHExecCommandOptions{cwd?: stringstdin?: string|stream.ReadableexecOptions?: ExecOptionsencoding?: BufferEncodingnoTrim?: booleanonChannel?: (clientChannel: ClientChannel)=>voidonStdout?: (chunk: Buffer)=>voidonStderr?: (chunk: Buffer)=>void}exportinterfaceSSHExecCommandResponse{stdout: stringstderr: stringcode: number|nullsignal: string|null}exportinterfaceSSHExecOptionsextendsSSHExecCommandOptions{stream?: 'stdout'|'stderr'|'both'}exportinterfaceSSHPutFilesOptions{sftp?: SFTPWrapper|nullconcurrency?: numbertransferOptions?: TransferOptions}exportinterfaceSSHGetPutDirectoryOptionsextendsSSHPutFilesOptions{tick?: (localFile: string,remoteFile: string,error: Error|null)=>voidvalidate?: (path: string)=>booleanrecursive?: boolean}exporttypeSSHMkdirMethod='sftp'|'exec'exporttypeSSHForwardInListener=(details: TcpConnectionDetails,accept: AcceptConnection<ClientChannel>,reject: RejectConnection,)=>voidexportinterfaceSSHForwardInDetails{dispose(): Promise<void>port: number}exporttypeSSHForwardInStreamLocalListener=(info: UNIXConnectionDetails,accept: AcceptConnection,reject: RejectConnection,)=>voidexportinterfaceSSHForwardInStreamLocalDetails{dispose(): Promise<void>}exportclassSSHErrorextendsError{code: string|nullconstructor(message: string,code?: string|null)}exportclassNodeSSH{connection: SSH2.Client|nullconnect(config: Config): Promise<this>isConnected(): booleanrequestShell(options?: PseudoTtyOptions|ShellOptions|false): Promise<ClientChannel>withShell(callback: (channel: ClientChannel)=>Promise<void>,options?: PseudoTtyOptions|ShellOptions|false,): Promise<void>requestSFTP(): Promise<SFTPWrapper>withSFTP(callback: (sftp: SFTPWrapper)=>Promise<void>): Promise<void>execCommand(givenCommand: string,options?: SSHExecCommandOptions): Promise<SSHExecCommandResponse>exec(command: string,parameters: string[],options?: SSHExecOptions&{stream?: 'stdout'|'stderr'},): Promise<string>exec(command: string,parameters: string[],options?: SSHExecOptions&{stream: 'both'},): Promise<SSHExecCommandResponse>mkdir(path: string,method?: SSHMkdirMethod,givenSftp?: SFTPWrapper|null): Promise<void>getFile(localFile: string,remoteFile: string,givenSftp?: SFTPWrapper|null,transferOptions?: TransferOptions|null,): Promise<void>putFile(localFile: string,remoteFile: string,givenSftp?: SFTPWrapper|null,transferOptions?: TransferOptions|null,): Promise<void>putFiles(files: {local: stringremote: string}[],{ concurrency,sftp: givenSftp, transferOptions }?: SSHPutFilesOptions,): Promise<void>putDirectory(localDirectory: string,remoteDirectory: string,{ concurrency,sftp: givenSftp, transferOptions, recursive, tick, validate }?: SSHGetPutDirectoryOptions,): Promise<boolean>getDirectory(localDirectory: string,remoteDirectory: string,{ concurrency,sftp: givenSftp, transferOptions, recursive, tick, validate }?: SSHGetPutDirectoryOptions,): Promise<boolean>forwardIn(remoteAddr: string,remotePort: number,onConnection?: SSHForwardInListener): Promise<SSHForwardInDetails>forwardOut(srcIP: string,srcPort: number,dstIP: string,dstPort: number): Promise<Channel>forwardInStreamLocal(socketPath: string,onConnection?: SSHForwardInStreamLocalListener,): Promise<SSHForwardInStreamLocalDetails>forwardOutStreamLocal(socketPath: string): Promise<Channel>dispose(): void}node-ssh requires extra dependencies while working under Typescript. Please install them as shown below
yarn add --dev @types/ssh2
# OR
npm install --save-dev @types/ssh2
If you're still running into issues, try adding these to your tsconfig.json
{
"compilerOptions": {
"moduleResolution": "node",
"allowSyntheticDefaultImports": true
}
}In some cases you have to enable keyboard-interactive user authentication.
Otherwise you will get an All configured authentication methods failed error.
constpassword='test'ssh.connect({host: 'localhost',username: 'steel',port: 22,
password,tryKeyboard: true,})// Or if you want to add some custom keyboard-interactive logic:ssh.connect({host: 'localhost',username: 'steel',port: 22,tryKeyboard: true,onKeyboardInteractive(name,instructions,instructionsLang,prompts,finish){if(prompts.length>0&&prompts[0].prompt.toLowerCase().includes('password')){finish([password])}}})For further information see: mscdex/ssh2#604
This project is licensed under the terms of MIT license. See the LICENSE file for more info.