@luminati-io/luminati-proxy
Version:
A configurable local proxy for luminati.io
273 lines (271 loc) • 9.86 kB
JavaScript
// LICENSE_CODE ZON ISC
'use strict'; /*jslint node:true, esnext:true*/
const semver = require('semver');
const etask = require('../util/etask.js');
const zerr = require('../util/zerr.js');
require('../lib/perr.js').run({});
const logger = require('../lib/logger.js').child({category: 'lum_node_index'});
const ssl = require('../lib/ssl.js');
const lpm_config = require('../util/lpm_config.js');
const lpm_file = require('../util/lpm_file.js');
const file = require('../util/file.js');
const child_process = require('child_process');
const path = require('path');
const util_lib = require('../lib/util.js');
const upgrader = require('./upgrader.js');
const string = require('../util/string.js'), {nl2sp} = string;
const pkg = require('../package.json');
const qw = string.qw;
let pm2;
try { pm2 = require('pm2'); }
catch(e){ logger.warn('could not load pm2: daemon mode not supported'); }
class Lum_node_index {
constructor(argv){
this.argv = argv;
}
is_daemon_running(list){
const daemon = list.find(p=>p.name==lpm_config.daemon_name);
return !!daemon &&
['online', 'launching'].includes(daemon.pm2_env.status);
}
pm2_cmd(command, opt){ return etask(function*pm2_cmd(){
this.on('uncaught', e=>{
if (e.message=='process name not found')
logger.notice('There is no running LPM daemon process');
else
logger.error('PM2: Uncaught exception: '+zerr.e2s(e));
});
this.on('finally', ()=>pm2.disconnect());
yield etask.nfn_apply(pm2, '.connect', []);
if (!Array.isArray(opt))
opt = [opt];
return yield etask.nfn_apply(pm2, '.'+command, opt);
}); }
start_daemon(){
return etask(function*start_daemon(){
this.on('uncaught', e=>{
logger.error('PM2: Uncaught exception: '+zerr.e2s(e));
});
this.on('finally', ()=>pm2.disconnect());
const script = path.resolve(__dirname, 'index.js');
logger.notice('Running in daemon: %s', script);
const daemon_start_opt = {
name: lpm_config.daemon_name,
script,
mergeLogs: false,
output: '/dev/null',
error: '/dev/null',
autorestart: true,
killTimeout: 5000,
restartDelay: 5000,
args: process.argv.filter(arg=>arg!='-d'&&!arg.includes('daemon')),
};
yield etask.nfn_apply(pm2, '.connect', []);
yield etask.nfn_apply(pm2, '.start', [daemon_start_opt]);
const bus = yield etask.nfn_apply(pm2, '.launchBus', []);
bus.on('log:out', data=>{
if (data.process.name!=lpm_config.daemon_name)
return;
process.stdout.write(data.data);
if (data.data.includes('Open admin browser') ||
data.data.includes('Shutdown'))
{
this.continue();
}
});
yield this.wait();
}); }
stop_daemon(){
const _this = this;
return etask(function*stop_daemon(){
this.on('uncaught', e=>{
logger.error('PM2: Uncaught exception: '+zerr.e2s(e));
});
this.on('finally', ()=>pm2.disconnect());
yield etask.nfn_apply(pm2, '.connect', []);
const pm2_list = yield etask.nfn_apply(pm2, '.list', []);
if (!_this.is_daemon_running(pm2_list))
return logger.notice('There is no running LPM daemon process');
const bus = yield etask.nfn_apply(pm2, '.launchBus', []);
let start_logging;
bus.on('log:out', data=>{
if (data.process.name!=lpm_config.daemon_name)
return;
start_logging = start_logging||data.data.includes('Shutdown');
if (!start_logging || !data.data.includes('NOTICE'))
return;
process.stdout.write(data.data);
});
bus.on('process:event', data=>{
if (data.process.name==lpm_config.daemon_name &&
['delete', 'stop'].includes(data.event))
{
this.continue();
}
});
yield etask.nfn_apply(pm2, '.stop', [lpm_config.daemon_name]);
yield this.wait();
}); }
restart_daemon(){
const _this = this;
return etask(function*(){
const pm2_list = yield _this.pm2_cmd('list');
if (_this.is_daemon_running(pm2_list))
{
logger.notice('Restarting daemon...');
yield _this.pm2_cmd('restart', lpm_config.daemon_name);
logger.notice('Daemon restarted');
}
});
}
show_status(){
const _this = this;
return etask(function*status(){
this.on('uncaught', e=>{
logger.error('Status: Uncaught exception: '+zerr.e2s(e));
});
const pm2_list = yield _this.pm2_cmd('list');
const running_daemon = _this.is_daemon_running(pm2_list);
const tasks = yield util_lib.get_lpm_tasks({all_processes: true});
if (!tasks.length && !running_daemon)
return logger.notice('There is no LPM process running');
let msg = 'Proxy manager status:\n';
if (running_daemon)
{
msg += 'Running in daemon mode. You can close it by '+
'running \'luminati --stop-daemon\'\n';
}
msg += util_lib.get_status_tasks_msg(tasks);
logger.notice(msg);
});
}
gen_cert(){
logger.notice('Generating cert');
ssl.gen_cert();
}
_cleanup_file(file_path){
try {
file.unlink_e(path.resolve(lpm_file.work_dir, file_path));
} catch(e){
logger.debug(e.message);
}
}
_cleanup_local_files(){
const local_files = qw`.luminati.jar .luminati.json
.luminati.db .luminati.db.0 .luminati.db.1 .luminati.db.2
.luminati.db.3 .luminati.db.4 .luminati.uuid`;
local_files.forEach(this._cleanup_file);
}
restart_on_child_exit(opt){
if (!this.child)
return;
this.child.removeListener('exit', this.restart_on_child_exit);
if (opt.cleanup)
this._cleanup_local_files();
setTimeout(()=>this.create_child(opt), 5000);
}
shutdown_on_child_exit(){
process.exit();
}
create_child(opt={}){
process.env.LUM_MAIN_CHILD = true;
const exec_argv = process.execArgv;
if (!lpm_config.is_win && this.is_node_compatible('>10.15.0'))
exec_argv.push('--max-http-header-size=80000');
if (this.argv.insecureHttpParser)
exec_argv.push('--insecure-http-parser');
const child_opt = {
stdio: 'inherit',
env: process.env,
execArgv: exec_argv,
};
this.child = child_process.fork(path.resolve(__dirname, 'lum_node.js'),
process.argv.slice(2), child_opt);
this.child.on('message', this.msg_handler.bind(this));
this.child.on('exit', this.shutdown_on_child_exit);
this.child.send(Object.assign(opt, {command: 'run', argv: this.argv}));
}
msg_handler(msg){
switch (msg.command)
{
case 'shutdown_master':
return process.exit();
case 'restart':
this.child.removeListener('exit', this.shutdown_on_child_exit);
this.child.on('exit', this.restart_on_child_exit.bind(this, msg));
this.child.kill();
break;
case 'upgrade':
this.upgrade();
break;
case 'downgrade':
this.downgrade();
break;
}
}
init_traps(){
['SIGTERM', 'SIGINT', 'uncaughtException'].forEach(sig=>{
process.on(sig, e=>{
setTimeout(()=>process.exit(), 5000);
});
});
if (lpm_config.is_win)
{
const readline = require('readline');
readline.createInterface({
input: process.stdin,
output: process.stdout,
}).on('SIGINT', ()=>process.emit('SIGINT'));
}
}
upgrade(opt={}){
upgrader.upgrade(error=>{
if (this.child)
this.child.send({command: 'upgrade_finished', error});
this.restart_daemon();
});
}
downgrade(){
upgrader.downgrade(error=>{
if (this.child)
this.child.send({command: 'downgrade_finished', error});
this.restart_daemon();
});
}
is_node_compatible(compare_ver){
const node_ver = process.versions.node;
return semver.satisfies(node_ver, compare_ver);
}
check_node_ver(){
const recommended_ver = pkg.recommendedNode;
const node_ver = process.versions.node;
if (!this.is_node_compatible(recommended_ver))
{
logger.warn(nl2sp`Node version is too old (${node_ver}). LPM
requires at least ${recommended_ver} to run correctly.`);
}
}
run(){
if (this.argv.startUpgrader)
return upgrader.start_upgrader();
if (this.argv.stopUpgrader)
return upgrader.stop_upgrader();
if (this.argv.daemon_opt.start)
return this.start_daemon();
if (this.argv.daemon_opt.stop)
return this.stop_daemon();
if (this.argv.status)
return this.show_status();
if (this.argv.genCert)
return this.gen_cert();
if (this.argv.upgrade)
return this.upgrade();
if (this.argv.downgrade)
return this.downgrade();
this.check_node_ver();
this.init_traps();
this.create_child();
}
}
module.exports = Lum_node_index;