mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-14 15:24:52 +09:00
remoteCommands added plugin and master API endpoint for executing things
This commit is contained in:
1 parent
be49deb170
commit
43c1f2ae93
7 files changed
+108
-8
No files matched your search
+3
-1
@@ -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
|
||||
@@ -44,4 +45,5 @@ static/external
|
||||
sharedPlugins/*
|
||||
!sharedPlugins/inventoryImports/
|
||||
!sharedPlugins/UPSdisplay/
|
||||
!sharedPlugins/remoteMap/
|
||||
!sharedPlugins/remoteMap/
|
||||
!sharedPlugins/remoteCommands/
|
||||
@@ -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());
|
||||
@@ -685,6 +687,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"}
|
||||
*/
|
||||
// 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
|
||||
@@ -792,6 +831,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