tunlr
#!/usr/bin/env node
/*
tunlr - simple SSH tunnel management
usage: tunlr [options] [command]
options:
-q quiet. suppress console output.
-c [filename] use [filename] as config. defaults to look for ~/.tunnelrc
commands:
list list available tunnel configs
start [name?] start a tunnel (or tunnels). if no [name] start all tunnels.
stop [name?] stop a tunnel (or tunnels). if no [name] stop all tunnels.
example:
$ tunlr -c /etc/tunnels.conf -q start &
example config:
{
tunnels:[
{
// same as: ssh -L 12345:localhost:80 example.com -N
description: "Some Text About this Tunnel",
name:"foo", // the name for start/stop/list
local: 12345, // local port to listen on
host: "localhost", // host to direct to
remote: 80, // remote port to connect to
connect: "example.com" // ssh server to do connect to
}
]
}
*/
var fs = require("fs"),
spawn = require("child_process").spawn,
// some doc somewhere claims this exists
// fork = require("child_process").fork,
// ugh = console.log(fork),
path = require("path"),
cmdline = process.argv.join(" "),
home = process.env.HOME,
files = [home + "/.tunnelrc"],
c = cmdline.match(/-cs+(.*)s+?/),
verbose = !(~cmdline.indexOf("-q"))
;
// handle -c {configfile} from cmdnline
if(c && c[1]){
var cfg = c[1];
if(cfg.charAt(0) == "."){
cfg = process.cwd() + "/" + cfg;
}
files = [path.resolve(cfg)];
}
function loadconfig(){
var cfg = {}, found = files.map(function(file){
var exists;
try{
exists = fs.statSync(file);
}catch(e){
return false;
}
return exists && fs.readFileSync(file, "utf8");
}).filter(function(item){ return item; });
if(found.length){
found.map(function(conf){
try{
var data = eval("(" + conf + ")");
}catch(e){
return false;
}
return data;
}).forEach(function(data){
if(data){
for(var i in data){
cfg[i] = data[i];
}
}
});
}else{
return false;
}
return cfg;
}
var lockfile_base = "/tmp/tunlr-"
function setLock(tunnel){
// setup a lockfile for the proc.pid
}
function clearLock(tunnel){
// clear a lockfile for proc.pid
}
function checkLock(tunnel){
// look if we had a .pid file this tunnel?
return false;
}
function start_tunnel(tunnel, persists, delay){
var args = ["-L", [tunnel.local, tunnel.host, tunnel.remote].join(":"), tunnel.connect, "-N"],
proc = tunnel.proc = spawn("ssh", args)
;
setLock(tunnel);
delay = delay || 0;
verbose && console.log('started', tunnel.name, proc.pid);
if(persists){
proc.on("exit", function(){
clearLock(tunnel);
verbose && console.log("exited", tunnel.name);
setTimeout(function(){
proc = tunnel.proc = start_tunnel(tunnel, persists, delay + 1000);
setLock(tunnel);
}, delay);
});
}
return proc;
}
var config = loadconfig();
if(config && config.tunnels){
if(~cmdline.indexOf("start")){
var starting, which = cmdline.match(/starts+(.*)?/);
if(which && which[1]){
starting = config.tunnels.filter(function(tunnel){
return tunnel.name == which[1];
})
}else{
starting = config.tunnels.map(function(tunnel){
if(!tunnel.only_manual){ return tunnel; }
}).filter(function(item){ return item; });
}
starting.forEach(function(tunnel){
start_tunnel(tunnel, true);
});
}else if(~cmdline.indexOf("stop")){
var stopping, whichs = cmdline.match(/stops+(.*)?/);
if(whichs && whichs[1]){
stopping = config.tunnels.filter(function(tunnel){
return tunnel.name == which[1];
});
}else{
stopping = config.tunnels;
}
stopping.forEach(function(tunnel){
if(tunnel.proc){
clearLock(tunnel.proc);
tunnel.proc.kill("SIGHUP");
}
});
}else if(~cmdline.indexOf("list")){
console.log("tunnels:");
config.tunnels.forEach(function(tunnel, i){
var running = checkLock(tunnel);
console.log(i + "t" + (running ? "*" : "") + tunnel.name + (tunnel.description ? "t" + tunnel.description : ""));
});
}else{
console.log("no command?");
}
}else{
console.log("no config found");
}
No comments yet.