WIP keep parsing and console output in remote, move other stuff out

This commit is contained in:
AJ ONeal 2018-09-25 00:45:32 -06:00
parent f3e5945afc
commit c3e9bbaa5a
3 changed files with 423 additions and 396 deletions

View File

@ -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,92 +314,186 @@ 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() { function parseConfig(err, text) {
if (200 !== resp.statusCode) { function handleConfig(err, config) {
console.warn(resp.statusCode); //console.log('CONFIG');
console.warn(body || ('get' + service + ' failed')); //console.log(config);
//cb(new Error("not okay"), body); state.config = config;
return; var verstrd = [ pkg.name + ' daemon v' + state.config.version ];
} if (state.config.version && state.config.version !== pkg.version) {
console.info(verstr.join(' '), verstrd.join(' '));
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 { } else {
finish(); console.info(verstr.join(' '));
} }
});
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 ('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("Either the telebit service was not already (and could not be started) or its socket could not be written to.");
console.error(err); console.error(err);
} else if ('ENOTSOCK' === err.code) {
console.error(err);
return; return;
} else {
console.error(err);
} }
require('../usr/share/install-launcher.js').install({ env: process.env }, function (err) { if (err) { process.exit(101); return; }
if (err) { fn(err); return; }
opts._taketwo = true; //
setTimeout(function () { // check for init first, before anything else
utils.request(opts, fn); // because it has arguments that may help in
}, 2500); // 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; return;
} }
if ('ENOTSOCK' === err.code) {
console.error(err); if (!state.config.relay || !state.config.token) {
return; if (!state.config.relay) {
state.config.relay = 'telebit.cloud';
} }
console.error(err);
return; //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);
}); });
req.end(); } else {
parseCli(state);
} }
, 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; 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 {
state._clientConfig = JSON.parse(text || '{}');
} catch(e1) {
try {
state._clientConfig = YAML.safeLoad(text || '{}');
} catch(e2) {
try {
state._clientConfig = TOML.parse(text || '');
} catch(e3) {
console.error(e1.message);
console.error(e2.message);
process.exit(1);
return;
}
}
}
state._clientConfig = camelCopy(state._clientConfig || {}) || {};
RC = require('../lib/remote-control-client.js').create(state);
if (!Object.keys(state._clientConfig).length) {
console.info('(' + state._ipc.comment + ": " + state._ipc.path + ')');
console.info(""); console.info("");
if (200 !== resp.statusCode) { }
if ((err && 'ENOENT' === err.code) || !Object.keys(state._clientConfig).length) {
if (!err || 'ENOENT' === err.code) {
//console.warn("Empty config file. Run 'telebit init' to configure.\n");
} else {
console.warn("Couldn't load config:\n\n\t" + err.message + "\n");
}
}
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." console.warn("'" + service + "' may have failed."
+ " Consider peaking at the logs either with 'journalctl -xeu telebit' or /opt/telebit/var/log/error.log"); + " Consider peaking at the logs either with 'journalctl -xeu telebit' or /opt/telebit/var/log/error.log");
console.warn(resp.statusCode, body); console.warn(err.statusCode, err.message);
//cb(new Error("not okay"), body); //cb(new Error("not okay"), body);
return; return;
} }
@ -413,6 +507,7 @@ var utils = {
body = JSON.parse(body); body = JSON.parse(body);
} catch(e) { } catch(e) {
// ignore // ignore
} }
if ("AWAIT_AUTH" === body.code) { if ("AWAIT_AUTH" === body.code) {
@ -439,62 +534,10 @@ var utils = {
} }
console.info(); 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) { function getToken(fn) {
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; state.relay = state.config.relay;
// { _otp, config: {} } // { _otp, config: {} }
@ -520,17 +563,7 @@ function getToken(err, state) {
state.config._otp = state.config._otp = authReq.otp; state.config._otp = state.config._otp = authReq.otp;
if (!state.config.token && state._can_pair) { if (!state.config.token && state._can_pair) {
console.info(""); console.info(TPLS.remote.code.replace(/0000/g, state.config._otp));
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(); next();
@ -541,7 +574,7 @@ function getToken(err, state) {
state._connecting = true; state._connecting = true;
// TODO use php-style object querification // TODO use php-style object querification
utils.putConfig('config', packConfig(state.config), function (err/*, body*/) { RC.request({ service: 'config', method: 'POST', data: state.config }, handleRemoteRequest('config', function (err/*, body*/) {
if (err) { if (err) {
state._error = err; state._error = err;
console.error("Error while initializing config [connect]:"); console.error("Error while initializing config [connect]:");
@ -550,7 +583,7 @@ function getToken(err, state) {
} }
console.info("waiting..."); console.info("waiting...");
next(); next();
}); }));
} }
, offer: function (token, next) { , offer: function (token, next) {
//console.log("[offer] Pairing Enabled by Relay"); //console.log("[offer] Pairing Enabled by Relay");
@ -565,7 +598,7 @@ function getToken(err, state) {
} catch(e) { } catch(e) {
console.warn("[warning] could not decode token"); console.warn("[warning] could not decode token");
} }
utils.putConfig('config', packConfig(state.config), function (err/*, body*/) { RC.request({ service: 'config', method: 'POST', data: state.config }, handleRemoteRequest('config', function (err/*, body*/) {
if (err) { if (err) {
state._error = err; state._error = err;
console.error("Error while initializing config [offer]:"); console.error("Error while initializing config [offer]:");
@ -574,14 +607,14 @@ function getToken(err, state) {
} }
//console.log("Pairing Enabled Locally"); //console.log("Pairing Enabled Locally");
next(); next();
}); }));
} }
, granted: function (_, next) { , granted: function (_, next) {
//console.log("[grant] Pairing complete!"); //console.log("[grant] Pairing complete!");
next(); next();
} }
, end: function () { , end: function () {
utils.putConfig('enable', [], function (err) { RC.request({ service: 'enable', method: 'POST', data: [] }, handleRemoteRequest('enable', function (err) {
if (err) { console.error(err); return; } if (err) { console.error(err); return; }
console.info("Success"); console.info("Success");
@ -601,148 +634,14 @@ function getToken(err, state) {
} }
// end workaround // end workaround
parseCli(state); //parseCli(state);
}); fn();
}));
} }
}); });
} }
function parseCli(/*state*/) { RC.request({ service: 'config', method: 'GET' }, handleRemoteRequest('config', handleConfig));
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) {
try {
state._clientConfig = JSON.parse(text || '{}');
} catch(e1) {
try {
state._clientConfig = YAML.safeLoad(text || '{}');
} catch(e2) {
try {
state._clientConfig = TOML.parse(text || '');
} catch(e3) {
console.error(e1.message);
console.error(e2.message);
process.exit(1);
return;
}
}
}
state._clientConfig = camelCopy(state._clientConfig || {}) || {};
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);
if (!Object.keys(state._clientConfig).length) {
console.info('(' + state._ipc.comment + ": " + state._ipc.path + ')');
console.info("");
}
if ((err && 'ENOENT' === err.code) || !Object.keys(state._clientConfig).length) {
if (!err || 'ENOENT' === err.code) {
//console.warn("Empty config file. Run 'telebit init' to configure.\n");
} else {
console.warn("Couldn't load config:\n\n\t" + err.message + "\n");
}
}
utils.request({ service: 'config' }, handleConfig);
} }
var parsers = { var parsers = {

View File

@ -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}"

View File

@ -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;
};