mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-12 23:04:52 +09:00
Assign game port from host configured port ranges
Add host.factorio_port_range config to hosts that specify a pool of ports that gets automatically assigned to instances which do not have a game_port set on them. This allows setting up a port range that's forwarded to the host on the router and then configure that port range on the host and have instances automatically get assigned an available port when they start up. Resolves: #448
This commit is contained in:
1 parent
9742eb1645
commit
8733575255
5 files changed
+76
-6
No files matched your search
@@ -288,6 +288,7 @@ module.exports = {
|
||||
":": "before",
|
||||
"||": "before",
|
||||
"&&": "before",
|
||||
"??": "before",
|
||||
},
|
||||
}],
|
||||
"padded-blocks": "off",
|
||||
|
||||
@@ -599,7 +599,9 @@ export default class Host extends lib.Link {
|
||||
let instanceConnection = this.instanceConnections.get(instanceId);
|
||||
this.send(
|
||||
new lib.InstanceStatusChangedEvent(
|
||||
instanceId, instanceConnection ? instanceConnection.instance.status : "stopped", undefined
|
||||
instanceId,
|
||||
instanceConnection ? instanceConnection.instance.status : "stopped",
|
||||
instanceConnection ? instanceConnection.instance.server.gamePort : this.gamePort(instanceId),
|
||||
)
|
||||
);
|
||||
|
||||
@@ -607,6 +609,10 @@ export default class Host extends lib.Link {
|
||||
await this.sendSaveListUpdate(instanceId, path.join(instanceInfo.path, "saves"));
|
||||
|
||||
// save a copy of the instance config
|
||||
await this.saveInstanceConfig(instanceInfo);
|
||||
}
|
||||
|
||||
async saveInstanceConfig(instanceInfo: { path: string, config: lib.InstanceConfig }) {
|
||||
let warnedOutput = {
|
||||
_warning: "Changes to this file will be overwritten by the controller's copy.",
|
||||
...instanceInfo.config.toJSON(),
|
||||
@@ -647,6 +653,50 @@ export default class Host extends lib.Link {
|
||||
return instanceInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieved assigned port of the given instance
|
||||
* @param instanceId - ID of the instance to get the assigned game port of:
|
||||
* @returns Assigned game port or undefined if it does not exist.
|
||||
*/
|
||||
gamePort(instanceId: number) {
|
||||
const instance = this.discoveredInstanceInfos.get(instanceId);
|
||||
if (!instance) {
|
||||
return undefined;
|
||||
}
|
||||
return (
|
||||
instance.config.get("factorio.game_port")
|
||||
?? instance.config.get("factorio.host_assigned_game_port")
|
||||
?? undefined
|
||||
);
|
||||
}
|
||||
|
||||
assignGamePort(instanceId: number) {
|
||||
const availablePorts = lib.parseRanges(this.config.get("host.factorio_port_range"), 1, 2**16 - 1);
|
||||
for (const [id, instance] of this.instanceInfos) {
|
||||
if (id === instanceId) {
|
||||
continue;
|
||||
}
|
||||
const port = (
|
||||
instance.config.get("factorio.game_port")
|
||||
?? instance.config.get("factorio.host_assigned_game_port")
|
||||
);
|
||||
if (port !== null) {
|
||||
availablePorts.delete(port);
|
||||
}
|
||||
}
|
||||
const instanceToAssign = this.instanceInfos.get(instanceId)!;
|
||||
const assignedPort = instanceToAssign.config.get("factorio.host_assigned_game_port");
|
||||
if (assignedPort !== null && availablePorts.has(assignedPort)) {
|
||||
return assignedPort;
|
||||
}
|
||||
if (!availablePorts.size) {
|
||||
throw new lib.RequestError("No available port left to assign from host.factorio_port_range");
|
||||
}
|
||||
const [newPort] = availablePorts;
|
||||
instanceToAssign.config.set("factorio.host_assigned_game_port", newPort);
|
||||
return newPort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize and connect an unloaded instance
|
||||
*
|
||||
@@ -923,7 +973,7 @@ export default class Host extends lib.Link {
|
||||
list.push(new lib.HostInstanceUpdate(
|
||||
instanceInfo.config.toRemote("controller"),
|
||||
instanceConnection ? instanceConnection.instance.status : "stopped",
|
||||
instanceConnection ? instanceConnection.instance.server.gamePort : undefined,
|
||||
instanceConnection ? instanceConnection.instance.server.gamePort : this.gamePort(instanceId),
|
||||
));
|
||||
}
|
||||
await this.send(new lib.InstancesUpdateRequest(list));
|
||||
@@ -986,6 +1036,10 @@ export default class Host extends lib.Link {
|
||||
|
||||
logger.info("Saving config");
|
||||
await lib.safeOutputFile(this.configPath, JSON.stringify(this.config, null, "\t"));
|
||||
for (const instanceInfo of this.instanceInfos.values()) {
|
||||
// Save in case host_assigned_game_port changed in the config.
|
||||
await this.saveInstanceConfig(instanceInfo);
|
||||
}
|
||||
|
||||
try {
|
||||
// Clear silly interval in pidfile library.
|
||||
|
||||
@@ -169,7 +169,7 @@ export default class Instance extends lib.Link {
|
||||
let serverOptions = {
|
||||
logger: this.logger,
|
||||
version: this.config.get("factorio.version"),
|
||||
gamePort: this.config.get("factorio.game_port") ?? undefined,
|
||||
gamePort: this.config.get("factorio.game_port") ?? host.assignGamePort(this.id),
|
||||
rconPort: this.config.get("factorio.rcon_port") ?? undefined,
|
||||
rconPassword: this.config.get("factorio.rcon_password") ?? undefined,
|
||||
enableWhitelist: this.config.get("factorio.enable_whitelist"),
|
||||
@@ -449,7 +449,7 @@ rcon.print(game.table_to_json(players))`.replace(/\r?\n/g, " ");
|
||||
new lib.InstanceStatusChangedEvent(
|
||||
this.id,
|
||||
status,
|
||||
status !== "stopped" ? this.server.gamePort : undefined,
|
||||
this.server.gamePort,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -158,6 +158,7 @@ export interface HostConfigFields {
|
||||
"host.controller_token": string;
|
||||
"host.tls_ca": string | null;
|
||||
"host.public_address": string;
|
||||
"host.factorio_port_range": string;
|
||||
"host.max_reconnect_delay": number;
|
||||
}
|
||||
|
||||
@@ -220,6 +221,14 @@ export class HostConfig extends classes.Config<HostConfigFields> {
|
||||
type: "string",
|
||||
initialValue: "localhost",
|
||||
},
|
||||
"host.factorio_port_range": {
|
||||
title: "Factorio port range",
|
||||
description:
|
||||
"Range of UDP ports to use for game connections. Supports both comma separated values and " +
|
||||
"ranges separated with a dash.",
|
||||
type: "string",
|
||||
initialValue: "34100-34199",
|
||||
},
|
||||
"host.max_reconnect_delay": {
|
||||
title: "Max Reconnect Delay",
|
||||
description: "Maximum delay to wait before attempting to reconnect WebSocket",
|
||||
@@ -237,6 +246,7 @@ export interface InstanceConfigFields {
|
||||
|
||||
"factorio.version": string;
|
||||
"factorio.game_port": number | null;
|
||||
"factorio.host_assigned_game_port": number | null;
|
||||
"factorio.rcon_port": number | null;
|
||||
"factorio.rcon_password": string | null;
|
||||
"factorio.player_online_autosave_slots": number;
|
||||
@@ -286,11 +296,16 @@ export class InstanceConfig extends classes.Config<InstanceConfigFields> {
|
||||
initialValue: "latest",
|
||||
},
|
||||
"factorio.game_port": {
|
||||
description: "UDP port to run game on, uses a random port if null",
|
||||
description: "UDP port to run game on, uses a port in host.factorio_port_range if null",
|
||||
restartRequired: true,
|
||||
type: "number",
|
||||
optional: true,
|
||||
},
|
||||
"factorio.host_assigned_game_port": {
|
||||
access: ["host"],
|
||||
type: "number",
|
||||
optional: true,
|
||||
},
|
||||
"factorio.rcon_port": {
|
||||
description: "TCP port to run RCON on, uses a random port if null",
|
||||
restartRequired: true,
|
||||
|
||||
@@ -20,7 +20,7 @@ describe("class Instance", function() {
|
||||
instanceConfig.set("instance.name", "foo");
|
||||
src = addr({ instanceId: instanceConfig.get("instance.id") });
|
||||
connector = new MockConnector(src, dst);
|
||||
instance = new Instance({}, connector, "dir", "factorioDir", instanceConfig);
|
||||
instance = new Instance({ assignGamePort: () => 1 }, connector, "dir", "factorioDir", instanceConfig);
|
||||
instance.server = new MockServer();
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user