├── version ├── examples ├── fixture │ ├── w2d.json │ └── dir │ │ ├── w2d.json │ │ └── subdir │ │ └── w2d.json ├── read-file.js ├── exec.js ├── put-dir.js ├── run.js └── run-multiple.js ├── test ├── travis-ssh.json ├── vagrant-ssh.json └── index.js ├── .travis.yml ├── Vagrantfile ├── .travis-ssh.sh ├── package.json ├── .gitignore ├── test-large-files.js ├── README.md └── index.js /version: -------------------------------------------------------------------------------- 1 | patch 1.0.28 2 | -------------------------------------------------------------------------------- /examples/fixture/w2d.json: -------------------------------------------------------------------------------- 1 | qsdsqd -------------------------------------------------------------------------------- /examples/fixture/dir/w2d.json: -------------------------------------------------------------------------------- 1 | qsdsqd -------------------------------------------------------------------------------- /examples/fixture/dir/subdir/w2d.json: -------------------------------------------------------------------------------- 1 | qsdsqd -------------------------------------------------------------------------------- /test/travis-ssh.json: -------------------------------------------------------------------------------- 1 | { 2 | "localhost":{ 3 | "user":"travis", 4 | "privateKey":"/home/travis/.ssh/id_rsa" 5 | }, 6 | "localhostpwd":{ 7 | "user":"travis", 8 | "privateKey":"/home/travis/.ssh/id_rsa" 9 | } 10 | } -------------------------------------------------------------------------------- /test/vagrant-ssh.json: -------------------------------------------------------------------------------- 1 | { 2 | "localhost":{ 3 | "user":"vagrant", 4 | "port":2222, 5 | "pwd":"vagrant" 6 | }, 7 | "localhostpwd":{ 8 | "user":"vagrant", 9 | "port":2222, 10 | "pwd":"vagrant" 11 | } 12 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: nodejs 2 | node_js: 3 | - "4.1" 4 | - "4.0" 5 | - "0.12" 6 | - "0.11" 7 | - "0.10" 8 | - "0.8" 9 | - "0.6" 10 | - "iojs" 11 | install: 12 | - npm i mocha -g 13 | - npm i 14 | script: 15 | - sh ./.travis-ssh.sh 16 | - npm test 17 | - ps aux | grep tail 18 | sudo: true -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | Vagrant.configure(2) do |config| 2 | 3 | config.vm.define "centos", primary: true do |centos| 4 | centos.vm.box_check_update = false 5 | centos.vm.box_url = "http://opscode-vm-bento.s3.amazonaws.com/vagrant/virtualbox/opscode_centos-7.0_chef-provisionerless.box" 6 | centos.vm.box = "centos" 7 | end 8 | 9 | config.vm.define "ubuntu", primary: true do |ubuntu| 10 | ubuntu.vm.box_check_update = false 11 | ubuntu.vm.box_url = "http://puppet-vagrant-boxes.puppetlabs.com/ubuntu-server-10044-x64-vbox4210-nocm.box" 12 | ubuntu.vm.box = "ubuntu" 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /.travis-ssh.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # from https://github.com/benoitbryon/xal/blob/master/.travis-ssh.sh 4 | 5 | 6 | # Allow ``ssh localhost`` with empty passphrase on travis-ci.org continuous 7 | # integration platform. 8 | 9 | # Make sure we are on Travis. 10 | if [ ! $TRAVIS ]; then 11 | echo "This script is made for travis-ci.org! It cannot run without \$TRAVIS." 12 | exit 1 13 | fi 14 | 15 | # Run SSH service, configure automatic access to localhost. 16 | sudo start ssh 17 | ssh-keygen -t rsa -f ~/.ssh/id_rsa -N "" -q 18 | cat ~/.ssh/id_rsa.pub >>~/.ssh/authorized_keys 19 | ssh-keyscan -t rsa localhost >>~/.ssh/known_hosts 20 | cat << EOF >> ~/.ssh/config 21 | Host localhost 22 | IdentityFile ~/.ssh/id_rsa 23 | EOF -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ssh2-utils", 3 | "version": "1.0.28", 4 | "description": "ssh2 utilities to easy usage of mscdex/ssh2", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "http://github.com/maboiteaspam/ssh2-utils.git" 9 | }, 10 | "scripts": { 11 | "test": "export DEBUG='ssh2-utils'; mocha" 12 | }, 13 | "keywords": [ 14 | "ssh2", 15 | "utitilities" 16 | ], 17 | "author": "maboiteaspam", 18 | "license": "WTF", 19 | "dependencies": { 20 | "async": "1.5.0", 21 | "debug": "2.1.3", 22 | "fs-extra": "0.26.2", 23 | "glob": "6.0.1", 24 | "path": "0.12.7", 25 | "ssh2": "0.4.12", 26 | "through": "2.3.8", 27 | "underscore": "1.8.3", 28 | "underscore.string": "3.2.2" 29 | }, 30 | "devDependencies": { 31 | "node-vagrant-bin": "^1.0.1", 32 | "should": "8.0.1" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Node template 3 | # Logs 4 | logs 5 | *.log 6 | 7 | # Runtime data 8 | pids 9 | *.pid 10 | *.seed 11 | .vagrant/ 12 | 13 | # Directory for instrumented libs generated by jscoverage/JSCover 14 | lib-cov 15 | 16 | # Coverage directory used by tools like istanbul 17 | coverage 18 | 19 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 20 | .grunt 21 | 22 | # node-waf configuration 23 | .lock-wscript 24 | 25 | # Compiled binary addons (http://nodejs.org/api/addons.html) 26 | build/Release 27 | 28 | # Dependency directory 29 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 30 | node_modules 31 | 32 | examples/pwd.json 33 | test/fixtures/ 34 | examples/fixture/dir2 35 | examples/fixture/dir3 36 | examples/fixture/w2d.json2 37 | examples/fixture/w2d.json3 38 | 39 | .idea/ 40 | 41 | .local.json 42 | 43 | .local 44 | -------------------------------------------------------------------------------- /examples/read-file.js: -------------------------------------------------------------------------------- 1 | 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | 5 | var pwd = require('./pwd.json'); 6 | /* 7 | var pwd = { 8 | "localhost":{ 9 | "user":"some", 10 | "pwd":"cred", 11 | "privateKey":"/absolute/path/to/.ssh/id_dsa" 12 | } 13 | }; 14 | */ 15 | 16 | var SSH2Utils = require('../index.js'); 17 | var ssh = new SSH2Utils(); 18 | ssh.log.level = 'verbose'; 19 | 20 | var fixture = __dirname+'/fixture/'; 21 | 22 | // with password 23 | (function(){ 24 | var host = { 25 | 'host':'127.0.0.1', 26 | port: 22, 27 | username: pwd.localhost.user, 28 | password: pwd.localhost.pwd 29 | }; 30 | 31 | ssh.readFile(host,fixture+'/w2d.json',fixture+'/w2d.json2', function(err, server){ 32 | if(err) return console.log(err) 33 | console.log(server) 34 | }); 35 | })(); 36 | 37 | // with key 38 | (function(){ 39 | var host = { 40 | 'host':'localhost', 41 | username: pwd.localhost.user, 42 | privateKey: fs.readFileSync(pwd.localhost.privateKey) // note that ~/ is not recognized 43 | }; 44 | 45 | ssh.readFile(host,fixture+'/w2d.json',fixture+'/w2d.json3', function(err, server){ 46 | if(err) return console.log(err) 47 | console.log(server) 48 | }); 49 | })(); 50 | -------------------------------------------------------------------------------- /examples/exec.js: -------------------------------------------------------------------------------- 1 | 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | 5 | var pwd = require('./pwd.json'); 6 | /* 7 | var pwd = { 8 | "localhost":{ 9 | "user":"some", 10 | "pwd":"cred", 11 | "privateKey":"/absolute/path/to/.ssh/id_dsa" 12 | } 13 | }; 14 | */ 15 | 16 | var SSH2Utils = require('../index.js'); 17 | var ssh = new SSH2Utils(); 18 | ssh.log.level = 'verbose'; 19 | 20 | // with password 21 | (function(){ 22 | var host = { 23 | 'host':'127.0.0.1', 24 | port: 22, 25 | username: pwd.localhost.user, 26 | password: pwd.localhost.pwd 27 | }; 28 | 29 | ssh.exec(host,'ls -alh', function(err, stdout, stderr, server){ 30 | if(err) return console.log(err) 31 | console.log(stdout) 32 | console.log(stderr) 33 | console.log(server) 34 | }); 35 | })(); 36 | 37 | // with key 38 | (function(){ 39 | var host = { 40 | 'host':'localhost', 41 | username: pwd.localhost.user, 42 | privateKey: fs.readFileSync(pwd.localhost.privateKey) // note that ~/ is not recognized 43 | }; 44 | 45 | ssh.exec(host,'ls -alh', function(err, stdout, stderr, server){ 46 | if(err) return console.log(err) 47 | console.log(stdout) 48 | console.log(stderr) 49 | console.log(server) 50 | }); 51 | })(); 52 | -------------------------------------------------------------------------------- /examples/put-dir.js: -------------------------------------------------------------------------------- 1 | 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | 5 | var pwd = require('./pwd.json'); 6 | /* 7 | var pwd = { 8 | "localhost":{ 9 | "user":"some", 10 | "pwd":"cred", 11 | "privateKey":"/absolute/path/to/.ssh/id_dsa" 12 | } 13 | }; 14 | */ 15 | 16 | var SSH2Utils = require('../index.js'); 17 | var ssh = new SSH2Utils(); 18 | ssh.log.level = 'verbose'; 19 | 20 | var fixture = __dirname+'/fixture/'; 21 | 22 | // with password 23 | (function(){ 24 | var host = { 25 | 'host':'127.0.0.1', 26 | port: 22, 27 | username: pwd.localhost.user, 28 | password: pwd.localhost.pwd 29 | }; 30 | 31 | ssh.putDir(host,fixture+'/dir/',fixture+'/dir2/', function(err, stdout, stderr, server){ 32 | if(err) return console.log(err) 33 | console.log(stdout) 34 | console.log(stderr) 35 | console.log(server) 36 | }); 37 | })(); 38 | 39 | // with key 40 | (function(){ 41 | var host = { 42 | 'host':'localhost', 43 | username: pwd.localhost.user, 44 | privateKey: fs.readFileSync(pwd.localhost.privateKey) // note that ~/ is not recognized 45 | }; 46 | 47 | ssh.putDir(host,fixture+'/dir/',fixture+'/dir3/', function(err, stdout, stderr, server){ 48 | if(err) return console.log(err) 49 | console.log(stdout) 50 | console.log(stderr) 51 | console.log(server) 52 | }); 53 | })(); 54 | -------------------------------------------------------------------------------- /examples/run.js: -------------------------------------------------------------------------------- 1 | 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | 5 | var pwd = require('./pwd.json'); 6 | /* 7 | var pwd = { 8 | "localhost":{ 9 | "user":"some", 10 | "pwd":"cred", 11 | "privateKey":"/absolute/path/to/.ssh/id_dsa" 12 | } 13 | }; 14 | */ 15 | 16 | var SSH2Utils = require('../index.js'); 17 | var ssh = new SSH2Utils(); 18 | ssh.log.level = 'verbose'; 19 | 20 | // with password 21 | (function(){ 22 | var host = { 23 | 'host':'127.0.0.1', 24 | port: 22, 25 | username: pwd.localhost.user, 26 | password: pwd.localhost.pwd 27 | }; 28 | 29 | ssh.run(host,'ls -alh', function(err, stream, stderr, server, conn){ 30 | if(err) return console.log(err) 31 | stream.on('data', function(d){ 32 | console.log(d+'') 33 | console.log(server) 34 | stream.write('\nexit\n') 35 | }); 36 | stderr.on('data', function(d){ 37 | console.log(d+'') 38 | }); 39 | stream.on('close', function(){ 40 | conn.end(); 41 | }); 42 | }); 43 | })(); 44 | 45 | // with key 46 | (function(){ 47 | var host = { 48 | 'host':'localhost', 49 | username: pwd.localhost.user, 50 | privateKey: fs.readFileSync(pwd.localhost.privateKey) // note that ~/ is not recognized 51 | }; 52 | 53 | ssh.run(host,'ls -alh', function(err, stream, stderr, server, conn){ 54 | if(err) return console.log(err) 55 | stream.on('data', function(d){ 56 | console.log(d+'') 57 | console.log(server) 58 | stream.write('\nexit\n') 59 | }); 60 | stderr.on('data', function(d){ 61 | console.log(d+'') 62 | }); 63 | stream.on('close', function(){ 64 | conn.end(); 65 | }); 66 | }); 67 | })(); 68 | -------------------------------------------------------------------------------- /examples/run-multiple.js: -------------------------------------------------------------------------------- 1 | 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | 5 | var pwd = require('./pwd.json'); 6 | /* 7 | var pwd = { 8 | "localhost":{ 9 | "user":"some", 10 | "pwd":"cred", 11 | "privateKey":"/absolute/path/to/.ssh/id_dsa" 12 | } 13 | }; 14 | */ 15 | 16 | var SSH2Utils = require('../index.js'); 17 | var ssh = new SSH2Utils(); 18 | ssh.log.level = 'verbose'; 19 | 20 | // with password 21 | (function(){ 22 | var host = { 23 | 'host':'localhost', 24 | username: pwd.localhost.user, 25 | password: pwd.localhost.pwd 26 | }; 27 | 28 | var cmds = [ 29 | 'echo hello', 30 | 'ls', 31 | 'time', 32 | "`All done!`" 33 | ]; 34 | 35 | var onDone = function(sessionText, sshObj){ 36 | console.log('All done') 37 | }; 38 | 39 | var onCommandComplete = function(command, response, server){ 40 | console.log((server.name||server.host)+' ' + require('moment')().format()); 41 | console.log(command); 42 | if(response) console.log(response); 43 | console.log(''); 44 | } 45 | 46 | ssh.runMultiple(host, cmds, onCommandComplete, onDone); 47 | 48 | // or 49 | // ssh.runMultiple(server, cmds, onDone); 50 | })(); 51 | 52 | 53 | 54 | // with key 55 | (function(){ 56 | var host = { 57 | 'host':'localhost', 58 | username: pwd.localhost.user, 59 | privateKey: fs.readFileSync(pwd.localhost.privateKey) // note that ~/ is not recognized 60 | }; 61 | 62 | var cmds = [ 63 | 'echo hello', 64 | 'ls', 65 | 'time', 66 | "`All done!`" 67 | ]; 68 | 69 | var onDone = function(sessionText, sshObj){ 70 | console.log('All done') 71 | }; 72 | 73 | var onCommandComplete = function(command, response, server){ 74 | log.info((server.name||server.host)+' ' + require('moment')().format()); 75 | console.log(command); 76 | if(response) console.log(response); 77 | console.log(''); 78 | } 79 | 80 | ssh.runMultiple(host, cmds, onCommandComplete, onDone); 81 | })(); 82 | -------------------------------------------------------------------------------- /test-large-files.js: -------------------------------------------------------------------------------- 1 | 2 | require('should'); 3 | var async = require('async'); 4 | var Vagrant = require('node-vagrant-bin'); 5 | 6 | var pwd = {}; 7 | if( process.env['TRAVIS'] ) 8 | pwd = require('./test/travis-ssh.json'); 9 | else 10 | pwd = require('./test/vagrant-ssh.json'); 11 | 12 | var SSH2Utils = require('./index.js'); 13 | var ssh = new SSH2Utils(); 14 | 15 | var hostPwd = { 16 | 'host':'127.0.0.1', 17 | port: pwd.localhostpwd.port || 22, 18 | username: pwd.localhostpwd.user, 19 | password: pwd.localhostpwd.pwd || undefined 20 | }; 21 | 22 | var prepareBox = function(done){ 23 | var cmds = [ 24 | 'rm -fr /home/vagrant/sample.txt', 25 | //'dd if=/dev/urandom of=/home/vagrant/sample.txt bs=1M count=1', 26 | 'yes 123456789 | head -1677772 > /home/vagrant/sample.txt', 27 | 'echo end >> /home/vagrant/sample.txt', 28 | 'ls -alh /home/vagrant/sample.txt' 29 | ]; 30 | ssh.exec(hostPwd, cmds, function(err,stdout,stderr,server,conn){ 31 | if(err) console.error(err); 32 | conn.end(); 33 | done(); 34 | }); 35 | }; 36 | var readUsingAStream = function(done){ 37 | ssh.streamReadFile(hostPwd, '/home/vagrant/sample.txt', function(err, stream,server,conn){ 38 | if(err) console.error(err); 39 | stream.on('data', function(){}); 40 | stream.on('close', function(){ 41 | conn.end(); 42 | done(); 43 | }); 44 | }); 45 | }; 46 | var readUsingExec = function(done){ 47 | ssh.streamReadFileSudo(hostPwd, '/home/vagrant/sample.txt', function(err, stream,server,conn){ 48 | if(err) console.error(err); 49 | stream.on('close', function(){ 50 | conn.end(); 51 | done(); 52 | }); 53 | }); 54 | }; 55 | 56 | var t0; 57 | var t1; 58 | var t2; 59 | var t3; 60 | async.series([ 61 | function(next){ 62 | var vagrant = new Vagrant(); 63 | vagrant.isRunning(function(running){ 64 | if(running===false){ 65 | vagrant.up('precise64',function(err,booted){ 66 | next(); 67 | }); 68 | }else{ 69 | next(); 70 | } 71 | }); 72 | }, 73 | function(next){ 74 | t0 = Date.now(); 75 | prepareBox(function(){ 76 | t1 = Date.now(); 77 | console.log('boot %s ms', t1-t0); 78 | next(); 79 | }); 80 | }, 81 | function(next){ 82 | readUsingAStream(function(){ 83 | t2 = Date.now(); 84 | console.log('readUsingAStream %s ms', t2-t1); 85 | next(); 86 | }); 87 | }, 88 | function(next){ 89 | readUsingExec(function(){ 90 | t3 = Date.now(); 91 | console.log('readUsingExec %s ms', t3-t2); 92 | next(); 93 | }); 94 | } 95 | ]); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SSH2Utils [![Build Status](https://travis-ci.org/maboiteaspam/ssh2-utils.svg?branch=master)](https://travis-ci.org/maboiteaspam/ssh2-utils) 2 | 3 | A library to ease use of excellent modules ssh2. 4 | 5 | Provide a set of methods to exec/run/getFile/putFile/getDir/putDir. 6 | 7 | --------------------------------------- 8 | 9 | 10 | # Install 11 | 12 | ```npm i ssh2-utils --save``` 13 | 14 | --------------------------------------- 15 | 16 | 17 | # Changes 18 | 19 | - 2015-12-17 : Tried to update to latest dependencies. Got problems.. Need to pass travis tests. Need to jump to 2.x after. 20 | 21 | # Documentation 22 | 23 | Automatic source code documentation generation 24 | 25 | is supported by jsdoc at https://maboiteaspam.github.io/ssh2-utils/docs/ 26 | 27 | Automatic tests documentation generation 28 | 29 | is support by mocha https://github.com/maboiteaspam/ssh2-utils/tree/gh-pages/mocha-tests.md 30 | 31 | 32 | ### API 33 | 34 | * [`SSH2Utils`](http://maboiteaspam.github.io/ssh2-utils/docs/SSH2Utils.html) 35 | * [`open`](http://maboiteaspam.github.io/ssh2-utils/docs/SSH2Utils.html#open) 36 | * [`exec`](http://maboiteaspam.github.io/ssh2-utils/docs/SSH2Utils.html#exec) 37 | * [`run`](http://maboiteaspam.github.io/ssh2-utils/docs/SSH2Utils.html#run) 38 | * [`runMultiple`](http://maboiteaspam.github.io/ssh2-utils/docs/SSH2Utils.html#runMultiple) 39 | * [`mktemp`](http://maboiteaspam.github.io/ssh2-utils/docs/SSH2Utils.html#mktemp) 40 | * [`readFile`](http://maboiteaspam.github.io/ssh2-utils/docs/SSH2Utils.html#readFile) 41 | * [`readFileSudo`](http://maboiteaspam.github.io/ssh2-utils/docs/SSH2Utils.html#readFileSudo) 42 | * [`getFile`](http://maboiteaspam.github.io/ssh2-utils/docs/SSH2Utils.html#getFile) 43 | * [`putDir`](http://maboiteaspam.github.io/ssh2-utils/docs/SSH2Utils.html#putDir) 44 | * [`putDirSudo`](http://maboiteaspam.github.io/ssh2-utils/docs/SSH2Utils.html#putDirSudo) 45 | * [`readDir`](http://maboiteaspam.github.io/ssh2-utils/docs/SSH2Utils.html#readDir) 46 | * [`putFile`](http://maboiteaspam.github.io/ssh2-utils/docs/SSH2Utils.html#putFile) 47 | * [`putFileSudo`](http://maboiteaspam.github.io/ssh2-utils/docs/SSH2Utils.html#putFileSudo) 48 | * [`mkdir`](http://maboiteaspam.github.io/ssh2-utils/docs/SSH2Utils.html#mkdir) 49 | * [`mkdirSudo`](http://maboiteaspam.github.io/ssh2-utils/docs/SSH2Utils.html#mkdirSudo) 50 | * [`rmdir`](http://maboiteaspam.github.io/ssh2-utils/docs/SSH2Utils.html#rmdir) 51 | * [`rmdirSudo`](http://maboiteaspam.github.io/ssh2-utils/docs/SSH2Utils.html#rmdirSudo) 52 | * [`writeFile`](http://maboiteaspam.github.io/ssh2-utils/docs/SSH2Utils.html#writeFile) 53 | * [`writeFileSudo`](http://maboiteaspam.github.io/ssh2-utils/docs/SSH2Utils.html#writeFileSudo) 54 | * [`getDir`](http://maboiteaspam.github.io/ssh2-utils/docs/SSH2Utils.html#getDir) 55 | * [`fileExists`](http://maboiteaspam.github.io/ssh2-utils/docs/SSH2Utils.html#fileExists) 56 | * [`fileExistsSudo`](http://maboiteaspam.github.io/ssh2-utils/docs/SSH2Utils.html#fileExistsSudo) 57 | * [`ensureFileContains`](http://maboiteaspam.github.io/ssh2-utils/docs/SSH2Utils.html#ensureFileContains) 58 | * [`ensureFileContainsSudo`](http://maboiteaspam.github.io/ssh2-utils/docs/SSH2Utils.html#ensureFileContainsSudo) 59 | * [`ensureEmptyDir`](http://maboiteaspam.github.io/ssh2-utils/docs/SSH2Utils.html#ensureEmptyDir) 60 | * [`ensureEmptyDirSudo`](http://maboiteaspam.github.io/ssh2-utils/docs/SSH2Utils.html#ensureEmptyDirSudo) 61 | * [`ensureOwnership`](http://maboiteaspam.github.io/ssh2-utils/docs/SSH2Utils.html#ensureOwnership) 62 | * [`streamReadFile`](http://maboiteaspam.github.io/ssh2-utils/docs/SSH2Utils.html#streamReadFile) 63 | * [`streamReadFileSudo`](http://maboiteaspam.github.io/ssh2-utils/docs/SSH2Utils.html#streamReadFileSudo) 64 | 65 | --------------------------------------- 66 | 67 | ### Examples 68 | 69 | 70 | ### SSH2Utils.exec(server, cmd, callback) 71 | 72 | Execute a command on remote server and return its output. 73 | 74 | __Arguments__ 75 | 76 | * `server` - An object of ssh server credentials. 77 | * `cmd` - A command line to execute on remote. 78 | * `callback(err,stdout,stderr,server,conn)` - A callback called on command line completion. 79 | * `err` is a Boolean. 80 | * `stdout` `stderr` are String. 81 | * `server` An ssh server credentials object. 82 | * `conn` An ssh Client object. 83 | 84 | __Examples__ 85 | 86 | ```js 87 | var SSH2Utils = require('ssh2-utils'); 88 | var ssh = new SSH2Utils(); 89 | 90 | var server = {host: "localhost", username:"user", password:"pwd" }; 91 | 92 | ssh.exec(server, 'ls', function(err,stdout,stderr){ 93 | if(err) console.log(err); 94 | console.log(stdout); 95 | console.log(stderr); 96 | }); 97 | ``` 98 | 99 | 100 | 101 | ### SSH2Utils.run(server, cmd, callback) 102 | 103 | Execute a command on remote server and return its streams. 104 | 105 | __Arguments__ 106 | 107 | * `server` - An object of ssh server credentials. 108 | * `cmd` - A command line to execute on remote. 109 | * `callback(err,stdout,stderr,server,conn)` - A callback called on command line sent. 110 | * `err` isa Boolean. 111 | * `stdout` `stderr` are Streams. 112 | * `server` An ssh server credentials object. 113 | * `conn` An ssh Client object. 114 | 115 | __Examples__ 116 | 117 | ```js 118 | var SSH2Utils = require('ssh2-utils'); 119 | var ssh = new SSH2Utils(); 120 | 121 | var server = {host: "localhost", username:"user", password:"pwd" }; 122 | 123 | ssh.run(server, ['ls','time'], function(err,stdout,stderr,server,conn){ 124 | if(err) console.log(err); 125 | stdout.on('data', function(){ 126 | console.log(''+data); 127 | }); 128 | stderr.on('data', function(){ 129 | console.log(''+data); 130 | }); 131 | stdout.on('close',function(){ 132 | conn.end(); 133 | }); 134 | }); 135 | ``` 136 | 137 | 138 | 139 | ### SSH2Utils.runMultiple(server, cmds, onCmdCplt, onDone) 140 | ##### SSH2Utils.runMultiple(server, cmds, onDone) 141 | 142 | Execute a series of command on remote server and returns their output. 143 | 144 | __Arguments__ 145 | 146 | * `server` - An object of ssh server credentials. 147 | * `cmds` - An array of commands line to execute on remote. 148 | * `onCmdCplt(command, response, server)` - A callback called on command line completion. 149 | * `command` the completed command line. 150 | * `response` the completed command line response. 151 | * `server` An ssh server credentials object. 152 | * `onDone(sessionText, sshObj)` - A callback called on session completion. 153 | * `err` an Error. 154 | * `sessionText` a String. 155 | * `sshObj` An ssh Client object. 156 | 157 | __Examples__ 158 | 159 | ```js 160 | var SSH2Utils = require('ssh2-utils'); 161 | var ssh = new SSH2Utils(); 162 | 163 | var server = {host: "localhost", username:"user", password:"pwd" }; 164 | 165 | ssh.runMultiple(server, ['ls','time'], function(sessionText, sshObj){ 166 | console.log(sessionText); 167 | }); 168 | ``` 169 | 170 | 171 | 172 | ### SSH2Utils.getFile(server, remoteFile, localPath, callback) 173 | 174 | Download a file from remote to local. 175 | 176 | __Arguments__ 177 | 178 | * `server` - An object of ssh server credentials. 179 | * `remoteFile` - A remote file path to read. 180 | * `localPath` - A local file path to write. 181 | * `callback(err)` - A callback called on command line completion. 182 | * `err` is an Error. 183 | * `server` An ssh server credentials object. 184 | * `conn` An ssh Client object. 185 | 186 | __Examples__ 187 | 188 | ```js 189 | var SSH2Utils = require('ssh2-utils'); 190 | var ssh = new SSH2Utils(); 191 | 192 | var server = {host: "localhost", username:"user", password:"pwd" }; 193 | 194 | ssh.getFile(server,'/tmp/from_some_remote','/tmp/to_some_local', function(err){ 195 | if(err) console.log(err); 196 | }); 197 | ``` 198 | 199 | 200 | 201 | ### SSH2Utils.putFile(server, localFile, remoteFile, callback) 202 | 203 | Put a file from local to remote 204 | 205 | __Arguments__ 206 | 207 | * `server` - An object of ssh server credentials. 208 | * `localFile` - A local file path to write. 209 | * `remoteFile` - A remote file path to read. 210 | * `callback(err)` - A callback called on command line completion. 211 | * `err` is an Error. 212 | * `server` An ssh server credentials object. 213 | * `conn` An ssh Client object. 214 | 215 | __Examples__ 216 | 217 | ```js 218 | var SSH2Utils = require('ssh2-utils'); 219 | var ssh = new SSH2Utils(); 220 | 221 | var server = {host: "localhost", username:"user", password:"pwd" }; 222 | 223 | ssh.putFile(server,'/tmp/to_some_local','/tmp/from_some_remote', function(err){ 224 | if(err) console.log(err); 225 | }); 226 | ``` 227 | 228 | 229 | 230 | ### SSH2Utils.putDir(server, localPath, remoteFile, callback) 231 | 232 | Put a local directory contents to a remote path. 233 | 234 | __Arguments__ 235 | 236 | * `server` - An object of ssh server credentials. 237 | * `localPath` - A local file path to write. 238 | * `remoteFile` - A remote file path to read. 239 | * `callback(err)` - A callback called on command line completion. 240 | * `err` is an Error. 241 | * `server` An ssh server credentials object. 242 | * `conn` An ssh Client object. 243 | 244 | __Examples__ 245 | 246 | ```js 247 | var SSH2Utils = require('ssh2-utils'); 248 | var ssh = new SSH2Utils(); 249 | 250 | var server = {host: "localhost", username:"user", password:"pwd" }; 251 | 252 | ssh.putDir(server,'/tmp/from_some_local','/tmp/to_some_remote', function(err){ 253 | if(err) console.log(err); 254 | }); 255 | ``` 256 | 257 | 258 | 259 | ### SSH2Utils.mkdir(server, remotePath, callback) 260 | 261 | Create a directory at remote path. 262 | 263 | __Arguments__ 264 | 265 | * `server` - An object of ssh server credentials. 266 | * `remotePath` - A remote path to create. 267 | * `callback(err)` - A callback called on command line completion. 268 | * `err` is an Error. 269 | * `server` An ssh server credentials object. 270 | * `conn` An ssh Client object. 271 | 272 | __Examples__ 273 | 274 | ```js 275 | var SSH2Utils = require('ssh2-utils'); 276 | var ssh = new SSH2Utils(); 277 | 278 | var server = {host: "localhost", username:"user", password:"pwd" }; 279 | 280 | ssh.mkdir(server,'/tmp/to_some_remote', function(err){ 281 | if(err) console.log(err); 282 | }); 283 | ``` 284 | 285 | 286 | 287 | ### SSH2Utils.rmdir(server, remotePath, callback) 288 | 289 | Deletes a directory at remote path. 290 | 291 | Effectively performs ``rm -fr remotePath``. 292 | 293 | __Arguments__ 294 | 295 | * `server` - An object of ssh server credentials. 296 | * `remotePath` - A remote path to delete. 297 | * `callback(err)` - A callback called on command line completion. 298 | * `err` is an Error. 299 | * `server` An ssh server credentials object. 300 | * `conn` An ssh Client object. 301 | 302 | __Examples__ 303 | 304 | ```js 305 | var SSH2Utils = require('ssh2-utils'); 306 | var ssh = new SSH2Utils(); 307 | 308 | var server = {host: "localhost", username:"user", password:"pwd" }; 309 | 310 | ssh.rmdir(server,'/tmp/to_some_remote', function(err){ 311 | if(err) console.log(err); 312 | }); 313 | ``` 314 | 315 | 316 | 317 | ### SSH2Utils.fileExists(server, remotePath, callback) 318 | 319 | Tests a path on remote. 320 | 321 | __Arguments__ 322 | 323 | * `server` - An object of ssh server credentials. 324 | * `remotePath` - A remote path to tests. 325 | * `callback(err)` - A callback called on command line completion. 326 | * `err` is an Error if file does not exists. 327 | * `server` An ssh server credentials object. 328 | * `conn` An ssh Client object. 329 | 330 | __Examples__ 331 | 332 | ```js 333 | var SSH2Utils = require('ssh2-utils'); 334 | var ssh = new SSH2Utils(); 335 | 336 | var server = {host: "localhost", username:"user", password:"pwd" }; 337 | 338 | ssh.fileExists(server,'/tmp/to_some_remote', function(err){ 339 | if(err) console.log(err); 340 | }); 341 | ``` 342 | 343 | 344 | --------------------------------------- 345 | 346 | # Suggestions 347 | 348 | On linux you may want to edit `/etc/ssh/ssh_config` and append 349 | ``` 350 | Host 127.0.0.1 351 | CheckHostIP no 352 | StrictHostKeyChecking no 353 | UserKnownHostsFile=/dev/null 354 | ``` 355 | 356 | This will help to have multiple vagrant box installed on the same machine. 357 | 358 | ------ 359 | 360 | On fedora you may want to create `/etc/polkit-1/rules.d/10.virt.rules` and add 361 | ``` 362 | polkit.addRule(function(action, subject) { 363 | polkit.log("action=" + action); 364 | polkit.log("subject=" + subject); 365 | var now = new Date(); 366 | polkit.log("now=" + now) 367 | if ((action.id == "org.libvirt.unix.manage" 368 | || action.id == "org.libvirt.unix.monitor") 369 | 370 | && subject.isInGroup("~~your username group~~") // <--- change HERE 371 | 372 | ) { 373 | return polkit.Result.YES; 374 | } 375 | return null; 376 | }); 377 | ``` 378 | 379 | This will help to prevent the system from asking the password. 380 | 381 | --------------------------------------- 382 | 383 | # Status 384 | 385 | In development. It needs some tests. some more methods implementation. 386 | 387 | --------------------------------------- 388 | 389 | # Test 390 | 391 | For fedora users, use `virtualbox`, 392 | 393 | ``` 394 | sudo dnf install vagrant 395 | # see http://www.if-not-true-then-false.com/2010/install-virtualbox-with-yum-on-fedora-centos-red-hat-rhel/ 396 | export VAGRANT_DEFAULT_PROVIDER=virtualbox 397 | mocha 398 | ``` 399 | 400 | or help me get started on `libvirt` to port forward on `vagrant@localhost -p 2222` 401 | 402 | ## More 403 | 404 | - https://github.com/mscdex/ssh2/ 405 | - https://github.com/maboiteaspam/check-machine 406 | - https://github.com/maboiteaspam/diff-machines 407 | 408 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | require('should'); 2 | var fs = require('fs-extra'); 3 | var _ = require('underscore'); 4 | var Vagrant = require('node-vagrant-bin'); 5 | 6 | var pwd = {}; 7 | if( process.env['TRAVIS'] ) 8 | pwd = require('./travis-ssh.json'); 9 | else 10 | pwd = require('./vagrant-ssh.json'); 11 | 12 | var SSH2Utils = require('../index.js'); 13 | var ssh = new SSH2Utils(); 14 | 15 | 16 | // use differnt credz to tests different scenarios 17 | var hostKey = { 18 | 'host': '127.0.0.1', 19 | 'port': pwd.localhost.port || 22, 20 | 'username': pwd.localhost.user, 21 | 'password': pwd.localhost.pwd || undefined, 22 | 'privateKey': pwd.localhost.privateKey?fs.readFileSync(pwd.localhost.privateKey):null 23 | }; 24 | 25 | var hostPwd = { 26 | 'host': '127.0.0.1', 27 | 'port': pwd.localhostpwd.port || 22, 28 | 'username': pwd.localhostpwd.user, 29 | 'password': pwd.localhostpwd.pwd || undefined 30 | }; 31 | 32 | // travis does not support password based auth 33 | if( process.env['TRAVIS'] ){ 34 | hostPwd.privateKey = hostKey.privateKey; 35 | } 36 | 37 | 38 | // in local, stop, start vagrant 39 | if( !process.env['TRAVIS'] ){ 40 | 41 | var vagrant = new Vagrant({ 42 | provider: 'virtualbox' 43 | }); 44 | 45 | var hasBooted = true; 46 | 47 | before(function(done){ 48 | this.timeout(50000); 49 | vagrant.isRunning(function(running){ 50 | if(running===false){ 51 | vagrant.up('precise64',function(err,booted){ 52 | hasBooted = booted; 53 | done(); 54 | }); 55 | }else{ 56 | hasBooted = false; 57 | done(); 58 | } 59 | }); 60 | }); 61 | 62 | after(function(done){ 63 | this.timeout(50000); 64 | vagrant.isRunning(function(running){ 65 | if(hasBooted){ 66 | vagrant.halt(function(){ 67 | done(); 68 | }); 69 | } else { 70 | done(); 71 | } 72 | }); 73 | }); 74 | 75 | } 76 | 77 | // the path to store files on the remote 78 | var tmpRemotePath = '/tmp/tmp_remote'; 79 | // the local path to et fixtures from 80 | var fixturePath = __dirname + '/fixtures/'; 81 | 82 | 83 | describe('ident', function(){ 84 | this.timeout(10000); 85 | it('exec can fail properly with password', function(done){ 86 | var wrongHost = { 87 | 'host':hostPwd.host, 88 | port: hostPwd.port, 89 | username: 'wrong', 90 | password: 'credentials' 91 | }; 92 | ssh.exec(wrongHost, 'ls -alh', function(err, stdout, stderr){ 93 | (!!err).should.be.true; 94 | (stdout).should.be.empty; 95 | (err.message).should.match(/failed/); 96 | (stderr).should.match(/failed/); 97 | done(); 98 | }); 99 | }); 100 | it('run can fail properly with private key', function(done){ 101 | var wrongHost = { 102 | 'host':hostKey.host, 103 | port: hostKey.port, 104 | username: 'wrong', 105 | privateKey: hostKey.privateKey 106 | }; 107 | ssh.run(wrongHost, 'ls -alh', function(err, stdout, stderr){ 108 | (!!err).should.be.true; 109 | (stdout===null).should.be.true; 110 | (err.message).should.match(/failed/); 111 | (stderr).should.match(/failed/); 112 | done(); 113 | }); 114 | }); 115 | }); 116 | 117 | describe('exec', function(){ 118 | this.timeout(10000); 119 | it('can execute command', function(done){ 120 | ssh.exec(hostPwd,'ls -alh /var/log/', function(err, stdout, stderr, server, conn){ 121 | (!!err).should.be.false; 122 | stdout.should.match(/root/); 123 | stderr.should.be.empty; 124 | conn.end(); 125 | done(); 126 | }); 127 | }); 128 | it('can execute command with private key', function(done){ 129 | ssh.exec(hostKey,'ls -alh /var/log/', function(err, stdout, stderr, server, conn){ 130 | (!!err).should.be.false; 131 | stdout.should.match(/root/); 132 | stderr.should.be.empty; 133 | conn.end(); 134 | done() 135 | }); 136 | }); 137 | it('can execute sudo command', function(done){ 138 | this.timeout(50000); 139 | ssh.exec(hostPwd,'sudo ls -alh', function(err, stdout, stderr, server, conn){ 140 | (!!err).should.be.false; 141 | stdout.should.match(new RegExp(server.username)); 142 | stderr.should.be.empty; 143 | // re use connection 144 | ssh.exec(conn,'sudo ls -alh', function(err, stdout, stderr){ 145 | (!!err).should.be.false; 146 | stdout.should.match(new RegExp(server.username)); 147 | stderr.should.be.empty; 148 | conn.end(); 149 | done(); 150 | }); 151 | }); 152 | }); 153 | it('can connect with private key and execute sudo command', function(done){ 154 | this.timeout(50000); 155 | ssh.exec(hostKey,'sudo ls -alh', function(err, stdout, stderr, server, conn){ 156 | (!!err).should.be.false; 157 | stdout.should.match(new RegExp(server.username)); 158 | stderr.should.be.empty; 159 | // re use connection 160 | ssh.exec(conn,'sudo ls -alh', function(err, stdout, stderr){ 161 | (!!err).should.be.false; 162 | stdout.should.match(new RegExp(server.username)); 163 | stderr.should.be.empty; 164 | conn.end(); 165 | done(); 166 | }); 167 | }); 168 | }); 169 | it('can fail properly', function(done){ 170 | ssh.exec(hostPwd,'ls -alh /nofile', function(err, stdout, stderr, server, conn){ 171 | (!!err).should.be.true; 172 | stderr.should.match(/No such file or directory/); 173 | err.message.should.match(/No such file or directory/); 174 | stdout.should.be.empty; 175 | conn.end(); 176 | done(); 177 | }); 178 | }); 179 | it('can fail properly', function(done){ 180 | ssh.exec(hostPwd, 'dsscdc', function(err, stdout, stderr, server, conn){ 181 | (!!err).should.be.true; 182 | stderr.should.match(/command not found/); 183 | err.message.should.match(/command not found/); 184 | stdout.should.be.empty; 185 | conn.end(); 186 | done(); 187 | }); 188 | }); 189 | it('can fail correctly when it can t execute a command', function(done){ 190 | ssh.exec(hostPwd, 'echo some >> /root/cannot', function(err,stdout,stderr, server, conn){ 191 | (!!err).should.be.true; 192 | err.message.should.match(/Permission denied/); 193 | conn.end(); 194 | done(); 195 | }); 196 | }); 197 | }); 198 | 199 | describe('exec multiple', function(){ 200 | this.timeout(10000); 201 | it('can execute multiple commands', function(done){ 202 | ssh.exec(hostPwd,['ls', 'ls -alh /var/log/'], function(err, stdout, stderr, server, conn){ 203 | (!!err).should.be.false; 204 | stdout.should.match(/root/); 205 | stderr.should.be.empty; 206 | conn.end(); 207 | done() 208 | }); 209 | }); 210 | it('can execute multiple sudo commands', function(done){ 211 | ssh.exec(hostPwd,['sudo ls', 'sudo ls -alh /var/log/'], function(err, stdout, stderr, server, conn){ 212 | (!!err).should.be.false; 213 | stdout.should.match(/root/); 214 | stderr.should.be.empty; 215 | conn.end(); 216 | done() 217 | }); 218 | }); 219 | it('can capture multiple outputs', function(done){ 220 | var doneCnt = 0; 221 | var doneEach = function(){ 222 | doneCnt++; 223 | }; 224 | ssh.exec(hostPwd,['ls', 'ls -alh /var/log/'], doneEach, function(err, stdout, stderr, server, conn){ 225 | (!!err).should.be.false; 226 | stdout.should.match(/root/); 227 | stderr.should.be.empty; 228 | doneCnt.should.eql(2); 229 | conn.end(); 230 | done() 231 | }); 232 | }); 233 | it('can capture multiple sudo outputs', function(done){ 234 | var doneCnt = 0; 235 | var doneEach = function(){ 236 | doneCnt++; 237 | }; 238 | ssh.exec(hostPwd,['sudo ls', 'sudo ls -alh /var/log/'], doneEach, function(err, stdout, stderr, server, conn){ 239 | (!!err).should.be.false; 240 | stdout.should.match(/root/); 241 | stderr.should.be.empty; 242 | doneCnt.should.eql(2); 243 | conn.end(); 244 | done() 245 | }); 246 | }); 247 | it('can fail properly', function(done){ 248 | ssh.exec(hostPwd, ['ls', 'ls -alh /nofile', 'ls -alh /var/log/'], function(err, stdout, stderr, server, conn){ 249 | (!!err).should.be.false; 250 | stdout.should.match(/root/); 251 | stderr.should.match(/No such file or directory/); 252 | conn.end(); 253 | done(); 254 | }); 255 | }); 256 | it('can fail properly', function(done){ 257 | var doneCnt = 0; 258 | var failedCnt = 0; 259 | var doneEach = function(err){ 260 | doneCnt++; 261 | if(err) failedCnt++; 262 | }; 263 | ssh.exec(hostPwd, ['ls', 'ls -alh /nofile', 'ls -alh /var/log/'], doneEach, function(err, stdout, stderr, server, conn){ 264 | (!!err).should.be.false; 265 | stdout.should.match(/root/); 266 | stderr.should.match(/No such file or directory/); 267 | doneCnt.should.eql(3); 268 | failedCnt.should.eql(1); 269 | conn.end(); 270 | done(); 271 | }); 272 | }); 273 | it('can process commands in order', function(done){ 274 | var cmds = [ 275 | 'echo "processed"', 276 | 'echo "in"', 277 | 'echo "order"' 278 | ]; 279 | ssh.exec(hostPwd, cmds, function(err, stdout, stderr, server, conn){ 280 | (!!err).should.be.false; 281 | stdout.replace(/\n/g, ' ').should.eql('processed in order '); 282 | conn.end(); 283 | done(); 284 | }); 285 | }); 286 | }); 287 | 288 | describe('run', function(){ 289 | this.timeout(10000); 290 | it('can execute sudo command with password', function(done){ 291 | var cmds = [ 292 | //'sudo tail -f /var/log/{auth.log,secure}', 293 | 'sudo tail -f /var/log/secure', 294 | 'sudo tail -f /var/log/auth.log' 295 | ]; 296 | ssh.run(hostPwd, cmds, function(err, stdouts, stderrs, server, conn){ 297 | (!!err).should.be.false; 298 | var stdout = ''; 299 | stdouts.on('data', function(data){ 300 | stdout+=''+data; 301 | }); 302 | setTimeout(function(){ 303 | // re use connection 304 | ssh.run(conn,'ls -alh /var/log/', function(err2, stdout2){ 305 | stdout2.on('data', function(data){ 306 | data.toString().should.match(/root/); 307 | stdout.toString().should.match(/sshd/); 308 | conn.end(); 309 | done(); 310 | }); 311 | }); 312 | },500); 313 | }); 314 | }); 315 | it('can execute sudo command with key', function(done){ 316 | var cmds = [ 317 | //'sudo tail -f /var/log/{auth.log,secure}', 318 | 'sudo tail -f /var/log/secure', 319 | 'sudo tail -f /var/log/auth.log' 320 | ]; 321 | ssh.run(hostKey, cmds, function(err, stdouts, stderrs, server, conn){ 322 | (!!err).should.be.false; 323 | var stdout = ''; 324 | stdouts.on('data', function(data){ 325 | stdout+=''+data; 326 | }); 327 | setTimeout(function(){ 328 | // re use connection 329 | ssh.run(conn,'ls -alh /var/log/', function(err2, stdout2){ 330 | stdout2.on('data', _.debounce(function(data){ 331 | data.toString().should.match(/root/); 332 | stdout.toString().should.match(/sshd/); 333 | conn.end(); 334 | done(); 335 | }, 500 ) ); 336 | }); 337 | },500); 338 | }); 339 | }); 340 | it('can fail properly', function(done){ 341 | ssh.run(hostPwd,'ls -alh /var/log/nofile', function(err, stdouts, stderrs, server, conn){ 342 | var stdout = ''; 343 | var stderr = ''; 344 | stdouts.on('data', function(data){ 345 | stdout+=''+data; 346 | }); 347 | stderrs.on('data', function(data){ 348 | stderr+=''+data; 349 | }); 350 | stdouts.on('close', function(){ 351 | stderr.should.match(/No such file or directory/) 352 | stdout.should.be.empty 353 | conn.end(); 354 | done(); 355 | }); 356 | (!!err).should.be.false; 357 | }); 358 | }); 359 | it('can fail properly', function(done){ 360 | ssh.run(hostPwd, 'dsscdc', function(err, stdouts, stderrs, server, conn){ 361 | var stdout = ''; 362 | var stderr = ''; 363 | stdouts.on('data', function(data){ 364 | stdout+=''+data; 365 | }); 366 | stderrs.on('data', function(data){ 367 | stderr+=''+data; 368 | }); 369 | stdouts.on('close', function(){ 370 | stderr.should.match(/command not found/); 371 | stdout.should.be.empty; 372 | conn.end(); 373 | done(); 374 | }); 375 | (!!err).should.be.false; 376 | }); 377 | }); 378 | }); 379 | 380 | describe('run multiple', function(){ 381 | this.timeout(10000); 382 | it('can execute multiple commands with sudo mixin using password', function(done){ 383 | var cmds = [ 384 | 'sudo ls -alh /var/log/{auth.log,secure}', 385 | 'sudo ls -alh /var/log/{auth.log,secure}', 386 | 'ls -alh /var/log/' 387 | ]; 388 | ssh.run(hostPwd, cmds, function(err, stdouts, stderrs, server, conn){ 389 | (!!err).should.be.false; 390 | var stdout = ''; 391 | stdouts.on('data', function(data){ 392 | stdout+=''+data; 393 | }); 394 | 395 | stdouts.on('close', function(data){ 396 | stdout.should.match(/root/); 397 | stdout.should.match(/total/); 398 | conn.end(); 399 | done(); 400 | }); 401 | 402 | }); 403 | }); 404 | it('can execute multiple commands with sudo mixin using key', function(done){ 405 | var cmds = [ 406 | 'sudo ls -alh /var/log/{auth.log,secure}', 407 | 'sudo ls -alh /var/log/{auth.log,secure}', 408 | 'ls -alh /var/log/' 409 | ]; 410 | ssh.run(hostKey, cmds, function(err, stdouts, stderrs, server, conn){ 411 | (!!err).should.be.false; 412 | var stdout = ''; 413 | stdouts.on('data', function(data){ 414 | stdout+=''+data; 415 | }); 416 | stdouts.on('close', function(){ 417 | stdout.should.match(/root/); 418 | stdout.should.match(/total/); 419 | conn.end(); 420 | done(); 421 | }); 422 | }); 423 | }); 424 | it('can fail properly', function(done){ 425 | var cmds = [ 426 | 'sudo tail -f /var/log/secure', 427 | 'sudo tail -f /var/log/auth.log', 428 | 'ls -alh /var/log/', 429 | 'ls -alh /var/log/nofile' 430 | ]; 431 | ssh.run(hostPwd, cmds, function(err, stdouts, stderrs, server, conn){ 432 | (!!err).should.be.false; 433 | var stdout = ''; 434 | var stderr = ''; 435 | stdouts.on('data', function(data){ 436 | stdout+=''+data; 437 | }); 438 | stderrs.on('data', function(data){ 439 | stderr+=''+data; 440 | }); 441 | stdouts.on('close', function(){ 442 | stderr.should.match(/No such file or directory/); 443 | stdout.should.match(/root/); 444 | stdout.should.match(/total/); 445 | conn.end(); 446 | done(); 447 | }); 448 | }); 449 | }); 450 | it('can fail properly', function(done){ 451 | var cmds = [ 452 | 'sudo tail -f ~/.bashrc', 453 | 'sudo tail -f /var/log/secure', 454 | 'sudo tail -f /var/log/auth.log', 455 | 'ls -alh /var/log/', 456 | 'dsscdc' 457 | ]; 458 | ssh.run(hostPwd, cmds, function(err, stdouts, stderrs, server, conn){ 459 | var stdout = ''; 460 | var stderr = ''; 461 | stdouts.on('data', function(data){ 462 | stdout+=''+data; 463 | }); 464 | stderrs.on('data', function(data){ 465 | stderr+=''+data; 466 | }); 467 | stdouts.on('close', function(){ 468 | stderr.should.match(/command not found/); 469 | stdout.should.match(/(pam_unix|debug)/); 470 | stdout.should.match(/root/); 471 | stdout.should.match(/total/); 472 | conn.end(); 473 | done(); 474 | }); 475 | (!!err).should.be.false; 476 | }); 477 | }); 478 | }); 479 | 480 | describe('sftp ensureEmptyDir', function(){ 481 | this.timeout(10000); 482 | 483 | before(function(done){ 484 | fs.mkdirsSync(fixturePath); 485 | ssh.rmdirSudo(hostPwd, '/home/vagrant/putdir-test', function(err, server, conn){ 486 | ssh.rmdirSudo(conn, '/tmp/empty-dir-sudo', function(err, server, conn){ 487 | ssh.rmdirSudo(conn, '/tmp/empty-dir-sudo-fail', function(err, stdout, stderr, server, conn){ 488 | done(); 489 | }); 490 | }); 491 | }); 492 | }); 493 | it('can ensure a remote dir is empty and exists', function(done){ 494 | ssh.ensureEmptyDir(hostPwd, '/home/vagrant/putdir-test', function(err, server, conn){ 495 | ssh.fileExists(conn, '/home/vagrant/putdir-test', function(err2, exists){ 496 | (!!err).should.be.false; 497 | (exists).should.be.true; 498 | conn.end(); 499 | done(); 500 | }); 501 | }); 502 | }); 503 | it('can ensure a remote dir is empty and exists via sudo', function(done){ 504 | ssh.ensureEmptyDirSudo(hostPwd, '/tmp/empty-dir-sudo', function(err, server, conn){ 505 | if(err) console.error(err); 506 | (!!err).should.be.false; 507 | ssh.fileExistsSudo(conn, '/tmp/empty-dir-sudo', function(err2, exists){ 508 | if(err2) console.error(err2); 509 | (!!err2).should.be.false; 510 | (exists).should.be.true; 511 | conn.end(); 512 | done(); 513 | }); 514 | }); 515 | }); 516 | it('can fail properly', function(done){ 517 | ssh.ensureEmptyDir(hostPwd, '/root/empty-dir-sudo-fail', function(err, server, conn){ 518 | ssh.fileExists(conn, '/root/empty-dir-sudo-fail', function(err2, exists){ 519 | (!!err).should.be.true; 520 | (exists).should.be.false; 521 | done(); 522 | }); 523 | }); 524 | }); 525 | }); 526 | 527 | describe('sftp fileExists', function(){ 528 | this.timeout(10000); 529 | 530 | it('can test file exists', function(done){ 531 | ssh.fileExists(hostPwd, '/home/vagrant/.bashrc', function(err, exists){ 532 | (!!err).should.be.false; 533 | (exists).should.be.true; 534 | done(); 535 | }); 536 | }); 537 | it('can ensure a remote path exists', function(done){ 538 | ssh.ensureEmptyDir(hostPwd, '/home/vagrant/fileExists-test', function(err, server, conn){ 539 | (!!err).should.be.false; 540 | ssh.fileExists(conn, '/home/vagrant/fileExists-test', function(err2, exists){ 541 | (!!err2).should.be.false; 542 | (exists).should.be.true; 543 | conn.end(); 544 | done(); 545 | }); 546 | }); 547 | }); 548 | it('can ensure a remote path exists via sudo', function(done){ 549 | ssh.ensureEmptyDirSudo(hostPwd, '/home/vagrant/fileExists-test', function(err, server, conn){ 550 | (!!err).should.be.false; 551 | ssh.fileExistsSudo(conn, '/home/vagrant/fileExists-test', function(err2, exists){ 552 | (!!err2).should.be.false; 553 | (exists).should.be.true; 554 | conn.end(); 555 | done(); 556 | }); 557 | }); 558 | }); 559 | it('can fail properly', function(done){ 560 | ssh.fileExists(hostPwd, '/root/fileExists-fail', function(err, exists, server, conn){ 561 | (!!err).should.be.true; 562 | (exists).should.be.false; 563 | conn.end(); 564 | done(); 565 | }); 566 | }); 567 | }); 568 | 569 | describe('sftp putDir', function(){ 570 | this.timeout(10000); 571 | var t = (new Date()).getTime(); 572 | 573 | before(function(done){ 574 | fs.mkdirsSync(fixturePath); 575 | ssh.exec(hostPwd, 'sudo rm -fr '+tmpRemotePath+'', function(err, stdout, stderr, server, conn){ 576 | ssh.exec(conn, 'sudo mkdir -p '+tmpRemotePath+'', function(){ 577 | ssh.exec(conn, 'sudo chmod -R 0777 '+tmpRemotePath+'', function(){ 578 | ssh.exec(conn, 'sudo rm -fr /root/putdir-test*', function(){ 579 | fs.mkdirsSync(fixturePath); 580 | fs.emptyDirSync(fixturePath); 581 | fs.writeFileSync(fixturePath+'/temp'+t, t); 582 | conn.end(); 583 | done(); 584 | }); 585 | }); 586 | }); 587 | }); 588 | }); 589 | it('can put a local dir to a remote', function(done){ 590 | ssh.putDir(hostPwd, fixturePath, tmpRemotePath+'/putdir-test', function(err, server, conn){ 591 | ssh.fileExists(conn, tmpRemotePath+'/putdir-test/temp'+t, function(err2, exists){ 592 | (!!err).should.be.false; 593 | (exists).should.be.true; 594 | conn.end(); 595 | done(); 596 | }); 597 | }); 598 | }); 599 | it('can put a local dir to a remote via sudo', function(done){ 600 | ssh.putDirSudo(hostPwd, fixturePath, '/root/putdir-test', function(err, server, conn){ 601 | if(err) console.error(err); 602 | (!!err).should.be.false; 603 | ssh.fileExistsSudo(conn, '/root/putdir-test/temp'+t, function(err2, exists){ 604 | if(err2) console.error(err2); 605 | (!!err2).should.be.false; 606 | (exists).should.be.true; 607 | conn.end(); 608 | done(); 609 | }); 610 | }); 611 | }); 612 | it('can fail properly', function(done){ 613 | ssh.putDir(hostPwd, fixturePath, '/root/putdir-test-fail', function(err, server, conn){ 614 | ssh.fileExists(conn, '/root/putdir-test-fail/temp'+t, function(err2, exists){ 615 | (!!err).should.be.true; 616 | (exists).should.be.false; 617 | conn.end(); 618 | done(); 619 | }); 620 | }); 621 | }); 622 | }); 623 | 624 | describe('sftp readFile', function(){ 625 | this.timeout(10000); 626 | it('can read a file from remote', function(done){ 627 | ssh.readFile(hostPwd, '/home/vagrant/.bashrc', function(err, data){ 628 | if(err) console.error(err); 629 | (!!err).should.be.false; 630 | data.should.match(/(export PATH|bashrc)/); // depending the os hosting the remote ssh, content vary 631 | done(); 632 | }); 633 | }); 634 | it('can read a file from remote via sudo', function(done){ 635 | ssh.readFileSudo(hostPwd, '/root/.bashrc', function(err, data){ 636 | if(err) console.error(err); 637 | (!!err).should.be.false; 638 | data.should.match(/bashrc/); 639 | done(); 640 | }); 641 | }); 642 | it('can properly fail permission', function(done){ 643 | this.timeout(25000); 644 | ssh.readFile(hostPwd, '/root/.bashrc', function(err, data){ 645 | if(err) console.error(err); 646 | (!!err).should.be.true; 647 | err.code.should.eql(3); 648 | err.message.should.match(/Permission denied/); 649 | done(); 650 | }); 651 | }); 652 | it('can properly fail to read a file from remote', function(done){ 653 | this.timeout(25000); 654 | ssh.readFile(hostPwd, '~/NoSuchFile', function(err, data){ 655 | if(err) console.error(err); 656 | (!!err).should.be.true; 657 | err.code.should.eql(2); 658 | err.message.should.match(/No such file/); 659 | done(); 660 | }); 661 | }); 662 | }); 663 | 664 | describe('sftp getFile', function(){ 665 | this.timeout(10000); 666 | var t = (new Date()).getTime(); 667 | 668 | before(function(done){ 669 | fs.mkdirsSync(fixturePath); 670 | ssh.exec(hostPwd, 'sudo rm -fr '+tmpRemotePath+'', function(err, stdout, stderr, server, conn){ 671 | ssh.exec(conn, 'sudo mkdir -p '+tmpRemotePath+'', function(){ 672 | ssh.exec(conn, 'sudo chmod -R 0777 '+tmpRemotePath+'', function(){ 673 | done(); 674 | }); 675 | }); 676 | }); 677 | }); 678 | 679 | it('can download a file', function(done){ 680 | ssh.writeFile(hostPwd, tmpRemotePath+'/remote'+t, t, function(err, server, conn){ 681 | (!!err).should.be.false; 682 | ssh.getFile(conn, tmpRemotePath+'/remote'+t, fixturePath + 'local'+t, function(err){ 683 | (!!err).should.be.false; 684 | fs.readFileSync(fixturePath + 'local'+t,'utf-8').should.eql(''+t); 685 | conn.end(); 686 | done(); 687 | }); 688 | }); 689 | }); 690 | 691 | }); 692 | 693 | describe('sftp mktemp', function(){ 694 | this.timeout(10000); 695 | var t = (new Date()).getTime(); 696 | 697 | it('can safely create a remote temporary directory', function(done){ 698 | ssh.mktemp(hostPwd, 'some', function(err, tempPath, server, conn){ 699 | ssh.writeFile(conn, tempPath+'/test', t, function(){ 700 | ssh.readFile(conn, tempPath+'/test', function(err, data){ 701 | (!!err).should.be.false; 702 | data.should.eql(''+t); 703 | conn.end(); 704 | done(); 705 | }); 706 | }); 707 | }); 708 | }); 709 | }); 710 | 711 | describe('sftp mkdir', function(){ 712 | this.timeout(10000); 713 | 714 | it('can create a directory', function(done){ 715 | ssh.mkdir(hostPwd, '/home/vagrant/examples', function(err,server,conn){ 716 | (!!err).should.be.false; 717 | ssh.fileExists(hostPwd, '/home/vagrant/examples', function(err){ 718 | (!!err).should.be.false; 719 | conn.end(); 720 | done(); 721 | }); 722 | }); 723 | }); 724 | 725 | it('can fail correctly when it can t mkdir', function(done){ 726 | ssh.mkdir(hostPwd, '/root/cannot', function(err){ 727 | (!!err).should.be.true; 728 | err.code.should.eql(3); 729 | err.message.should.match(/Permission denied/); 730 | done(); 731 | }); 732 | }); 733 | }); 734 | 735 | describe('sftp rmdir', function(){ 736 | this.timeout(10000); 737 | 738 | it('can delete a directory', function(done){ 739 | ssh.mkdir(hostPwd, '/home/vagrant/examples', function(err, server, conn){ 740 | (!!err).should.be.false; 741 | ssh.fileExists(conn, '/home/vagrant/examples', function(err, exists){ 742 | (!!err).should.be.false; 743 | (exists).should.be.true; 744 | ssh.rmdir(conn, '/home/vagrant/examples', function(err, server, conn){ 745 | (!!err).should.be.false; 746 | ssh.fileExists(conn, '/home/vagrant/examples', function(err, exists){ 747 | (!!err).should.be.true; 748 | (exists).should.be.false; 749 | (err.message).should.match(/No such file/i); 750 | conn.end(); 751 | done(); 752 | }); 753 | }); 754 | }); 755 | }); 756 | }); 757 | 758 | it('can fail correctly when it can t rmdir', function(done){ 759 | ssh.exec(hostPwd, 'sudo mkdir -p /root/some', function(err,stdout,sterr,sever,conn){ 760 | ssh.rmdir(conn, '/root/some', function(err){ 761 | (!!err).should.be.true; 762 | err.code.should.eql(3); 763 | err.message.should.match(/Permission denied/); 764 | conn.end(); 765 | done(); 766 | }); 767 | }); 768 | }); 769 | }); 770 | 771 | describe('sftp writeFile', function(){ 772 | this.timeout(10000); 773 | var t = (new Date()).getTime(); 774 | 775 | before(function(done){ 776 | fs.mkdirsSync(fixturePath); 777 | ssh.exec(hostPwd, 'sudo rm -fr '+tmpRemotePath+'', function(err, stdout, stderr, server, conn){ 778 | ssh.exec(conn, 'sudo mkdir -p '+tmpRemotePath+'', function(){ 779 | ssh.exec(conn, 'sudo chmod -R 0777 '+tmpRemotePath+'', function(){ 780 | done(); 781 | }); 782 | }); 783 | }); 784 | }); 785 | 786 | it('can write a file content', function(done){ 787 | ssh.writeFile(hostPwd, tmpRemotePath+'/remote2'+t, t, function(err,server,conn){ 788 | (!!err).should.be.false; 789 | ssh.fileExists(conn, tmpRemotePath+'/remote2'+t, function(err){ 790 | (!!err).should.be.false; 791 | ssh.getFile(conn, tmpRemotePath+'/remote2'+t, fixturePath + 'local2'+t, function(err){ 792 | (!!err).should.be.false; 793 | fs.readFileSync(fixturePath + 'local2'+t,'utf-8').should.eql(''+t); 794 | conn.end(); 795 | done(); 796 | }); 797 | }); 798 | }); 799 | }); 800 | 801 | it('can fail correctly when it can t write a file', function(done){ 802 | ssh.writeFile(hostPwd, '/root/cannot', 'some', function(err){ 803 | (!!err).should.be.true; 804 | err.code.should.eql(3); 805 | err.message.should.match(/Permission denied/); 806 | done(); 807 | }); 808 | }); 809 | }); 810 | 811 | describe('sftp ensureFileContains', function(){ 812 | this.timeout(10000); 813 | var t = (new Date()).getTime(); 814 | 815 | before(function(done){ 816 | fs.mkdirsSync(fixturePath); 817 | ssh.exec(hostPwd, 'sudo rm -fr '+tmpRemotePath+'', function(err, stdout, stderr, server, conn){ 818 | ssh.exec(conn, 'sudo mkdir -p '+tmpRemotePath+'', function(){ 819 | ssh.exec(conn, 'sudo chmod -R 0777 '+tmpRemotePath+'', function(){ 820 | done(); 821 | }); 822 | }); 823 | }); 824 | }); 825 | 826 | it('can ensure a file contains a certain piece of text', function(done){ 827 | ssh.writeFile(hostPwd, tmpRemotePath+'/remote5'+t, t, function(err){ 828 | (!!err).should.be.false; 829 | ssh.ensureFileContains(hostPwd, tmpRemotePath+'/remote5'+t, t, function(err, contains){ 830 | (!!err).should.be.false; 831 | (contains).should.be.true; 832 | done(err); 833 | }); 834 | }); 835 | }); 836 | 837 | it('can ensure a file contains a certain piece of text via sudo', function(done){ 838 | t++; 839 | fs.writeFileSync(fixturePath + 'local'+t, t); 840 | ssh.putFileSudo(hostPwd, fixturePath + 'local'+t, '/root/remote8'+t, function(err){ 841 | (!!err).should.be.false; 842 | ssh.ensureFileContainsSudo(hostPwd, '/root/remote8'+t, t, function(err, contains){ 843 | (!!err).should.be.false; 844 | (contains).should.be.true; 845 | done(err); 846 | }); 847 | }); 848 | }); 849 | 850 | it('can fail correctly', function(done){ 851 | t++; 852 | ssh.ensureFileContains(hostPwd, '/root/some'+t, t, function(err, contains){ 853 | (!!err).should.be.true; 854 | (contains).should.be.false; 855 | done(); 856 | }); 857 | }); 858 | 859 | }); 860 | 861 | describe('sftp putFile', function(){ 862 | this.timeout(10000); 863 | var t = (new Date()).getTime(); 864 | 865 | before(function(done){ 866 | fs.mkdirsSync(fixturePath); 867 | ssh.exec(hostPwd, 'sudo rm -fr '+tmpRemotePath+'', function(err, stdout, stderr, server, conn){ 868 | ssh.exec(conn, 'sudo mkdir -p '+tmpRemotePath+'', function(){ 869 | ssh.exec(conn, 'sudo chmod -R 0777 '+tmpRemotePath+'', function(){ 870 | done(); 871 | }); 872 | }); 873 | }); 874 | }); 875 | 876 | it('can put file on remote', function(done){ 877 | fs.writeFileSync(fixturePath + 'local'+t, t); 878 | ssh.putFile(hostPwd, fixturePath + 'local'+t, tmpRemotePath+'/remote'+t, function(err, server, conn){ 879 | (!!err).should.be.false; 880 | ssh.ensureFileContains(conn, tmpRemotePath+'/remote'+t, t, function(err, contains){ 881 | (!!err).should.be.false; 882 | (contains).should.be.true; 883 | conn.end(); 884 | done(); 885 | }); 886 | }); 887 | }); 888 | 889 | it('can put file on remote via sudo', function(done){ 890 | t++; 891 | fs.writeFileSync(fixturePath + 'local'+t, t); 892 | ssh.putFileSudo(hostPwd, fixturePath + 'local'+t, '/root/some'+t, function(err){ 893 | (!!err).should.be.false; 894 | ssh.ensureFileContainsSudo(hostPwd, '/root/some'+t, t, function(err, contains){ 895 | (!!err).should.be.false; 896 | (contains).should.be.true; 897 | done(err); 898 | }); 899 | }); 900 | }); 901 | 902 | }); 903 | 904 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 2 | var path = require('path'); 3 | var async = require('async'); 4 | var Client = require('ssh2').Client; 5 | var glob = require("glob"); 6 | var fs = require("fs-extra"); 7 | var through = require('through'); 8 | var _ = require("underscore"); 9 | var _s = require("underscore.string"); 10 | 11 | var pkg = require('./package.json'); 12 | var debug = require('debug')(pkg.name); 13 | 14 | 15 | 16 | /** 17 | * @throw err if then is null 18 | * @param then 19 | * @param err 20 | */ 21 | var returnOrThrow = function(then, err){ 22 | if(then){ 23 | var args = Array.prototype.slice.call(arguments); 24 | args.shift(); 25 | then.apply(null, args); 26 | } else if(err) { 27 | debug('returnOrThrow '+ err); 28 | throw err; 29 | } 30 | }; 31 | 32 | var scanLocalDirectory = function(localPath, then){ 33 | // scan local directories 34 | var options = { 35 | cwd: localPath 36 | }; 37 | glob( '**/', options, function (er, dirs) { 38 | // scan local files 39 | options.nodir = true; 40 | glob( '**', options, function (er, files) { 41 | then(dirs, files); 42 | }); 43 | }); 44 | }; 45 | 46 | /** 47 | * Server credentials information 48 | * It can use password or key 49 | * to login, or run sudo command 50 | * transparently 51 | * 52 | * @note It is a class to support documentation 53 | * @constructor 54 | */ 55 | function ServerCredentials(){ 56 | this.username = ''; 57 | this.password = ''; 58 | this.host = 'localhost'; 59 | this.port = 22; 60 | this.privateKey = ''; 61 | } 62 | 63 | /** 64 | * sudo challenge completion over ssh 65 | * 66 | * If the login succeed, hasLogin is true 67 | * 68 | * @param stream Stream 69 | * @param pwd string 70 | * @param then callback(bool hasLogin) 71 | */ 72 | var sudoChallenge = function(stream, pwd, then){ 73 | 74 | debug('waiting for sudo'); 75 | 76 | var hasReceivedData = false; 77 | var hasChallenge = false; 78 | 79 | // this is a general timeout on the command 80 | // passed this 10 secs, it fails 81 | var tChallenge = setTimeout(function(){ 82 | debug('Login has failed by timeout'); 83 | stream.removeListener('data', checkPwdInput); 84 | if (then) then(true); 85 | }, 10000); 86 | 87 | var checkPwdInput = function(data){ 88 | 89 | data = ''+data; 90 | hasReceivedData = true; 91 | 92 | // there can t be anything to resolve 93 | // if the challenge has not been sent 94 | if(!hasChallenge ){ 95 | 96 | // first data is always the challenge 97 | if( data.match(/\[sudo\] password/) || data.match(/Password:/) ){ 98 | hasChallenge = true; 99 | debug('Challenge started...'); 100 | // if so send the password on stdin 101 | stream.write(pwd+'\n'); 102 | 103 | }else{ 104 | 105 | // otherwise, 106 | // the command has probably ran successfully 107 | clearTimeout(tChallenge); 108 | stream.removeListener('data', checkPwdInput); 109 | debug('Login done without a challenge'); 110 | if (then) then(false); 111 | 112 | } 113 | 114 | // once the challenge is set, 115 | // it must be concluded 116 | // right after it s beginning 117 | } else if(hasChallenge){ 118 | 119 | clearTimeout(tChallenge); 120 | stream.removeListener('data', checkPwdInput); 121 | 122 | hasChallenge = false; 123 | // this case handle only en. 124 | if(data.match(/Sorry, try again/) || data.match(/Password:/) ){ 125 | debug('... Failed to resolve the challenge'); 126 | if (then) then(true); 127 | }else{ 128 | debug('... Challenge was successfully resolved'); 129 | if (then) then(false); 130 | } 131 | } 132 | }; 133 | stream.on('data', checkPwdInput); 134 | 135 | // this is for commands like rm -f /some 136 | var checkEmptyOutputCommands = function(){ 137 | if(!hasReceivedData && !hasChallenge){ 138 | clearTimeout(tChallenge); 139 | stream.removeListener('data', checkPwdInput); 140 | stream.removeListener('data', checkEmptyOutputCommands); 141 | debug('Login was done, without a challenge, without a data'); 142 | if (then) then(false); 143 | } 144 | }; 145 | stream.on('close', checkEmptyOutputCommands); 146 | }; 147 | 148 | // todo 149 | // better not to do that as it s a global 150 | process.setMaxListeners(100); 151 | /** 152 | * 153 | * @constructor 154 | */ 155 | function SSH2Utils(){} 156 | 157 | /** 158 | * opens ssh connection 159 | * 160 | * @param server ServerCredentials 161 | * @param done (err, ssh2.Client conn) 162 | */ 163 | var connect = function(server, done){ 164 | 165 | if(!server){ 166 | throw new Error('missing server parameter') 167 | } 168 | 169 | if( server instanceof Client ){ 170 | debug('re using existing connection'); 171 | done(false, server); 172 | }else{ 173 | server.username = server.username || server.userName || server.user; // it is acceptable 174 | debug('%s@%s:%s',server.username,server.host,server.port); 175 | 176 | if(!server.username){ 177 | throw new Error('invalid server parameter') 178 | } 179 | 180 | var conn = new Client(); 181 | conn.on('ready', function() { 182 | Object.keys(server).forEach(function(k){ 183 | if(conn[k]){ 184 | throw 'Cannot redefine existing field '+k+' on ssh2Client object, it already exists.' 185 | } 186 | conn[k] = server[k]; 187 | }); 188 | done(null, conn); 189 | }); 190 | 191 | try{ 192 | conn.connect(server); 193 | 194 | debug('connecting'); 195 | 196 | conn.on('error',function(stderr){ 197 | if(stderr) debug(''+stderr); 198 | done(stderr, null); 199 | }); 200 | 201 | 202 | // manage process termination 203 | conn.pendingStreams = []; 204 | var superEnd = conn.end; 205 | conn.end = function(){ 206 | debug('end connection %s', conn.pendingStreams.length); 207 | conn.pendingStreams.forEach(function(stream, i){ 208 | debug('kill stream %s', i); 209 | stream.kill(conn.pendingStreams.length); 210 | }); 211 | conn.pendingStreams = []; 212 | superEnd.call(conn); 213 | }; 214 | // manage user pressing ctrl+C 215 | var sigIntSent = function(){ 216 | conn.end(); 217 | }; 218 | process.on('SIGINT', sigIntSent); 219 | conn.on('close',function(){ 220 | try{ 221 | process.removeListener('SIGINT', sigIntSent); 222 | }catch(ex){} 223 | }); 224 | conn.on('end',function(){ 225 | try{ 226 | process.removeListener('SIGINT', sigIntSent); 227 | }catch(ex){} 228 | }); 229 | }catch(ex){ 230 | debug(''+ex); 231 | done(ex, null); 232 | } 233 | } 234 | }; 235 | 236 | /** 237 | * 238 | * @param cmd String 239 | * @param stream Stream 240 | */ 241 | var sendSigInt = function(cmd, stream, length){ 242 | debug('sendSigInt '+cmd); 243 | try{ 244 | // this is a workaround for more ssh implementations 245 | for(var i=0;i-1) conn.pendingStreams.splice(k,1); 302 | }); 303 | conn.pendingStreams.push(stream); 304 | }); 305 | }; 306 | 307 | /** 308 | * @see connect 309 | */ 310 | SSH2Utils.prototype.getConnReady = connect; 311 | 312 | /** 313 | * Executes a command and return its output 314 | * like child_process.exec. 315 | * non-interactive 316 | * 317 | * also take care of 318 | * - remote program termination with ctrl+C 319 | * 320 | * @param server ServerCredentials|ssh2.Client 321 | * @param cmd String 322 | * @param done callback(bool err, String stdout, String stderr, ServerCredentials server, ssh2.Client conn) 323 | */ 324 | SSH2Utils.prototype.execOne = function(server, cmd, done){ 325 | 326 | connect(server, function(err, conn){ 327 | if( err) return returnOrThrow(done, err, '', ''+err, server, conn); 328 | 329 | sudoExec(conn, server, cmd, function(err, stream){ 330 | if( err) return returnOrThrow(done, err, '', ''+err, server, conn); 331 | 332 | var stderr = ''; 333 | var stdout = ''; 334 | stream.stderr.on('data', function(data){ 335 | stderr += data.toString(); 336 | }); 337 | stream.on('data', function(data){ 338 | stdout += data.toString(); 339 | }); 340 | 341 | stream.on('close', function(){ 342 | var fineErr = null; 343 | if(stderr){ 344 | fineErr = new Error(_s.trim(stderr)); 345 | debug('stdout %j', stdout); 346 | debug('stderr %j', stderr); 347 | } 348 | returnOrThrow(done, fineErr, stdout, stderr, server, conn); 349 | }); 350 | }); 351 | }); 352 | 353 | }; 354 | 355 | /** 356 | * Executes a command and return its output 357 | * like child_process.exec. 358 | * non-interactive 359 | * 360 | * also take care of 361 | * - remote program termination with ctrl+C 362 | * 363 | * If cmd is an array of string, 364 | * they are executed in serie, 365 | * respective output of each stdout / stderr is join then returned. 366 | * 367 | * @param server ServerCredentials|ssh2.Client 368 | * @param cmd String|[String] 369 | * @param doneEach callback(bool err, String stdout, String stderr, ServerCredentials server, ssh2.Client conn) 370 | * @param done callback(bool err, String stdout, String stderr, ServerCredentials server, ssh2.Client conn) 371 | */ 372 | SSH2Utils.prototype.exec = function(server, cmd, doneEach, done){ 373 | 374 | var that = this; 375 | if(_.isString(cmd)){ 376 | cmd = [cmd]; 377 | } 378 | if(!done&& _.isFunction(doneEach) ){ 379 | done = doneEach; 380 | doneEach = null; 381 | } 382 | var cmds = []; 383 | var conn_; 384 | var err_; 385 | var stdout_ = ''; 386 | var stderr_ = ''; 387 | cmd.forEach(function(c){ 388 | cmds.push(function(next){ 389 | that.execOne(conn_ || server, c, function(err, stdout, stderr, server, conn){ 390 | conn_ = conn; 391 | err_ = err; 392 | stdout_ += stdout; 393 | stderr_ += stderr; 394 | if(doneEach) doneEach(err, stdout, stderr, server, conn); 395 | next(); 396 | }); 397 | }); 398 | }); 399 | 400 | async.series(cmds, function(){ 401 | returnOrThrow(done, err_, stdout_, stderr_, server, conn_); 402 | }); 403 | 404 | }; 405 | 406 | /** 407 | * Executes a command and return its stream, 408 | * like of child_process.spawn. 409 | * interactive 410 | * 411 | * also take care of 412 | * - manage sudo cmd 413 | * - log errors to output 414 | * 415 | * If cmd is an array, they are executed in serie, 416 | * the pipe is open asap, 417 | * you ll receive each stdout stderr data in serie 418 | * 419 | * @param server ServerCredentials|ssh2.Client 420 | * @param cmd String|[String] 421 | * @param doneEach callback(bool err, String stdout, String stderr, ServerCredentials server, ssh2.Client conn) 422 | * @param done callback(bool err, Stream stdout, Stream stderr, ServerCredentials server, ssh2.Client conn) 423 | */ 424 | SSH2Utils.prototype.run = function(server, cmd, doneEach, done){ 425 | var stdoutStream = through(); 426 | var stderrStream = through(); 427 | if(_.isString(cmd)){ 428 | cmd = [cmd]; 429 | } 430 | if(!done&& _.isFunction(doneEach) ){ 431 | done = doneEach; 432 | doneEach = null; 433 | } 434 | var cmds = []; 435 | var conn_; 436 | var err_; 437 | var stream_; 438 | connect(server, function(err, conn){ 439 | if(err) return returnOrThrow(done, err, null, ''+err, server, conn); 440 | cmd.forEach(function(c, i){ 441 | cmds.push(function(next){ 442 | sudoExec(conn, server, c, function(err, stream){ 443 | if(err) return returnOrThrow(done, err, stream, stream.stderr, server, conn); 444 | 445 | conn_ = conn; 446 | err_ = err; 447 | 448 | (function(stream, i){ 449 | var onStdoutData = function(d){ 450 | stdoutStream.emit('data', d); 451 | }; 452 | var onStderrData = function(d){ 453 | stderrStream.emit('data', d); 454 | }; 455 | stream.on('data', onStdoutData); 456 | stream.stderr.on('data', onStderrData); 457 | var onClose = function(err){ 458 | setTimeout(function(){ 459 | if(i===cmds.length){ 460 | stdoutStream.emit('close', err); 461 | } 462 | stream.removeListener('close', onClose); 463 | stream.removeListener('data', onStdoutData); 464 | stream.stderr.removeListener('data', onStderrData); 465 | },500); 466 | }; 467 | stream.on('close', onClose); 468 | })(stream, i+1); 469 | 470 | if(!stream_){ // execute only once 471 | returnOrThrow(done, err, stdoutStream, stderrStream, server, conn); // boooh. take care of this one, it is not writable. 472 | // call it like this, with two fn callback, ssh2.run(host, cmd, fnEach, fnDone), to get access writable stream in fnEach 473 | } 474 | 475 | if(doneEach) doneEach(err, stream, stream.stderr, server, conn); 476 | 477 | stream_ = stream; 478 | next(); 479 | 480 | }); 481 | }) 482 | }); 483 | 484 | async.series(cmds, function(){ 485 | if(!stream_){ 486 | returnOrThrow(done, err_, stdoutStream, stderrStream, server, conn_); 487 | } 488 | }); 489 | 490 | }); 491 | 492 | }; 493 | 494 | /** 495 | * Executes a set of multiple and sequential commands. 496 | * 497 | * @param server ServerCredentials|ssh2.Client 498 | * @param cmds [String] 499 | * @param cmdComplete callback(String command, String response, ServerCredentials server) 500 | * @param then callback(err, String allSessionText, ServerCredentials server) 501 | */ 502 | SSH2Utils.prototype.runMultiple = SSH2Utils.prototype.run; 503 | 504 | /** 505 | * Reads a file on the remote 506 | * 507 | * @param server ServerCredentials|ssh2.Client 508 | * @param remoteFile String 509 | * @param then callback(err, String content, ServerCredentials server, ssh2.Client conn) 510 | */ 511 | SSH2Utils.prototype.readFile = function(server, remoteFile, then){ 512 | 513 | var content = ''; 514 | connect(server, function(err, conn){ 515 | if(err) return returnOrThrow(then, err, content, server, conn); 516 | 517 | conn.sftp(function(err, sftp){ 518 | if(err) return returnOrThrow(then, err, content, server, conn); 519 | 520 | debug('createReadStream %s', remoteFile); 521 | var stream = sftp.createReadStream(remoteFile); 522 | stream.on('data', function(d){ 523 | content += ''+d; 524 | }); 525 | var finish = function(readErr){ 526 | stream.removeListener('error', finish); 527 | stream.removeListener('close', finish); 528 | returnOrThrow(then, readErr, content, server, conn); 529 | }; 530 | stream.on('error', finish); 531 | stream.on('close', finish); 532 | }); 533 | }); 534 | }; 535 | 536 | /** 537 | * Reads a file on the remote via sudo 538 | * 539 | * @param server ServerCredentials|ssh2.Client 540 | * @param remoteFile String 541 | * @param then callback(err, String content, ServerCredentials server, ssh2.Client conn) 542 | */ 543 | SSH2Utils.prototype.readFileSudo = function(server, remoteFile, then){ 544 | 545 | var content = ''; 546 | this.run(server, 'sudo cat '+remoteFile+'', function(err, stdout, stderr, server, conn){ 547 | if(err) return returnOrThrow(then, err, content, server, conn); 548 | 549 | var readErr; 550 | stdout.on('data', function(d){ 551 | content += ''+d; 552 | }); 553 | stdout.on('error', function(e){ 554 | readErr = e; 555 | }); 556 | stdout.on('close', function(){ 557 | returnOrThrow(then, readErr, content, server, conn); 558 | }); 559 | }); 560 | }; 561 | 562 | /** 563 | * Reads a large file on the remote 564 | * 565 | * @param server ServerCredentials|ssh2.Client 566 | * @param remoteFile String 567 | * @param then callback(err, Stream data, ServerCredentials server, ssh2.Client conn) 568 | */ 569 | SSH2Utils.prototype.streamReadFile = function(server, remoteFile, then){ 570 | 571 | connect(server, function(err,conn){ 572 | conn.sftp(function(err, sftp){ 573 | var stream = sftp.createReadStream(remoteFile); 574 | returnOrThrow(then, err, stream, server, conn); 575 | }); 576 | }); 577 | }; 578 | 579 | /** 580 | * Reads a large file on the remote via sudo 581 | * 582 | * @param server ServerCredentials|ssh2.Client 583 | * @param remoteFile String 584 | * @param then callback(err, Stream data, ServerCredentials server, ssh2.Client conn) 585 | */ 586 | SSH2Utils.prototype.streamReadFileSudo = function(server, remoteFile, then){ 587 | this.run(server, 'sudo cat '+remoteFile+'', function(err, stdout, stderr, server, conn){ 588 | returnOrThrow(then, err, stdout, server, conn); 589 | }); 590 | }; 591 | 592 | /** 593 | * Downloads a file to the local 594 | * 595 | * @param server ServerCredentials|ssh2.Client 596 | * @param remoteFile String 597 | * @param localPath String 598 | * @param then callback(err, ServerCredentials server, ssh2.Client conn) 599 | */ 600 | SSH2Utils.prototype.getFile = function(server, remoteFile, localPath, then){ 601 | 602 | connect(server, function(err,conn){ 603 | conn.sftp(function(err, sftp){ 604 | if(err) return returnOrThrow(then, err, server, conn); 605 | sftp.fastGet(remoteFile, localPath, function(err){ 606 | returnOrThrow(then, err, server, conn); 607 | }); 608 | }); 609 | }); 610 | }; 611 | 612 | /** 613 | * Ensure a remote file contains a certain text piece of text 614 | * 615 | * @param server ServerCredentials|ssh2.Client 616 | * @param remoteFile String 617 | * @param contain String 618 | * @param then callback(err, Bool contains, ServerCredentials server, ssh2.Client conn) 619 | */ 620 | SSH2Utils.prototype.ensureFileContains = function(server, remoteFile, contain, then){ 621 | var that = this; 622 | that.exec(server, 'grep "'+contain+'" '+remoteFile, function(err, stdout, stderr, server, conn){ 623 | var found = stdout.length>0 && stdout.match(contain); 624 | if(found){ 625 | then(err, true, server, conn); 626 | } else { 627 | that.exec(conn, 'echo "'+contain+'" >> '+remoteFile+'', function(err, stdout, stderr, server, conn){ 628 | that.exec(conn, 'grep "'+contain+'" '+remoteFile, function(err, stdout, stderr, server, conn){ 629 | then(err, (stdout.length>0 && stdout.match(contain)), server, conn); 630 | }); 631 | }); 632 | } 633 | }); 634 | }; 635 | 636 | /** 637 | * Ensure a remote file contains a certain text piece of text 638 | * 639 | * @param server ServerCredentials|ssh2.Client 640 | * @param remoteFile String 641 | * @param contain String 642 | * @param then callback(err, Bool contains, ServerCredentials server, ssh2.Client conn) 643 | */ 644 | SSH2Utils.prototype.ensureFileContainsSudo = function(server, remoteFile, contain, then){ 645 | var that = this; 646 | that.exec(server, 'sudo grep "'+contain+'" '+remoteFile, function(err, stdout, stderr, server, conn){ 647 | var found = stdout.length>0 && stdout.match(contain); 648 | if(found){ 649 | then(err, true, server, conn); 650 | } else { 651 | that.exec(conn, 'sudo echo "'+contain+'" >> '+remoteFile, function(err,stdout,stderr,server,conn){ 652 | then(err, !!err, server, conn); 653 | }); 654 | } 655 | }); 656 | }; 657 | /** 658 | * Ensure a remote file contains a certain text piece of text 659 | * 660 | * @param server ServerCredentials|ssh2.Client 661 | * @param remoteFile String 662 | * @param content String 663 | * @param then callback(err, Bool contains, ServerCredentials server, ssh2.Client conn) 664 | */ 665 | SSH2Utils.prototype.prependFile = function(server, remoteFile, content, then){ 666 | var that = this; 667 | that.mktemp(server, pkg.name, function(err, tmpPath, server, conn){ 668 | if(err) return returnOrThrow(then, err, server, conn); 669 | that.writeFile(server, tmpPath+'/t', content, function(err){ 670 | if(err) return returnOrThrow(then, err, server, conn); 671 | that.exec(server, 'echo '+remoteFile+' >> '+tmpPath+'/t', content, function(err){ 672 | if(err) return returnOrThrow(then, err, server, conn); 673 | that.exec(server, 'echo '+tmpPath+'/t > '+remoteFile, content, function(err){ 674 | if(err) return returnOrThrow(then, err, server, conn); 675 | that.exec(server, 'rm '+tmpPath+'/t ', content, function(err){ 676 | if(err) return returnOrThrow(then, err, server, conn); 677 | }); 678 | }); 679 | }); 680 | }); 681 | }); 682 | }; 683 | 684 | /** 685 | * Ensure a remote file contains a certain text piece of text 686 | * 687 | * @param server ServerCredentials|ssh2.Client 688 | * @param remoteFile String 689 | * @param content String 690 | * @param then callback(err, Bool contains, ServerCredentials server, ssh2.Client conn) 691 | */ 692 | SSH2Utils.prototype.prependFileSudo = function(server, remoteFile, content, then){ 693 | var that = this; 694 | that.mktemp(server, pkg.name, function(err, tmpPath, server, conn){ 695 | if(err) return returnOrThrow(then, err, server, conn); 696 | that.writeFileSudo(server, tmpPath+'/t', content, function(err){ 697 | if(err) return returnOrThrow(then, err, server, conn); 698 | that.exec(server, 'sudo echo '+remoteFile+' >> '+tmpPath+'/t', content, function(err){ 699 | if(err) return returnOrThrow(then, err, server, conn); 700 | that.exec(server, 'sudo echo '+tmpPath+'/t > '+remoteFile, content, function(err){ 701 | if(err) return returnOrThrow(then, err, server, conn); 702 | that.exec(server, 'sudo rm '+tmpPath+'/t ', content, function(err){ 703 | if(err) return returnOrThrow(then, err, server, conn); 704 | }); 705 | }); 706 | }); 707 | }); 708 | }); 709 | }; 710 | 711 | /** 712 | * Uploads a file on the remote remote 713 | * 714 | * @param server ServerCredentials|ssh2.Client 715 | * @param localFile String 716 | * @param remoteFile String 717 | * @param then callback(err, ServerCredentials server, ssh2.Client conn) 718 | */ 719 | SSH2Utils.prototype.putFile = function(server, localFile, remoteFile, then){ 720 | 721 | debug('from %s to %s', path.relative(__dirname,localFile), remoteFile); 722 | 723 | remoteFile = remoteFile.replace(/[\\]/g,'/'); // windows needs this 724 | var remotePath = path.dirname(remoteFile); 725 | this.mkdir(server, remotePath, function(err, server, conn){ 726 | if(err) return returnOrThrow(then, err, server, conn); 727 | 728 | conn.sftp(function(err, sftp){ 729 | if(err) return returnOrThrow(then, err, server, conn); 730 | 731 | debug('put %s %s', 732 | path.relative(process.cwd(),localFile), path.relative(remotePath,remoteFile)); 733 | 734 | sftp.fastPut(localFile, remoteFile, function(err){ 735 | returnOrThrow(then, err, server, conn); 736 | }); 737 | }); 738 | }); 739 | 740 | }; 741 | 742 | /** 743 | * Uploads a file on the remote via sudo support 744 | * 745 | * @param server ServerCredentials|ssh2.Client 746 | * @param localFile String 747 | * @param remoteFile String 748 | * @param then callback(err, ServerCredentials server, ssh2.Client conn) 749 | */ 750 | SSH2Utils.prototype.putFileSudo = function(server, localFile, remoteFile, then){ 751 | 752 | var that = this; 753 | 754 | debug('from %s to %s', path.relative(__dirname, localFile), remoteFile); 755 | 756 | remoteFile = remoteFile.replace(/[\\]/g,'/'); // windows needs this 757 | var remotePath = path.dirname(remoteFile); 758 | var fileName = path.basename(remoteFile); 759 | 760 | this.mktemp(server, pkg.name, function(err, tmpPath, server, conn){ 761 | if(err) return returnOrThrow(then, err, server, conn); 762 | 763 | conn.sftp(function(err, sftp){ 764 | if(err) return returnOrThrow(then, err, server, conn); 765 | 766 | debug('put %s %s', 767 | path.relative(process.cwd(), localFile), path.relative(remotePath, remoteFile)); 768 | 769 | sftp.fastPut(localFile, tmpPath+'/'+fileName, function(err){ 770 | if(err) return returnOrThrow(then, err, server, conn); 771 | 772 | that.mkdirSudo(conn,remotePath, function(err){ 773 | if(err) return returnOrThrow(then, err, server, conn); 774 | 775 | that.exec(conn, 'sudo cp '+tmpPath+'/'+fileName+' '+remoteFile, function(err, stdout, stderr){ 776 | if(err) return returnOrThrow(then, err, server, conn); 777 | 778 | that.rmdirSudo(conn, tmpPath+'/'+fileName, function(err){ 779 | returnOrThrow(then, err, server, conn); 780 | }); 781 | 782 | }); 783 | 784 | }); 785 | 786 | }); 787 | }); 788 | }); 789 | 790 | }; 791 | 792 | /** 793 | * Writes content to a remote file 794 | * 795 | * @param server ServerCredentials|ssh2.Client 796 | * @param remoteFile String 797 | * @param content String 798 | * @param then callback(err, ServerCredentials server, ssh2.Client conn) 799 | */ 800 | SSH2Utils.prototype.writeFile = function(server, remoteFile, content, then){ 801 | 802 | debug('write to %s',remoteFile); 803 | 804 | remoteFile = remoteFile.replace(/[\\]/g,'/'); // windows needs this 805 | 806 | var remotePath = path.dirname(remoteFile); 807 | debug('mkdir %s', remotePath); 808 | this.mkdir(server, remotePath, function(err, server, conn){ 809 | if(err){ 810 | return returnOrThrow(then, err, server, conn); 811 | } 812 | 813 | debug('write %s', remoteFile); 814 | 815 | conn.sftp(function sftpOpen(err, sftp){ 816 | if(err){ 817 | return returnOrThrow(then, err, server, conn); 818 | } 819 | try{ 820 | debug('stream start'); 821 | var wStream = sftp.createWriteStream(remoteFile, {flags: 'w+', encoding: null}); 822 | wStream.on('error', function (err) { 823 | debug('stream error %j', err); 824 | wStream.removeAllListeners('finish'); 825 | returnOrThrow(then, err, server, conn); 826 | }); 827 | wStream.on('finish', function () { 828 | debug('stream finish'); 829 | returnOrThrow(then, err, server, conn); 830 | }); 831 | wStream.end(''+content); 832 | }catch(ex){ 833 | debug('stream ex %j', ex); 834 | return returnOrThrow(then, ex, server, conn); 835 | } 836 | }); 837 | }); 838 | }; 839 | 840 | /** 841 | * Writes content to a remote file 842 | * 843 | * @param server ServerCredentials|ssh2.Client 844 | * @param remoteFile String 845 | * @param content String 846 | * @param then callback(err, ServerCredentials server, ssh2.Client conn) 847 | */ 848 | SSH2Utils.prototype.writeFileSudo = function(server, remoteFile, content, then){ 849 | throw 'todo'; 850 | }; 851 | 852 | /** 853 | * Tells if a file exists on remote 854 | * by trying to open handle on it. 855 | * 856 | * @param server ServerCredentials|ssh2.Client 857 | * @param remoteFile String 858 | * @param then callback(err, exists, ServerCredentials server, ssh2.Client conn) 859 | */ 860 | SSH2Utils.prototype.fileExists = function(server, remoteFile, then){ 861 | 862 | remoteFile = remoteFile.replace(/[\\]/g,'/'); // windows needs this 863 | debug('fileExists %s',remoteFile); 864 | 865 | connect(server, function sshConnect(err, conn){ 866 | if (err) return returnOrThrow(then, err, server, conn); 867 | conn.sftp(function sftpOpen(err, sftp){ 868 | if (err) return returnOrThrow(then, err, server, conn); 869 | sftp.open(remoteFile, 'r', function stfpOpenFileHandle(err, handle){ 870 | if(handle) sftp.close(handle); 871 | returnOrThrow(then, err, !err, server, conn); 872 | }) 873 | }); 874 | }); 875 | }; 876 | 877 | /** 878 | * Tells if a file exists on remote 879 | * by trying to open handle on it. 880 | * 881 | * @param server ServerCredentials|ssh2.Client 882 | * @param remoteFile String 883 | * @param then callback(err, exists, ServerCredentials server, ssh2.Client conn) 884 | */ 885 | SSH2Utils.prototype.fileExistsSudo = function(server, remoteFile, then){ 886 | 887 | remoteFile = path.normalize(remoteFile).replace(/\\/g, '/'); 888 | var remoteFileName = path.basename(remoteFile); 889 | var remotePath = path.dirname(remoteFile); 890 | debug('fileExistsSudo %s', remoteFile); 891 | 892 | this.exec(server, 'sudo ls -alh '+remotePath+'/', function(err, stdout, stderr, server, conn){ 893 | returnOrThrow(then, err, !!stdout.match(remoteFileName), server, conn); 894 | }); 895 | }; 896 | 897 | /** 898 | * Deletes a file or directory 899 | * rm -fr /some/path 900 | * 901 | * @param server ServerCredentials|ssh2.Client 902 | * @param remotePath String 903 | * @param then callback(err, ServerCredentials server, ssh2.Client conn) 904 | */ 905 | SSH2Utils.prototype.rmdir = function(server, remotePath, then){ 906 | debug('rmdir %s',remotePath); 907 | this.exec(server, 'rm -fr '+remotePath, function rmdir (err, stderr, stdout, server, conn){ 908 | var fineErr = null; 909 | if( stdout ){ 910 | fineErr = new Error(stdout); 911 | fineErr.code = 3; 912 | } 913 | returnOrThrow(then, fineErr, server, conn); 914 | }); 915 | }; 916 | 917 | /** 918 | * Deletes a file or directory 919 | * sudo rm -fr /some/path 920 | * 921 | * @param server ServerCredentials|ssh2.Client 922 | * @param remotePath String 923 | * @param then callback(err, ServerCredentials server, ssh2.Client conn) 924 | */ 925 | SSH2Utils.prototype.rmdirSudo = function(server, remotePath, then){ 926 | debug('rmdirSudo %s', remotePath); 927 | this.exec(server, 'sudo rm -fr '+remotePath, function rmdirSudo (err, stderr, stdout, server, conn){ 928 | var fineErr = null; 929 | if( stdout ){ 930 | fineErr = new Error(stdout); 931 | fineErr.code = 3; 932 | } 933 | returnOrThrow(then, fineErr, server, conn); 934 | }); 935 | }; 936 | 937 | /** 938 | * Creates a concurrent-safe temporary remote directory. 939 | * 940 | * It does not attempt to keep track of temp files created during the session. 941 | * Thus it won t delete them on connection close. 942 | * 943 | * @param server ServerCredentials|ssh2.Client 944 | * @param suffix String 945 | * @param then callback(err, tmpDirName, ServerCredentials server, ssh2.Client conn) 946 | */ 947 | SSH2Utils.prototype.mktemp = function(server, suffix, then){ 948 | debug('mktemp %s',suffix); 949 | this.exec(server, 'mktemp -d --suffix='+suffix, function mkdir (err, stderr, stdout, server, conn){ 950 | // if response is done on stderr when everything s fine, 951 | // errors may go into stdout or fd.pipe[3], it is unclear and for sure untested 952 | var tempPath = _s.trim(stderr); 953 | returnOrThrow(then, null, tempPath, server, conn); 954 | }); 955 | }; 956 | 957 | /** 958 | * Creates a remote directory 959 | * 960 | * @param server ServerCredentials|ssh2.Client 961 | * @param remotePath String 962 | * @param then callback(err, ServerCredentials server, ssh2.Client conn) 963 | */ 964 | SSH2Utils.prototype.mkdir = function(server, remotePath, then){ 965 | debug('mkdir %s',remotePath); 966 | this.exec(server, 'mkdir -p '+remotePath, function mkdir (err, stderr, stdout, server, conn){ 967 | var fineErr = null; 968 | if( stdout ){ 969 | fineErr = new Error(stdout); 970 | fineErr.code = 3; 971 | } 972 | returnOrThrow(then, fineErr, server, conn); 973 | }); 974 | }; 975 | 976 | /** 977 | * Creates a remote directory 978 | * 979 | * @param server ServerCredentials|ssh2.Client 980 | * @param remotePath String 981 | * @param then callback(err, ServerCredentials server, ssh2.Client conn) 982 | */ 983 | SSH2Utils.prototype.mkdirSudo = function(server, remotePath, then){ 984 | debug('mkdirSudo %s',remotePath); 985 | this.exec(server, 'sudo mkdir -p '+remotePath, function mkdirSudo (err, stderr, stdout, server, conn){ 986 | var fineErr = null; 987 | if( stdout ){ 988 | fineErr = new Error(stdout); 989 | fineErr.code = 3; 990 | } 991 | returnOrThrow(then, fineErr, server, conn); 992 | }); 993 | }; 994 | 995 | /** 996 | * Ensure a remote directory exists and is empty 997 | * 998 | * @param server ServerCredentials|ssh2.Client 999 | * @param remotePath String 1000 | * @param then callback(err, ServerCredentials server, ssh2.Client conn) 1001 | */ 1002 | SSH2Utils.prototype.ensureEmptyDir = function(server, remotePath, then){ 1003 | debug('ensureEmptyDir %s',remotePath); 1004 | var that = this; 1005 | that.rmdir(server, remotePath, function(err, server, conn){ 1006 | if(err) return returnOrThrow(then, err, server, conn); 1007 | that.mkdir(server, remotePath, function(err, server, conn){ 1008 | returnOrThrow(then, err, server, conn); 1009 | }); 1010 | }); 1011 | }; 1012 | 1013 | /** 1014 | * Ensure a remote directory exists and is empty 1015 | * 1016 | * @param server ServerCredentials|ssh2.Client 1017 | * @param remotePath String 1018 | * @param then callback(err, ServerCredentials server, ssh2.Client conn) 1019 | */ 1020 | SSH2Utils.prototype.ensureEmptyDirSudo = function(server, remotePath, then){ 1021 | debug('ensureEmptyDir %s',remotePath); 1022 | var that = this; 1023 | that.rmdirSudo(server, remotePath, function(err, server, conn){ 1024 | if(err) return returnOrThrow(then, err, server, conn); 1025 | that.mkdirSudo(server, remotePath, function(err, server, conn){ 1026 | returnOrThrow(then, err, server, conn); 1027 | }); 1028 | }); 1029 | }; 1030 | 1031 | /** 1032 | * Ensure a file belongs to connected user 1033 | * by sudo chmod -R /path 1034 | * 1035 | * @param server ServerCredentials|ssh2.Client 1036 | * @param remotePath String 1037 | * @param then callback(err, ServerCredentials server, ssh2.Client conn) 1038 | */ 1039 | SSH2Utils.prototype.ensureOwnership = function(server, remotePath, then){ 1040 | debug('ensureWritable %s',remotePath); 1041 | var that = this; 1042 | server.username = server.username || server.userName || server.user; 1043 | that.exec(server, 'sudo chown -R '+server.username+':'+server.username+' '+remotePath, function(err, stdout, stderr, server, conn) { 1044 | returnOrThrow(then, err, server, conn); 1045 | }); 1046 | }; 1047 | 1048 | /** 1049 | * Uploads a local directory to the remote. 1050 | * Partly in series, partly parallel. 1051 | * Proceed such 1052 | * sudo rm -fr /remotePath 1053 | * sudo mkdir -p /remotePath 1054 | * recursive sftp mkdir 1055 | * recursive sftp put 1056 | * 1057 | * @param server ServerCredentials|ssh2.Client 1058 | * @param localPath String 1059 | * @param remotePath String 1060 | * @param then callback(err, ServerCredentials server, ssh2.Client conn) 1061 | */ 1062 | SSH2Utils.prototype.putDir = function(server, localPath, remotePath, then){ 1063 | var that = this; 1064 | that.ensureEmptyDir(server, remotePath, function(err, server, conn){ 1065 | if(err) return returnOrThrow(then, err, server, conn); 1066 | 1067 | conn.sftp(function(err, sftp){ 1068 | if(err) return returnOrThrow(then, err, server, conn); 1069 | 1070 | debug('ready'); 1071 | scanLocalDirectory(localPath, function(dirs, files){ 1072 | 1073 | // create remote directories 1074 | var dirHandlers = []; 1075 | dirs.forEach(function(f){ 1076 | dirHandlers.push(function(next){ 1077 | var to = path.join(remotePath, f).replace(/[\\]/g,'/'); // windows needs this 1078 | debug(pkg.name, 'mkdir %s', to); 1079 | that.mkdir(server, to, function(err){ 1080 | if(err) debug('mkdir %s %s', to, err.message); 1081 | next(); 1082 | }); 1083 | }) 1084 | }); 1085 | 1086 | // push files to remote 1087 | var filesHandlers = []; 1088 | files.forEach(function(f){ 1089 | filesHandlers.push(function(next){ 1090 | var from = path.join(localPath, f); 1091 | var to = path.join(remotePath, f).replace(/[\\]/g,'/'); // windows needs this 1092 | debug(pkg.name, 'put %s %s', path.relative(process.cwd(), from), to); 1093 | sftp.fastPut(from, to, function(err){ 1094 | if(err) debug('fastPut %s %s %s', from, to, err.message); 1095 | next(); 1096 | }); 1097 | }) 1098 | }); 1099 | 1100 | // then push the scanned files and directories 1101 | async.series(dirHandlers, function(){ 1102 | async.parallelLimit(filesHandlers, 4, function(){ 1103 | returnOrThrow(then, err, server, conn); 1104 | }); 1105 | }); 1106 | }); 1107 | }); 1108 | }); 1109 | }; 1110 | 1111 | /** 1112 | * Uploads a local directory to the remote. 1113 | * Partly in series, partly parallel. 1114 | * Proceed such 1115 | * sudo rm -fr /remotePath 1116 | * sudo mkdir -p /remotePath 1117 | * recursive sftp mkdir 1118 | * recursive sftp put 1119 | * 1120 | * @param server ServerCredentials|ssh2.Client 1121 | * @param localPath String 1122 | * @param remotePath String 1123 | * @param then callback(err, ServerCredentials server, ssh2.Client conn) 1124 | */ 1125 | SSH2Utils.prototype.putDirSudo = function(server, localPath, remotePath, then){ 1126 | 1127 | var that = this; 1128 | 1129 | var tmpRemotePath = path.join('/tmp/ssh2-utils/', remotePath); 1130 | that.ensureEmptyDirSudo(server, remotePath, function(err, server, conn){ 1131 | if(err) return returnOrThrow(then, err, server, conn); 1132 | 1133 | that.ensureEmptyDirSudo(conn, tmpRemotePath, function(err, server, conn){ 1134 | if(err) return returnOrThrow(then, err, server, conn); 1135 | 1136 | that.ensureOwnership(conn, tmpRemotePath, function(err, server, conn) { 1137 | if (err) return returnOrThrow(then, err, server, conn); 1138 | 1139 | conn.sftp(function(err, sftp){ 1140 | if(err) return returnOrThrow(then, err, server, conn); 1141 | 1142 | debug('ready'); 1143 | scanLocalDirectory(localPath, function(dirs, files){ 1144 | 1145 | // create remote directories 1146 | var dirHandlers = []; 1147 | dirs.forEach(function(f){ 1148 | dirHandlers.push(function(next){ 1149 | var to = path.join(tmpRemotePath, f).replace(/[\\]/g,'/'); 1150 | debug(pkg.name, 'mkdir %s', to); 1151 | that.mkdirSudo(to, function(err){ 1152 | if(err) debug('mkdir %s %s', to, err.message); 1153 | next(); 1154 | }); 1155 | }) 1156 | }); 1157 | 1158 | // push files to remote 1159 | var filesHandlers = []; 1160 | files.forEach(function(f){ 1161 | filesHandlers.push(function(next){ 1162 | var from = path.join(localPath, f); 1163 | var to = path.join(tmpRemotePath, f).replace(/[\\]/g,'/'); // windows needs this 1164 | debug(pkg.name, 'put %s %s', path.relative(process.cwd(), from), to); 1165 | sftp.fastPut(from, to, function(err){ 1166 | if(err) debug('fastPut %s %s %s', from, to, err.message); 1167 | next(); 1168 | }); 1169 | }) 1170 | }); 1171 | 1172 | // then push the scanned files and directories 1173 | async.series(dirHandlers, function(){ 1174 | async.parallelLimit(filesHandlers, 4, function(){ 1175 | if(err) return returnOrThrow(then, err, server, conn); 1176 | that.exec(conn, 'sudo cp -R '+path.join(tmpRemotePath, '*')+' '+remotePath+'/', function(err, stdout, stderr, server, conn){ 1177 | if(err) return returnOrThrow(then, err, server, conn); 1178 | that.rmdirSudo(conn, tmpRemotePath, function(err, server, conn){ 1179 | returnOrThrow(then, err, server, conn); 1180 | }); 1181 | }); 1182 | }); 1183 | }); 1184 | }); 1185 | }); 1186 | 1187 | }); 1188 | 1189 | 1190 | }); 1191 | }); 1192 | 1193 | }; 1194 | 1195 | /** 1196 | * Downloads a remote directory to the local. 1197 | * remote traverse directories over sftp. 1198 | * then get files in parallel 1199 | * 1200 | * @param server ServerCredentials|ssh2.Client 1201 | * @param remotePath String 1202 | * @param localPath String 1203 | * @param allDone callback(err, ServerCredentials server, ssh2.Client conn) 1204 | */ 1205 | SSH2Utils.prototype.getDir = function(server, remotePath, localPath, allDone){ 1206 | 1207 | var that = this; 1208 | server.username = server.username || server.userName || server.user; 1209 | 1210 | connect(server, function(err,conn){ 1211 | conn.sftp(function(err, sftp){ 1212 | if (err) throw err; 1213 | 1214 | debug('ready'); 1215 | 1216 | var files = []; 1217 | var dirs = []; 1218 | function readdir(p, then){ 1219 | sftp.readdir(p, function sftpReaddir(err,list){ 1220 | if (err) throw err; 1221 | var toRead = []; 1222 | list.forEach(function(item){ 1223 | var fpath = p+'/'+item.filename; 1224 | toRead.push(function(done){ 1225 | sftp.stat(fpath, function sftpStats(err,stat){ 1226 | if (err) throw err; 1227 | if(stat.isDirectory()){ 1228 | dirs.push(fpath.replace(remotePath, '' ) ); 1229 | readdir(fpath,done); 1230 | }else if(stat.isFile()){ 1231 | files.push(fpath.replace(remotePath, '' ) ); 1232 | done(); 1233 | } 1234 | }); 1235 | }); 1236 | }); 1237 | async.parallelLimit(toRead,4, function(){ 1238 | if(then) then(dirs,files); 1239 | }); 1240 | }); 1241 | } 1242 | 1243 | readdir(remotePath, function(dirs,files){ 1244 | var todoDirs = []; 1245 | var todoFiles = []; 1246 | dirs.forEach(function(dir){ 1247 | todoDirs.push(function(done){ 1248 | fs.mkdirs(localPath+dir,done); 1249 | }); 1250 | }); 1251 | files.forEach(function(file){ 1252 | todoFiles.push(function(done){ 1253 | that.readFile(server, remotePath+file, localPath+file, done); 1254 | }); 1255 | }); 1256 | 1257 | async.parallelLimit(todoDirs,4, function(){ 1258 | async.parallelLimit(todoFiles,4, allDone); 1259 | }); 1260 | }); 1261 | 1262 | }); 1263 | }); 1264 | }; 1265 | 1266 | module.exports = SSH2Utils; 1267 | --------------------------------------------------------------------------------