mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-15 07:24:52 +09:00
Merge branch 'dev' into pr/71
This commit is contained in:
commit
51e41835ee
7 files changed
+141
-12
No files matched your search
@@ -34,6 +34,7 @@ Factorio*
|
||||
database/items.db
|
||||
config.json
|
||||
package-lock.json
|
||||
secret-api-token.txt
|
||||
mod_cache.json
|
||||
static/pictures/spritesheet.js
|
||||
static/pictures/spritesheet.png
|
||||
@@ -45,4 +46,5 @@ sharedPlugins/*
|
||||
!sharedPlugins/inventoryImports/
|
||||
!sharedPlugins/UPSdisplay/
|
||||
!sharedPlugins/remoteMap/
|
||||
!sharedPlugins/remoteCommands/
|
||||
!sharedPlugins/researchSync/
|
||||
@@ -445,7 +445,7 @@ function instanceManagement() {
|
||||
global.subscribedFiles = {};
|
||||
}
|
||||
if(pluginConfig.binary == "nodePackage"){
|
||||
// require index.js.main() of plugin and execute it as a function
|
||||
// require index.js.main() of plugin and execute it as a class
|
||||
let pluginClass = require("./sharedPlugins/" + pluginDirectories[I] + "/index.js");
|
||||
plugins[I] = new pluginClass(combinedConfig, function(data){
|
||||
if(data.toString('utf8')[0] != "/") {
|
||||
@@ -453,6 +453,8 @@ function instanceManagement() {
|
||||
} else {
|
||||
messageInterface(data.toString('utf8'));
|
||||
}
|
||||
}, { // extra functions to pass in object. Should have done it like this from the start, but won't break backwards compat.
|
||||
socket, // socket.io connection to master (and ES6 destructuring, yay)
|
||||
});
|
||||
if(pluginConfig.scriptOutputFileSubscription && typeof pluginConfig.scriptOutputFileSubscription == "string"){
|
||||
if(global.subscribedFiles[pluginConfig.scriptOutputFileSubscription]) {
|
||||
|
||||
+6
-1
@@ -8,6 +8,10 @@
|
||||
"__comment2": "IP the master web server runs at, can be ignored on the master system",
|
||||
"masterIP": "localhost",
|
||||
"masterPort": 8080,
|
||||
"masterAuthToken": "",
|
||||
|
||||
"__commentAuthSecret": "This is a long random string generated by keyboard mashing. Please mash you own string for security.",
|
||||
"masterAuthSecret": "anfeåpvunuennuusnvzunsrevunåzeufnewnufasiojfåqwoiuajknwkjfdabewfkjadfnewåfundjkcvnewmfqewrmn123",
|
||||
|
||||
"__comment3": "Your factorio.com username for factorio MP services, also username will be admin",
|
||||
"username": "",
|
||||
@@ -30,5 +34,6 @@
|
||||
"entriesPerSecond": 0.01666666667
|
||||
},
|
||||
"logItemTransfers": false,
|
||||
"uploadModsToMaster": true
|
||||
"uploadModsToMaster": true,
|
||||
"allowRemoteCommandExecution": false
|
||||
}
|
||||
@@ -36,13 +36,15 @@ const averagedTimeSeries = require("averaged-timeseries");
|
||||
const deepmerge = require("deepmerge");
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
var nedb = require("nedb");
|
||||
const nedb = require("nedb");
|
||||
const jwt = require('jsonwebtoken');
|
||||
const bcrypt = require('bcryptjs');
|
||||
|
||||
var express = require("express");
|
||||
var ejs = require("ejs");
|
||||
const express = require("express");
|
||||
const ejs = require("ejs");
|
||||
// Required for express post requests
|
||||
var bodyParser = require("body-parser");
|
||||
var fileUpload = require('express-fileupload');
|
||||
const bodyParser = require("body-parser");
|
||||
const fileUpload = require('express-fileupload');
|
||||
var app = express();
|
||||
|
||||
app.use(bodyParser.json());
|
||||
@@ -96,7 +98,10 @@ setInterval(()=>{
|
||||
prometheusConnectedInstancesCounter.set(numberOfActiveSlaves);
|
||||
},10000);
|
||||
/**
|
||||
Prometheus metrics endpoint. Returns performance and usage metrics in a prometheus readable format.
|
||||
GET Prometheus metrics endpoint. Returns performance and usage metrics in a prometheus readable format.
|
||||
@memberof clusterioMaster
|
||||
@instance
|
||||
@alias /metrics
|
||||
*/
|
||||
app.get('/metrics', (req, res) => {
|
||||
endpointHitCounter.labels(req.route.path).inc();
|
||||
@@ -185,8 +190,12 @@ process.on('SIGINT', function () {
|
||||
process.exit(2);
|
||||
});
|
||||
|
||||
// world ID management
|
||||
// slaves post here to tell the server they exist
|
||||
/**
|
||||
POST World ID management. Slaves post here to tell the server they exist
|
||||
@memberof clusterioMaster
|
||||
@instance
|
||||
@alias /api/getID
|
||||
*/
|
||||
app.post("/api/getID", function(req,res) {
|
||||
endpointHitCounter.labels(req.route.path).inc();
|
||||
let reqStartTime = Date.now();
|
||||
@@ -210,7 +219,12 @@ app.post("/api/getID", function(req,res) {
|
||||
}
|
||||
httpRequestDurationMilliseconds.labels(req.route.path).observe(Date.now()-reqStartTime);
|
||||
});
|
||||
|
||||
/**
|
||||
POST Allows you to add metadata related to slaves for other tools, like owner data, descriptions or statistics.
|
||||
@memberof clusterioMaster
|
||||
@instance
|
||||
@alias /api/editSlaveMeta
|
||||
*/
|
||||
app.post("/api/editSlaveMeta", function(req,res) {
|
||||
endpointHitCounter.labels(req.route.path).inc();
|
||||
let reqStartTime = Date.now();
|
||||
@@ -254,6 +268,12 @@ app.post("/api/getSlavesMeta", function (req, res) {
|
||||
});
|
||||
// mod management
|
||||
// should handle uploading and checking if mods are uploaded
|
||||
/**
|
||||
POST Check if a mod has been uploaded to the master before. Only checks against filename, not hash.
|
||||
@memberof clusterioMaster
|
||||
@instance
|
||||
@alias /api/checkMod
|
||||
*/
|
||||
app.post("/api/checkMod", function(req,res) {
|
||||
endpointHitCounter.labels(req.route.path).inc();
|
||||
let reqStartTime = Date.now();
|
||||
@@ -273,6 +293,12 @@ app.post("/api/checkMod", function(req,res) {
|
||||
res.end();
|
||||
httpRequestDurationMilliseconds.labels(req.route.path).observe(Date.now()-reqStartTime);
|
||||
});
|
||||
/**
|
||||
POST endpoint for uploading mods to the master server. Required for automatic mod downloads with factorioClusterioClient (electron app, see seperate repo)
|
||||
@memberof clusterioMaster
|
||||
@instance
|
||||
@alias /api/uploadMod
|
||||
*/
|
||||
app.post("/api/uploadMod", function(req,res) {
|
||||
endpointHitCounter.labels(req.route.path).inc();
|
||||
let reqStartTime = Date.now();
|
||||
@@ -292,7 +318,12 @@ app.post("/api/uploadMod", function(req,res) {
|
||||
});
|
||||
}
|
||||
});
|
||||
// endpoint for getting information about all our slaves
|
||||
/**
|
||||
GET endpoint for getting information about all our slaves
|
||||
@memberof clusterioMaster
|
||||
@instance
|
||||
@alias /api/slaves
|
||||
*/
|
||||
app.get("/api/slaves", function(req, res) {
|
||||
endpointHitCounter.labels(req.route.path).inc();
|
||||
let reqStartTime = Date.now();
|
||||
@@ -706,6 +737,43 @@ app.post("/api/getTimelineStats", function(req,res) {
|
||||
}
|
||||
});
|
||||
/**
|
||||
POST endpoint for running commands on slaves.
|
||||
Requires x-access-token header to be set. Find you api token in secret-api-token.txt on the master (after running it once)
|
||||
|
||||
@memberof clusterioMaster
|
||||
@instance
|
||||
@alias api/runCommand
|
||||
@param {object} JSON {instanceID:19412312, command:"/c game.print('hello')"}
|
||||
@returns {object} Status {auth: bool, message: "Informative error", data:{}}
|
||||
*/
|
||||
// write an auth token to file
|
||||
fs.writeFileSync("secret-api-token.txt", jwt.sign({ id: "api" }, config.masterAuthSecret, {
|
||||
expiresIn: 86400*365 // expires in 1 year
|
||||
}));
|
||||
app.post("/api/runCommand", (req,res) => {
|
||||
var token = req.headers['x-access-token'];
|
||||
if(!token) return res.status(401).send({ auth: false, message: 'No token provided.' });
|
||||
|
||||
jwt.verify(token, config.masterAuthSecret, function(err, decoded) {
|
||||
if(err) return res.status(500).send({ auth: false, message: 'Failed to authenticate token.' });
|
||||
|
||||
// validate request.body
|
||||
let body = req.body;
|
||||
if(body.instanceID
|
||||
&& Object.keys(wsSlaves).includes(body.instanceID)
|
||||
&& body.command
|
||||
&& typeof body.command == "string"
|
||||
&& body.command[0] == "/"){
|
||||
// execute command
|
||||
wsSlaves[body.instanceID].runCommand(body.command, data => {
|
||||
res.status(200).send({auth: true, message: "success", data});
|
||||
});
|
||||
} else {
|
||||
res.status(400).send({auth: true, message: "Error: invalid request.body"});
|
||||
}
|
||||
});
|
||||
});
|
||||
/**
|
||||
GET endpoint. Returns factorio's base locale as a JSON object.
|
||||
|
||||
@memberof clusterioMaster
|
||||
@@ -813,6 +881,19 @@ class wsSlave {
|
||||
});
|
||||
}
|
||||
});
|
||||
// handle command return values
|
||||
this.commandsWaitingForReturn = {};
|
||||
this.socket.on("runCommandReturnValue", resp => {
|
||||
if(resp.commandID && resp.body && this.commandsWaitingForReturn[resp.commandID] && this.commandsWaitingForReturn[resp.commandID].callback && typeof this.commandsWaitingForReturn[resp.commandID].callback == "function"){
|
||||
this.commandsWaitingForReturn[resp.commandID].callback(resp.body);
|
||||
delete this.commandsWaitingForReturn[resp.commandID];
|
||||
}
|
||||
});
|
||||
}
|
||||
runCommand(command, callback){
|
||||
let commandID = Math.random().toString();
|
||||
this.socket.emit("runCommand", {command, commandID});
|
||||
this.commandsWaitingForReturn[commandID] = {callback, timestamp: Date.now()};
|
||||
}
|
||||
}
|
||||
io.on('connection', function (socket) {
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
"app-root-path": "^2.0.1",
|
||||
"as-table": "^1.0.31",
|
||||
"averaged-timeseries": "^1.4.0",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"body-parser": "^1.18.2",
|
||||
"codecov": "^2.3.1",
|
||||
"deepmerge": "^1.5.2",
|
||||
@@ -33,6 +34,7 @@
|
||||
"hash-files": "^1.1.1",
|
||||
"html5-validator": "^1.0.7",
|
||||
"ini": "^1.3.5",
|
||||
"jsonwebtoken": "^8.2.1",
|
||||
"leveldown": "^1.9.0",
|
||||
"linvodb3": "^3.25.1",
|
||||
"luamin": "^1.0.4",
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
Config for clusterio remote command plugin.
|
||||
Allows you to execute commands on slaves with a web API.
|
||||
|
||||
Careful, may allow for RCE vulnerabilities.
|
||||
*/
|
||||
var modInfo = require("./info.json");
|
||||
module.exports = {
|
||||
// Name of package. For display somewhere I guess.
|
||||
name: "remoteCommands",
|
||||
version: "1.0.0",
|
||||
binary: "nodePackage",
|
||||
description: "Config for clusterio remote command plugin. Allows you to execute commands on slaves with a web API.",
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
module.exports.main = class remoteCommands {
|
||||
constructor(mergedConfig, messageInterface, extras){
|
||||
console.log = messageInterface;
|
||||
this.messageInterface = messageInterface;
|
||||
this.config = mergedConfig;
|
||||
this.socket = extras.socket;
|
||||
|
||||
if(this.config.allowRemoteCommandExecution){
|
||||
this.socket.on("runCommand", data => {
|
||||
let {commandID} = data;
|
||||
this.messageInterface(data.command);
|
||||
if(commandID){
|
||||
this.socket.emit("runCommandReturnValue", {
|
||||
commandID,
|
||||
body: {
|
||||
info: "Command was sent to factorio",
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user