├── 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 [](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 |
--------------------------------------------------------------------------------