var fs = require('fs-extra'); var https = require('follow-redirects').https; var needle = require("needle"); var child_process = require('child_process'); var path = require('path'); var syncRequest = require('sync-request'); var request = require("request") var ncp = require('ncp').ncp; var Rcon = require('simple-rcon'); var hashFiles = require('hash-files'); // require config.json var config = require('./config'); // Functions var deleteFolderRecursive = function (path) { if (fs.existsSync(path)) { fs.readdirSync(path).forEach(function (file, index) { var curPath = path + "/" + file; if (fs.lstatSync(curPath).isDirectory()) { // recurse deleteFolderRecursive(curPath); } else { // delete file fs.unlinkSync(curPath); } }); fs.rmdirSync(path); } }; function getDirectories(srcpath) { return fs.readdirSync(srcpath).filter(function (file) { return fs.statSync(path.join(srcpath, file)).isDirectory(); }); } if (!fs.existsSync("./instances/")) { fs.mkdirSync("instances"); } if (!fs.existsSync("./sharedMods/")) { fs.mkdirSync("sharedMods"); } const instance = process.argv[3]; const instancedirectory = './instances/' + instance; const command = process.argv[2]; // handle commandline parameters if (!command || command == "help" || command == "--help") { console.error("Usage: ") console.error("node client.js start [instance name]") console.error("node client.js list") console.error("node client.js delete [instance]") console.error("To download the latest version of the Clusterio lua mod, do") console.error("node client.js download") process.exit(1) } else if (command == "list") { console.dir(getDirectories("./instances/")); process.exit(1) } else if (command == "delete") { if (!process.argv[3]) { console.error("Usage: node client.js delete [instance]"); process.exit(1) } else if (typeof process.argv[3] == "string" && fs.existsSync("./instances/" + process.argv[3]) && process.argv[3] != "/" && process.argv[3] != "") { deleteFolderRecursive("./instances/" + process.argv[3]); console.log("Deleted instance " + process.argv[3]) process.exit(1) } else { console.error("Instance not found: " + process.argv[3]); process.exit(1) } } else if (command == "download") { console.log("Downloading mods..."); // get JSON data about 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; if(url) { console.log(url); var file = fs.createWriteStream("sharedMods/"+name); var request = https.get(url, function(response) { response.pipe(file); console.log("Downloaded "+name) }); } } else if (command == "start" && typeof instance == "string" && instance != "/" && !fs.existsSync(instancedirectory)) { // if instance does not exist, create it console.log("Creating instance...") fs.mkdirSync(instancedirectory); fs.mkdirSync(instancedirectory + "/script-output/"); fs.writeFileSync(instancedirectory + "/script-output/output.txt", "") fs.writeFileSync(instancedirectory + "/script-output/orders.txt", "") fs.writeFileSync(instancedirectory + "/script-output/txbuffer.txt", "") fs.mkdirSync(instancedirectory + "/mods/") // fs.symlinkSync('../../../sharedMods', instancedirectory + "/mods", 'junction') // This is broken because it can only take a file as first argument, not a folder fs.writeFileSync(instancedirectory + "/config.ini", "[path]\r\n\ read-data=__PATH__executable__/../../data\r\n\ write-data=__PATH__executable__/../../../instances/" + instance + "\r\n\ "); var instconf = { "factorioPort": Math.floor(Math.random() * 65535), "clientPort": Math.floor(Math.random() * 65535), "clientPassword": Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 8), } console.log(instconf) fs.writeFileSync(instancedirectory + "/config.json", JSON.stringify(instconf, null, 4)); var serversettings = { "name": config.username + "'s clusterio " + instance, "description": config.description, "tags": ["clusterio"], "max_players": "20", "visibility": config.visibility, "username": config.username, "token": config.usertoken, "game_password": config.game_password, "verify_user_identity": config.verify_user_identity, "admins": [config.username], "allow_commands": config.allow_commands, "autosave_interval": 10, "autosave_slots": 5, "afk_autokick_interval": 0, "auto_pause": config.auto_pause, } fs.writeFileSync(instancedirectory + "/server-settings.json", JSON.stringify(serversettings, null, 4)); var createSave = child_process.spawnSync( './' + config.factorioDirectory + '/bin/x64/factorio', [ '-c', instancedirectory + '/config.ini', '--create', instancedirectory + '/save.zip', ] ) console.log("Instance created!") } else if (command == "start" && typeof instance == "string" && instance != "/" && fs.existsSync(instancedirectory)) { // Exit if no instance specified (it should be, just a safeguard) if(instancedirectory != "./instances/undefined"){ var instanceconfig = require(instancedirectory + '/config'); } else { process.exit(1) } // move mods from ./sharedMods to the instances mod directory console.log("Moving shared mods...") //ncp("./sharedMods/", instancedirectory + "/mods", function (err) { fs.copySync('sharedMods', instancedirectory + "/mods") process.on('SIGINT', function () { console.log("Caught interrupt signal"); //serverprocess.stdin.write('/quit') }); // Spawn factorio server //var serverprocess = child_process.exec(commandline) var serverprocess = child_process.spawn( './' + config.factorioDirectory + '/bin/x64/factorio', [ '-c', instancedirectory + '/config.ini', '--start-server', instancedirectory + '/save.zip', '--rcon-port', instanceconfig.clientPort, '--rcon-password', instanceconfig.clientPassword, '--server-settings', instancedirectory + '/server-settings.json', '--port', instanceconfig.factorioPort ], { 'stdio': ['pipe', 'pipe', 'pipe'] } ) serverprocess.on('close', (code) => { console.log(`child process exited with code ${code}`); process.exit(); }); serverprocess.stdout.on('data', (chunk) => { console.log('OUT: ' + chunk); }) serverprocess.stderr.on('data', (chunk) => { console.log('ERR: ' + chunk); }) // connect to the server with rcon // IP, port, password var client = new Rcon({ host: 'localhost', port: instanceconfig.clientPort, password: instanceconfig.clientPassword, timeout: 0 }); // wait a few seconds to let the server finish starting before connecting rcon //TODO: catch '2.033 Info RemoteCommandProcessor.cpp:97: Starting RCON interface at port 35002' from stdout maybe? setTimeout(() => { client.connect(); }, 5000); client.on('authenticated', function () { console.log('Authenticated!'); }).on('connected', function () { console.log('Connected!'); // getID(); }).on('disconnected', function () { console.log('Disconnected!'); // now reconnect client.connect(); }); // set some globals confirmedOrders = []; lastSignalCheck = Date.now(); // world IDs ------------------------------------------------------------------ hashMods(instance, function(modHashes){ setInterval(getID, 10000); getID() function getID() { var payload = { time: Date.now(), rconPort: instanceconfig.clientPort, rconPassword: instanceconfig.clientPassword, serverPort: instanceconfig.factorioPort, unique: instanceconfig.clientPassword.hashCode(), publicIP: config.publicIP, // IP of the server should be global for all instances, so we pull that straight from the config mods:modHashes } require('getmac').getMac(function (err, mac) { if (err) throw err payload.mac = mac console.log(payload) needle.post(config.masterIP + ":" + config.masterPort + '/getID', payload, function (err, response, body) { if (response && response.body) { // In the future we might be interested in whether or not we actually manage to send it, but honestly I don't care. console.log(response.body) } }); }) } }) // Mod uploading and management ----------------------------------------------- // get mod names and hashes // string: instance, function: callback setTimeout(function(){hashMods(instance, uploadMods)}, 5000); function uploadMods(modHashes) { // [{modName:string,hash:string}, ... ] for(i=0;i