WIP keep parsing and console output in remote, move other stuff out
This commit is contained in:
parent
f3e5945afc
commit
c3e9bbaa5a
|
@ -8,11 +8,11 @@ var os = require('os');
|
||||||
//var url = require('url');
|
//var url = require('url');
|
||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var http = require('http');
|
|
||||||
//var https = require('https');
|
//var https = require('https');
|
||||||
var YAML = require('js-yaml');
|
var YAML = require('js-yaml');
|
||||||
var TOML = require('toml');
|
var TOML = require('toml');
|
||||||
var TPLS = TOML.parse(fs.readFileSync(path.join(__dirname, "../lib/en-us.toml"), 'utf8'));
|
var TPLS = TOML.parse(fs.readFileSync(path.join(__dirname, "../lib/en-us.toml"), 'utf8'));
|
||||||
|
|
||||||
/*
|
/*
|
||||||
if ('function' !== typeof TOML.stringify) {
|
if ('function' !== typeof TOML.stringify) {
|
||||||
TOML.stringify = require('json2toml');
|
TOML.stringify = require('json2toml');
|
||||||
|
@ -314,395 +314,142 @@ function askForConfig(state, mainCb) {
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
|
|
||||||
var utils = {
|
var RC;
|
||||||
request: function request(opts, fn) {
|
|
||||||
if (!opts) { opts = {}; }
|
|
||||||
var service = opts.service || 'config';
|
|
||||||
var req = http.request({
|
|
||||||
socketPath: state._ipc.path
|
|
||||||
, method: opts.method || 'GET'
|
|
||||||
, path: '/rpc/' + service
|
|
||||||
}, function (resp) {
|
|
||||||
var body = '';
|
|
||||||
|
|
||||||
function finish() {
|
|
||||||
if (200 !== resp.statusCode) {
|
|
||||||
console.warn(resp.statusCode);
|
|
||||||
console.warn(body || ('get' + service + ' failed'));
|
|
||||||
//cb(new Error("not okay"), body);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!body) { fn(null, null); return; }
|
|
||||||
|
|
||||||
try {
|
|
||||||
body = JSON.parse(body);
|
|
||||||
} catch(e) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
|
|
||||||
fn(null, body);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (resp.headers['content-length']) {
|
|
||||||
resp.on('data', function (chunk) {
|
|
||||||
body += chunk.toString();
|
|
||||||
});
|
|
||||||
resp.on('end', function () {
|
|
||||||
finish();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
req.on('error', function (err) {
|
|
||||||
// ENOENT - never started, cleanly exited last start, or creating socket at a different path
|
|
||||||
// ECONNREFUSED - leftover socket just needs to be restarted
|
|
||||||
if ('ENOENT' === err.code || 'ECONNREFUSED' === err.code) {
|
|
||||||
if (opts._taketwo) {
|
|
||||||
console.error("Either the telebit service was not already (and could not be started) or its socket could not be written to.");
|
|
||||||
console.error(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
require('../usr/share/install-launcher.js').install({ env: process.env }, function (err) {
|
|
||||||
if (err) { fn(err); return; }
|
|
||||||
opts._taketwo = true;
|
|
||||||
setTimeout(function () {
|
|
||||||
utils.request(opts, fn);
|
|
||||||
}, 2500);
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ('ENOTSOCK' === err.code) {
|
|
||||||
console.error(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.error(err);
|
|
||||||
return;
|
|
||||||
});
|
|
||||||
req.end();
|
|
||||||
}
|
|
||||||
, putConfig: function putConfig(service, args, fn) {
|
|
||||||
var req = http.request({
|
|
||||||
socketPath: state._ipc.path
|
|
||||||
, method: 'POST'
|
|
||||||
, path: '/rpc/' + service + '?_body=' + encodeURIComponent(JSON.stringify(args))
|
|
||||||
}, function (resp) {
|
|
||||||
|
|
||||||
function finish() {
|
|
||||||
if ('function' === typeof fn) {
|
|
||||||
fn(null, resp);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.info("");
|
|
||||||
if (200 !== resp.statusCode) {
|
|
||||||
console.warn("'" + service + "' may have failed."
|
|
||||||
+ " Consider peaking at the logs either with 'journalctl -xeu telebit' or /opt/telebit/var/log/error.log");
|
|
||||||
console.warn(resp.statusCode, body);
|
|
||||||
//cb(new Error("not okay"), body);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!body) {
|
|
||||||
console.info("👌");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
body = JSON.parse(body);
|
|
||||||
} catch(e) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("AWAIT_AUTH" === body.code) {
|
|
||||||
console.info(body.message);
|
|
||||||
} else if ("CONFIG" === body.code) {
|
|
||||||
delete body.code;
|
|
||||||
//console.info(TOML.stringify(body));
|
|
||||||
console.info(YAML.safeDump(body));
|
|
||||||
} else {
|
|
||||||
if ('http' === body.module) {
|
|
||||||
// TODO we'll support slingshot-ing in the future
|
|
||||||
if (String(body.local) === String(parseInt(body.local, 10))) {
|
|
||||||
console.info('> Forwarding https://' + body.remote + ' => localhost:' + body.local);
|
|
||||||
} else {
|
|
||||||
console.info('> Serving ' + body.local + ' as https://' + body.remote);
|
|
||||||
}
|
|
||||||
} else if ('tcp' === body.module) {
|
|
||||||
console.info('> Forwarding ' + state.config.relay + ':' + body.remote + ' => localhost:' + body.local);
|
|
||||||
} else if ('ssh' === body.module) {
|
|
||||||
//console.info('> Forwarding ' + state.config.relay + ' -p ' + JSON.stringify(body) + ' => localhost:' + body.local);
|
|
||||||
console.info('> Forwarding ssh+https (openssl proxy) => localhost:' + body.local);
|
|
||||||
} else {
|
|
||||||
console.info(JSON.stringify(body, null, 2));
|
|
||||||
}
|
|
||||||
console.info();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var body = '';
|
|
||||||
if (resp.headers['content-length']) {
|
|
||||||
resp.on('data', function (chunk) {
|
|
||||||
body += chunk.toString();
|
|
||||||
});
|
|
||||||
resp.on('end', function () {
|
|
||||||
finish();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
req.on('error', function (err) {
|
|
||||||
console.error('Put Config Error:');
|
|
||||||
console.error(err);
|
|
||||||
return;
|
|
||||||
});
|
|
||||||
req.end();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Two styles:
|
|
||||||
// http 3000
|
|
||||||
// http modulename
|
|
||||||
function makeRpc(key) {
|
|
||||||
if (key !== argv[0]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
utils.putConfig(argv[0], argv.slice(1));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function packConfig(config) {
|
|
||||||
return Object.keys(config).map(function (key) {
|
|
||||||
var val = config[key];
|
|
||||||
if ('undefined' === val) {
|
|
||||||
throw new Error("'undefined' used as a string value");
|
|
||||||
}
|
|
||||||
if ('undefined' === typeof val) {
|
|
||||||
//console.warn('[DEBUG]', key, 'is present but undefined');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (val && 'object' === typeof val && !Array.isArray(val)) {
|
|
||||||
val = JSON.stringify(val);
|
|
||||||
}
|
|
||||||
return key + ':' + val; // converts arrays to strings with ,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getToken(err, state) {
|
|
||||||
if (err) {
|
|
||||||
console.error("Error while initializing config [init]:");
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
state.relay = state.config.relay;
|
|
||||||
|
|
||||||
// { _otp, config: {} }
|
|
||||||
common.api.token(state, {
|
|
||||||
error: function (err/*, next*/) {
|
|
||||||
console.error("[Error] common.api.token:");
|
|
||||||
console.error(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
, directory: function (dir, next) {
|
|
||||||
//console.log('[directory] Telebit Relay Discovered:');
|
|
||||||
//console.log(dir);
|
|
||||||
state._apiDirectory = dir;
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
, tunnelUrl: function (tunnelUrl, next) {
|
|
||||||
//console.log('[tunnelUrl] Telebit Relay Tunnel Socket:', tunnelUrl);
|
|
||||||
state.wss = tunnelUrl;
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
, requested: function (authReq, next) {
|
|
||||||
//console.log("[requested] Pairing Requested");
|
|
||||||
state.config._otp = state.config._otp = authReq.otp;
|
|
||||||
|
|
||||||
if (!state.config.token && state._can_pair) {
|
|
||||||
console.info("");
|
|
||||||
console.info("==============================================");
|
|
||||||
console.info(" Hey, Listen! ");
|
|
||||||
console.info("==============================================");
|
|
||||||
console.info(" ");
|
|
||||||
console.info(" GO CHECK YOUR EMAIL! ");
|
|
||||||
console.info(" ");
|
|
||||||
console.info(" DEVICE PAIR CODE: 0000 ".replace(/0000/g, state.config._otp));
|
|
||||||
console.info(" ");
|
|
||||||
console.info("==============================================");
|
|
||||||
console.info("");
|
|
||||||
}
|
|
||||||
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
, connect: function (pretoken, next) {
|
|
||||||
//console.log("[connect] Enabling Pairing Locally...");
|
|
||||||
state.config.pretoken = pretoken;
|
|
||||||
state._connecting = true;
|
|
||||||
|
|
||||||
// TODO use php-style object querification
|
|
||||||
utils.putConfig('config', packConfig(state.config), function (err/*, body*/) {
|
|
||||||
if (err) {
|
|
||||||
state._error = err;
|
|
||||||
console.error("Error while initializing config [connect]:");
|
|
||||||
console.error(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.info("waiting...");
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
, offer: function (token, next) {
|
|
||||||
//console.log("[offer] Pairing Enabled by Relay");
|
|
||||||
state.config.token = token;
|
|
||||||
if (state._error) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
state._connecting = true;
|
|
||||||
try {
|
|
||||||
require('jsonwebtoken').decode(token);
|
|
||||||
//console.log(require('jsonwebtoken').decode(token));
|
|
||||||
} catch(e) {
|
|
||||||
console.warn("[warning] could not decode token");
|
|
||||||
}
|
|
||||||
utils.putConfig('config', packConfig(state.config), function (err/*, body*/) {
|
|
||||||
if (err) {
|
|
||||||
state._error = err;
|
|
||||||
console.error("Error while initializing config [offer]:");
|
|
||||||
console.error(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
//console.log("Pairing Enabled Locally");
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
, granted: function (_, next) {
|
|
||||||
//console.log("[grant] Pairing complete!");
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
, end: function () {
|
|
||||||
utils.putConfig('enable', [], function (err) {
|
|
||||||
if (err) { console.error(err); return; }
|
|
||||||
console.info("Success");
|
|
||||||
|
|
||||||
// workaround for https://github.com/nodejs/node/issues/21319
|
|
||||||
if (state._useTty) {
|
|
||||||
setTimeout(function () {
|
|
||||||
console.info("Some fun things to try first:\n");
|
|
||||||
console.info(" ~/telebit http ~/public");
|
|
||||||
console.info(" ~/telebit tcp 5050");
|
|
||||||
console.info(" ~/telebit ssh auto");
|
|
||||||
console.info();
|
|
||||||
console.info("Press any key to continue...");
|
|
||||||
console.info();
|
|
||||||
process.exit(0);
|
|
||||||
}, 0.5 * 1000);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// end workaround
|
|
||||||
|
|
||||||
parseCli(state);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseCli(/*state*/) {
|
|
||||||
var special = [
|
|
||||||
'false', 'none', 'off', 'disable'
|
|
||||||
, 'true', 'auto', 'on', 'enable'
|
|
||||||
];
|
|
||||||
if (-1 !== argv.indexOf('init')) {
|
|
||||||
utils.putConfig('list', []/*, function (err) {
|
|
||||||
}*/);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ([ 'ssh', 'http', 'tcp' ].some(function (key) {
|
|
||||||
if (key !== argv[0]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (argv[1]) {
|
|
||||||
if (String(argv[1]) === String(parseInt(argv[1], 10))) {
|
|
||||||
// looks like a port
|
|
||||||
argv[1] = parseInt(argv[1], 10);
|
|
||||||
} else if (/\/|\\/.test(argv[1])) {
|
|
||||||
// looks like a path
|
|
||||||
argv[1] = path.resolve(argv[1]);
|
|
||||||
// TODO make a default assignment here
|
|
||||||
} else if (-1 === special.indexOf(argv[1])) {
|
|
||||||
console.error("Not sure what you meant by '" + argv[1] + "'.");
|
|
||||||
console.error("Remember: paths should begin with ." + path.sep + ", like '." + path.sep + argv[1] + "'");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
utils.putConfig(argv[0], argv.slice(1));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
})) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ([ 'status', 'enable', 'disable', 'restart', 'list', 'save' ].some(makeRpc)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
help();
|
|
||||||
process.exit(11);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleConfig(err, config) {
|
|
||||||
//console.log('CONFIG');
|
|
||||||
//console.log(config);
|
|
||||||
state.config = config;
|
|
||||||
var verstrd = [ pkg.name + ' daemon v' + state.config.version ];
|
|
||||||
if (state.config.version && state.config.version !== pkg.version) {
|
|
||||||
console.info(verstr.join(' '), verstrd.join(' '));
|
|
||||||
} else {
|
|
||||||
console.info(verstr.join(' '));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (err) { console.error(err); process.exit(101); return; }
|
|
||||||
|
|
||||||
//
|
|
||||||
// check for init first, before anything else
|
|
||||||
// because it has arguments that may help in
|
|
||||||
// the next steps
|
|
||||||
//
|
|
||||||
if (-1 !== argv.indexOf('init')) {
|
|
||||||
parsers.init(argv, getToken);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!state.config.relay || !state.config.token) {
|
|
||||||
if (!state.config.relay) {
|
|
||||||
state.config.relay = 'telebit.cloud';
|
|
||||||
}
|
|
||||||
|
|
||||||
//console.log("question the user?", Date.now());
|
|
||||||
askForConfig(state, function (err, state) {
|
|
||||||
// no errors actually get passed, so this is just future-proofing
|
|
||||||
if (err) { throw err; }
|
|
||||||
|
|
||||||
if (!state.config.token && state._can_pair) {
|
|
||||||
state.config._otp = common.otp();
|
|
||||||
}
|
|
||||||
|
|
||||||
//console.log("done questioning:", Date.now());
|
|
||||||
if (!state.token && !state.config.token) {
|
|
||||||
getToken(err, state);
|
|
||||||
} else {
|
|
||||||
parseCli(state);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//console.log("no questioning:");
|
|
||||||
parseCli(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseConfig(err, text) {
|
function parseConfig(err, text) {
|
||||||
|
function handleConfig(err, config) {
|
||||||
|
//console.log('CONFIG');
|
||||||
|
//console.log(config);
|
||||||
|
state.config = config;
|
||||||
|
var verstrd = [ pkg.name + ' daemon v' + state.config.version ];
|
||||||
|
if (state.config.version && state.config.version !== pkg.version) {
|
||||||
|
console.info(verstr.join(' '), verstrd.join(' '));
|
||||||
|
} else {
|
||||||
|
console.info(verstr.join(' '));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('ENOENT' === err.code || 'ECONNREFUSED' === err.code) {
|
||||||
|
console.error("Either the telebit service was not already (and could not be started) or its socket could not be written to.");
|
||||||
|
console.error(err);
|
||||||
|
} else if ('ENOTSOCK' === err.code) {
|
||||||
|
console.error(err);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
if (err) { process.exit(101); return; }
|
||||||
|
|
||||||
|
//
|
||||||
|
// check for init first, before anything else
|
||||||
|
// because it has arguments that may help in
|
||||||
|
// the next steps
|
||||||
|
//
|
||||||
|
if (-1 !== argv.indexOf('init')) {
|
||||||
|
parsers.init(argv, function (err) {
|
||||||
|
if (err) {
|
||||||
|
console.error("Error while initializing config [init]:");
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
getToken(function (err) {
|
||||||
|
if (err) {
|
||||||
|
console.error("Error while getting token [init]:");
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
parseCli(state);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!state.config.relay || !state.config.token) {
|
||||||
|
if (!state.config.relay) {
|
||||||
|
state.config.relay = 'telebit.cloud';
|
||||||
|
}
|
||||||
|
|
||||||
|
//console.log("question the user?", Date.now());
|
||||||
|
askForConfig(state, function (err, state) {
|
||||||
|
// no errors actually get passed, so this is just future-proofing
|
||||||
|
if (err) { throw err; }
|
||||||
|
|
||||||
|
if (!state.config.token && state._can_pair) {
|
||||||
|
state.config._otp = common.otp();
|
||||||
|
}
|
||||||
|
|
||||||
|
//console.log("done questioning:", Date.now());
|
||||||
|
if (!state.token && !state.config.token) {
|
||||||
|
if (err) {
|
||||||
|
console.error("Error while initializing config [init]:");
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
getToken(function (err) {
|
||||||
|
if (err) {
|
||||||
|
console.error("Error while getting token [init]:");
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
parseCli(state);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
parseCli(state);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//console.log("no questioning:");
|
||||||
|
parseCli(state);
|
||||||
|
}
|
||||||
|
function parseCli(/*state*/) {
|
||||||
|
var special = [
|
||||||
|
'false', 'none', 'off', 'disable'
|
||||||
|
, 'true', 'auto', 'on', 'enable'
|
||||||
|
];
|
||||||
|
if (-1 !== argv.indexOf('init')) {
|
||||||
|
RC.request({ service: 'list', method: 'POST', data: [] }, handleRemoteRequest('list'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([ 'ssh', 'http', 'tcp' ].some(function (key) {
|
||||||
|
if (key !== argv[0]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (argv[1]) {
|
||||||
|
if (String(argv[1]) === String(parseInt(argv[1], 10))) {
|
||||||
|
// looks like a port
|
||||||
|
argv[1] = parseInt(argv[1], 10);
|
||||||
|
} else if (/\/|\\/.test(argv[1])) {
|
||||||
|
// looks like a path
|
||||||
|
argv[1] = path.resolve(argv[1]);
|
||||||
|
// TODO make a default assignment here
|
||||||
|
} else if (-1 === special.indexOf(argv[1])) {
|
||||||
|
console.error("Not sure what you meant by '" + argv[1] + "'.");
|
||||||
|
console.error("Remember: paths should begin with ." + path.sep + ", like '." + path.sep + argv[1] + "'");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
RC.request({ service: argv[0], method: 'POST', data: argv.slice(1) }, handleRemoteRequest(argv[0]));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
})) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Two styles:
|
||||||
|
// http 3000
|
||||||
|
// http modulename
|
||||||
|
function makeRpc(key) {
|
||||||
|
if (key !== argv[0]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
RC.request({ service: argv[0], method: 'POST', data: argv.slice(1) }, handleRemoteRequest(argv[0]));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if ([ 'status', 'enable', 'disable', 'restart', 'list', 'save' ].some(makeRpc)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
help();
|
||||||
|
process.exit(11);
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
state._clientConfig = JSON.parse(text || '{}');
|
state._clientConfig = JSON.parse(text || '{}');
|
||||||
} catch(e1) {
|
} catch(e1) {
|
||||||
|
@ -721,13 +468,7 @@ function parseConfig(err, text) {
|
||||||
}
|
}
|
||||||
|
|
||||||
state._clientConfig = camelCopy(state._clientConfig || {}) || {};
|
state._clientConfig = camelCopy(state._clientConfig || {}) || {};
|
||||||
common._init(
|
RC = require('../lib/remote-control-client.js').create(state);
|
||||||
// make a default working dir and log dir
|
|
||||||
state._clientConfig.root || path.join(os.homedir(), '.local/share/telebit')
|
|
||||||
, (state._clientConfig.root && path.join(state._clientConfig.root, 'etc'))
|
|
||||||
|| path.resolve(common.DEFAULT_CONFIG_PATH, '..')
|
|
||||||
);
|
|
||||||
state._ipc = common.pipename(state._clientConfig, true);
|
|
||||||
|
|
||||||
if (!Object.keys(state._clientConfig).length) {
|
if (!Object.keys(state._clientConfig).length) {
|
||||||
console.info('(' + state._ipc.comment + ": " + state._ipc.path + ')');
|
console.info('(' + state._ipc.comment + ": " + state._ipc.path + ')');
|
||||||
|
@ -742,7 +483,165 @@ function parseConfig(err, text) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
utils.request({ service: 'config' }, handleConfig);
|
function handleRemoteRequest(service, fn) {
|
||||||
|
return function (err, body) {
|
||||||
|
if ('function' === typeof fn) {
|
||||||
|
fn(err, body); // XXX was resp
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.info("");
|
||||||
|
if (err) {
|
||||||
|
console.warn("'" + service + "' may have failed."
|
||||||
|
+ " Consider peaking at the logs either with 'journalctl -xeu telebit' or /opt/telebit/var/log/error.log");
|
||||||
|
console.warn(err.statusCode, err.message);
|
||||||
|
//cb(new Error("not okay"), body);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!body) {
|
||||||
|
console.info("👌");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
body = JSON.parse(body);
|
||||||
|
} catch(e) {
|
||||||
|
// ignore
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("AWAIT_AUTH" === body.code) {
|
||||||
|
console.info(body.message);
|
||||||
|
} else if ("CONFIG" === body.code) {
|
||||||
|
delete body.code;
|
||||||
|
//console.info(TOML.stringify(body));
|
||||||
|
console.info(YAML.safeDump(body));
|
||||||
|
} else {
|
||||||
|
if ('http' === body.module) {
|
||||||
|
// TODO we'll support slingshot-ing in the future
|
||||||
|
if (String(body.local) === String(parseInt(body.local, 10))) {
|
||||||
|
console.info('> Forwarding https://' + body.remote + ' => localhost:' + body.local);
|
||||||
|
} else {
|
||||||
|
console.info('> Serving ' + body.local + ' as https://' + body.remote);
|
||||||
|
}
|
||||||
|
} else if ('tcp' === body.module) {
|
||||||
|
console.info('> Forwarding ' + state.config.relay + ':' + body.remote + ' => localhost:' + body.local);
|
||||||
|
} else if ('ssh' === body.module) {
|
||||||
|
//console.info('> Forwarding ' + state.config.relay + ' -p ' + JSON.stringify(body) + ' => localhost:' + body.local);
|
||||||
|
console.info('> Forwarding ssh+https (openssl proxy) => localhost:' + body.local);
|
||||||
|
} else {
|
||||||
|
console.info(JSON.stringify(body, null, 2));
|
||||||
|
}
|
||||||
|
console.info();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getToken(fn) {
|
||||||
|
state.relay = state.config.relay;
|
||||||
|
|
||||||
|
// { _otp, config: {} }
|
||||||
|
common.api.token(state, {
|
||||||
|
error: function (err/*, next*/) {
|
||||||
|
console.error("[Error] common.api.token:");
|
||||||
|
console.error(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
, directory: function (dir, next) {
|
||||||
|
//console.log('[directory] Telebit Relay Discovered:');
|
||||||
|
//console.log(dir);
|
||||||
|
state._apiDirectory = dir;
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
, tunnelUrl: function (tunnelUrl, next) {
|
||||||
|
//console.log('[tunnelUrl] Telebit Relay Tunnel Socket:', tunnelUrl);
|
||||||
|
state.wss = tunnelUrl;
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
, requested: function (authReq, next) {
|
||||||
|
//console.log("[requested] Pairing Requested");
|
||||||
|
state.config._otp = state.config._otp = authReq.otp;
|
||||||
|
|
||||||
|
if (!state.config.token && state._can_pair) {
|
||||||
|
console.info(TPLS.remote.code.replace(/0000/g, state.config._otp));
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
, connect: function (pretoken, next) {
|
||||||
|
//console.log("[connect] Enabling Pairing Locally...");
|
||||||
|
state.config.pretoken = pretoken;
|
||||||
|
state._connecting = true;
|
||||||
|
|
||||||
|
// TODO use php-style object querification
|
||||||
|
RC.request({ service: 'config', method: 'POST', data: state.config }, handleRemoteRequest('config', function (err/*, body*/) {
|
||||||
|
if (err) {
|
||||||
|
state._error = err;
|
||||||
|
console.error("Error while initializing config [connect]:");
|
||||||
|
console.error(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.info("waiting...");
|
||||||
|
next();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
, offer: function (token, next) {
|
||||||
|
//console.log("[offer] Pairing Enabled by Relay");
|
||||||
|
state.config.token = token;
|
||||||
|
if (state._error) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
state._connecting = true;
|
||||||
|
try {
|
||||||
|
require('jsonwebtoken').decode(token);
|
||||||
|
//console.log(require('jsonwebtoken').decode(token));
|
||||||
|
} catch(e) {
|
||||||
|
console.warn("[warning] could not decode token");
|
||||||
|
}
|
||||||
|
RC.request({ service: 'config', method: 'POST', data: state.config }, handleRemoteRequest('config', function (err/*, body*/) {
|
||||||
|
if (err) {
|
||||||
|
state._error = err;
|
||||||
|
console.error("Error while initializing config [offer]:");
|
||||||
|
console.error(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//console.log("Pairing Enabled Locally");
|
||||||
|
next();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
, granted: function (_, next) {
|
||||||
|
//console.log("[grant] Pairing complete!");
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
, end: function () {
|
||||||
|
RC.request({ service: 'enable', method: 'POST', data: [] }, handleRemoteRequest('enable', function (err) {
|
||||||
|
if (err) { console.error(err); return; }
|
||||||
|
console.info("Success");
|
||||||
|
|
||||||
|
// workaround for https://github.com/nodejs/node/issues/21319
|
||||||
|
if (state._useTty) {
|
||||||
|
setTimeout(function () {
|
||||||
|
console.info("Some fun things to try first:\n");
|
||||||
|
console.info(" ~/telebit http ~/public");
|
||||||
|
console.info(" ~/telebit tcp 5050");
|
||||||
|
console.info(" ~/telebit ssh auto");
|
||||||
|
console.info();
|
||||||
|
console.info("Press any key to continue...");
|
||||||
|
console.info();
|
||||||
|
process.exit(0);
|
||||||
|
}, 0.5 * 1000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// end workaround
|
||||||
|
|
||||||
|
//parseCli(state);
|
||||||
|
fn();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
RC.request({ service: 'config', method: 'GET' }, handleRemoteRequest('config', handleConfig));
|
||||||
}
|
}
|
||||||
|
|
||||||
var parsers = {
|
var parsers = {
|
||||||
|
|
|
@ -452,5 +452,17 @@ The secret flags are:
|
||||||
[remote]
|
[remote]
|
||||||
version = "telebit remote v{version}"
|
version = "telebit remote v{version}"
|
||||||
|
|
||||||
|
code = "
|
||||||
|
==============================================
|
||||||
|
Hey, Listen!
|
||||||
|
==============================================
|
||||||
|
|
||||||
|
GO CHECK YOUR EMAIL!
|
||||||
|
|
||||||
|
DEVICE PAIR CODE: 0000
|
||||||
|
|
||||||
|
==============================================
|
||||||
|
"
|
||||||
|
|
||||||
[daemon]
|
[daemon]
|
||||||
version = "telebit daemon v{version}"
|
version = "telebit daemon v{version}"
|
||||||
|
|
|
@ -0,0 +1,116 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var os = require('os');
|
||||||
|
var path = require('path');
|
||||||
|
var http = require('http');
|
||||||
|
|
||||||
|
var common = require('./cli-common.js');
|
||||||
|
|
||||||
|
function packConfig(config) {
|
||||||
|
return Object.keys(config).map(function (key) {
|
||||||
|
var val = config[key];
|
||||||
|
if ('undefined' === val) {
|
||||||
|
throw new Error("'undefined' used as a string value");
|
||||||
|
}
|
||||||
|
if ('undefined' === typeof val) {
|
||||||
|
//console.warn('[DEBUG]', key, 'is present but undefined');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (val && 'object' === typeof val && !Array.isArray(val)) {
|
||||||
|
val = JSON.stringify(val);
|
||||||
|
}
|
||||||
|
return key + ':' + val; // converts arrays to strings with ,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.create = function (state) {
|
||||||
|
common._init(
|
||||||
|
// make a default working dir and log dir
|
||||||
|
state._clientConfig.root || path.join(os.homedir(), '.local/share/telebit')
|
||||||
|
, (state._clientConfig.root && path.join(state._clientConfig.root, 'etc'))
|
||||||
|
|| path.resolve(common.DEFAULT_CONFIG_PATH, '..')
|
||||||
|
);
|
||||||
|
state._ipc = common.pipename(state._clientConfig, true);
|
||||||
|
|
||||||
|
function makeResponder(service, resp, fn) {
|
||||||
|
var body = '';
|
||||||
|
|
||||||
|
function finish() {
|
||||||
|
var err;
|
||||||
|
|
||||||
|
if (200 !== resp.statusCode) {
|
||||||
|
err = new Error(body || ('get' + service + ' failed'));
|
||||||
|
err.statusCode = resp.statusCode;
|
||||||
|
err.code = "E_REQUEST";
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
body = JSON.parse(body);
|
||||||
|
} catch(e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
fn(err, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!resp.headers['content-length'] && !resp.headers['content-type']) {
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO use readable
|
||||||
|
resp.on('data', function (chunk) {
|
||||||
|
body += chunk.toString();
|
||||||
|
});
|
||||||
|
resp.on('end', finish);
|
||||||
|
}
|
||||||
|
|
||||||
|
var RC = {};
|
||||||
|
RC.request = function request(opts, fn) {
|
||||||
|
if (!opts) { opts = {}; }
|
||||||
|
var service = opts.service || 'config';
|
||||||
|
var args = opts.data;
|
||||||
|
if (args && 'control' === service) {
|
||||||
|
args = packConfig(args);
|
||||||
|
}
|
||||||
|
var json = JSON.stringify(args);
|
||||||
|
var url = '/rpc/' + service;
|
||||||
|
if (json) {
|
||||||
|
url += ('?_body=' + encodeURIComponent(json));
|
||||||
|
}
|
||||||
|
var method = opts.method || (args && 'POST') || 'GET';
|
||||||
|
var req = http.request({
|
||||||
|
socketPath: state._ipc.path
|
||||||
|
, method: method
|
||||||
|
, path: url
|
||||||
|
}, function (resp) {
|
||||||
|
makeResponder(service, resp, fn);
|
||||||
|
});
|
||||||
|
|
||||||
|
req.on('error', function (err) {
|
||||||
|
// ENOENT - never started, cleanly exited last start, or creating socket at a different path
|
||||||
|
// ECONNREFUSED - leftover socket just needs to be restarted
|
||||||
|
if ('ENOENT' === err.code || 'ECONNREFUSED' === err.code) {
|
||||||
|
if (opts._taketwo) {
|
||||||
|
fn(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
require('../usr/share/install-launcher.js').install({ env: process.env }, function (err) {
|
||||||
|
if (err) { fn(err); return; }
|
||||||
|
opts._taketwo = true;
|
||||||
|
setTimeout(function () {
|
||||||
|
RC.request(opts, fn);
|
||||||
|
}, 2500);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn(err);
|
||||||
|
});
|
||||||
|
if ('POST' === method && opts.data) {
|
||||||
|
req.write(json || opts.data);
|
||||||
|
}
|
||||||
|
req.end();
|
||||||
|
};
|
||||||
|
return RC;
|
||||||
|
};
|
Loading…
Reference in New Issue