Skip to content

Instantly share code, notes, and snippets.

@mattpowell
Last active December 10, 2015 19:59
Show Gist options
  • Save mattpowell/4485458 to your computer and use it in GitHub Desktop.
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...
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;
{
"name": "ssh-tunnel",
"author": "Matt Powell",
"description": "tbd",
"main": "./index.js",
"version": "0.0.1"
}
@mattpowell
Copy link
Author

Now, you can run this: npm install https://gist.github.com/4485458/download

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment