// no npm! const os = require('os') const fs = require('fs') const dns = require('dns') const net = require('net') const ini = require('./ini') const path = require('path') const http = require('http') const https = require('https') const { spawn, exec } = require('child_process') // FIXME: disable rpc if desired const VERSION = 0.6 console.log('lokinet launcher version', VERSION, 'registered') function log() { var args = [] for (var i in arguments) { var arg = arguments[i] //console.log('arg type', arg, 'is', typeof(arg)) if (typeof (arg) == 'object') { arg = JSON.stringify(arg) } args.push(arg) } console.log('NETWORK:', args.join(' ')) } function getBoundIPv4s() { var nics = os.networkInterfaces() var ipv4s = [] for (var adapter in nics) { var ips = nics[adapter] for (var ipIdx in ips) { var ipMap = ips[ipIdx] if (ipMap.address.match(/\./)) { ipv4s.push(ipMap.address) } } } return ipv4s } var auto_config_test_port, auto_config_test_host, auto_config_test_ips // this doesn't need to connect completely to get our ip // it won't get our IP if DNS doesn't work function getNetworkIP(callback) { // randomly select an ip //log('getNetworkIP', auto_config_test_ips) var ip = auto_config_test_ips[Math.floor(Math.random() * auto_config_test_ips.length)] //log('getNetworkIP from', ip, auto_config_test_port) var socket = net.createConnection(auto_config_test_port, ip) socket.setTimeout(5000) socket.on('connect', function () { callback(undefined, socket.address().address) socket.end() }) var abort = false socket.on('timeout', function () { if (socket.address().address) { abort = true var resultIp = socket.address().address log('getNetworkIP timeout but still got outgoing IP:', resultIp) socket.destroy() callback(undefined, resultIp) } else { // don't have what we need, just wait it out, maybe we'll get lucky //log('getNetworkIP timeout') //callback('timeout', 'error') } }) socket.on('error', function (e) { console.error('NETWORK: getNetworkIP error', e) // FIXME: maybe a retry here log('getNetworkIP failure test', socket.address().address) log('getNetworkIP failure, retry?') if (abort) { log('getNetworkIP already timed out') return } callback(e, 'error') }) } function getIfNameFromIP(ip) { var nics = os.networkInterfaces() for (var adapter in nics) { var ips = nics[adapter] for (var ipIdx in ips) { var ipMap = ips[ipIdx] if (ipMap.address == ip) { return adapter } } } return '' } const urlparser = require('url') function httpGet(url, cb) { const urlDetails = urlparser.parse(url) //console.log('httpGet url', urlDetails) //console.log('httpGet', url) //console.trace('who started dis', url) var protoClient = http if (urlDetails.protocol == 'https:') { protoClient = https } // well somehow this can get hung on macos var abort = false var watchdog = setInterval(function () { if (shuttingDown) { //if (cb) cb() // [', url, '] log('hung httpGet but have shutdown request, calling back early and setting abort flag') clearInterval(watchdog) abort = true cb() return } }, 5000) protoClient.get({ hostname: urlDetails.hostname, protocol: urlDetails.protocol, port: urlDetails.port, path: urlDetails.path, timeout: 5000, }, (resp) => { //log('httpGet setting up handlers') clearInterval(watchdog) resp.setEncoding('binary') let data = '' // A chunk of data has been recieved. resp.on('data', (chunk) => { data += chunk }) // The whole response has been received. Print out the result. resp.on('end', () => { log('result code', resp.statusCode) if (abort) { // we already called back return } if (resp.statusCode == 404) { console.error('NETWORK:', url, 'is not found') cb() return } cb(data) }) }).on("error", (err) => { console.error("NETWORK: httpGet Error: ", err.message, 'port', urlDetails.port) cb() }) } function dynDNSHandler(data, cb) { } function getPublicIPv6(cb) { //v6.ident.me } var getPublicIPv4_retries = 0 function getPublicIPv4(cb) { // trust more than one source // randomly find 2 matching sources // dns is faster than http // dig +short myip.opendns.com @resolver1.opendns.com // httpGet doesn't support https yet... var publicIpServices = [{ url: 'https://api.ipify.org' }, { url: 'https://ipinfo.io/ip' }, { url: 'https://ipecho.net/plain' }, //{ url: 'http://api.ipify.org' }, //{ url: 'http://ipinfo.io/ip' }, //{ url: 'http://ipecho.net/plain' }, { url: 'http://ifconfig.me' }, { url: 'http://ipv4.icanhazip.com' }, { url: 'http://v4.ident.me' }, { url: 'http://checkip.amazonaws.com' }, //{ url: 'https://checkip.dyndns.org', handler: dynDNSHandler }, ] var service = [] service[0] = Math.floor(Math.random() * publicIpServices.length) service[1] = Math.floor(Math.random() * publicIpServices.length) var done = [false, false] function markDone(idx, value) { if (value === undefined) value = '' done[idx] = value.trim() let ready = true //log('done', done) for (var i in done) { if (done[i] === false) { ready = false log('getPublicIPv4', i, 'is not ready') break } } if (!ready) return log('getPublicIPv4 look ups are done', done) if (done[0] != done[1]) { // try 2 random services again getPublicIPv4_retries++ if (getPublicIPv4_retries > 10) { console.error('NAT detection: Can\'t determine public IP address') process.exit() } getPublicIPv4(cb) } else { // return //log("found public IP", done[0]) cb(done[0]) } } function doCall(number) { httpGet(publicIpServices[service[number]].url, function (ip) { if (ip === false) { service[number] = (Math.random() * publicIpServices.length) // retry console.warn(publicIpServices[service[number]].url, 'failed, retrying') doCall(number) return } console.log(number, publicIpServices[service[number]].url, ip) markDone(number, ip) }) } doCall(0) doCall(1) } // used for generating temp filenames function randomString(len) { var text = "" var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" for (var i = 0; i < len; i++) text += possible.charAt(Math.floor(Math.random() * possible.length)) return text } function isDnsPort(ip, port, cb) { const resolver = new dns.Resolver() resolver.setServers([ip + ':' + port]) resolver.resolve(auto_config_test_host, function (err, records) { if (err) console.error('resolve error:', err) log(auto_config_test_host, records) cb(records !== undefined) }) } function testDNSForLokinet(server, cb) { const resolver = new dns.Resolver() resolver.setServers([server]) // incase server is 127.0.0.1:undefined try { resolver.resolve('localhost.loki', function (err, records) { if (err) console.error('NETWORK: localhost.loki resolve err:', err) //log(server, 'localhost.loki test results', records) cb(records) }) } catch (e) { console.error('NETWORK: testDNSForLokinet error, incorrect server?', server) cb() } } function lookup(host, cb) { var resolver = new dns.Resolver() //console.log('lokinet lookup servers', runningConfig.dns.bind) resolver.setServers([runningConfig.dns.bind]) resolver.resolve(host, function (err, records) { if (err) { // not as bad... that's at least a properly formatted response if (err.code == 'ENOTFOUND') { records = null } else // leave bad if (err.code == 'ETIMEOUT') { records = undefined } else { console.error('lokinet lookup unknown err', err) } } //console.log(host, 'lokinet dns test results', records) cb(records) }) } function findLokiNetDNS(cb) { const localIPs = getBoundIPv4s() var checksLeft = 0 var servers = [] function checkDone() { if (shuttingDown) { //if (cb) cb() log('not going to start lokinet, shutting down') return } checksLeft-- if (checksLeft <= 0) { log('readResolv done') cb(servers) } } /* var resolvers = dns.getServers() console.log('Current resolvers', resolvers) // check local DNS servers in resolv config for(var i in resolvers) { const server = resolvers[i] var idx = localIPs.indexOf(server) if (idx != -1) { // local DNS server console.log('local DNS server detected', server) checksLeft++ testDNSForLokinet(server, function(isLokinet) { if (isLokinet) { // lokinet console.log(server, 'is a lokinet DNS server') servers.push(server) } checkDone() }) } } */ // maybe check all local ips too for (var i in localIPs) { const server = localIPs[i] checksLeft++ testDNSForLokinet(server, function (isLokinet) { if (isLokinet !== undefined) { // lokinet log(server, 'is a lokinet DNS server') servers.push(server) } checkDone() }) } } function readResolv(dns_ip, cb) { const localIPs = getBoundIPv4s() var servers = [] var checksLeft = 0 //log('make sure we exclude?', dns_ip) function checkDone() { if (shuttingDown) { //if (cb) cb() log('not going to start lokinet, shutting down') return } checksLeft-- //log('readResolv reports left', checksLeft) if (checksLeft <= 0) { log('readResolv done') cb(servers) } } var resolvers = dns.getServers() log('Current resolvers', resolvers) for (var i in resolvers) { const server = resolvers[i] if (server == dns_ip) { log('preventing DNS loop on', dns_ip) continue } var idx = localIPs.indexOf(server) if (idx != -1) { log('local DNS server detected', server) checksLeft++ // wait for it testDNSForLokinet(server, function (isLokinet) { if (isLokinet === undefined) { // not lokinet log(server, 'is not a lokinet DNS server') servers.push(server) } checkDone() }) } else { // non-local log('found remote DNS server', server) servers.push(server) } } checksLeft++ checkDone() /* const data = fs.readFileSync('/etc/resolv.conf', 'utf-8') const lines = data.split(/\n/) for(var i in lines) { var line = lines[i].trim() if (line.match(/#/)) { var parts = line.split(/#/) line = parts[0].trim() } // done reducing if (!line) continue if (line.match(/^nameserver /)) { const server = line.replace(/^nameserver /, '') var idx = localIPs.indexOf(server) if (idx != -1) { console.log('local DNS server detected', server) const resolver = new dns.Resolver() resolver.setServers([server]) checksLeft++ resolver.resolve('localhost.loki', function(err, records) { //if (err) console.error(err) //console.log('local dns test results', records) if (records === undefined) { // not lokinet console.log(server, 'is not a lokinet DNS server') servers.push(server) } checkDone() }) } else { // non-local console.log('found remote DNS server', server) servers.push(server) } continue } checkDone() console.error('readResolv unknown', line) } return servers */ } // this can really delay the start of lokinet function findFreePort53(ips, index, cb) { log('testing', ips[index], 'port 53') isDnsPort(ips[index], 53, function (res) { //console.log('isDnsPort res', res) // false if (!res) { log('Found free port 53 on', ips[index], index) cb(ips[index]) return } log('Port 53 is not free on', ips[index], index) if (index + 1 == ips.length) { cb() return } findFreePort53(ips, index + 1, cb) }) } // https://stackoverflow.com/a/40686853 function mkDirByPathSync(targetDir, { isRelativeToScript = false } = {}) { const sep = path.sep const initDir = path.isAbsolute(targetDir) ? sep : '' const baseDir = isRelativeToScript ? __dirname : '.' return targetDir.split(sep).reduce((parentDir, childDir) => { const curDir = path.resolve(baseDir, parentDir, childDir) try { fs.mkdirSync(curDir) } catch (err) { if (err.code === 'EEXIST') { // curDir already exists! return curDir } // To avoid `EISDIR` error on Mac and `EACCES`-->`ENOENT` and `EPERM` on Windows. if (err.code === 'ENOENT') { // Throw the original parentDir error on curDir `ENOENT` failure. throw new Error(`EACCES: permission denied, mkdir '${parentDir}'`) } const caughtErr = ['EACCES', 'EPERM', 'EISDIR'].indexOf(err.code) > -1 if (!caughtErr || caughtErr && curDir === path.resolve(targetDir)) { throw err // Throw if it's just the last created dir. } } return curDir }, initDir) } function makeMultiplatformPath(path) { return path } var cleanUpBootstrap = false var cleanUpIni = false function generateINI(config, need, markDone, cb) { const homeDir = os.homedir() //console.log('homeDir', homeDir) //const data = fs.readFileSync(homeDir + '/.lokinet/lokinet.ini', 'utf-8') //const jConfig = iniToJSON(data) //console.dir(jConfig) //const iConfig = jsonToINI(jConfig) //console.log(iConfig) var upstreams, lokinet_free53Ip, lokinet_nic var use_lokinet_rpc_port = config.rpc_port var lokinet_bootstrap_path = homeDir + '/.lokinet/bootstrap.signed' var lokinet_nodedb = homeDir + '/.lokinet/netdb' if (config.netid) { lokinet_nodedb += '-' + config.netid } if (!fs.existsSync(lokinet_nodedb)) { log('making', lokinet_nodedb) mkDirByPathSync(lokinet_nodedb) } var upstreamDNS_servers = [] var params = { upstreamDNS_servers: upstreamDNS_servers, lokinet_free53Ip: lokinet_free53Ip, lokinet_nodedb: lokinet_nodedb, lokinet_bootstrap_path: lokinet_bootstrap_path, lokinet_nic: lokinet_nic, use_lokinet_rpc_port: use_lokinet_rpc_port, } if (config.bootstrap_url) { httpGet(config.bootstrap_url, function (bootstrapData) { if (bootstrapData) { cleanUpBootstrap = true const tmpRcPath = os.tmpdir() + '/' + randomString(8) + '.lokinet_signed' fs.writeFileSync(tmpRcPath, bootstrapData, 'binary') log('boostrap wrote', bootstrapData.length, 'bytes to', tmpRcPath) //lokinet_bootstrap_path = tmpRcPath params.lokinet_bootstrap_path = tmpRcPath config.bootstrap_path = tmpRcPath } markDone('bootstrap', params) }) } else { // seed version //params.lokinet_bootstrap_path = '' markDone('bootstrap', params) } readResolv(config.dns_ip, function (servers) { upstreamDNS_servers = servers params.upstreamDNS_servers = servers upstreams = 'upstream=' + servers.join('\nupstream=') markDone('upstream', params) }) log('trying', 'http://' + config.rpc_ip + ':' + config.rpc_port) httpGet('http://' + config.rpc_ip + ':' + config.rpc_port, function (testData) { //log('rpc has', testData) if (testData !== undefined) { log('Bumping RPC port', testData) // FIXME: retest new port use_lokinet_rpc_port = use_lokinet_rpc_port + 1 params.use_lokinet_rpc_port = use_lokinet_rpc_port } markDone('rpcCheck', params) }) var skipDNS = false if (config.dns_ip || config.dns_port) { skipDNS = true markDone('dnsBind', params) } getNetworkIP(function (e, ip) { if (ip == 'error' || !ip) { console.error('NETWORK: can\'t detect default adapter IP address') // can't handle the exits here because we don't know if it's an actual requirements // if we need netIf or dnsBind if (done.netIf !== undefined || done.dnsBind !== undefined) { process.exit() } } log('detected outgoing interface ip', ip) lokinet_nic = getIfNameFromIP(ip) params.lokinet_nic = lokinet_nic params.interfaceIP = ip log('detected outgoing interface', lokinet_nic) markDone('netIf', params) if (skipDNS) return var tryIps = ['127.0.0.1'] if (os.platform() == 'linux') { tryIps.push('127.3.2.1') } tryIps.push(ip) findFreePort53(tryIps, 0, function (free53Ip) { if (free53Ip === undefined) { console.error('NETWORK: Cant automatically find an IP to put a lokinet DNS server on') // can't handle the exits here because we don't know if it's an actual requirements if (done.dnsBind !== undefined) { process.exit() } } lokinet_free53Ip = free53Ip params.lokinet_free53Ip = free53Ip log('binding DNS port 53 to', free53Ip) markDone('dnsBind', params) }) }) getPublicIPv4(function (ip) { //log('generateINI - ip', ip) params.publicIP = ip markDone('publicIP', params) }) } // unified post auto-config adjustments // disk to running function applyConfig(file_config, config_obj) { // bootstrap section // router mode: bootstrap is optional (might be a seed if not) // client mode: bootstrap is required, can't have a seed client if (file_config.bootstrap_path) { config_obj.bootstrap = { 'add-node': file_config.bootstrap_path } } // router section if (file_config.nickname) { config_obj.router.nickname = file_config.nickname } // set default netid based on testnet if (file_config.lokid && file_config.lokid.network == "test") { config_obj.router.netid = 'service' //runningConfig.network['ifaddr'] = '10.254.0.1/24' // hack for Ryan's box } if (file_config.netid) { config_obj.router.netid = file_config.netid } // network section if (file_config.ifname) { config_obj.network.ifname = file_config.ifname } if (file_config.ifaddr) { config_obj.network.ifaddr = file_config.ifaddr } // dns section if (file_config.dns_ip || file_config.dns_port) { // FIXME: dynamic dns ip // we'd have to move the DNS autodetection here // detect free port 53 on ip // for now just make sure we have sane defaults var ip = file_config.dns_ip if (!ip) ip = '127.0.0.1' var dnsPort = file_config.dns_port if (dnsPort === undefined) dnsPort = 53 config_obj.dns.bind = ip + ':' + dnsPort } } var runningConfig = {} var genSnCallbackFired function generateSerivceNodeINI(config, cb) { const homeDir = os.homedir() var done = { bootstrap: false, upstream: false, rpcCheck: false, dnsBind: false, netIf: false, publicIP: false, } if (config.publicIP) { done.publicIP = undefined } genSnCallbackFired = false function markDone(completeProcess, params) { done[completeProcess] = true let ready = true for (var i in done) { if (!done[i]) { ready = false log(i, 'is not ready') break } } if (shuttingDown) { //if (cb) cb() log('not going to start lokinet, shutting down') return } if (!ready) return // we may have un-required proceses call markDone after we started if (genSnCallbackFired) return genSnCallbackFired = true var keyPath = homeDir + '/.loki/' // if (config.lokid.data_dir) { keyPath = config.lokid.data_dir // make sure it has a trailing slash if (keyPath[keyPath.length - 1] != '/') { keyPath += '/' } } if (config.lokid.network == "test" || config.lokid.network == "demo") { keyPath += 'testnet/' } keyPath += 'key' log('markDone params', JSON.stringify(params)) log('PUBLIC', params.publicIP, 'IFACE', params.interfaceIP) var useNAT = false if (params.publicIP != params.interfaceIP) { log('NAT DETECTED MAKE SURE YOU FORWARD UDP PORT', config.public_port, 'on', params.publicIP, 'to', params.interfaceIP) useNAT = true } log('Drafting lokinet service node config') // FIXME: lock down identity.private for storage server runningConfig = { router: { nickname: 'ldl', }, dns: { upstream: params.upstreamDNS_servers, bind: params.lokinet_free53Ip + ':53', }, netdb: { dir: params.lokinet_nodedb, }, bind: { // will be set after }, network: {}, api: { enabled: true, bind: config.rpc_ip + ':' + params.use_lokinet_rpc_port }, lokid: { enabled: true, jsonrpc: config.lokid.rpc_ip + ':' + config.lokid.rpc_port, username: config.lokid.rpc_user, password: config.lokid.rpc_pass, 'service-node-seed': keyPath } } if (useNAT) { runningConfig.router['public-ip'] = params.publicIP runningConfig.router['public-port'] = config.public_port } // inject manual NAT config? if (config.public_ip) { runningConfig.router['public-ip'] = config.public_ip runningConfig.router['public-port'] = config.public_port } runningConfig.bind[params.lokinet_nic] = config.public_port if (config.internal_port) { runningConfig.bind[params.lokinet_nic] = config.internal_port } applyConfig(config, runningConfig) // optional bootstrap (might be a seed if not) // doesn't work //runningConfig.network['type'] = 'null' // disable exit //runningConfig.network['enabled'] = true; cb(ini.jsonToINI(runningConfig)) } generateINI(config, done, markDone, cb) } var genClientCallbackFired function generateClientINI(config, cb) { var done = { bootstrap: false, upstream: false, rpcCheck: false, dnsBind: false, } genClientCallbackFired = false function markDone(completeProcess, params) { done[completeProcess] = true let ready = true for (var i in done) { if (!done[i]) { ready = false log(i, 'is not ready') break } } if (!ready) return // make sure we didn't already start if (genClientCallbackFired) return genClientCallbackFired = true if (!params.use_lokinet_rpc_port) { // use default because we enable it params.use_lokinet_rpc_port = 1190 } log('Drafting lokinet client config') runningConfig = { router: { nickname: 'ldl', }, dns: { upstream: params.upstreamDNS_servers, bind: params.lokinet_free53Ip + ':53', }, netdb: { dir: params.lokinet_nodedb, }, network: {}, api: { enabled: true, bind: config.rpc_ip + ':' + params.use_lokinet_rpc_port }, } applyConfig(config, runningConfig) // a bootstrap is required, can't have a seed client if (!runningConfig.bootstrap) { console.error('no bootstrap for client') process.exit() } cb(ini.jsonToINI(runningConfig)) } generateINI(config, done, markDone, cb) } var shuttingDown var lokinet var lokinetLogging = true function preLaunchLokinet(config, cb) { //console.log('userInfo', os.userInfo('utf8')) //console.log('started as', process.getuid(), process.geteuid()) // check user permissions if (os.platform() == 'darwin') { if (process.getuid() != 0) { console.error('MacOS requires you start this with sudo') process.exit() } // leave the linux commentary for later /* } else { if (process.getuid() == 0) { console.error('Its not recommended you run this as root') } */ } if (os.platform() == 'linux') { // not root-like exec('getcap ' + config.binary_path, function (error, stdout, stderr) { //console.log('stdout', stdout) // src/loki-network/lokinet = cap_net_bind_service,cap_net_admin+eip if (!(stdout.match(/cap_net_bind_service/) && stdout.match(/cap_net_admin/))) { if (process.getgid() != 0) { conole.log(config.binary_path, 'does not have setcap. Please setcap the binary (make install usually does this) or run launcher root one time, so we can') process.exit() } else { // are root log('going to try to setcap your binary, so you dont need root') exec('setcap cap_net_admin,cap_net_bind_service=+eip ' + config.binary_path, function (error, stdout, stderr) { log('binary permissions upgraded') }) } } }) } // lokinet will crash if this file is zero bytes if (fs.existsSync('profiles.dat')) { var stats = fs.statSync('profiles.dat') if (!stats.size) { log('cleaning router profiles') fs.unlinkSync('profiles.dat') } } const tmpDir = os.tmpdir() const tmpPath = tmpDir + '/' + randomString(8) + '.lokinet_ini' cleanUpIni = true config.ini_writer(config, function (iniData) { if (shuttingDown) { //if (cb) cb() log('not going to write lokinet config, shutting down') return } log(iniData, 'as', tmpPath) fs.writeFileSync(tmpPath, iniData) config.ini_file = tmpPath cb() }) } function launchLokinet(config, cb) { if (shuttingDown) { //if (cb) cb() log('not going to start lokinet, shutting down') return } if (!fs.existsSync(config.ini_file)) { log('lokinet config file', config.ini_file, 'does not exist') process.exit() } // command line options var cli_options = [config.ini_file] if (config.verbose) { cli_options.push('-v') } console.log('network: launching', config.binary_path, cli_options.join(' ')) lokinet = spawn(config.binary_path, cli_options) if (!lokinet) { console.error('failed to start lokinet, exiting...') // proper shutdown? process.exit() } lokinet.killed = false lokinet.stdout.on('data', (data) => { var parts = data.toString().split(/\n/) parts.pop() data = parts.join('\n') if (module.exports.onMessage) { module.exports.onMessage(data) } }) lokinet.stderr.on('data', (data) => { if (module.exports.onError) { module.exports.onError(data) } }) lokinet.on('close', (code) => { log(`lokinet process exited with code ${code}`) // code 0 means clean shutdown lokinet.killed = true // clean up // if we have a temp bootstrap, clean it if (cleanUpBootstrap && runningConfig.bootstrap['add-node'] && fs.existsSync(runningConfig.bootstrap['add-node'])) { fs.unlinkSync(runningConfig.bootstrap['add-node']) } if (cleanUpIni && fs.existsSync(config.ini_file)) { fs.unlinkSync(config.ini_file) } if (!shuttingDown) { if (config.restart) { // restart it in 30 seconds to avoid pegging the cpu setTimeout(function () { log('loki_daemon is still running, restarting lokinet') launchLokinet(config) }, 30 * 1000) } else { // don't restart... } } // else we're shutting down }) if (cb) cb() } function checkConfig(config) { if (config === undefined) config = {} if (config.auto_config_test_ips === undefined) config.auto_config_test_ips = ['1.1.1.1', '8.8.8.8'] if (config.auto_config_test_host === undefined) config.auto_config_test_host = 'www.imdb.com' if (config.auto_config_test_port === undefined) config.auto_config_test_port = 80 auto_config_test_port = config.auto_config_test_port auto_config_test_host = config.auto_config_test_host auto_config_test_ips = config.auto_config_test_ips if (config.binary_path === undefined) config.binary_path = '/usr/local/bin/lokinet' // we don't always want a bootstrap (seed mode) // maybe if no port we shouldn't configure it if (config.rpc_ip === undefined) config.rpc_ip = '127.0.0.1' if (config.rpc_port === undefined) config.rpc_port = 0 // set public_port ? } function waitForUrl(url, cb) { httpGet(url, function (data) { //console.log('rpc data', data) // will be undefined if down (ECONNREFUSED) // if success // Unauthorized Access

401 Unauthorized

if (data) { cb() } else { // no data could me 404 if (shuttingDown) { //if (cb) cb() log('not going to start lokinet, shutting down') return } setTimeout(function () { waitForUrl(url, cb) }, 1000) } }) } function startServiceNode(config, cb) { // FIXME: if no bootstrap stomp it // but allow for seed nodes (no bootstrap)? checkConfig(config) config.ini_writer = generateSerivceNodeINI config.restart = true // FIXME: check for bootstrap stomp and strip it // only us lokinet devs will need to make our own seed node preLaunchLokinet(config, function () { // test lokid rpc port first // also this makes sure the service key file exists var url = 'http://' + config.lokid.rpc_user + ':' + config.lokid.rpc_pass + '@' + config.lokid.rpc_ip + ':' + config.lokid.rpc_port log('lokinet waiting for lokid RPC server') waitForUrl(url, function () { launchLokinet(config, cb) }) }) } function startClient(config, cb) { checkConfig(config) if (config.bootstrap_path === undefined && config.bootstrap_url === undefined) config.bootstrap_url = 'https://i2p.rocks/bootstrap.signed' config.ini_writer = generateClientINI preLaunchLokinet(config, function () { launchLokinet(config, cb) }) } // return a truish value if so function isRunning() { // should we block until port is responding? return lokinet } // copied from lib function isPidRunning(pid) { if (pid === undefined) { console.trace('isPidRunning was passed undefined, reporting not running') return false } try { process.kill(pid, 0) //console.log('able to kill', pid) return true } catch (e) { if (e.code == 'ERR_INVALID_ARG_TYPE') { // means pid was undefined return true } if (e.code == 'ESRCH') { // not running return false } if (e.code == 'EPERM') { // we're don't have enough permissions to signal this process return true } console.log(pid, 'isRunning', e.code) return false } } // intent to stop lokinet and don't restart it var retries = 0 function stop() { shuttingDown = true if (lokinet && lokinet.killed) { console.warn('lokinet already stopped') retries++ if (retries > 3) { // 3 exits in a row, something isn't dying // just quit out process.exit() } return } log('requesting lokinet be shutdown') if (lokinet && !lokinet.killed) { log('sending SIGINT to lokinet', lokinet.pid) process.kill(lokinet.pid, 'SIGINT') lokinet.killed = true // HACK: lokinet on macos can not be killed if rpc port is in use var monitorTimerStart = Date.now() var monitorTimer = setInterval(function () { if (!isPidRunning(lokinet.pid)) { // launcher can't exit until this interval is cleared clearInterval(monitorTimer) } else { var diff = Date.now() - monitorTimerStart if (diff > 15 * 1000) { // reach 15 secs and lokinet is still running // escalate it console.error('Lokinet is still running 15s after we intentionally stopped lokinet?') process.kill(lokinet.pid, 'SIGKILL') } else if (diff > 30 * 1000) { // reach 30 secs and lokinet is still running // escalate it console.error('Lokinet is still running 30s after we intentionally killed lokinet?') var handles = process._getActiveHandles() console.log('handles', handles.length) for (var i in handles) { var handle = handles[i] console.log(i, 'type', handle._type, handle) } console.log('requests', process._getActiveRequests().length) console.log('forcing exit') process.exit() } } }, 1000) // this timer will stop the system from shutting down /* setTimeout(function() { try { // check to see if still running process.kill(lokinet.pid, 0) log('sending SIGKILL to lokinet') process.kill(lokinet.pid, 'SIGKILL') } catch(e) { console.error('Launcher is still running 15s after we intentionally stopped lokinet?') var handles = process._getActiveHandles() console.log('handles', handles.length) for(var i in handles) { var handle = handles[i] console.log(i, 'type', handle._type, handle) } console.log('requests', process._getActiveRequests().length) process.exit() } }, 15 * 1000) */ } } // isRunning covers this too well function getPID() { return (lokinet && !lokinet.killed && lokinet.pid) ? lokinet.pid : 0 } function enableLogging() { lokinetLogging = true } function disableLogging() { console.log('Disabling lokinet logging') lokinetLogging = false } function getLokiNetIP(cb) { function checkDNS() { log('lokinet seems to be running, querying', runningConfig.dns.bind) // where's our DNS server? //log('RunningConfig says our lokinet\'s DNS is on', runningConfig.dns.bind) testDNSForLokinet(runningConfig.dns.bind, function (ips) { //log('lokinet test', ips) if (ips && ips.length) { cb(ips[0]) } else { // , retrying console.error('cant communicate with lokinet DNS') /* //process.exit() setTimeout(function() { getLokiNetIP(cb) }, 1000) */ cb() } }) } if (runningConfig.api.enabled) { log('wait for lokinet startup', runningConfig.api) var url = 'http://' + runningConfig.api.bind + '/' waitForUrl(url, function () { checkDNS() }) } else { checkDNS() } } module.exports = { startServiceNode: startServiceNode, startClient: startClient, checkConfig: checkConfig, findLokiNetDNS: findLokiNetDNS, lookup: lookup, isRunning: isRunning, stop: stop, getLokiNetIP: getLokiNetIP, enableLogging: enableLogging, disableLogging: disableLogging, getPID: getPID, // FIXME: should we allow hooking of log() too? onMessage: function (data) { if (lokinetLogging) { console.log(`lokinet: ${data}`) } }, onError: function (data) { console.log(`lokineterr: ${data}`) }, }