449 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			449 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 'use strict';
 | |
| 
 | |
| //var fs = require('fs');
 | |
| var os = require('os');
 | |
| var mkdirp = require('mkdirp');
 | |
| var exec = require('child_process').exec;
 | |
| var path = require('path');
 | |
| 
 | |
| var Launcher = module.exports;
 | |
| Launcher._killAll = function (fn) {
 | |
|   var psList = require('ps-list');
 | |
|   psList().then(function (procs) {
 | |
|     procs.forEach(function (proc) {
 | |
|       if ('node' === proc.name && /\btelebitd\b/i.test(proc.cmd)) {
 | |
|         console.log(proc);
 | |
|         process.kill(proc.pid);
 | |
|         return true;
 | |
|       }
 | |
|     });
 | |
|     // Two things:
 | |
|     // 1) wait to see if the process dies
 | |
|     // 2) wait to give time for the socket to connect
 | |
|     setTimeout(function () {
 | |
|       if (fn) { fn(null); return; }
 | |
|     }, 1.75 * 1000);
 | |
|   });
 | |
| };
 | |
| Launcher._getError = function getError(err, stderr) {
 | |
|   if (err) { return err; }
 | |
|   if (stderr) {
 | |
|     err = new Error(stderr);
 | |
|     err.code = 'ELAUNCHER';
 | |
|     return err;
 | |
|   }
 | |
| };
 | |
| Launcher._detect = function (things, fn) {
 | |
|   if (things.launcher) {
 | |
|     if ('string' === typeof things.launcher) {
 | |
|       fn(null, things.launcher);
 | |
|       return;
 | |
|     }
 | |
|     if ('function' === typeof things.launcher) {
 | |
|       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('command -v launchctl', things._execOpts, function (err, stdout, stderr) {
 | |
|         err = Launcher._getError(err, stderr);
 | |
|         fn(err, 'launchctl');
 | |
|       });
 | |
|     } else {
 | |
|       exec('command -v systemctl', things._execOpts, function (err, stdout, stderr) {
 | |
|         err = Launcher._getError(err, stderr);
 | |
|         fn(err, 'systemctl');
 | |
|       });
 | |
|     }
 | |
|   } else {
 | |
|     // https://stackoverflow.com/questions/17908789/how-to-add-an-item-to-registry-to-run-at-startup-without-uac
 | |
|     // wininit? regedit? SCM?
 | |
|     // REG ADD "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" /V "My App" /t REG_SZ /F /D "C:\MyAppPath\MyApp.exe"
 | |
|     // https://www.microsoft.com/developerblog/2015/11/09/reading-and-writing-to-the-windows-registry-in-process-from-node-js/
 | |
|     // https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/reg-add
 | |
|     // 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', things._execOpts, function (err, stdout, stderr) {
 | |
|       //console.log((stdout||'').trim());
 | |
|       if (stderr) {
 | |
|         console.error(stderr);
 | |
|       }
 | |
|       fn(err, 'reg.exe');
 | |
|     });
 | |
|   }
 | |
| };
 | |
| 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
 | |
|   var installLauncher = require('./template-launcher');
 | |
| 
 | |
|   // 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;
 | |
|   }
 | |
|   things.argv = things.argv || process.argv;
 | |
|   things._execOpts = { windowsHide: true, env: things.env };
 | |
|   var telebitRoot = path.join(__dirname, '../..');
 | |
|   var vars = {
 | |
|     telebitPath: telebitRoot
 | |
|   , telebitUser: os.userInfo().username
 | |
|   , telebitGroup: (/^darwin/i.test(os.platform()) ? 'staff' : os.userInfo().username)
 | |
|   , telebitRwDirs: [
 | |
|       telebitRoot
 | |
|     , path.join(os.homedir(), '.config/telebit')
 | |
|     , path.join(os.homedir(), '.local/share/telebit')
 | |
|     ]
 | |
|   , 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')
 | |
|   , telebitdJs: path.join(telebitRoot, 'bin/telebitd.js')
 | |
|   , telebitConfig: path.join(os.homedir(), '.config/telebit/telebit.yml')
 | |
|   , telebitdConfig: path.join(os.homedir(), '.config/telebit/telebitd.yml')
 | |
|   , TELEBIT_LOG_DIR: path.join(os.homedir(), '.local/share/telebit/var/log')
 | |
|   , TELEBIT_SOCK_DIR: path.join(os.homedir(), '.local/share/telebit/var/run')
 | |
|   };
 | |
|   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(' ');
 | |
|   var launchers = {
 | |
|     '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 args = [
 | |
|         path.join(telebitRoot, 'bin/telebitd.js')
 | |
|       , 'daemon'
 | |
|       , '--config'
 | |
|       , vars.telebitdConfig
 | |
|       ];
 | |
|       var subprocess = spawn(
 | |
|         vars.telebitNode
 | |
|       , args
 | |
|       , { detached: true
 | |
|         , stdio: [ 'ignore', stdout, stderr ]
 | |
|         }
 | |
|       );
 | |
|       //console.log('[debug]', vars.telebitNode, args.join(' '));
 | |
|       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;
 | |
|       });
 | |
| 
 | |
|       // Two things:
 | |
|       // 1) wait to see if the process dies
 | |
|       // 2) wait to give time for the socket to connect
 | |
|       setTimeout(function () {
 | |
|         if (fn) { fn(err); return; }
 | |
|       }, 1.75 * 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'));
 | |
|         installLauncher.sync({
 | |
|             file: {
 | |
|               tpl: vars.telebitBinTpl
 | |
|             , launcher: path.join(vars.telebitPath, 'bin/telebit')
 | |
|             , executable: true
 | |
|           }
 | |
|         , vars: vars
 | |
|         });
 | |
|         installLauncher({
 | |
|           file: {
 | |
|               tpl: path.join(vars.telebitPath, 'usr/share/dist/etc/skel/Library/LaunchAgents/cloud.telebit.remote.plist.tpl')
 | |
|             , launcher: launcher
 | |
|             }
 | |
|           , vars: vars
 | |
|         });
 | |
|         var launcherstr = (vars.userspace ? "" : "sudo ") + "launchctl ";
 | |
|         var execstr = launcherstr + "unload -w " + launcher;
 | |
|         exec(execstr, things._execOpts, function (/*err, stdout, stderr*/) {
 | |
|           // we probably only need to skip the stderr (saying that it can't stop something that isn't started)
 | |
|           //err = Launcher._getError(err, stderr);
 | |
|           //if (err) { fn(err); return; }
 | |
|           //console.log((stdout||'').trim());
 | |
|           //console.log('unload worked?');
 | |
|           execstr = launcherstr + "load -w " + launcher;
 | |
|           exec(execstr, things._execOpts, function (err, stdout, stderr) {
 | |
|             err = Launcher._getError(err, stderr);
 | |
|             if (err) { fn(err); return; }
 | |
|             //console.log((stdout||'').trim());
 | |
|             //console.log('load worked?');
 | |
|             setTimeout(function () {
 | |
|               fn(null);
 | |
|             }, 1.25 * 1000);
 | |
|           });
 | |
|         });
 | |
|       } catch(e) {
 | |
|         console.error("'" + launcher + "' error:");
 | |
|         console.error(e);
 | |
|         if (fn) { fn(e); return; }
 | |
|       }
 | |
|     }
 | |
|    , 'systemctl': function () {
 | |
|       var launcher = path.join(os.homedir(), '.config/systemd/user/telebit.service');
 | |
|       var launchername = 'telebit.service';
 | |
|       try {
 | |
|         mkdirp.sync(path.join(os.homedir(), '.config/systemd/user'));
 | |
|         installLauncher({
 | |
|           file: {
 | |
|             tpl: path.join(vars.telebitPath, 'usr/share/dist/etc/skel/.config/systemd/user/telebit.service.tpl')
 | |
|           , launcher: launcher
 | |
|           }
 | |
|         , vars: vars
 | |
|         }, function () {
 | |
|           // IMPORTANT
 | |
|           // It's a dangerous to go alone, take this:
 | |
|           // SYSTEMD_LOG_LEVEL=debug journalctl -xef --user-unit=telebit
 | |
|           // (makes debugging systemd issues not "easy" per se, but possible)
 | |
|           var launcherstr = (vars.userspace ? "" : "sudo ") + "systemctl " + (vars.userspace ? "--user " : "");
 | |
|           var execstr = launcherstr + "daemon-reload";
 | |
|           exec(execstr, things._execOpts, function (err, stdout, stderr) {
 | |
|             err = Launcher._getError(err, stderr);
 | |
|             if (err) { fn(err); return; }
 | |
|             //console.log((stdout||'').trim());
 | |
|             var execstr = launcherstr + "enable " + launchername;
 | |
|             exec(execstr, things._execOpts, function (err, stdout, stderr) {
 | |
|               err = Launcher._getError(err, stderr && !/Created symlink/i.test(stderr) && stderr || '');
 | |
|               if (err) { fn(err); return; }
 | |
|               //console.log((stdout||'').trim());
 | |
|               var execstr = launcherstr + "restart " + launchername;
 | |
|               exec(execstr, things._execOpts, function (err, stdout, stderr) {
 | |
|                 err = Launcher._getError(err, stderr);
 | |
|                 if (err) { fn(err); return; }
 | |
|                 //console.log((stdout||'').trim());
 | |
|                 setTimeout(function () {
 | |
|                   var execstr = launcherstr + "status " + launchername;
 | |
|                   exec(execstr, things._execOpts, function (err, stdout, stderr) {
 | |
|                     err = Launcher._getError(err, stderr);
 | |
|                     if (err) { fn(err); return; }
 | |
|                     if (!/active.*running/i.test(stdout)) {
 | |
|                       err = new Error("systemd failed to start '" + launchername + "'");
 | |
|                     }
 | |
|                     if (err) { fn(err); return; }
 | |
|                     //console.log((stdout||'').trim());
 | |
|                     fn(null);
 | |
|                   });
 | |
|                 }, 1.25 * 1000);
 | |
|               });
 | |
|             });
 | |
|           });
 | |
|         });
 | |
|       } 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 '
 | |
|         + '"' + things.argv[0] + ' /c '  // something like C:\\Program Files (x64)\nodejs\node.exe
 | |
|         + [ path.join(__dirname, 'bin/telebitd.js')
 | |
|           , 'daemon'
 | |
|           , '--config'
 | |
|           , path.join(os.homedir(), '.config/telebit/telebitd.yml')
 | |
|           ].join(' ')
 | |
|         + '" /F'
 | |
|         ;
 | |
|       exec(cmd, things._execOpts, function (err, stdout, stderr) {
 | |
|         err = Launcher._getError(err, stderr);
 | |
|         if (err) { fn(err); return; }
 | |
|         // need to start it for the first time ourselves
 | |
|         run(null, 'node');
 | |
|       });
 | |
|     }
 | |
|   };
 | |
| 
 | |
|   function run(err, launcher) {
 | |
|     if (err) {
 | |
|       console.error("No luck with '" + launcher + "', trying a child process instead...");
 | |
|       console.error(err);
 | |
|       launcher = 'node';
 | |
|     }
 | |
| 
 | |
|     if (launchers[launcher]) {
 | |
|       // console.log('Launching with launcher ' + launcher);
 | |
|       mkdirp.sync(path.join(vars.telebitPath, 'bin'));
 | |
|       mkdirp.sync(vars.TELEBIT_LOG_DIR);
 | |
|       mkdirp.sync(vars.TELEBIT_SOCK_DIR);
 | |
|       launchers[launcher]();
 | |
|       return;
 | |
|     } else {
 | |
|       console.error("No launcher handler for '" + launcher+ "'");
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   things._vars = vars;
 | |
|   things._userspace = vars.userspace;
 | |
|   Launcher._detect(things, run);
 | |
| };
 | |
| Launcher.uninstall = function (things, fn) {
 | |
|   if (!fn) { fn = function (err) { if (err) { console.error(err); } }; }
 | |
|   things = 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;
 | |
|   }
 | |
|   things.argv = things.argv || process.argv;
 | |
|   things._execOpts = { windowsHide: true, env: things.env };
 | |
|   var vars = {
 | |
|     telebitUser: os.userInfo().username
 | |
|   };
 | |
|   vars.userspace = (!things.telebitUser || (things.telebitUser === os.userInfo().username)) ? true : false;
 | |
|   var launchers = {
 | |
|     'node': function () {
 | |
|       Launcher._killAll(fn);
 | |
|     }
 | |
|   , 'launchctl': function () {
 | |
|       var launcher = path.join(os.homedir(), 'Library/LaunchAgents/cloud.telebit.remote.plist');
 | |
|       try {
 | |
|         var launcherstr = (vars.userspace ? "" : "sudo ") + "launchctl ";
 | |
|         var execstr = launcherstr + "unload -w " + launcher;
 | |
|         exec(execstr, things._execOpts, function (err, stdout, stderr) {
 | |
|           // we probably only need to skip the stderr (saying that it can't stop something that isn't started)
 | |
|           //err = Launcher._getError(err, stderr);
 | |
|           //if (err) { fn(err); return; }
 | |
|           //console.log((stdout||'').trim());
 | |
|           //console.log('unload worked?');
 | |
|           err = Launcher._getError(err, stderr);
 | |
|           if (err) { fn(err); return; }
 | |
|           //console.log((stdout||'').trim());
 | |
|           //console.log('load worked?');
 | |
|           setTimeout(function () {
 | |
|             fn(null);
 | |
|           }, 1.25 * 1000);
 | |
|         });
 | |
|       } catch(e) {
 | |
|         console.error("'" + launcher + "' error (uninstall):");
 | |
|         console.error(e);
 | |
|         if (fn) { fn(e); return; }
 | |
|       }
 | |
|     }
 | |
|    , 'systemctl': function () {
 | |
|       var launcher = path.join(os.homedir(), '.config/systemd/user/telebit.service');
 | |
|       var launchername = 'telebit.service';
 | |
|       try {
 | |
|         mkdirp.sync(path.join(os.homedir(), '.config/systemd/user'));
 | |
|         // IMPORTANT
 | |
|         // It's a dangerous to go alone, take this:
 | |
|         // SYSTEMD_LOG_LEVEL=debug journalctl -xef --user-unit=telebit
 | |
|         // (makes debugging systemd issues not "easy" per se, but possible)
 | |
|         var launcherstr = (vars.userspace ? "" : "sudo ") + "systemctl " + (vars.userspace ? "--user " : "");
 | |
|         var execstr = launcherstr + "disable " + launchername;
 | |
|         exec(execstr, things._execOpts, function (err, stdout, stderr) {
 | |
|           err = Launcher._getError(err, stderr && !/Removed symlink/i.test(stderr) && stderr || '');
 | |
|           if (err) { fn(err); return; }
 | |
|           //console.log((stdout||'').trim());
 | |
|           var execstr = launcherstr + "stop " + launchername;
 | |
|           exec(execstr, things._execOpts, function (err, stdout, stderr) {
 | |
|             err = Launcher._getError(err, stderr);
 | |
|             if (err) { fn(err); return; }
 | |
|             //console.log((stdout||'').trim());
 | |
|             setTimeout(function () {
 | |
|               var execstr = launcherstr + "status " + launchername;
 | |
|               exec(execstr, things._execOpts, function (err, stdout, stderr) {
 | |
|                 err = Launcher._getError(err, stderr);
 | |
|                 if (err) { fn(err); return; }
 | |
|                 if (!/inactive.*dead/i.test(stdout)) {
 | |
|                   err = new Error("systemd failed to stop '" + launchername + "'");
 | |
|                 }
 | |
|                 if (err) { fn(err); return; }
 | |
|                 //console.log((stdout||'').trim());
 | |
|                 fn(null);
 | |
|               });
 | |
|             }, 1.25 * 1000);
 | |
|           });
 | |
|         });
 | |
|       } 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");
 | |
|       }
 | |
|       var cmd = 'reg.exe add "HKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run"'
 | |
|         + ' /V "Telebit" /F'
 | |
|         ;
 | |
|       exec(cmd, things._execOpts, function (err, stdout, stderr) {
 | |
|         err = Launcher._getError(err, stderr);
 | |
|         if (err) { fn(err); return; }
 | |
|         // need to start it for the first time ourselves
 | |
|         kill(null, 'node');
 | |
|       });
 | |
|     }
 | |
|   };
 | |
| 
 | |
|   function kill(err, launcher) {
 | |
|     if (err) {
 | |
|       console.error("No luck with '" + launcher + "', trying a process.kill() instead...");
 | |
|       console.error(err);
 | |
|       launcher = 'node';
 | |
|     }
 | |
| 
 | |
|     if (launchers[launcher]) {
 | |
|       launchers[launcher]();
 | |
|       return;
 | |
|     } else {
 | |
|       console.error("No launcher handler (uninstall) for '" + launcher + "'");
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   things._vars = vars;
 | |
|   things._userspace = vars.userspace;
 | |
|   Launcher._detect(things, kill);
 | |
| };
 | |
| 
 | |
| if (module === require.main) {
 | |
|   Launcher.install({
 | |
|     argv: process.argv
 | |
|   , env: process.env
 | |
|   }, function (err) {
 | |
|     if (err) { console.error(err); return; }
 | |
|     console.log("Telebit launched, or so it seems.");
 | |
|   });
 | |
| }
 |