From 98131ee66e9b55f9f75b03110d0384c09626f115 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Vest=C3=B8l?= Date: Fri, 13 Jul 2018 22:56:10 +0200 Subject: [PATCH] Very big changes to help custom config paths --- client.js | 3 + lib/authenticate.js | 26 ++- lib/getInstances.js | 24 +- lib/manager/modManager.js | 452 +++++++++++++++++++------------------- master.js | 10 +- 5 files changed, 265 insertions(+), 250 deletions(-) diff --git a/client.js b/client.js index f638e727..6847545c 100644 --- a/client.js +++ b/client.js @@ -454,6 +454,9 @@ write-data=${ path.resolve(config.instanceDirectory, instance) }\r\n confirmedOrders = []; lastSignalCheck = Date.now(); }); +} else { + console.error("Invalid arguments, quitting."); + process.exit(1); } // ensure instancemanagement only ever runs once diff --git a/lib/authenticate.js b/lib/authenticate.js index b13404c5..2c859604 100644 --- a/lib/authenticate.js +++ b/lib/authenticate.js @@ -1,15 +1,17 @@ const jwt = require('jsonwebtoken'); -const config = require("./../config"); +// const config = require("./../config"); -var middleware = function (req, res, next) { - var token = req.headers['x-access-token']; - if(!token) return res.status(401).send({ auth: false, message: 'No token provided.', endpoint: req.route.path}); - - jwt.verify(token, config.masterAuthSecret, function(err, decoded) { - if(err) return res.status(500).send({ auth: false, message: 'Failed to authenticate token.', endpoint: req.route.path}); - next(); - }); -} - -module.exports = {middleware}; +module.exports = function(config){ + return { + middleware: function (req, res, next) { + var token = req.headers['x-access-token']; + if(!token) return res.status(401).send({ auth: false, message: 'No token provided.', endpoint: req.route.path}); + + jwt.verify(token, config.masterAuthSecret, function(err, decoded) { + if(err) return res.status(500).send({ auth: false, message: 'Failed to authenticate token.', endpoint: req.route.path}); + next(); + }); + }, + } +}; diff --git a/lib/getInstances.js b/lib/getInstances.js index da61d237..3565c0e2 100644 --- a/lib/getInstances.js +++ b/lib/getInstances.js @@ -1,15 +1,17 @@ const fileOps = require("_app/fileOps"); -const config = require("./../config"); +// const config = require("./../config"); -module.exports = { - sync: function(){ - let instances; - try { - instances = fileOps.getDirectoriesSync(config.instanceDirectory); - } catch (e) { - // there are no instances created yet, probably missing the folder - instances = []; - } - return instances; +module.exports = function(config) { + return { + sync: function(){ + let instances; + try { + instances = fileOps.getDirectoriesSync(config.instanceDirectory); + } catch (e) { + // there are no instances created yet, probably missing the folder + instances = []; + } + return instances; + }, } } \ No newline at end of file diff --git a/lib/manager/modManager.js b/lib/manager/modManager.js index 943838dc..cb259be2 100644 --- a/lib/manager/modManager.js +++ b/lib/manager/modManager.js @@ -14,9 +14,8 @@ const sLog = require("single-line-log").stdout; const query = require('cli-interact').getYesNo; const fileOps = require("_app/fileOps"); -const config = require("./../../config"); +// const config = require("./../../config"); -const instances = require("./../getInstances.js").sync(); function ellipse(str, max){ return str.length > (max - 3) ? str.substring(0,max-3) + '...' : str; @@ -24,242 +23,249 @@ function ellipse(str, max){ // https://stackoverflow.com/questions/15900485/correct-way-to-convert-size-in-bytes-to-kb-mb-gb-in-javascript function formatBytes(a,b){if(0==a)return"0 Bytes";var c=1024,d=b||2,e=["Bytes","KB","MB","GB","TB","PB","EB","ZB","YB"],f=Math.floor(Math.log(a)/Math.log(c));return parseFloat((a/Math.pow(c,f)).toFixed(d))+" "+e[f]}; -/** -Lists mods for an instance as a table. Warning, handles printing internally. -@param {string} instance - Name of the instance or "shared" -@example -var modManager = require("modManager"); -modManager.listMods("testInstance"); -// console.logs table of mods installed on instance -modManager.listMods("shared"); -// console.logs mods located in /sharedMods as a nice table -*/ -module.exports.listMods = function(instance){ - return new Promise((resolve, reject) => { - if(instance && instances.includes(instance)){ - factorioAPI.init(false, config.instanceDirectory+'/'+instance+'/instanceMods', config.instanceDirectory+'/'+instance+'/saves', config.factorio_version); - factorioAPI.authenticate({ - username: config.username, - token: config.token, - require_ownership: false - }).then(token => { - console.log("* Currently installed:"); - factorioAPI.readModZips().then(list => { - console.log(asTable(list.map(mod => { - return { - name:(mod.title || mod.name), - version:mod.version, - date:mod.date, - factorio_ver:mod.factorio_version, - author:mod.author, - } - }))); - }); - // Had issues earlier due to bug in node-factorio-api, see https://github.com/Danacus/node-factorio-api/issues/2 - // bug is resolved, thanks to @Danacus and @brotherpot - factorioAPI.getModsFromSaves().then(list => { - console.log("* Mods used in save:"); - console.log(asTable(list[0].mods)) - }); - }).catch(reason => { - console.log("Failed to authenticate to factorio.com! Please check your connection and the username+token used for authentication in config.json"); - resolve(); - }); - } else { - // list shared mods in /sharedMods/ - factorioAPI.init(false, 'sharedMods/', '', config.factorio_version); - factorioAPI.authenticate({ - username: config.username, - token: config.token, - require_ownership: false - }).then(() => { - console.log("\r\n") - console.log("* Mods shared by all instances:") - factorioAPI.getInstalledMods().then(mods => { - console.log(asTable(mods)); - resolve(); - }); - }).catch(e => { - reject(new Error(e)); - }); - } - - // get JSON data about GH releases - /*let res = syncRequest('GET', 'https://api.github.com/repos/Danielv123/factorioClusterioMod/releases', {"headers":{"User-Agent":"Fuck you for requiring user agents!"}}); - let url = JSON.parse(res.getBody())[0].assets[0].browser_download_url; - let name = JSON.parse(res.getBody())[0].assets[0].name; - console.log(JSON.parse(res.getBody())) +module.exports = function(config){ + if(!config) config = require("./../../config"); + const instances = require("./../getInstances.js")(config).sync(); + + return { + /** + Lists mods for an instance as a table. Warning, handles printing internally. + @param {string} instance - Name of the instance or "shared" + + @example + var modManager = require("modManager"); + modManager.listMods("testInstance"); + // console.logs table of mods installed on instance + modManager.listMods("shared"); + // console.logs mods located in /sharedMods as a nice table */ - }); -} -/** -Searches mods.factorio.com for mods matching a search term and prints -the results to console as a neat looking table - -@param {string} searchTerm What to search for -*/ -module.exports.findMods = function(searchTerm){ - return new Promise((resolve, reject) => { - factorioAPI.init(false); - factorioAPI.authenticate({ - username: config.username, - token: config.token, - require_ownership: false - }).then(token => { - factorioAPI.searchMods(searchTerm).then(mods => { - let result = asTable(mods.reverse().map(mod => { - return { - title:ellipse(mod.title, 50), - name:ellipse(mod.name, 30), - downloads:mod.downloads_count, - updated: moment(mod.updated_at).format("DD.MM.YY"), - latest_version: mod.latest_release.version, - owner:mod.owner, - //license: mod.license_name, - } - })); - resolve(result); - // console.log(result); - }); - }).catch(e => { - if(e.contains("Insufficient information")) console.log("We are missing some parameters required for logging in! Make sure your factorio username and token are specified in config.json."); - reject(new Error(e)); - }); - }); -} - -module.exports.addMod = function(term, instance){ - return new Promise((resolve, reject) => { - if(instance == "shared"){ // modDirectory has to end in / - var modDirectory = "sharedMods/"; - } else { - var modDirectory = config.instanceDirectory+"/"+instance+"/instanceMods/"; - } - if(term && term.length > 0){ - if(term.includes("mods.factorio.com/mods/")){ // https://mods.factorio.com/mods/Danielv123/ChooChoo - // treat it as a link download - factorioAPI.init(false, modDirectory, "", config.factorio_version); - factorioAPI.authenticate({ - username: config.username, - token: config.token, - require_ownership: false - }).then(() => { - factorioAPI.searchMods(term.split("/")[term.split("/").length-1]).then(body => { - console.log("Im gonna download: "+body[0].name + " in version: " + body[0].latest_release.version); - factorioAPI.downloadMod( {name: body[0].name, version: body[0].latest_release.version}).then(()=>{ - console.log("Mod downloaded"); - resolve(); + listMods: function(instance){ + return new Promise((resolve, reject) => { + if(instance && instances.includes(instance)){ + factorioAPI.init(false, config.instanceDirectory+'/'+instance+'/instanceMods', config.instanceDirectory+'/'+instance+'/saves', config.factorio_version); + factorioAPI.authenticate({ + username: config.username, + token: config.token, + require_ownership: false + }).then(token => { + console.log("* Currently installed:"); + factorioAPI.readModZips().then(list => { + console.log(asTable(list.map(mod => { + return { + name:(mod.title || mod.name), + version:mod.version, + date:mod.date, + factorio_ver:mod.factorio_version, + author:mod.author, + } + }))); }); + // Had issues earlier due to bug in node-factorio-api, see https://github.com/Danacus/node-factorio-api/issues/2 + // bug is resolved, thanks to @Danacus and @brotherpot + factorioAPI.getModsFromSaves().then(list => { + console.log("* Mods used in save:"); + console.log(asTable(list[0].mods)) + }); + }).catch(reason => { + console.log("Failed to authenticate to factorio.com! Please check your connection and the username+token used for authentication in config.json"); + resolve(); }); - }); - } else if(term.includes("github.com")){ // https://github.com/Danielv123/factorioClusterioMod - // treat it as a github releases download - let apiURL = "https://api.github.com/repos/" + term.split("/")[3] + "/" + term.split("/")[4] + "/releases"; // https://api.github.com/repos/Danielv123/factorioClusterioMod/releases - - let res = syncRequest('GET', apiURL, {"headers":{"User-Agent":"Fuck you for requiring user agents!"}}); - let url = JSON.parse(res.getBody())[0].assets[0].browser_download_url; - let name = JSON.parse(res.getBody())[0].assets[0].name; - if(url) { - console.log("Downloading " + url); - let file = fs.createWriteStream(modDirectory + name); - let bytesDownloaded = 0; - let downloadStartTime = Date.now(); - https.get(url, function(response) { - let bytesToDownload = response.headers['content-length']; - response.pipe(file); - response.on("data", chunk => { - bytesDownloaded += chunk.length; - let downloadTimeElapsed = Date.now() - downloadStartTime; - let bytesPerSecond = bytesDownloaded / (downloadTimeElapsed/1000); - let timeToComplete = Math.floor((bytesToDownload - bytesDownloaded)/bytesPerSecond); - sLog("Downloading "+name+" "+(bytesDownloaded/bytesToDownload*100).toPrecision(3)+"% ("+formatBytes(bytesDownloaded)+" / "+formatBytes(bytesToDownload)+")\r\n"+ - "about "+timeToComplete+" seconds remaining ("+formatBytes(bytesPerSecond)+"/s)"); - }).on("end", x => { - sLog.clear(); - console.log(" Downloaded in "+moment.utc(moment.duration(Date.now()-downloadStartTime).as('milliseconds')).format('mm:ss')); // because sLog.clear() doesn't seem to do the job of ending the line properly + } else { + // list shared mods in /sharedMods/ + factorioAPI.init(false, 'sharedMods/', '', config.factorio_version); + factorioAPI.authenticate({ + username: config.username, + token: config.token, + require_ownership: false + }).then(() => { + console.log("\r\n") + console.log("* Mods shared by all instances:") + factorioAPI.getInstalledMods().then(mods => { + console.log(asTable(mods)); resolve(); }); + }).catch(e => { + reject(new Error(e)); }); } - } else { - // treat it as a search and download the top result - factorioAPI.init(false, modDirectory, "", config.factorio_version); + + // get JSON data about GH releases + /*let res = syncRequest('GET', 'https://api.github.com/repos/Danielv123/factorioClusterioMod/releases', {"headers":{"User-Agent":"Fuck you for requiring user agents!"}}); + let url = JSON.parse(res.getBody())[0].assets[0].browser_download_url; + let name = JSON.parse(res.getBody())[0].assets[0].name; + console.log(JSON.parse(res.getBody())) + */ + }); + }, + /** + Searches mods.factorio.com for mods matching a search term and prints + the results to console as a neat looking table + + @param {string} searchTerm What to search for + */ + findMods: function(searchTerm){ + return new Promise((resolve, reject) => { + factorioAPI.init(false); factorioAPI.authenticate({ username: config.username, token: config.token, require_ownership: false - }).then(() => { - factorioAPI.searchMods(term).then(body => { - console.log(body) - console.log("Im gonna download: "+body[0].name + " in version: " + body[0].latest_release.version); - factorioAPI.downloadMod( {name: body[0].name, version: body[0].latest_release.version}).then(()=>{ - console.log("Mod downloaded"); - resolve(); - }); + }).then(token => { + factorioAPI.searchMods(searchTerm).then(mods => { + let result = asTable(mods.reverse().map(mod => { + return { + title:ellipse(mod.title, 50), + name:ellipse(mod.name, 30), + downloads:mod.downloads_count, + updated: moment(mod.updated_at).format("DD.MM.YY"), + latest_version: mod.latest_release.version, + owner:mod.owner, + //license: mod.license_name, + } + })); + resolve(result); + // console.log(result); }); + }).catch(e => { + if(e.contains("Insufficient information")) console.log("We are missing some parameters required for logging in! Make sure your factorio username and token are specified in config.json."); + reject(new Error(e)); + }); + }); + }, + addMod: function(term, instance){ + return new Promise((resolve, reject) => { + if(instance == "shared"){ // modDirectory has to end in / + var modDirectory = "sharedMods/"; + } else { + var modDirectory = config.instanceDirectory+"/"+instance+"/instanceMods/"; + } + if(term && term.length > 0){ + if(term.includes("mods.factorio.com/mods/")){ // https://mods.factorio.com/mods/Danielv123/ChooChoo + // treat it as a link download + factorioAPI.init(false, modDirectory, "", config.factorio_version); + factorioAPI.authenticate({ + username: config.username, + token: config.token, + require_ownership: false + }).then(() => { + factorioAPI.searchMods(term.split("/")[term.split("/").length-1]).then(body => { + console.log("Im gonna download: "+body[0].name + " in version: " + body[0].latest_release.version); + factorioAPI.downloadMod( {name: body[0].name, version: body[0].latest_release.version}).then(()=>{ + console.log("Mod downloaded"); + resolve(); + }); + }); + }); + } else if(term.includes("github.com")){ // https://github.com/Danielv123/factorioClusterioMod + // treat it as a github releases download + let apiURL = "https://api.github.com/repos/" + term.split("/")[3] + "/" + term.split("/")[4] + "/releases"; // https://api.github.com/repos/Danielv123/factorioClusterioMod/releases + + let res = syncRequest('GET', apiURL, {"headers":{"User-Agent":"Fuck you for requiring user agents!"}}); + let url = JSON.parse(res.getBody())[0].assets[0].browser_download_url; + let name = JSON.parse(res.getBody())[0].assets[0].name; + if(url) { + console.log("Downloading " + url); + let file = fs.createWriteStream(modDirectory + name); + let bytesDownloaded = 0; + let downloadStartTime = Date.now(); + https.get(url, function(response) { + let bytesToDownload = response.headers['content-length']; + response.pipe(file); + response.on("data", chunk => { + bytesDownloaded += chunk.length; + let downloadTimeElapsed = Date.now() - downloadStartTime; + let bytesPerSecond = bytesDownloaded / (downloadTimeElapsed/1000); + let timeToComplete = Math.floor((bytesToDownload - bytesDownloaded)/bytesPerSecond); + sLog("Downloading "+name+" "+(bytesDownloaded/bytesToDownload*100).toPrecision(3)+"% ("+formatBytes(bytesDownloaded)+" / "+formatBytes(bytesToDownload)+")\r\n"+ + "about "+timeToComplete+" seconds remaining ("+formatBytes(bytesPerSecond)+"/s)"); + }).on("end", x => { + sLog.clear(); + console.log(" Downloaded in "+moment.utc(moment.duration(Date.now()-downloadStartTime).as('milliseconds')).format('mm:ss')); // because sLog.clear() doesn't seem to do the job of ending the line properly + resolve(); + }); + }); + } + } else { + // treat it as a search and download the top result + factorioAPI.init(false, modDirectory, "", config.factorio_version); + factorioAPI.authenticate({ + username: config.username, + token: config.token, + require_ownership: false + }).then(() => { + factorioAPI.searchMods(term).then(body => { + console.log(body) + console.log("Im gonna download: "+body[0].name + " in version: " + body[0].latest_release.version); + factorioAPI.downloadMod( {name: body[0].name, version: body[0].latest_release.version}).then(()=>{ + console.log("Mod downloaded"); + resolve(); + }); + }); + }); + } + } else { + reject(new Error("Invalid search term")); + } + }); + }, + removeMod: function removeMod(term, instance, force = false){ + return new Promise((resolve, reject) => { + if(instance == "shared"){ // modDirectory has to end in / + var modDirectory = "sharedMods/"; + } else { + var modDirectory = config.instanceDirectory+"/"+instance+"/instanceMods/"; + } + if(term && typeof term == "string" && term.length > 0){ + let mods = fs.readdirSync(modDirectory); + let modsMatchingFilter = mods.filter(mod => mod.toLowerCase().includes(term.toLowerCase())); + if(modsMatchingFilter.length < 1){ + console.log("No mods found matching search"); + } else if(modsMatchingFilter.length == 1){ + console.log("Deleting "+modsMatchingFilter[0]); + fs.unlinkSync(modDirectory+modsMatchingFilter[0]); + console.log("Deleted."); + } else if(modsMatchingFilter.length > 1){ + console.log("Found multiple mods"); + modsMatchingFilter.forEach(mod => console.log(mod)); + if(query("Delete all mods matching filter?")){ + let modsToDelete = modsMatchingFilter.length; + for(let i = 0; i < modsToDelete; i++){ + fs.unlinkSync(modDirectory+modsMatchingFilter[i]); + console.log("Deleted "+modsMatchingFilter[i]); + } + } else console.log("Did not delete mods.") + } + resolve(); + } else { + reject(new Error("Invalid search term")); + } + }); + }, + + // 'https://api.github.com/repos/Danielv123/factorioClusterioMod/releases' + /** + Download mod from github repo by looking for the first binary download in + the latest release, saving it to /sharedMods + + @param {string} url a github repo URL, for example https://api.github.com/repos/Danielv123/factorioClusterioMod/releases. Must link to /releases + */ + downloadFromGitURL: function(url){ + console.log("Downloading mod from "+url); + // get JSON data about releases + let res = syncRequest('GET', url, {"headers":{"User-Agent":"Please don't require setting a user agent"}}); + let downloadUrl = JSON.parse(res.getBody())[0].assets[0].browser_download_url; + let name = JSON.parse(res.getBody())[0].assets[0].name; + console.log(JSON.parse(res.getBody())) + if(downloadUrl) { + console.log(downloadUrl); + let file = fs.createWriteStream("sharedMods/"+name); + https.get(downloadUrl, function(response) { + response.pipe(file); + console.log("Downloaded "+name); }); } - } else { - reject(new Error("Invalid search term")); - } - }); -} -module.exports.removeMod = function removeMod(term, instance, force = false){ - return new Promise((resolve, reject) => { - if(instance == "shared"){ // modDirectory has to end in / - var modDirectory = "sharedMods/"; - } else { - var modDirectory = config.instanceDirectory+"/"+instance+"/instanceMods/"; - } - if(term && typeof term == "string" && term.length > 0){ - let mods = fs.readdirSync(modDirectory); - let modsMatchingFilter = mods.filter(mod => mod.toLowerCase().includes(term.toLowerCase())); - if(modsMatchingFilter.length < 1){ - console.log("No mods found matching search"); - } else if(modsMatchingFilter.length == 1){ - console.log("Deleting "+modsMatchingFilter[0]); - fs.unlinkSync(modDirectory+modsMatchingFilter[0]); - console.log("Deleted."); - } else if(modsMatchingFilter.length > 1){ - console.log("Found multiple mods"); - modsMatchingFilter.forEach(mod => console.log(mod)); - if(query("Delete all mods matching filter?")){ - let modsToDelete = modsMatchingFilter.length; - for(let i = 0; i < modsToDelete; i++){ - fs.unlinkSync(modDirectory+modsMatchingFilter[i]); - console.log("Deleted "+modsMatchingFilter[i]); - } - } else console.log("Did not delete mods.") - } - resolve(); - } else { - reject(new Error("Invalid search term")); - } - }); -} - -// 'https://api.github.com/repos/Danielv123/factorioClusterioMod/releases' -/** -Download mod from github repo by looking for the first binary download in -the latest release, saving it to /sharedMods - -@param {string} url a github repo URL, for example https://api.github.com/repos/Danielv123/factorioClusterioMod/releases. Must link to /releases -*/ -module.exports.downloadFromGitURL = function(url){ - console.log("Downloading mod from "+url); - // get JSON data about releases - let res = syncRequest('GET', url, {"headers":{"User-Agent":"Please don't require setting a user agent"}}); - let downloadUrl = JSON.parse(res.getBody())[0].assets[0].browser_download_url; - let name = JSON.parse(res.getBody())[0].assets[0].name; - console.log(JSON.parse(res.getBody())) - if(downloadUrl) { - console.log(downloadUrl); - let file = fs.createWriteStream("sharedMods/"+name); - https.get(downloadUrl, function(response) { - response.pipe(file); - console.log("Downloaded "+name); - }); + }, } } /* diff --git a/master.js b/master.js index 72080996..577691e9 100644 --- a/master.js +++ b/master.js @@ -52,7 +52,7 @@ const stringUtils = require("./lib/stringUtils"); const fileOps = require("_app/fileOps"); // homemade express middleware for token auth -const authenticate = require("./lib/authenticate"); +const authenticate = require("./lib/authenticate")(config); /** Sync */ function randomStringAsBase64Url(size) { @@ -279,7 +279,9 @@ db.items.removeItem = function(object) { } // store slaves and inventory in a .json full of JSON data -process.on('SIGINT', async function () { +process.on('SIGINT', shutdown); // ctrl + c +process.on('SIGHUP', shutdown); // terminal closed +async function shutdown() { console.log('Ctrl-C...'); let exitStartTime = Date.now(); // set insane limit to slave length, if its longer than this we are probably being ddosed or something @@ -304,8 +306,8 @@ process.on('SIGINT', async function () { } } console.log("Exited in "+(Date.now()-exitStartTime)+"ms"); - process.exit(2); -}); + process.exit(0); +} /** POST World ID management. Slaves post here to tell the server they exist