mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-11 23:04:52 +09:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc2625a7db | ||
|
|
2e756b1649 | ||
|
|
55df1bae8a | ||
|
|
a4ee6f82cb |
No files matched your search
+7
-5
@@ -2,20 +2,22 @@ FROM node:9.11.2
|
||||
RUN apt-get update && apt install git curl tar -y
|
||||
RUN mkdir factorioClusterio
|
||||
|
||||
RUN git clone -b master https://github.com/Danielv123/factorioClusterio.git && cd factorioClusterio && npm install
|
||||
RUN cd factorioClusterio && curl -o factorio.tar.gz -L https://www.factorio.com/get-download/latest/headless/linux64 && tar -xf factorio.tar.gz
|
||||
#RUN git clone -b master https://github.com/Danielv123/factorioClusterio.git && cd factorioClusterio && npm install
|
||||
COPY . /factorioClusterio
|
||||
RUN cd /factorioClusterio && npm install
|
||||
RUN cd /factorioClusterio && curl -o factorio.tar.gz -L https://www.factorio.com/get-download/latest/headless/linux64 && tar -xf factorio.tar.gz && rm factorio.tar.gz
|
||||
|
||||
WORKDIR factorioClusterio
|
||||
WORKDIR /factorioClusterio
|
||||
RUN mkdir instances sharedMods
|
||||
RUN cp config.json.dist config.json
|
||||
|
||||
RUN node client.js download
|
||||
|
||||
LABEL maintainer "Sir3lit@gmail.com"
|
||||
LABEL maintainer "danielv@live.no"
|
||||
|
||||
EXPOSE 8080 34167
|
||||
VOLUME /factorioClusterio/instances
|
||||
VOLUME /factorioClusterio/sharedMods
|
||||
VOLUME /factorioClusterio/sharedPlugins
|
||||
|
||||
CMD RCONPORT="$RCONPORT" FACTORIOPORT="$FACTORIOPORT" MODE="$MODE" node $MODE\.js start $INSTANCE
|
||||
CMD RCONPORT="$RCONPORT" FACTORIOPORT="34167" MODE="$MODE" node $MODE\.js start $INSTANCE
|
||||
@@ -0,0 +1,15 @@
|
||||
import { getItemDatabase } from "./getItemDatabase";
|
||||
import { getSlaveDatabase } from "./getSlaveDatabase";
|
||||
|
||||
export type db = {
|
||||
items: {[name:string]: any},
|
||||
slaves: Slaves,
|
||||
}
|
||||
|
||||
export function getDatabase(config: ClusterioConfig) {
|
||||
let db: db = {
|
||||
items: getItemDatabase(config),
|
||||
slaves: getSlaveDatabase(config),
|
||||
};
|
||||
return db
|
||||
}
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
declare module "getFactorioLocale" {
|
||||
interface callback {
|
||||
(error: Error, factorioLocale?: any): void
|
||||
}
|
||||
export function asObject(factorioDirectory: string, languageCode: string, callback: callback): void
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import * as fs from "fs"
|
||||
import * as path from "path"
|
||||
|
||||
export function getItemDatabase(config: ClusterioConfig): {[name:string]: number | Function} {
|
||||
var items: {[name: string]: number | Function} = {}
|
||||
try{
|
||||
let x = fs.statSync(path.resolve(config.databaseDirectory, "items.json"));
|
||||
console.log(`loading items from ${path.resolve(config.databaseDirectory, "items.json")}`);
|
||||
items = JSON.parse(fs.readFileSync(path.resolve(config.databaseDirectory, "items.json")).toString());
|
||||
} catch (e){
|
||||
items = {};
|
||||
}
|
||||
items.addItem = function addItem(object: item): boolean {
|
||||
if(object.name == "addItem" || object.name == "removeItem") {
|
||||
console.error("Fuck you, that would screw everything up if you named your item that.");
|
||||
return false;
|
||||
} else {
|
||||
if(this[object.name] && Number(this[object.name]) != NaN){
|
||||
this[object.name] = Number(this[object.name]) + Number(object.count);
|
||||
} else {
|
||||
this[object.name] = object.count;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
},
|
||||
items.removeItem = function removeItem(object: item): boolean {
|
||||
if(object.name == "addItem" || object.name == "removeItem") {
|
||||
console.error("Fuck you, that would screw everything up if you named your item that.");
|
||||
return false;
|
||||
} else {
|
||||
if(this[object.name] && Number(this[object.name]) != NaN){
|
||||
this[object.name] = Number(this[object.name]) - Number(object.count);
|
||||
} else {
|
||||
this[object.name] = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return items
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import * as fs from "fs"
|
||||
import * as path from "path"
|
||||
|
||||
export function getSlaveDatabase(config: ClusterioConfig): Slaves {
|
||||
var slaves: Slaves = {};
|
||||
try{
|
||||
let x = fs.statSync(path.resolve(config.databaseDirectory, "/slaves.json"));
|
||||
console.log(`loading slaves from path.resolve(config.databaseDirectory, "slaves.json")`);
|
||||
slaves = JSON.parse(fs.readFileSync(path.resolve(config.databaseDirectory, "slaves.json"), "utf-8"));
|
||||
} catch (e){
|
||||
slaves = {};
|
||||
}
|
||||
return slaves
|
||||
}
|
||||
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
type MasterPluginInstance = {
|
||||
config: object,
|
||||
pluginConfig: object,
|
||||
path: string,
|
||||
socketio: any,
|
||||
express: any,
|
||||
Prometheus: any,
|
||||
onExit?: Function,
|
||||
}
|
||||
type pluginConfig = {
|
||||
name: string,
|
||||
}
|
||||
interface MasterPlugin {
|
||||
main: MasterPluginInstance,
|
||||
pluginConfig: pluginConfig,
|
||||
}
|
||||
+83
-108
@@ -1,3 +1,6 @@
|
||||
import { getDatabase } from "./lib/getDatabase";
|
||||
import { db } from "./lib/getDatabase";
|
||||
|
||||
/**
|
||||
Clusterio master server. Facilitates communication between slaves through
|
||||
a webserver, storing data related to slaves like production graphs and
|
||||
@@ -13,7 +16,9 @@ node master.js
|
||||
// Attempt updating
|
||||
// const updater = require("./updater.js");
|
||||
// updater.update().then(console.log);
|
||||
|
||||
namespace Master {
|
||||
// homebrew types
|
||||
//import {MasterPlugin} from "master"
|
||||
(async ()=>{
|
||||
|
||||
// Set the process title, shows up as the title of the CMD window on windows
|
||||
@@ -24,7 +29,7 @@ process.title = "clusterioMaster";
|
||||
process.on('unhandledRejection', r => console.log(r));
|
||||
|
||||
// configgy stuff
|
||||
debug = false;
|
||||
const debug = false;
|
||||
|
||||
// reduces loadtime from 1700 ms to 1200 ms on my i3 + SSD
|
||||
// tested by logging Date.now() here and when the webserver started listening
|
||||
@@ -63,7 +68,7 @@ const fileOps = require("_app/fileOps");
|
||||
const authenticate = require("./lib/authenticate")(config);
|
||||
|
||||
/** Sync */
|
||||
function randomStringAsBase64Url(size) {
|
||||
function randomStringAsBase64Url(size: Number) {
|
||||
return base64url(crypto.randomBytes(size));
|
||||
}
|
||||
if (!fs.existsSync("secret-api-token.txt")) {
|
||||
@@ -105,11 +110,11 @@ app.set('view engine', 'html');
|
||||
app.engine('html', ejs.renderFile);
|
||||
|
||||
// give ejs access to some interesting information
|
||||
app.use(function(req, res, next){
|
||||
app.use(function(req: Request, res: any, next: any){
|
||||
res.locals.res = res;
|
||||
res.locals.req = req;
|
||||
res.locals.masterPlugins = masterPlugins;
|
||||
res.locals.slaves = slaves;
|
||||
res.locals.slaves = db.slaves;
|
||||
res.locals.moment = moment;
|
||||
next();
|
||||
});
|
||||
@@ -205,15 +210,15 @@ app.get('/metrics', (req, res) => {
|
||||
});
|
||||
|
||||
function registerMoreMetrics(){
|
||||
for(let instanceID in slaves){
|
||||
for(let instanceID in db.slaves){
|
||||
// playercount
|
||||
try{
|
||||
prometheusPlayerCountGauge.labels(instanceID, slaves[instanceID].instanceName).set(Number(slaves[instanceID].playerCount) || 0);
|
||||
prometheusPlayerCountGauge.labels(instanceID, db.slaves[instanceID].instanceName).set(Number(db.slaves[instanceID].playerCount) || 0);
|
||||
}catch(e){}
|
||||
// UPS
|
||||
try{
|
||||
prometheusUPSGauge.labels(instanceID, slaves[instanceID].instanceName).set(Number(slaves[instanceID].meta.UPS) || 60);
|
||||
if(slaves[instanceID].meta.tick && typeof slaves[instanceID].meta.tick === "number") prometheusUPSGauge.labels(instanceID, slaves[instanceID].instanceName).set(Number(slaves[instanceID].meta.tick) || 0);
|
||||
prometheusUPSGauge.labels(instanceID, db.slaves[instanceID].instanceName).set(Number(db.slaves[instanceID].meta.UPS) || 60);
|
||||
if(db.slaves[instanceID].meta.tick && typeof db.slaves[instanceID].meta.tick === "number") prometheusUPSGauge.labels(instanceID, db.slaves[instanceID].instanceName).set(Number(db.slaves[instanceID].meta.tick) || 0);
|
||||
}catch(e){}
|
||||
}
|
||||
// inventory
|
||||
@@ -224,58 +229,15 @@ function registerMoreMetrics(){
|
||||
}
|
||||
// Slave count
|
||||
let numberOfActiveSlaves = 0;
|
||||
for(let instance in slaves){
|
||||
if(Date.now() - Number(slaves[instance].time) < 1000 * 30) numberOfActiveSlaves++;
|
||||
for(let instance in db.slaves){
|
||||
if(Date.now() - Number(db.slaves[instance].time) < 1000 * 30) numberOfActiveSlaves++;
|
||||
}
|
||||
prometheusConnectedInstancesCounter.set(numberOfActiveSlaves);
|
||||
}
|
||||
|
||||
// set up database
|
||||
const db = {};
|
||||
const db: db = getDatabase(config);
|
||||
|
||||
(function(){
|
||||
try{
|
||||
let x = fs.statSync(path.resolve(config.databaseDirectory, "/slaves.json"));
|
||||
console.log(`loading slaves from path.resolve(config.databaseDirectory, "slaves.json")`);
|
||||
slaves = JSON.parse(fs.readFileSync(path.resolve(config.databaseDirectory, "slaves.json")));
|
||||
} catch (e){
|
||||
slaves = {};
|
||||
}
|
||||
try{
|
||||
x = fs.statSync(path.resolve(config.databaseDirectory, "items.json"));
|
||||
console.log(`loading items from ${path.resolve(config.databaseDirectory, "items.json")}`);
|
||||
db.items = JSON.parse(fs.readFileSync(path.resolve(config.databaseDirectory, "items.json")));
|
||||
} catch (e){
|
||||
db.items = {};
|
||||
}
|
||||
})()
|
||||
|
||||
db.items.addItem = function(object) {
|
||||
if(object.name == "addItem" || object.name == "removeItem") {
|
||||
console.error("Fuck you, that would screw everything up if you named your item that.");
|
||||
return false;
|
||||
} else {
|
||||
if(this[object.name] && Number(this[object.name]) != NaN){
|
||||
this[object.name] = Number(this[object.name]) + Number(object.count);
|
||||
} else {
|
||||
this[object.name] = object.count;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
db.items.removeItem = function(object) {
|
||||
if(object.name == "addItem" || object.name == "removeItem") {
|
||||
console.error("Fuck you, that would screw everything up if you named your item that.");
|
||||
return false;
|
||||
} else {
|
||||
if(this[object.name] && Number(this[object.name]) != NaN){
|
||||
this[object.name] = Number(this[object.name]) - Number(object.count);
|
||||
} else {
|
||||
this[object.name] = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// store slaves and inventory in a .json full of JSON data
|
||||
process.on('SIGINT', shutdown); // ctrl + c
|
||||
@@ -284,17 +246,17 @@ 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
|
||||
if(slaves && Object.keys(slaves).length < 2000000){
|
||||
if(db.slaves && Object.keys(db.slaves).length < 2000000){
|
||||
console.log("saving to slaves.json");
|
||||
fs.writeFileSync(path.resolve(config.databaseDirectory, "slaves.json"), JSON.stringify(slaves));
|
||||
} else if(slaves) {
|
||||
console.log("Slave database too large, not saving ("+Object.keys(slaves).length+")");
|
||||
fs.writeFileSync(path.resolve(config.databaseDirectory, "slaves.json"), JSON.stringify(db.slaves));
|
||||
} else if(db.slaves) {
|
||||
console.log("Slave database too large, not saving ("+Object.keys(db.slaves).length+")");
|
||||
}
|
||||
if(db.items && Object.keys(db.items).length < 50000){
|
||||
console.log("saving to items.json");
|
||||
fs.writeFileSync(path.resolve(config.databaseDirectory, "items.json"), JSON.stringify(db.items));
|
||||
} else if(slaves) {
|
||||
console.log("Item database too large, not saving ("+Object.keys(slaves).length+")");
|
||||
} else if(db.slaves) {
|
||||
console.log("Item database too large, not saving ("+Object.keys(db.slaves).length+")");
|
||||
}
|
||||
for(let i in masterPlugins){
|
||||
let plugin = masterPlugins[i];
|
||||
@@ -314,7 +276,7 @@ POST World ID management. Slaves post here to tell the server they exist
|
||||
@instance
|
||||
@alias /api/getID
|
||||
*/
|
||||
app.post("/api/getID", authenticate.middleware, function(req,res) {
|
||||
app.post("/api/getID", authenticate.middleware, function(req, res) {
|
||||
endpointHitCounter.labels(req.route.path).inc();
|
||||
// request.body should be an object
|
||||
// {rconPort, rconPassword, serverPort, mac, time}
|
||||
@@ -324,12 +286,12 @@ app.post("/api/getID", authenticate.middleware, function(req,res) {
|
||||
if(debug){
|
||||
console.log(req.body)
|
||||
}
|
||||
if(!slaves[req.body.unique]){
|
||||
slaves[req.body.unique] = {};
|
||||
if(!db.slaves[req.body.unique]){
|
||||
db.slaves[req.body.unique] = {};
|
||||
}
|
||||
for(k in req.body){
|
||||
for(let k in req.body){
|
||||
if(k != "meta" && req.body.hasOwnProperty(k)){
|
||||
slaves[req.body.unique][k] = req.body[k];
|
||||
db.slaves[req.body.unique][k] = req.body[k];
|
||||
}
|
||||
}
|
||||
// console.log("Slave registered: " + slaves[req.body.unique].mac + " : " + slaves[req.body.unique].serverPort+" at " + slaves[req.body.unique].publicIP + " with name " + slaves[req.body.unique].instanceName);
|
||||
@@ -348,11 +310,11 @@ app.post("/api/editSlaveMeta", authenticate.middleware, function(req,res) {
|
||||
|
||||
if(req.body && req.body.instanceID && req.body.password && req.body.meta){
|
||||
// check for editing permissions
|
||||
if(slaves[req.body.instanceID] && slaves[req.body.instanceID].rconPassword == req.body.password){
|
||||
if(!slaves[req.body.instanceID].meta){
|
||||
slaves[req.body.instanceID].meta = {};
|
||||
if(db.slaves[req.body.instanceID] && db.slaves[req.body.instanceID].rconPassword == req.body.password){
|
||||
if(!db.slaves[req.body.instanceID].meta){
|
||||
db.slaves[req.body.instanceID].meta = {};
|
||||
}
|
||||
slaves[req.body.instanceID].meta = deepmerge(slaves[req.body.instanceID].meta, req.body.meta, {clone:true});
|
||||
db.slaves[req.body.instanceID].meta = deepmerge(db.slaves[req.body.instanceID].meta, req.body.meta, {clone:true});
|
||||
let metaPortion = JSON.stringify(req.body.meta);
|
||||
if(metaPortion.length > 50) metaPortion = metaPortion.substring(0,20) + "...";
|
||||
// console.log("Updating slave "+slaves[req.body.instanceID].instanceName+": " + slaves[req.body.instanceID].mac + " : " + slaves[req.body.instanceID].serverPort+" at " + slaves[req.body.instanceID].publicIP, metaPortion);
|
||||
@@ -373,7 +335,7 @@ app.post("/api/getSlaveMeta", function (req, res) {
|
||||
console.log("body", req.body);
|
||||
if(req.body && req.body.instanceID && req.body.password){
|
||||
console.log("returning meta for ", req.body.instanceID);
|
||||
res.send(JSON.stringify(slaves[req.body.instanceID].meta))
|
||||
res.send(JSON.stringify(db.slaves[req.body.instanceID].meta))
|
||||
} else {
|
||||
res.status(400);
|
||||
res.send('{"INVALID REQUEST":1}');
|
||||
@@ -389,9 +351,9 @@ POST Check if a mod has been uploaded to the master before. Only checks against
|
||||
*/
|
||||
app.post("/api/checkMod", function(req,res) {
|
||||
endpointHitCounter.labels(req.route.path).inc();
|
||||
let files = fs.readdirSync(masterModFolder);
|
||||
let files: string[] = fs.readdirSync(masterModFolder);
|
||||
let found = false;
|
||||
files.forEach(file => {
|
||||
files.forEach((file) => {
|
||||
if(file == req.body.modName) {
|
||||
found = true;
|
||||
}
|
||||
@@ -414,7 +376,7 @@ app.post("/api/uploadMod", authenticate.middleware, function(req,res) {
|
||||
endpointHitCounter.labels(req.route.path).inc();
|
||||
if (req.files && req.files.file) {
|
||||
// console.log(req.files.file);
|
||||
req.files.file.mv(path.resolve(config.databaseDirectory, "masterMods", req.files.file.name), function(err) {
|
||||
req.files.file.mv(path.resolve(config.databaseDirectory, "masterMods", req.files.file.name), function(err: Error) {
|
||||
if (err) {
|
||||
res.status(500).send(err);
|
||||
} else {
|
||||
@@ -433,7 +395,7 @@ GET endpoint for getting information about all our slaves
|
||||
@instance
|
||||
@alias /api/slaves
|
||||
*/
|
||||
let slaveCache = {
|
||||
let slaveCache: {timestamp:number, cache?:Slaves} = {
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
app.get("/api/slaves", function(req, res) {
|
||||
@@ -441,9 +403,9 @@ app.get("/api/slaves", function(req, res) {
|
||||
res.header("Access-Control-Allow-Origin", "*");
|
||||
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
|
||||
if(!slaveCache.cache || Date.now() - slaveCache.timestamp > 5000){
|
||||
let copyOfSlaves = JSON.parse(JSON.stringify(slaves));
|
||||
let copyOfSlaves: Slaves = JSON.parse(JSON.stringify(db.slaves));
|
||||
// filter out the rcon password because thats kindof not a safe thing to share
|
||||
for(key in copyOfSlaves){
|
||||
for(let key in copyOfSlaves){
|
||||
copyOfSlaves[key].rconPassword = "hidden";
|
||||
}
|
||||
slaveCache.cache = copyOfSlaves;
|
||||
@@ -452,8 +414,8 @@ app.get("/api/slaves", function(req, res) {
|
||||
|
||||
res.send(slaveCache.cache);
|
||||
});
|
||||
var recievedItemStatisticsBySlaveID = {};
|
||||
var sentItemStatisticsBySlaveID = {};
|
||||
var recievedItemStatisticsBySlaveID: any = {};
|
||||
var sentItemStatisticsBySlaveID: any = {};
|
||||
/*
|
||||
var recievedItemStatistics = new averagedTimeSeries({
|
||||
maxEntries: config.itemStats.maxEntries,
|
||||
@@ -475,7 +437,7 @@ app.post("/api/place", authenticate.middleware, function(req, res) {
|
||||
endpointHitCounter.labels(req.route.path).inc();
|
||||
res.header("Access-Control-Allow-Origin", "*");
|
||||
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
|
||||
let x;
|
||||
let x: item;
|
||||
try {
|
||||
x = JSON.parse(req.body);
|
||||
} catch (e) {
|
||||
@@ -486,8 +448,8 @@ app.post("/api/place", authenticate.middleware, function(req, res) {
|
||||
}
|
||||
if(!x.instanceID) {
|
||||
x.instanceID = "unknown";
|
||||
Object.keys(slaves).forEach(instanceID => {
|
||||
if(slaves[instanceID].instanceName == req.body.instanceName) {
|
||||
Object.keys(db.slaves).forEach(instanceID => {
|
||||
if(db.slaves[instanceID].instanceName == req.body.instanceName) {
|
||||
x.instanceID = instanceID;
|
||||
}
|
||||
});
|
||||
@@ -539,7 +501,7 @@ POST endpoint to remove items from DB when client orders items.
|
||||
@param {string} [itemStack.instanceName="unknown"] the name of an instance for identification in statistics, as provided when launching it. ex node client.js start [name]
|
||||
@returns {itemStack} the number of items actually removed, may be lower than what was asked for due to shortages.
|
||||
*/
|
||||
_doleDivisionFactor = {}; //If the server regularly can't fulfill requests, this number grows until it can. Then it slowly shrinks back down.
|
||||
var _doleDivisionFactor: {[name:string]: number} = {}; //If the server regularly can't fulfill requests, this number grows until it can. Then it slowly shrinks back down.
|
||||
app.post("/api/remove", authenticate.middleware, function(req, res) {
|
||||
endpointHitCounter.labels(req.route.path).inc();
|
||||
const doleDivisionRetardation = 10; //lower rates will equal more dramatic swings
|
||||
@@ -554,7 +516,7 @@ app.post("/api/remove", authenticate.middleware, function(req, res) {
|
||||
if(!object.instanceName) {
|
||||
object.instanceName = "unknown";
|
||||
}
|
||||
if(slaves[object.instanceID]) object.instanceName = slaves[object.instanceID].instanceName;
|
||||
if(db.slaves[object.instanceID]) object.instanceName = db.slaves[object.instanceID].instanceName;
|
||||
let item = db.items[object.name]
|
||||
// console.dir(doc);
|
||||
if(!item
|
||||
@@ -672,18 +634,18 @@ app.get("/api/inventoryAsObject", function(req, res) {
|
||||
|
||||
@memberof clusterioMaster
|
||||
@instance
|
||||
@alias api/modData
|
||||
@alias api/modmeta
|
||||
@returns {object} JSON
|
||||
*/
|
||||
|
||||
// wrap a request in an promise
|
||||
async function downloadPage(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
request(url, (error, response, body) => {
|
||||
resolve(body);
|
||||
});
|
||||
});
|
||||
}
|
||||
async function downloadPage(url: string): Promise<object> {
|
||||
return new Promise((resolve, reject) => {
|
||||
request(url, (_error: any, _response: any, body: object) => {
|
||||
resolve(body);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
app.get("/api/modmeta", async function(req, res) {
|
||||
endpointHitCounter.labels(req.route.path).inc();
|
||||
@@ -709,7 +671,7 @@ 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) {
|
||||
jwt.verify(token, config.masterAuthSecret, function(err: Error) {
|
||||
if(err) return res.status(500).send({ auth: false, message: 'Failed to authenticate token.' });
|
||||
|
||||
// validate request.body
|
||||
@@ -736,7 +698,7 @@ app.post("/api/runCommand", (req,res) => {
|
||||
&& body.command[0] == "/")
|
||||
{
|
||||
// execute command
|
||||
wsSlaves[body.instanceID].runCommand(body.command, data => {
|
||||
wsSlaves[body.instanceID].runCommand(body.command, (data: string) => {
|
||||
res.status(200).send({auth: true, message: "success", data});
|
||||
});
|
||||
} else {
|
||||
@@ -754,7 +716,7 @@ GET endpoint. Returns factorio's base locale as a JSON object.
|
||||
*/
|
||||
app.get("/api/getFactorioLocale", function(req,res){
|
||||
endpointHitCounter.labels(req.route.path).inc();
|
||||
getFactorioLocale.asObject(config.factorioDirectory, "en", (err, factorioLocale) => {
|
||||
getFactorioLocale.asObject(config.factorioDirectory, "en", (_err, factorioLocale) => {
|
||||
res.send(factorioLocale);
|
||||
});
|
||||
});
|
||||
@@ -763,7 +725,7 @@ if(args.sslPort || config.sslPort){
|
||||
var certificate = fs.readFileSync(path.resolve(config.databaseDirectory, 'certificates/cert.crt'));
|
||||
var privateKey = fs.readFileSync(path.resolve(config.databaseDirectory, 'certificates/cert.key'));
|
||||
|
||||
httpsServer = require("https").createServer({
|
||||
var httpsServer = require("https").createServer({
|
||||
key: privateKey,
|
||||
cert: certificate
|
||||
}, app).listen(args.sslPort || config.sslPort);
|
||||
@@ -772,7 +734,7 @@ if(args.sslPort || config.sslPort){
|
||||
}
|
||||
}
|
||||
if(args.masterPort || config.masterPort){
|
||||
server = require("http").Server(app);
|
||||
var server = require("http").Server(app);
|
||||
server.listen(args.masterPort || config.masterPort || 8080, function () {
|
||||
console.log("Listening on port %s...", server.address().port);
|
||||
});
|
||||
@@ -785,6 +747,9 @@ const ioMetrics = require("socket.io-prometheus");
|
||||
ioMetrics(io);
|
||||
var slaveMappers = {};
|
||||
class slaveMapper {
|
||||
instanceID: string
|
||||
socket: any
|
||||
lastBeat: number
|
||||
constructor(instanceID, socket) {
|
||||
this.instanceID = instanceID;
|
||||
this.socket = socket;
|
||||
@@ -815,6 +780,10 @@ class slaveMapper {
|
||||
}
|
||||
var mapRequesters = {};
|
||||
class mapRequester {
|
||||
requesterID: string
|
||||
socket: any
|
||||
instanceID: string
|
||||
lastBeat: number
|
||||
constructor(requesterID, socket, instanceID){
|
||||
this.requesterID = requesterID;
|
||||
this.socket = socket;
|
||||
@@ -854,7 +823,13 @@ class mapRequester {
|
||||
}
|
||||
/* Websockets for send and recieve combinators */
|
||||
var wsSlaves = {};
|
||||
var wsChatRecievers = [];
|
||||
var wsAlertRecievers = [];
|
||||
class wsSlave {
|
||||
instanceID: string
|
||||
socket: any
|
||||
lastBeat: number
|
||||
commandsWaitingForReturn: object
|
||||
constructor(instanceID, socket){
|
||||
this.instanceID = instanceID;
|
||||
this.socket = socket;
|
||||
@@ -886,8 +861,8 @@ class wsSlave {
|
||||
prometheusWsUsageCounter.labels('gameChat', this.instanceID).inc();
|
||||
if(typeof data === "object"){
|
||||
data.timestamp = Date.now();
|
||||
if(!global.wsChatRecievers) global.wsChatRecievers = [];
|
||||
global.wsChatRecievers.forEach(socket => {
|
||||
if(!wsChatRecievers) wsChatRecievers = [];
|
||||
wsChatRecievers.forEach(socket => {
|
||||
socket.emit("gameChat", data);
|
||||
});
|
||||
}
|
||||
@@ -897,8 +872,8 @@ class wsSlave {
|
||||
prometheusWsUsageCounter.labels('alert', this.instanceID).inc();
|
||||
if(typeof alert === "object"){
|
||||
alert.timestamp = Date.now();
|
||||
if(!global.wsAlertRecievers) global.wsAlertRecievers = [];
|
||||
global.wsAlertRecievers.forEach(socket => {
|
||||
if(!wsAlertRecievers) wsAlertRecievers = [];
|
||||
wsAlertRecievers.forEach(socket => {
|
||||
socket.emit("alert", alert);
|
||||
});
|
||||
}
|
||||
@@ -953,27 +928,26 @@ io.on('connection', function (socket) {
|
||||
});
|
||||
socket.on("registerChatReciever", function(data){
|
||||
prometheusWsUsageCounter.labels("registerChatReciever", "other").inc();
|
||||
if(!global.wsChatRecievers) global.wsChatRecievers = [];
|
||||
global.wsChatRecievers.push(socket);
|
||||
if(!wsChatRecievers) wsChatRecievers = [];
|
||||
wsChatRecievers.push(socket);
|
||||
});
|
||||
socket.on("registerAlertReciever", function(data){
|
||||
prometheusWsUsageCounter.labels("registerAlertReciever", "other").inc();
|
||||
if(!global.wsAlertRecievers) global.wsAlertRecievers = [];
|
||||
global.wsAlertRecievers.push(socket);
|
||||
if(!wsAlertRecievers) wsAlertRecievers = [];
|
||||
wsAlertRecievers.push(socket);
|
||||
});
|
||||
});
|
||||
|
||||
// handle plugins on the master
|
||||
pluginManagement();
|
||||
masterPlugins = [];
|
||||
var masterPlugins: MasterPlugin[] = [];
|
||||
async function pluginManagement(){
|
||||
let startPluginLoad = Date.now();
|
||||
masterPlugins = await getPlugins();
|
||||
console.log("All plugins loaded in "+(Date.now() - startPluginLoad)+"ms");
|
||||
return;
|
||||
}
|
||||
|
||||
async function getPlugins(){
|
||||
async function getPlugins(): Promise<MasterPlugin[]>{
|
||||
const pluginManager = require("./lib/manager/pluginManager.js")(config);
|
||||
let plugins = [];
|
||||
let pluginsToLoad = await pluginManager.getPlugins();
|
||||
@@ -1008,3 +982,4 @@ async function getPlugins(){
|
||||
}
|
||||
|
||||
})();
|
||||
}
|
||||
@@ -5,6 +5,10 @@
|
||||
"repository": "https://github.com/Danielv123/factorioClusterio",
|
||||
"main": "master.js",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"watch-ts": "tsc -w",
|
||||
"watch-node": "nodemon dist/master.js",
|
||||
"watch": "concurrently -k -p \"[{name}]\" -n \"TypeScript, Node\" -c \"yello.bold, cyan.bold\" \"npm run watch-ts\" \"npm run watch-node\"",
|
||||
"test": "./node_modules/.bin/istanbul cover ./node_modules/mocha/bin/_mocha -- \"./{{,node_modules/_app/**/},!(node_modules)/**/}*.spec.js\" -R spec || echo .",
|
||||
"start": "echo Run node client.js start [instance] to start slaves && node master.js",
|
||||
"cover": "./node_modules/.bin/istanbul cover ./node_modules/mocha/bin/_mocha -- \"./{{,node_modules/_app/**/},!(node_modules)/**/}*.spec.js\" -R spec || echo . && codecov -t f7b38085-36eb-4d72-84e6-64721910171e",
|
||||
@@ -19,6 +23,17 @@
|
||||
"author": "Danielv123",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@types/bcrypt": "^3.0.0",
|
||||
"@types/body-parser": "^1.17.0",
|
||||
"@types/deepmerge": "^2.2.0",
|
||||
"@types/ejs": "^2.6.3",
|
||||
"@types/express": "^4.16.1",
|
||||
"@types/fs-extra": "^5.0.5",
|
||||
"@types/mkdirp": "^0.5.2",
|
||||
"@types/moment": "^2.13.0",
|
||||
"@types/needle": "^2.0.4",
|
||||
"@types/request": "^2.48.1",
|
||||
"@types/socket.io": "^2.1.2",
|
||||
"app-root-path": "^2.0.1",
|
||||
"as-table": "^1.0.31",
|
||||
"averaged-timeseries": "^1.4.0",
|
||||
@@ -65,6 +80,7 @@
|
||||
"socket.io-client": "^2.0.4",
|
||||
"socket.io-prometheus": "^0.2.1",
|
||||
"sync-request": "^4.0.1",
|
||||
"typescript-require": "^0.2.10",
|
||||
"underscore": "^1.8.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
var mkdirp = require("mkdirp-promise");
|
||||
var mkdirpSync = require("mkdirp").sync;
|
||||
var fs = require("fs");
|
||||
var events = require("events");
|
||||
var path = require("path");
|
||||
|
||||
var objectOps = require("./../../lib/objectOps");
|
||||
|
||||
var LinvoDB = require("linvodb3");
|
||||
// LinvoDB.dbPath = "./database/linvodb/";
|
||||
|
||||
|
||||
let allowLogging = true;
|
||||
let log = string => (allowLogging)? console.log(string):""
|
||||
|
||||
module.exports = class chunkStore {
|
||||
constructor(name, chunkSize = 32, path = "./chunkStore/"){
|
||||
if(!name || (typeof name != "string" && typeof name != "number")) throw new Error("chunkstore requires a name string as first param");
|
||||
name = name.toString();
|
||||
this.name = name;
|
||||
this.chunkSize = chunkSize;
|
||||
this.path = (path[path.length] == "/") ? path : path+"/";
|
||||
mkdirpSync(path+name);
|
||||
LinvoDB.dbPath = this.path;
|
||||
|
||||
this.db = new LinvoDB(name, {}, {});
|
||||
|
||||
this.eventEmitter = new events.EventEmitter();
|
||||
}
|
||||
setEntity(x/**{number}*/, y/**{number}*/, newEntity/**{object}*/){
|
||||
return new Promise((resolve, reject) => {
|
||||
x = Math.floor(x);
|
||||
y = Math.floor(y);
|
||||
this.db.find({x, y}, (err, docs) => {
|
||||
if(typeof newEntity == "object"){
|
||||
if(err) reject(err);
|
||||
let entityDoc = {x, y, entity: newEntity};
|
||||
if(docs && docs[0] && docs[0]._id){
|
||||
entityDoc._id = docs[0]._id;
|
||||
}
|
||||
this.db.save(entityDoc, (err,docs) => {
|
||||
if(err) reject(err);
|
||||
resolve(docs);
|
||||
this.eventEmitter.emit("change", docs)
|
||||
});
|
||||
} else {
|
||||
docs.forEach(doc => {
|
||||
doc.remove(()=>{
|
||||
// removed :)
|
||||
});
|
||||
});
|
||||
resolve();
|
||||
this.eventEmitter.emit("change", {x,y});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
getEntity(x, y){
|
||||
return new Promise((resolve, reject) => {
|
||||
this.db.find({x:Math.floor(x), y:Math.floor(y)}, (err, docs) => {
|
||||
if(err) reject(err);
|
||||
resolve(docs);
|
||||
});
|
||||
});
|
||||
}
|
||||
onEntityChange(eventhandler/**function*/){
|
||||
if(typeof eventhandler == "function") {
|
||||
this.eventEmitter.on("change", eventhandler);
|
||||
}
|
||||
}
|
||||
getChunk(x, y){
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
/*
|
||||
// make new chunk in case we can't find an existing one
|
||||
let chunk = {
|
||||
dataObject: {},
|
||||
position:{x,y}, // fancy ES6 feature that generates to {x:x, y:y}
|
||||
chunkSize:this.chunkSize
|
||||
};
|
||||
fs.readFile(this.path+this.name+"/"+x+"/"+y, (err, chunkFile) => {
|
||||
|
||||
if(err){
|
||||
log(err)
|
||||
resolve(chunk);
|
||||
} else {
|
||||
if(objectOps.isJSON(chunkFile)) {
|
||||
log("returned from file")
|
||||
let chunk = JSON.parse(chunkFile);
|
||||
resolve(chunk);
|
||||
} else {
|
||||
log("file failed")
|
||||
resolve(chunk);
|
||||
}
|
||||
}
|
||||
}); // add x and y values as well and we are done?
|
||||
*/
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
var assert = require("assert");
|
||||
|
||||
var chunkStore = require("./chunkStore.js");
|
||||
|
||||
describe("remoteMap/chunkStore.js", () => {
|
||||
// define some values for our test
|
||||
const inserterX = 14.5; // rounds to 15
|
||||
const inserterY = -17.5; // rounds to -17, +64 would be 47
|
||||
const inserterName = "inserter";
|
||||
|
||||
let store // make our store elevated
|
||||
it("is a constructor", ()=>{
|
||||
assert.equal(typeof chunkStore, "function");
|
||||
// create a new store and elevate it
|
||||
store = new chunkStore("910239"/* instanceID, use defaults for the rest */, undefined, "./database/unitTests/chunkStore/");
|
||||
assert.equal(store.name, "910239");
|
||||
});
|
||||
it("allows you to store the locations of entities", done => {
|
||||
store.setEntity(inserterX, inserterY, {name:inserterName, otherData:"can be arbitrary"}).then(() => {
|
||||
done();
|
||||
// nothing to do here really
|
||||
});
|
||||
});
|
||||
it("returns entities at positions you query for", done => {
|
||||
setTimeout(()=>{
|
||||
store.getEntity(inserterX, inserterY).then(entities => {
|
||||
entity = entities[0]
|
||||
// console.log(entity)
|
||||
assert.equal(entity.x, 14);
|
||||
assert.equal(entity.y, -18);
|
||||
assert.equal(entity.entity.name, "inserter");
|
||||
assert.equal(entity.entity.otherData, "can be arbitrary");
|
||||
done();
|
||||
});
|
||||
},300);
|
||||
});
|
||||
it("deletes entities when not provided with an object as 3rd arg", done => {
|
||||
store.setEntity(inserterX, inserterY).then(entity => {
|
||||
assert(entity === undefined);
|
||||
setTimeout(()=>{
|
||||
store.getEntity(inserterX, inserterY).then(entities => {
|
||||
// console.log(entities)
|
||||
assert.equal(entities.length, 0, "Why are we getting results, it should be empty?");
|
||||
done();
|
||||
});
|
||||
},300);
|
||||
});
|
||||
});
|
||||
describe("validates input by throwing when it is invalid", ()=>{
|
||||
it("throws if called without a string as the first parameter", ()=>{
|
||||
assert.throws(()=>{
|
||||
let throwingChunkStore = new chunkStore(undefined, undefined, "asdasda");
|
||||
});
|
||||
assert.throws(()=>{
|
||||
let throwingChunkStore = new chunkStore({}, undefined, "asdasdas");
|
||||
});
|
||||
});
|
||||
it("converts numbers to strings automatically if they are provided as the first parameter", ()=>{
|
||||
assert.ok(()=>{
|
||||
let nonThrowingChunkStore = new chunkStore(42, undefined, "./database/unitTests/chunkStore/");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"esModuleInterop": false,
|
||||
"target": "es6",
|
||||
"noImplicitAny": false,
|
||||
"moduleResolution": "node",
|
||||
"sourceMap": true,
|
||||
"outDir": "dist",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"*": [
|
||||
"node_modules/",
|
||||
"src/types/*",
|
||||
"master.d.ts",
|
||||
"types/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"**/*"
|
||||
]
|
||||
}
|
||||
Vendored
+39
@@ -0,0 +1,39 @@
|
||||
type ClusterioConfig = {
|
||||
factorioDirectory: string,
|
||||
instanceDirectory: string,
|
||||
databaseDirectory: string,
|
||||
publicIP: string,
|
||||
masterIP: string,
|
||||
masterPort: number,
|
||||
sslPort: number,
|
||||
sslCert: string,
|
||||
sslPrivKey: string,
|
||||
masterAuthToken: string,
|
||||
masterAuthSecret: string,
|
||||
username: string,
|
||||
token: string,
|
||||
factorio_version: string,
|
||||
verify_user_identity: boolean,
|
||||
auto_pause: boolean,
|
||||
allow_commands: "admins-only" | "true" | "false",
|
||||
game_password: string,
|
||||
description: string,
|
||||
visibility: {
|
||||
public: boolean,
|
||||
lan: boolean
|
||||
},
|
||||
itemStats: {
|
||||
maxEntries: number,
|
||||
entriesPerSecond: number
|
||||
},
|
||||
logItemTransfers: boolean,
|
||||
disableFairItemDistribution: boolean,
|
||||
uploadModsToMaster: boolean,
|
||||
msBetweenCommands: 10,
|
||||
allowRemoteCommandExecution: boolean,
|
||||
enableCrossServerShout: boolean,
|
||||
mirrorAllChat: boolean,
|
||||
autosaveInterval: 600000,
|
||||
disablePrometheusPushgateway: boolean,
|
||||
disableImportsOfEverythingExceptElectricity: boolean
|
||||
}
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
type Mod = {
|
||||
modName: string,
|
||||
hash: string,
|
||||
}
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
interface Request {
|
||||
route?: any,
|
||||
body?: requestBody,
|
||||
}
|
||||
interface requestBody extends Body {
|
||||
unique: string,
|
||||
|
||||
}
|
||||
|
||||
Vendored
+15
@@ -0,0 +1,15 @@
|
||||
interface Slave {
|
||||
time?: string,
|
||||
rconPort?: string,
|
||||
rconPassword?: string,
|
||||
serverPort?: string,
|
||||
unique?: string, // this one isn't really optional, but I got tired of fighting with types and rewriting logic in master.ts
|
||||
publicIP?: string,
|
||||
mods?: Mod[],
|
||||
instanceName?: string,
|
||||
playerCount?: string | number,
|
||||
mac?: string,
|
||||
meta?: {
|
||||
[name:string]:any,
|
||||
},
|
||||
}
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
type Slaves = {
|
||||
[name: string]: Slave,
|
||||
}
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
type item = {
|
||||
name: string,
|
||||
count: number,
|
||||
instanceName?: string,
|
||||
instanceID?: string,
|
||||
}
|
||||
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
type MasterPluginInstance = {
|
||||
config: object,
|
||||
pluginConfig: object,
|
||||
path: string,
|
||||
socketio: any,
|
||||
express: any,
|
||||
Prometheus: any,
|
||||
onExit?: Function,
|
||||
}
|
||||
type pluginConfig = {
|
||||
name: string,
|
||||
}
|
||||
interface MasterPlugin {
|
||||
main: MasterPluginInstance,
|
||||
pluginConfig: pluginConfig,
|
||||
}
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
interface slaveMapper {
|
||||
instanceID: string,
|
||||
}
|
||||
Reference in new issue
Block a user