Last active
December 10, 2015 19:59
-
-
Save mattpowell/4485458 to your computer and use it in GitHub Desktop.
ssh-tunnel.js - I've been needing to set up tunnels in a couple of random projects lately, so, I thought I'd throw up what I've been using in to a gist so I can add it to my package.json's. I'll eventually make a real npm module out of this, but, want to flesh out and harden a few things first...
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var Util = require('util'), | |
Ee = require('events').EventEmitter, | |
Fs = require('fs'), | |
Spawn = require('child_process').exec, | |
Crypto = require('crypto'); | |
//this probably won't work well on other systems. also, this technique seems pretty fragile | |
function isRunning(pid, callback) { | |
Spawn('ps -hp ' + pid +' -o command', function(err, stdout) { | |
var isSuccess = !err && stdout.toString().indexOf('ssh') > -1; | |
callback(isSuccess); | |
}); | |
} | |
//hmm, this also seems a bit fragile :/ | |
function kill(pid, callback) { | |
Spawn('kill -9 ' + pid, function(err, stdout) { | |
var isSuccess = !err && stdout.toString() === ''; | |
callback(isSuccess); | |
}); | |
} | |
function killOldProcess(pidName, callback) { | |
Fs.readFile('.pid' + pidName, function(err, buf) { | |
var oldPid = !err && buf.toString(); | |
if (oldPid) { | |
isRunning(oldPid, function(isRunning) { | |
if (isRunning) { | |
kill(oldPid, callback); | |
}else { | |
callback && callback(false); | |
} | |
}); | |
}else { | |
callback && callback(false); | |
} | |
}); | |
} | |
function clean(pidName, callback) { | |
killOldProcess(pidName, function() { | |
Fs.writeFileSync('.pid' + pidName, ''); | |
callback(); | |
}); | |
} | |
function connect(localPort, remotePort, host, pidName, callback) { | |
var rtn = {}, | |
isCalled = false; | |
function run() { | |
var child = Spawn('ssh -v -A -N -L ' + localPort + ':127.0.0.1:' + remotePort + ' ' + host, function(err, stdout) { | |
!isCalled && callback(err); | |
//it died or it couldn't connect or it was terminated | |
//console.log("IN CALLBACK", arguments); | |
}); | |
child.stderr.on('data', function isStartedListener(buf) { | |
var isStarted = buf.toString().indexOf('Entering interactive session.') > -1; | |
if (isStarted) { | |
child.removeListener('data', isStartedListener); | |
process.nextTick(function() { | |
!isCalled && callback(null, child); | |
isCalled = true | |
}); | |
} | |
}); | |
Fs.writeFileSync('.pid' + pidName, child.pid);//TODO: should this be a config? | |
rtn.process = child; | |
} | |
killOldProcess(pidName, run); | |
return rtn; | |
} | |
function disconnect(process, pidName, callback) { | |
var timeout; | |
//remove exit handler | |
process.removeAllListeners('exit'); | |
//do a nice disconnect first | |
if (process.connected) { | |
process.disconnect(); | |
} | |
//if we're not disconnected after 1 second, then kill. also, do some clean-up | |
timeout = setTimeout(function() { | |
clean(pidName, callback); | |
}, 1E3); | |
} | |
var globalPidNames = []; | |
function Tunnel( host, localPort, callback ) { | |
var parts = host.split(':'), md5; | |
Ee.call(this); | |
this.host = parts[0]; | |
this.remotePort = parts[1] || localPort; | |
this.localPort = localPort; | |
md5 = Crypto.createHash('md5'); | |
md5.update(this.host + this.remotePort + '' + this.localPort); | |
this.pidName = md5.digest('hex'); | |
globalPidNames.push(this.pidName); | |
if (typeof(callback) === 'function') { | |
this.connect(callback); | |
} | |
} | |
Tunnel.prototype = new Ee; | |
Tunnel.prototype.connect = function( callback ) { | |
var self = this; | |
connect(this.localPort, this.remotePort, this.host, this.pidName, function( err, cnx ) { | |
var isSuccess = !err && cnx; | |
if (isSuccess) { | |
self.cnx = cnx; | |
cnx.on('exit', self.handleExit); | |
self.emit('connected'); | |
} | |
callback && callback(isSuccess); | |
}); | |
}; | |
Tunnel.prototype.disconnect = function( callback ) { | |
var self = this; | |
disconnect(this.cnx, this.pidName, function() { | |
delete self.cnx; | |
self.emit('disconnected'); | |
callback && callback(); | |
}); | |
}; | |
Tunnel.prototype.handleExit = function() { | |
console.log('ssh tunnel is down'); | |
this.emit('disconnected'); | |
clean(this.pidName, function() { | |
//? | |
}); | |
}; | |
Tunnel.prototype.isConnected = function() { | |
return !!this.cnx;// && this.cnx.connected; | |
}; | |
process.on('exit', function() { | |
console.log('process going down'); | |
//clean all registered pids | |
}); | |
module.exports = Tunnel; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"name": "ssh-tunnel", | |
"author": "Matt Powell", | |
"description": "tbd", | |
"main": "./index.js", | |
"version": "0.0.1" | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Now, you can run this:
npm install https://gist.github.com/4485458/download