diff --git a/bin/telebit.js b/bin/telebit.js index 449d200..02e01cb 100755 --- a/bin/telebit.js +++ b/bin/telebit.js @@ -8,7 +8,7 @@ var os = require('os'); //var url = require('url'); var path = require('path'); var http = require('http'); -var https = require('https'); +//var https = require('https'); var YAML = require('js-yaml'); var recase = require('recase').create({}); var camelCopy = recase.camelCopy.bind(recase); @@ -335,7 +335,72 @@ function askForConfig(answers, mainCb) { } var utils = { - putConfig: function putConfig(service, args, fn) { + request: function request(opts, fn) { + if (!opts) { opts = {}; } + var service = opts.service || 'config'; + var req = http.get({ + socketPath: state._ipc.path + , method: opts.method || 'GET' + , path: '/rpc/' + service + }, function (resp) { + var body = ''; + + function finish() { + console.info(""); + 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; + utils.request(opts, fn); + }); + return; + } + if ('ENOTSOCK' === err.code) { + console.error(err); + return; + } + console.error(err); + return; + }); + } +, putConfig: function putConfig(service, args, fn) { // console.log('got it', service, args); var req = http.get({ socketPath: state._ipc.path @@ -516,53 +581,58 @@ var parsers = { throw new Error("init must be the first argument"); } argv.shift(); - // init --foo bar - argv.forEach(function (arg, i) { - if (!/^--/.test(arg)) { return; } - if (-1 !== bool.indexOf(arg)) { - answers['_' + arg.replace(/^--/, '')] = true; - } - if (/^-/.test(argv[i + 1])) { - throw new Error(argv[i + 1] + ' requires an argument'); - } - answers[arg] = argv[i + 1]; - }); - // init foo:bar - argv.forEach(function (arg) { - if (/^--/.test(arg)) { return; } - var parts = arg.split(/:/g); - if (2 !== parts.length) { - throw new Error("bad option to init: '" + arg + "'"); - } - if (answers[parts[0]]) { - throw new Error("duplicate key to init '" + parts[0] + "'"); - } - answers[parts[0]] = parts[1]; - }); - if (!answers._advanced && !answers.relay) { - answers.relay = 'telebit.cloud'; - } - - askForConfig(answers, function (err, answers) { + utils.request({ service: 'config' }, function (err/*, body*/) { if (err) { cb(err); return; } - if (!answers.token && answers._can_pair) { - answers._otp = common.otp(); - console.log(""); - console.log("=============================================="); - console.log(" Hey, Listen! "); - console.log("=============================================="); - console.log(" "); - console.log(" GO CHECK YOUR EMAIL! "); - console.log(" "); - console.log(" DEVICE PAIR CODE: 0000 ".replace(/0000/g, answers._otp)); - console.log(" "); - console.log("=============================================="); - console.log(""); + // init --foo bar + argv.forEach(function (arg, i) { + if (!/^--/.test(arg)) { return; } + if (-1 !== bool.indexOf(arg)) { + answers['_' + arg.replace(/^--/, '')] = true; + } + if (/^-/.test(argv[i + 1])) { + throw new Error(argv[i + 1] + ' requires an argument'); + } + answers[arg] = argv[i + 1]; + }); + // init foo:bar + argv.forEach(function (arg) { + if (/^--/.test(arg)) { return; } + var parts = arg.split(/:/g); + if (2 !== parts.length) { + throw new Error("bad option to init: '" + arg + "'"); + } + if (answers[parts[0]]) { + throw new Error("duplicate key to init '" + parts[0] + "'"); + } + answers[parts[0]] = parts[1]; + }); + + if (!answers._advanced && !answers.relay) { + answers.relay = 'telebit.cloud'; } - cb(null, answers); + askForConfig(answers, function (err, answers) { + if (err) { cb(err); return; } + + if (!answers.token && answers._can_pair) { + answers._otp = common.otp(); + console.log(""); + console.log("=============================================="); + console.log(" Hey, Listen! "); + console.log("=============================================="); + console.log(" "); + console.log(" GO CHECK YOUR EMAIL! "); + console.log(" "); + console.log(" DEVICE PAIR CODE: 0000 ".replace(/0000/g, answers._otp)); + console.log(" "); + console.log("=============================================="); + console.log(""); + } + + cb(null, answers); + }); }); } }; diff --git a/usr/share/install-launcher.js b/usr/share/install-launcher.js index b74a73d..5616770 100644 --- a/usr/share/install-launcher.js +++ b/usr/share/install-launcher.js @@ -6,7 +6,9 @@ var mkdirp = require('mkdirp'); var exec = require('child_process').exec; var path = require('path'); -module.exports.install = function (things) { +var Launcher = module.exports; +Launcher.install = function (things, fn) { + if (!fn) { fn = function (err) { if (err) { console.error(err); } }; } things = things || {}; // in some future version we can take this file out // and accept process.env from things @@ -15,11 +17,12 @@ module.exports.install = function (things) { // Right now this is just for npm install -g and npx if (things.env) { things.env.PATH = things.env.PATH || process.env.PATH; + } else { + things.env = process.env; } - var execOpts = { windowsHide: true, env: things.env || process.env }; - var userspace = (!things.telebitUser || (things.telebitUser === os.userInfo().username)) ? true : false; + things.argv = things.argv || process.argv; + things._execOpts = { windowsHide: true, env: things.env }; var telebitRoot = path.join(__dirname, '../..'); - var telebitBinTpl = path.join(telebitRoot, 'usr/share/dist/bin/telebit.tpl'); var vars = { telebitPath: telebitRoot , telebitUser: os.userInfo().username @@ -29,7 +32,7 @@ module.exports.install = function (things) { , path.join(os.homedir(), '.config/telebit') , path.join(os.homedir(), '.local/share/telebit') ] - , telebitNode: (process.argv[0]||'').replace(/\.exe/i, '') // path.join(telebitRoot, 'bin/node') + , telebitNode: (things.argv[0]||'').replace(/\.exe/i, '') // path.join(telebitRoot, 'bin/node') , telebitBin: path.join(telebitRoot, 'bin/telebit') , telebitdBin: path.join(telebitRoot, 'bin/telebitd') , telebitJs: path.join(telebitRoot, 'bin/telebit.js') @@ -37,79 +40,129 @@ module.exports.install = function (things) { , telebitConfig: path.join(os.homedir(), '.config/telebit/telebit.yml') , telebitdConfig: path.join(os.homedir(), '.config/telebit/telebitd.yml') }; + vars.telebitBinTpl = path.join(telebitRoot, 'usr/share/dist/bin/telebit.tpl'); vars.telebitNpm = path.resolve(vars.telebitNode, '../npm'); vars.nodePath = path.resolve(vars.telebitNode, '../lib/node_modules'); vars.npmConfigPrefix = path.resolve(vars.telebitNode, '..'); + vars.userspace = (!things.telebitUser || (things.telebitUser === os.userInfo().username)) ? true : false; if (-1 === vars.telebitRwDirs.indexOf(vars.npmConfigPrefix)) { vars.telebitRwDirs.push(vars.npmConfigPrefix); } vars.telebitRwDirs = vars.telebitRwDirs.join(' '); + function getError(err, stderr) { + if (err) { return err; } + if (stderr) { + err = new Error(stderr); + err.code = 'ELAUNCHER'; + return err; + } + } var launchers = { - 'launchctl': function () { + 'node': function () { + var fs = require('fs'); + var spawn = require('child_process').spawn; + var logpath = path.join(os.homedir(), '.local/share/telebit/var/log'); + try { + mkdirp.sync(logpath); + } catch(e) { + if (fn) { fn(e); return; } + return; + } + var stdout = fs.openSync(path.join(logpath, 'info.log'), 'a'); + var stderr = fs.openSync(path.join(logpath, 'error.log'), 'a'); + + var killed = 0; + var err; + var subprocess = spawn( + vars.telebitNode + , [ path.join(__dirname, '../../bin/telebitd.js') + , 'daemon' + , '--config' + , vars.telebitdConfig + ] + , { detached: true + , stdio: [ 'ignore', stdout, stderr ] + } + ); + subprocess.unref(); + subprocess.on('error', function (_err) { + err = _err; + killed += 1; + }); + subprocess.on('exit', function (code, signal) { + if (!err) { err = new Error('' + code + ' ' + signal + ' failure to launch'); } + killed += 1; + }); + + setTimeout(function () { + if (fn) { fn(null); return; } + }, 1 * 1000); + return; + } + , 'launchctl': function () { var launcher = path.join(os.homedir(), 'Library/LaunchAgents/cloud.telebit.remote.plist'); try { mkdirp.sync(path.join(os.homedir(), 'Library/LaunchAgents')); - mkdirp.sync(path.join(telebitRoot, 'bin')); + mkdirp.sync(path.join(vars.telebitPath, 'bin')); installLauncher.sync({ - file: { - tpl: telebitBinTpl - , launcher: path.join(telebitRoot, 'bin/telebit') + file: { + tpl: vars.telebitBinTpl + , launcher: path.join(vars.telebitPath, 'bin/telebit') } , vars: vars }); installLauncher({ file: { - tpl: path.join(telebitRoot, 'usr/share/dist/etc/skel/Library/LaunchAgents/cloud.telebit.remote.plist.tpl') - , launcher: launcher - } - , vars: vars - }); - var launcherstr = (userspace ? "" : "sudo ") + "launchctl "; - exec(launcherstr + "unload -w " + launcher, execOpts, function (err, stdout, stderr) { - if (err) { console.error(err); } - console.log((stdout||'').trim()); - if (stderr) { - console.error(stderr); - } - console.log('unload worked?'); - exec(launcherstr + "load -w " + launcher, execOpts, function (err, stdout, stderr) { - if (err) { console.error(err); } - console.log((stdout||'').trim()); - if (stderr) { - console.error(stderr); + tpl: path.join(vars.telebitPath, 'usr/share/dist/etc/skel/Library/LaunchAgents/cloud.telebit.remote.plist.tpl') + , launcher: launcher } - console.log('load worked?'); + , vars: vars + }); + var launcherstr = (vars.userspace ? "" : "sudo ") + "launchctl "; + exec(launcherstr + "unload -w " + launcher, things._execOpts, function (err, stdout, stderr) { + err = getError(err, stderr); + if (err) { fn(err); return; } + //console.log((stdout||'').trim()); + //console.log('unload worked?'); + exec(launcherstr + "load -w " + launcher, things._execOpts, function (err, stdout, stderr) { + err = getError(err, stderr); + if (err) { fn(err); return; } + //console.log((stdout||'').trim()); + //console.log('load worked?'); + fn(null); }); }); } catch(e) { console.error("'" + launcher + "' error:"); console.error(e); + if (fn) { fn(e); return; } } } - , 'systemctl': function () { + , 'systemctl': function () { var launcher = path.join(os.homedir(), '.config/systemd/user/telebit.service'); try { mkdirp.sync(path.join(os.homedir(), '.config/systemd/user')); installLauncher({ file: { - tpl: path.join(telebitRoot, 'usr/share/dist/etc/skel/.config/systemd/user/telebit.service.tpl') + tpl: path.join(vars.telebitPath, 'usr/share/dist/etc/skel/.config/systemd/user/telebit.service.tpl') , launcher: launcher } , vars: vars }, function () { - var launcherstr = (userspace ? "" : "sudo ") + "systemctl " + (userspace ? "--user " : ""); - exec(launcherstr + "daemon-reload", execOpts, function (err, stdout, stderr) { - if (err) { console.error(err); } - console.log((stdout||'').trim()); - if (stderr) { console.error(stderr); } - exec(launcherstr + "enable " + launcher, execOpts, function (err, stdout, stderr) { - if (err) { console.error(err); } - console.log((stdout||'').trim()); - if (stderr) { console.error(stderr); } - exec(launcherstr + "restart " + launcher, execOpts, function (err, stdout, stderr) { - if (err) { console.error(err); } - console.log((stdout||'').trim()); - if (stderr) { console.error(stderr); } + var launcherstr = (vars.userspace ? "" : "sudo ") + "systemctl " + (vars.userspace ? "--user " : ""); + exec(launcherstr + "daemon-reload", things._execOpts, function (err, stdout, stderr) { + err = getError(err, stderr); + if (err) { fn(err); return; } + //console.log((stdout||'').trim()); + exec(launcherstr + "enable " + launcher, things._execOpts, function (err, stdout, stderr) { + err = getError(err, stderr); + if (err) { fn(err); return; } + //console.log((stdout||'').trim()); + exec(launcherstr + "restart " + launcher, things._execOpts, function (err, stdout, stderr) { + err = getError(err, stderr); + if (err) { fn(err); return; } + //console.log((stdout||'').trim()); + fn(null); }); }); }); @@ -117,13 +170,17 @@ module.exports.install = function (things) { } catch(e) { console.error("'" + launcher + "' error:"); console.error(e); + if (fn) { fn(e); return; } } } , 'reg.exe': function () { + if (!vars.userspace) { + console.warn("sysetm-level, privileged services are not yet supported on windows"); + } vars.telebitNode += '.exe'; var cmd = 'reg.exe add "HKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run"' + ' /V "Telebit" /t REG_SZ /D ' - + '"' + process.argv[0] + ' /c ' // something like C:\\Program Files (x64)\nodejs\node.exe + + '"' + things.argv[0] + ' /c ' // something like C:\\Program Files (x64)\nodejs\node.exe + [ path.join(__dirname, 'bin/telebitd.js') , 'daemon' , '--config' @@ -131,25 +188,24 @@ module.exports.install = function (things) { ].join(' ') + '" /F' ; - exec(cmd, execOpts, function (err, stdout, stderr) { - console.log((stdout||'').trim()); - if (stderr) { - console.error(stderr); - } - run(err, 'launchctl'); - }); + exec(cmd, things._execOpts, function (err, stdout, stderr) { + err = getError(err, stderr); + if (err) { fn(err); return; } + //console.log((stdout||'').trim()); + fn(null); + }); } }; function run(err, launcher) { if (err) { - console.error("No luck with '" + launcher + "'"); + console.error("No luck with '" + launcher + "', trying a child process instead..."); console.error(err); - return; + launcher = 'node'; } if (launchers[launcher]) { - console.log('Launching with launcher ' + launcher); + // console.log('Launching with launcher ' + launcher); launchers[launcher](); return; } else { @@ -157,25 +213,32 @@ module.exports.install = function (things) { } } + if (things.launcher) { + if ('string' === typeof things.launcher) { + run(null, things.launcher); + return; + } + if ('function' === typeof things.launcher) { + things._vars = vars; + things._userspace = vars.userspace; + things.launcher(things); + return; + } + } + // could have used "command-exists" but I'm trying to stay low-dependency // os.platform(), os.type() if (!/^win/i.test(os.platform())) { if (/^darwin/i.test(os.platform())) { - exec('type -p launchctl', execOpts, function (err, stdout, stderr) { - console.log((stdout||'').trim()); - if (stderr) { - console.error(stderr); - } + exec('type -p launchctl', things._execOpts, function (err, stdout, stderr) { + err = getError(err, stderr); run(err, 'launchctl'); - }); + }); } else { - exec('type -p systemctl', execOpts, function (err, stdout, stderr) { - console.log((stdout||'').trim()); - if (stderr) { - console.error(stderr); - } + exec('type -p systemctl', things._execOpts, function (err, stdout, stderr) { + err = getError(err, stderr); run(err, 'systemctl'); - }); + }); } } else { // https://stackoverflow.com/questions/17908789/how-to-add-an-item-to-registry-to-run-at-startup-without-uac @@ -186,8 +249,8 @@ module.exports.install = function (things) { // https://social.msdn.microsoft.com/Forums/en-US/5b318f44-281e-4098-8dee-3ba8435fa391/add-registry-key-for-autostart-of-app-in-ice?forum=quebectools // utils.elevate // https://github.com/CatalystCode/windows-registry-node - exec('where reg.exe', execOpts, function (err, stdout, stderr) { - console.log((stdout||'').trim()); + exec('where reg.exe', things._execOpts, function (err, stdout, stderr) { + console.log((stdout||'').trim()); if (stderr) { console.error(stderr); } @@ -197,5 +260,11 @@ module.exports.install = function (things) { }; if (module === require.main) { - module.exports.install({}); + module.exports.install({ + argv: process.argv + , env: process.env + }, function (err) { + if (err) { console.error(err); return; } + console.log("Telebit launched, or so it seems."); + }); }