Rename Slave to Host

The role of a slave as a host for Factorio servers is not well reflected
in its name and has caused confusion among users.  Rename slave to host
to better reflect its role as a host of Factorio servers.
This commit is contained in:
Hornwitser committed 2023-02-22 15:30:51 +01:00
1 parent 70ad295a89
commit 6fc3abafd5
101 files changed
+1168 -1168

No files matched your search

+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
- run: pnpm i --no-optional --no-frozen-lockfile - run: pnpm i --no-optional --no-frozen-lockfile
- run: wget -q -O factorio.tar.gz https://www.factorio.com/get-download/latest/headless/linux64 && tar -xf factorio.tar.gz && rm factorio.tar.gz - run: wget -q -O factorio.tar.gz https://www.factorio.com/get-download/latest/headless/linux64 && tar -xf factorio.tar.gz && rm factorio.tar.gz
- run: node packages/lib/build_mod --source-dir packages/slave/lua/clusterio_lib --output-dir temp/test/mods - run: node packages/lib/build_mod --source-dir packages/host/lua/clusterio_lib --output-dir temp/test/mods
- run: wget -q -O temp/test/mods/subspace_storage_1.99.8.zip -L https://github.com/clusterio/subspace_storage/releases/download/1.99.6/subspace_storage_1.99.8.zip - run: wget -q -O temp/test/mods/subspace_storage_1.99.8.zip -L https://github.com/clusterio/subspace_storage/releases/download/1.99.6/subspace_storage_1.99.8.zip
- name: Run tests - name: Run tests
if: ${{ matrix.node-version != '12.x' }} if: ${{ matrix.node-version != '12.x' }}
+1 -1
View File
@@ -37,7 +37,7 @@ Factorio*
/plugin-list.json /plugin-list.json
/config-controller.json /config-controller.json
/config-control.json /config-control.json
/config-slave.json /config-host.json
pnpm-lock.yaml pnpm-lock.yaml
mod_cache.json mod_cache.json
/static/ /static/
+13 -13
View File
@@ -8,11 +8,11 @@
Removed the Hotpatch scenario and the depency on it for getting code into the game. Removed the Hotpatch scenario and the depency on it for getting code into the game.
Added a save patcher than runs before starting up Factorio that patches in lua modules based on the event_loader lib into the savegame. Added a save patcher than runs before starting up Factorio that patches in lua modules based on the event_loader lib into the savegame.
Regular freeplay games can now be used with Clusterio and will be compatible without having to convert them to Hotpatch. Regular freeplay games can now be used with Clusterio and will be compatible without having to convert them to Hotpatch.
- Daemonized slaves. - Daemonized hosts.
Slaves now have the ability to run multiple Factorio instances, starting and stopping them individually. Hosts now have the ability to run multiple Factorio instances, starting and stopping them individually.
To manage this the local command line interface has been replaced with a remote interface on the controller that can be accessed through the clusterioctl cli tool. To manage this the local command line interface has been replaced with a remote interface on the controller that can be accessed through the clusterioctl cli tool.
- Rewritten the communication between slaves and the controller from scratch. - Rewritten the communication between hosts and the controller from scratch.
The new system is based on a WebSocket connection between the slaves and the controller and provides efficient validated bi-directonal communication. The new system is based on a WebSocket connection between the hosts and the controller and provides efficient validated bi-directonal communication.
- Rewritten the plugin system from scratch. - Rewritten the plugin system from scratch.
Plugins now inherit from a base class and use the same WebSocket connection Clusterio uses to communicate. Plugins now inherit from a base class and use the same WebSocket connection Clusterio uses to communicate.
- New configuration system with support for initializing configs when needed, modifying config entries using built-in commands, and updating instance configuration remotely. - New configuration system with support for initializing configs when needed, modifying config entries using built-in commands, and updating instance configuration remotely.
@@ -37,8 +37,8 @@
Previously this was handled by the playerManager plugin. Previously this was handled by the playerManager plugin.
- Added stripping of long paths in the Factorio server log. - Added stripping of long paths in the Factorio server log.
- Added option to configure maximum number of commands to send in parallel. - Added option to configure maximum number of commands to send in parallel.
- Added option to auto start instances on slave startup. - Added option to auto start instances on host startup.
- Added metric mapping of slave and instance ids to their names, allowing them to be displayed by name in queries that join with the mapping. - Added metric mapping of host and instance ids to their names, allowing them to be displayed by name in queries that join with the mapping.
- Added option to configure timeout for exported Prometheus metrics. - Added option to configure timeout for exported Prometheus metrics.
- Added option to set address to bind HTTP(S) port to. - Added option to set address to bind HTTP(S) port to.
- Added an overview of the installed plugins to the web interface. - Added an overview of the installed plugins to the web interface.
@@ -69,16 +69,16 @@
- Removed factorio_version from config. - Removed factorio_version from config.
The version installed is auto detected and used instead. The version installed is auto detected and used instead.
- Removed the playerManager specific command CLI tools/delete_player.js. - Removed the playerManager specific command CLI tools/delete_player.js.
- Creating an instance, assigning it to a slave, creating a save for an instance and starting an instance is now four separate commands. - Creating an instance, assigning it to a host, creating a save for an instance and starting an instance is now four separate commands.
- Removed oddball limits to slaves.json size. - Removed oddball limits to hosts.json size.
- Moved slave specific and instance specific configuration into their own configuration files. - Moved host specific and instance specific configuration into their own configuration files.
- Removed unused binary option from plugin config. - Removed unused binary option from plugin config.
- Removed info and shout command from globalChat plugin - Removed mirrorAllChat and enableCrossServerShout configuration options for globalChat plugin. - Removed info and shout command from globalChat plugin - Removed mirrorAllChat and enableCrossServerShout configuration options for globalChat plugin.
- Removed UPSdisplay plugin. - Removed UPSdisplay plugin.
UPS statistics is exported by the statistics exporter plugin. UPS statistics is exported by the statistics exporter plugin.
- Controller now defaults to hosting on https on port 8443. - Controller now defaults to hosting on https on port 8443.
- Renamed master to controller. - Renamed master to controller.
- Renamed client to slave. - Renamed slave/client to host.
- Removed rotation of factorio-current.log files. - Removed rotation of factorio-current.log files.
### Breaking Changes ### Breaking Changes
@@ -101,7 +101,7 @@
- Removed the remoteCommands plugin and the old runCommand interface. - Removed the remoteCommands plugin and the old runCommand interface.
Breaks playerManager and external tools sending commands. Breaks playerManager and external tools sending commands.
- Removed broken serverManager plugin. - Removed broken serverManager plugin.
- Removed fields info, time, rconPort, rconPassword, serverPort, unique, mods, instanceName, playerCount, mac, and meta from the slaves in the slave database. - Removed fields info, time, rconPort, rconPassword, serverPort, unique, mods, instanceName, playerCount, mac, and meta from the hosts in the host database.
- Removed getInstanceName and lib/clusterTools. - Removed getInstanceName and lib/clusterTools.
Breaks playerManager, and discordChat. Breaks playerManager, and discordChat.
- Removed the /api/rconPasswords, and /api/slaves endpoints. - Removed the /api/rconPasswords, and /api/slaves endpoints.
@@ -110,7 +110,7 @@
Breaks researchSync, and UPSdisplay. Breaks researchSync, and UPSdisplay.
- Removed the hello event from the socket connection handshake. - Removed the hello event from the socket connection handshake.
Breaks playerManager, trainTeleports, serverSelect, and discordChat Breaks playerManager, trainTeleports, serverSelect, and discordChat
- Changed the format of database/slaves.json. - Changed the format of database/hosts.json.
- Removed the output file subscription system. - Removed the output file subscription system.
Breaks inventoryImports, playerManager, trainTeleports, serverSelect and researchSync. Breaks inventoryImports, playerManager, trainTeleports, serverSelect and researchSync.
- Removed the factorioOutput hook from instance plugins. - Removed the factorioOutput hook from instance plugins.
@@ -135,7 +135,7 @@
- Removed msBetweenCommands config option. - Removed msBetweenCommands config option.
The RCON is instead limited to 5 concurrent commands. The RCON is instead limited to 5 concurrent commands.
- Removed allowRemoteCommandExecution config option. - Removed allowRemoteCommandExecution config option.
Remote commands are always allowed with the move to controller managing slaves/instances. Remote commands are always allowed with the move to controller managing hosts/instances.
- Removed `--databaseDirectory`, `--controllerPort`, and `--sslPort` command line arguments from the controller. - Removed `--databaseDirectory`, `--controllerPort`, and `--sslPort` command line arguments from the controller.
- Implemented a new config system that replaces the old. - Implemented a new config system that replaces the old.
Breaks all plugins. Breaks all plugins.
+1 -1
View File
@@ -25,7 +25,7 @@ RUN pnpm install
COPY --from=subspace_storage_builder /subspace_storage/dist/ /clusterio/mods/ COPY --from=subspace_storage_builder /subspace_storage/dist/ /clusterio/mods/
# Build Lua library mod # Build Lua library mod
RUN node packages/lib/build_mod --build --source-dir packages/slave/lua/clusterio_lib \ RUN node packages/lib/build_mod --build --source-dir packages/host/lua/clusterio_lib \
&& mv dist/* mods/ \ && mv dist/* mods/ \
&& mkdir temp \ && mkdir temp \
&& mkdir temp/test \ && mkdir temp/test \
+20 -20
View File
@@ -37,7 +37,7 @@ If you are starting a new cluster, it's highly recommended to use the 2.0 alpha.
* [Managing Factorio mods](#managing-factorio-mods) * [Managing Factorio mods](#managing-factorio-mods)
* [Setting up shared storage](#setting-up-shared-storage) * [Setting up shared storage](#setting-up-shared-storage)
* [Running Clusterio](#running-clusterio) * [Running Clusterio](#running-clusterio)
* [Setting up remote slaves](#setting-up-remote-slaves) * [Setting up remote hosts](#setting-up-remote-hosts)
* [Setting up clusterioctl](#setting-up-clusterioctl) * [Setting up clusterioctl](#setting-up-clusterioctl)
* [Common problems](#Common-problems) * [Common problems](#Common-problems)
@@ -128,7 +128,7 @@ Clusterio has *very* limited support for using docker.
sudo docker run --name controller -e MODE=controller -p 1234:8080 -d -it --restart=unless-stopped danielvestol/clusterio sudo docker run --name controller -e MODE=controller -p 1234:8080 -d -it --restart=unless-stopped danielvestol/clusterio
sudo docker run --name slave -e MODE=slave -e INSTANCE=world1 -v /srv/clusterio/instances:/clusterio/instances -p 1235:34167 -it --restart=unless-stopped danielvestol/clusterio sudo docker run --name host -e MODE=host -e INSTANCE=world1 -v /srv/clusterio/instances:/clusterio/instances -p 1235:34167 -it --restart=unless-stopped danielvestol/clusterio
The -v flag is used to specify the instance directory. The -v flag is used to specify the instance directory.
Your instances (save files etc) will be stored there. Your instances (save files etc) will be stored there.
@@ -175,9 +175,9 @@ Then tell clusterio that this plugin exists by adding it as a plugin.
npx clusteriocontroller plugin add @clusterio/plugin-subspace_storage npx clusteriocontroller plugin add @clusterio/plugin-subspace_storage
This adds it to the default `plugin-list.json` file which in the shared folder setup is loaded by controller, slave and ctl. This adds it to the default `plugin-list.json` file which in the shared folder setup is loaded by controller, host and ctl.
If you have slaves or ctl installed on separate computers (or directories) then you need to repeat the plugin install process for all of them. If you have hosts or ctl installed on separate computers (or directories) then you need to repeat the plugin install process for all of them.
The clusteriocontroller, clusterioslave and clusterioctl commands has the plugin sub-command so you do not need to install clusteriocontroller to add plugins, instead use the clusterio command you have available. The clusteriocontroller, clusteriohost and clusterioctl commands has the plugin sub-command so you do not need to install clusteriocontroller to add plugins, instead use the clusterio command you have available.
For development purposes the `plugin add` command supports adding plugins by the absolute path to them, or a relative path that must start with either . or .. (which will then be resolved to an absolute path). For development purposes the `plugin add` command supports adding plugins by the absolute path to them, or a relative path that must start with either . or .. (which will then be resolved to an absolute path).
@@ -189,10 +189,10 @@ You can change the port used with the command
npx clusteriocontroller config set controller.http_port 1234 npx clusteriocontroller config set controller.http_port 1234
When changing the port you will also need to change the address slaves connect with. When changing the port you will also need to change the address hosts connect with.
For the standalone installation mode you can use For the standalone installation mode you can use
npx clusterioslave config set slave.controller_url http://localhost:1234/ npx clusteriohost config set host.controller_url http://localhost:1234/
If you plan to make your cluster available externally set the address If you plan to make your cluster available externally set the address
that it will be accessible under with, for example that it will be accessible under with, for example
@@ -208,7 +208,7 @@ See the [readme for @clusterio/controller](/packages/controller/README.md) for m
## Managing Factorio mods ## Managing Factorio mods
Clusterio currently only provides a manual method for running mods on servers by copying them either to each instance's individual `mods` folder, or by copying them into a slave installation's `sharedMods` folder which will link/copy them to the `mods` folder of each instance on that slave if the file doesn't already exist in the `mods` folder for that instance. Clusterio currently only provides a manual method for running mods on servers by copying them either to each instance's individual `mods` folder, or by copying them into a host installation's `sharedMods` folder which will link/copy them to the `mods` folder of each instance on that host if the file doesn't already exist in the `mods` folder for that instance.
Mods are copied from `sharedMods` on Windows and symlinked on Linux, this means that when mods are removed from `sharedMods` the mods will also be removed from the instance's `mods` folder on Linux if it got there by being linked by Clusterio, but not on Windows. Mods are copied from `sharedMods` on Windows and symlinked on Linux, this means that when mods are removed from `sharedMods` the mods will also be removed from the instance's `mods` folder on Linux if it got there by being linked by Clusterio, but not on Windows.
For managing the mod settings, the `mod-settings.dat` and `mod-list.json` files will also be linked/copied over if present in `sharedMods`, but since Factorio creates these files automatically it might be necessary to delete them from each instance's `mods` folder for changes to them in `sharedMods` to take effect. For managing the mod settings, the `mod-settings.dat` and `mod-list.json` files will also be linked/copied over if present in `sharedMods`, but since Factorio creates these files automatically it might be necessary to delete them from each instance's `mods` folder for changes to them in `sharedMods` to take effect.
@@ -219,45 +219,45 @@ Better remote management of mods is a planned feature.
## Setting up shared storage ## Setting up shared storage
The chests teleporting items to and from the shared storage on the controller has been moved into the Subspace Storage mod and plugin. The chests teleporting items to and from the shared storage on the controller has been moved into the Subspace Storage mod and plugin.
To get this to work you will need to install the Subspace Storage plugin and copy the [Subspace Storage mod](https://mods.factorio.com/mod/subspace_storage) and the [Clusterio Lib mod](https://mods.factorio.com/mod/clusterio_lib) into the `sharedMods` folder on each slave installation. To get this to work you will need to install the Subspace Storage plugin and copy the [Subspace Storage mod](https://mods.factorio.com/mod/subspace_storage) and the [Clusterio Lib mod](https://mods.factorio.com/mod/clusterio_lib) into the `sharedMods` folder on each host installation.
Do not install the [Clusterio mod](https://mods.factorio.com/mod/clusterio), this was for the old 1.2.x version of Clusterio and is not compatible with Clusterio 2.0. Do not install the [Clusterio mod](https://mods.factorio.com/mod/clusterio), this was for the old 1.2.x version of Clusterio and is not compatible with Clusterio 2.0.
## Running Clusterio ## Running Clusterio
After completing the installation start up the controller and at least one slave separately. After completing the installation start up the controller and at least one host separately.
The installer provides the `run-controller` and `run-slave` scripts to make this simple. The installer provides the `run-controller` and `run-host` scripts to make this simple.
Once the controller process is running you can log into the web interface which is hosted by default on http://localhost:8080/ (adjust the port number if you changed it), use the admin authentication token provided from the installation to log in. Once the controller process is running you can log into the web interface which is hosted by default on http://localhost:8080/ (adjust the port number if you changed it), use the admin authentication token provided from the installation to log in.
The basics of setting up a Factorio server from the web interface is to create an instance, assign it to a slave and then click start. The basics of setting up a Factorio server from the web interface is to create an instance, assign it to a host and then click start.
### Running via Systemd ### Running via Systemd
The install script creates systemd service scripts for clusteriocontroller and clusterioslave (if applicable) to start up as the user than ran the installer. The install script creates systemd service scripts for clusteriocontroller and clusteriohost (if applicable) to start up as the user than ran the installer.
If you have copied these files over to `/etc/systemd/system/` then you can startup Clusterio as a background service using: If you have copied these files over to `/etc/systemd/system/` then you can startup Clusterio as a background service using:
#### To start as a service run: #### To start as a service run:
sudo systemctl start clusteriocontroller # for the controller sudo systemctl start clusteriocontroller # for the controller
sudo systemctl start clusterioslave # for each server hosting a slave sudo systemctl start clusteriohost # for each physical server intended to host Factorio servers
#### To automatically get it to start on boot: #### To automatically get it to start on boot:
sudo systemctl enable clusteriocontroller # for the controller sudo systemctl enable clusteriocontroller # for the controller
sudo systemctl enable clusterioslave # for each server hosting a slave sudo systemctl enable clusteriohost # for each physical server intended to host Factorio servers
## Setting up remote slaves ## Setting up remote hosts
Run the installer as described in the installation section and choose "Slave only" as the operating mode to install, it'll ask for a controller URL and an authentication token. Run the installer as described in the installation section and choose "Host only" as the operating mode to install, it'll ask for a controller URL and an authentication token.
The URL is the same as what is needed to connect to the web interface, and the authentication token can be generated on the Slaves page in the web interface. The URL is the same as what is needed to connect to the web interface, and the authentication token can be generated on the Hosts page in the web interface.
Once you start up the slave it should show up in the Slaves list and be available for assigning and running instances on. Once you start up the host it should show up in the Hosts list and be available for assigning and running instances on.
## Setting up clusterioctl ## Setting up clusterioctl
There's a command line interface available for Clusterio which is installed separately with the same installer as for the controller and slave. There's a command line interface available for Clusterio which is installed separately with the same installer as for the controller and host.
Run the installer as described in the installation section and choose "Ctl only" as the operating mode to install, you can do this in the same directory as you have installed other Clusterio component(s) to. Run the installer as described in the installation section and choose "Ctl only" as the operating mode to install, you can do this in the same directory as you have installed other Clusterio component(s) to.
The installer will ask for a controller URL and an authentication token, these are the same as you would use to connect to the web interface. The installer will ask for a controller URL and an authentication token, these are the same as you would use to connect to the web interface.
If you want to use a different user for the command line interface, you can generate an authentication token for an existing user with If you want to use a different user for the command line interface, you can generate an authentication token for an existing user with
+2 -2
View File
@@ -4,6 +4,6 @@ services:
controller: controller:
build: . build: .
command: npx clusteriocontroller run command: npx clusteriocontroller run
slave: host:
build: . build: .
command: npx clusterioslave run command: npx clusteriohost run
+2 -2
View File
@@ -11,8 +11,8 @@ Links represents one side of a two way communication pipe, connectors deal with
The Link class represents one side of a two way communication channel where the actual communication is done by a connector associated with the link. The Link class represents one side of a two way communication channel where the actual communication is done by a connector associated with the link.
The link also knows what the endpoint types it's connected to are via the source and target attributes. The link also knows what the endpoint types it's connected to are via the source and target attributes.
The endpoint types currently implemented are controller, slave, instance, and control. The endpoint types currently implemented are controller, host, instance, and control.
Although slave and instances run in the same Node.js program they use virtual links to communicate between them in order to simplify the communication architecture. Although host and instances run in the same Node.js program they use virtual links to communicate between them in order to simplify the communication architecture.
Upon creation the link will register with the connector in order to receive and process messages from it. Upon creation the link will register with the connector in order to receive and process messages from it.
The messages received are validated using the validator registered for the type of the message. The messages received are validated using the validator registered for the type of the message.
+6 -6
View File
@@ -2,7 +2,7 @@
<sub>This document describes the implementation and inner working of the configuration system in Clusterio, and unless you're developing Clusterio it's probably not going to be very useful for you.</sub> <sub>This document describes the implementation and inner working of the configuration system in Clusterio, and unless you're developing Clusterio it's probably not going to be very useful for you.</sub>
Clusterio uses a group based configuration system to manage settings for the controller, instances, slaves, and the clusterioctl utility. Clusterio uses a group based configuration system to manage settings for the controller, instances, hosts, and the clusterioctl utility.
Plugins get their own config group for the controller and instances which they can add their own fields to. Plugins get their own config group for the controller and instances which they can add their own fields to.
The fields are defined early in the startup, and the groups and their fields for disabled plugins will still be created. The fields are defined early in the startup, and the groups and their fields for disabled plugins will still be created.
@@ -17,10 +17,10 @@ The `define` method takes an object as argument with the following properties:
**access**: **access**:
The locations this config field can be read and modified from. The locations this config field can be read and modified from.
This is an array of strings which can contain the values `"controller"`, `"slave"` and `"control"`. This is an array of strings which can contain the values `"controller"`, `"host"` and `"control"`.
If a party is missing in this array then that party will not receive the value of this field when the config is shared, and will be unable to modify the field. If a party is missing in this array then that party will not receive the value of this field when the config is shared, and will be unable to modify the field.
Takes the value of `defaultAccess` set on the config group class if not passed. Takes the value of `defaultAccess` set on the config group class if not passed.
Note that instance config groups should have both `"controller"` and `"slave"` for all of its fields or unexpected behaviour may occur. Note that instance config groups should have both `"controller"` and `"host"` for all of its fields or unexpected behaviour may occur.
**type**: **type**:
The type of config value this field will support. The type of config value this field will support.
@@ -73,6 +73,6 @@ Values for all config fields are created when the config is initialized, as well
Once a field has a value it is never automatically removed, even if the field or group it was part of no longer exists. Once a field has a value it is never automatically removed, even if the field or group it was part of no longer exists.
This ensures that plugin configuration is not lost should the server be started up without the plugins that defined the configuration installed. This ensures that plugin configuration is not lost should the server be started up without the plugins that defined the configuration installed.
For instance configs, they are kept on the controller and serialized and sent over to the relevant slaves on startup. For instance configs, they are kept on the controller and serialized and sent over to the relevant hosts on startup.
This makes it possible to edit instance configs when the slave the instance is on is offline. This makes it possible to edit instance configs when the host the instance is on is offline.
A copy is also stored in the instance directory on the slave, and this config will be deliverd to the controller on slave startup, which enables instances to be copied between clusters as long as their IDs are unique. A copy is also stored in the instance directory on the host, and this config will be deliverd to the controller on host startup, which enables instances to be copied between clusters as long as their IDs are unique.
+26 -26
View File
@@ -76,7 +76,7 @@ Defaults to "database/certificates/cert.key".
### controller.auth_secret ### controller.auth_secret
Base64 encoded authentication secret used to verify tokens issued to slaves, users, and sessions. Base64 encoded authentication secret used to verify tokens issued to hosts, users, and sessions.
This should contain 256 bytes of random data, and if changed will cause all tokens become invalid. This should contain 256 bytes of random data, and if changed will cause all tokens become invalid.
Keep this secret if leaked an attacker could easily compromise the cluster. Keep this secret if leaked an attacker could easily compromise the cluster.
@@ -104,7 +104,7 @@ Defaults to 60.
### controller.metrics_timeout ### controller.metrics_timeout
Timeout in seconds before a call to gather metrics from a slave times out. Timeout in seconds before a call to gather metrics from a host times out.
This should be less than both the timeout and collection interval configured for the collection job in Prometheus. This should be less than both the timeout and collection interval configured for the collection job in Prometheus.
Defaults to 8. Defaults to 8.
@@ -134,24 +134,24 @@ Plugins not loaded on the controller will not be loaded on instances.
Defaults to true. Defaults to true.
## Slave Configuration ## Host Configuration
### slave.name ### host.name
Name of the slave. Name of the host.
Shows up in slave lists and is used to reference this slave in the clusterioctl command line interface. Shows up in host lists and is used to reference this host in the clusterioctl command line interface.
Defaults to "New Slave". Defaults to "New Host".
### slave.id ### host.id
Immutable numeric id of the slave which uniquely identifies this slave in the cluster. Immutable numeric id of the host which uniquely identifies this host in the cluster.
Defaults to a random 31 bit number. Defaults to a random 31 bit number.
### slave.factorio_directory ### host.factorio_directory
Directory to look for the Factorio server(s) in. Directory to look for the Factorio server(s) in.
This can either point to a Factorio server directory, or to a directory containing multiple versions of the Factorio server. This can either point to a Factorio server directory, or to a directory containing multiple versions of the Factorio server.
@@ -159,46 +159,46 @@ This can either point to a Factorio server directory, or to a directory containi
Defaults to "factorio". Defaults to "factorio".
### slave.instances_directory ### host.instances_directory
Directory to store instances in. Directory to store instances in.
One sub-directory will be created in it for each instance assigned to this slave and these sub-directories will contain logs, saves, and various auto generated configuration files for the instances. One sub-directory will be created in it for each instance assigned to this host and these sub-directories will contain logs, saves, and various auto generated configuration files for the instances.
Defaults to "instances". Defaults to "instances".
### slave.controller_url ### host.controller_url
URL to connect to the controller to. URL to connect to the controller to.
Defaults to "http://localhost:8080/". Defaults to "http://localhost:8080/".
### slave.controller_token ### host.controller_token
Access token used for authenticating with the controller. Access token used for authenticating with the controller.
You can generate an access token with `clusterioctl slave generate-token --id <slave-id>`, or use the `clusterioctl slave create-config` to create a new slave config with the correct url and token in it. You can generate an access token with `clusterioctl host generate-token --id <host-id>`, or use the `clusterioctl host create-config` to create a new host config with the correct url and token in it.
Defaults to "enter token here". Defaults to "enter token here".
### slave.tls_ca ### host.tls_ca
Path to certificate in PEM format to use for validating a TLS connection to the controller. Path to certificate in PEM format to use for validating a TLS connection to the controller.
If you have a self signed certificate on the controller you will need to copy the certificate to your slaves and set it as the certificate authority with this option. If you have a self signed certificate on the controller you will need to copy the certificate to your hosts and set it as the certificate authority with this option.
Defaults to null meaning use Node.js's default set of trusted certificate authorities. Defaults to null meaning use Node.js's default set of trusted certificate authorities.
### slave.public_address ### host.public_address
External address instances hosted on this slave can be accessed on. External address instances hosted on this host can be accessed on.
This is used by plugins like Server Select to give the correct address to switch between instances in-game. This is used by plugins like Server Select to give the correct address to switch between instances in-game.
Defaults to "localhost". Defaults to "localhost".
### slave.max_reconnect_delay ### host.max_reconnect_delay
Maximum delay in seconds to wait after connection to the controller is dropped before attempting to reconnect to it. Maximum delay in seconds to wait after connection to the controller is dropped before attempting to reconnect to it.
The actual delay on each reconnect will be a random number between 0 and this configured value to avoid all clients trying to reconnect at the same time. The actual delay on each reconnect will be a random number between 0 and this configured value to avoid all clients trying to reconnect at the same time.
@@ -222,19 +222,19 @@ Imutable numeric id of the instance which uniquely identifies this instance in t
Defaluts to a random 31 bit integer. Defaluts to a random 31 bit integer.
### instance.assigned_slave ### instance.assigned_host
Slave this instance is assigned to. Host this instance is assigned to.
To change this you need to use the separate assign instance interface. To change this you need to use the separate assign instance interface.
After being assigned to a slave the directory for the instance is created on the slave. After being assigned to a host the directory for the instance is created on the host.
Defaults to null meaning not assigned. Defaults to null meaning not assigned.
### instance.auto_start ### instance.auto_start
If enabled start this instance when the slave it is assigned to is started up. If enabled start this instance when the host it is assigned to is started up.
Does not affect start or stop of the instance while the slave is running. Does not affect start or stop of the instance while the host is running.
Defaults to false. Defaults to false.
@@ -242,7 +242,7 @@ Defaults to false.
### factorio.version ### factorio.version
Version of Factorio to use for this instance. Version of Factorio to use for this instance.
Can be a version like "1.0.0" or special string "latest" meaning the latest version of Factorio found on the slave. Can be a version like "1.0.0" or special string "latest" meaning the latest version of Factorio found on the host.
Defaults to "latest". Defaults to "latest".
+2 -2
View File
@@ -69,14 +69,14 @@ The `plugins/` directory contains the core plugins for the project and can be ad
The documentation uses the binaries provided by the packages, but for the development environment you will need to call the packages directly: The documentation uses the binaries provided by the packages, but for the development environment you will need to call the packages directly:
npx clusteriocontroller -> node packages/controller npx clusteriocontroller -> node packages/controller
npx clusterioslave -> node packages/slave npx clusteriohost -> node packages/host
npx clusterioctl -> node packages/ctl npx clusterioctl -> node packages/ctl
Note that to use `clusterioctl` you will have to create a control config first: Note that to use `clusterioctl` you will have to create a control config first:
node packages/controller bootstrap create-ctl-config [admin-user] node packages/controller bootstrap create-ctl-config [admin-user]
Once you've set up the cluster you can use `node packages/controller run` to run the controller and `node packages/slave run` to run the slave which connects to the controller. Once you've set up the cluster you can use `node packages/controller run` to run the controller and `node packages/host run` to run the host which connects to the controller.
For web development on the controller there are also the following flags: For web development on the controller there are also the following flags:
+14 -14
View File
@@ -12,8 +12,8 @@ Events and requests are defined as instances of the `Request` and `Event` classe
All messages sent and received are validated with JSON schemas to catch potential errors in sending messages and avoid having to write lots of validation code in message handlers. All messages sent and received are validated with JSON schemas to catch potential errors in sending messages and avoid having to write lots of validation code in message handlers.
Messages are sent by calling the `.send()` method of the message instance and passing a `Link` instance to send it over and the data to send. Messages are sent by calling the `.send()` method of the message instance and passing a `Link` instance to send it over and the data to send.
For the controller there's a `Link` instance for each slave and control connection stored in the `WsServer` class. For the controller there's a `Link` instance for each host and control connection stored in the `WsServer` class.
For slaves the `Slave` instance is a link to the controller, and each `Instance` instance is an in-memory link to the `Slave` instance and has a corresponding `InstanceConnection` link stored on the `Slave` instance. For hosts the `Host` instance is a link to the controller, and each `Instance` instance is an in-memory link to the `Host` instance and has a corresponding `InstanceConnection` link stored on the `Host` instance.
For ctl and web_ui there's a `Control` instance that acts as the link to the controller. For ctl and web_ui there's a `Control` instance that acts as the link to the controller.
Messages received invokes message handlers that were registered when the link was brought up. Messages received invokes message handlers that were registered when the link was brought up.
@@ -24,7 +24,7 @@ The handlers are defined on the link class itself for core messages, and on the
## Connection flow ## Connection flow
Clients connect via WebSocket to the `/api/socket` endpoint and upon establishing connection to the server the connection is in the handshake phase and the server will send a `hello` message to the client. Clients connect via WebSocket to the `/api/socket` endpoint and upon establishing connection to the server the connection is in the handshake phase and the server will send a `hello` message to the client.
If the client has no previous session with the server it sends a `register_slave` or `register_control` message depending on what kind of client it is to the server. If the client has no previous session with the server it sends a `register_host` or `register_control` message depending on what kind of client it is to the server.
If the register is valid then the server will respond with a `ready` message with a session token and the connection has left the handshake phase, otherwise the server will close the connection with a WebSocket close code indicating the kind of failure. If the register is valid then the server will respond with a `ready` message with a session token and the connection has left the handshake phase, otherwise the server will close the connection with a WebSocket close code indicating the kind of failure.
Upon leaving the handshake phase both parties should start sending heartbeat messages with the sequence number of the last received message. Upon leaving the handshake phase both parties should start sending heartbeat messages with the sequence number of the last received message.
@@ -33,7 +33,7 @@ Messages sent over the link should be stored in a send buffer and only cleared f
If the connection goes stale or is unexpectedly closed the client can reastablish the same session by connecting to the server again and sending a `resume` message after received the `hello` from the server. If the connection goes stale or is unexpectedly closed the client can reastablish the same session by connecting to the server again and sending a `resume` message after received the `hello` from the server.
If the session resume is successful then the server will respond with a `continue` message and the connection is active again. If the session resume is successful then the server will respond with a `continue` message and the connection is active again.
The client should then re-send all messages it has in its send buffer that is greater than the `last_seq` number in the `continue` message. The client should then re-send all messages it has in its send buffer that is greater than the `last_seq` number in the `continue` message.
Otherwise the server will respond with `invalidate` and the client is expected to drop its send buffer start a new session with `register_slave` or `register_control`. Otherwise the server will respond with `invalidate` and the client is expected to drop its send buffer start a new session with `register_host` or `register_control`.
![Connection flow](../assets/connection_flow.svg) ![Connection flow](../assets/connection_flow.svg)
@@ -102,17 +102,17 @@ Sent by the controller when the WebSocket connection has been opened.
- `plugins: Object<string, string>` - - `plugins: Object<string, string>` -
Object mapping plugin names to plugin version for plugins that are loaded on the controller. Object mapping plugin names to plugin version for plugins that are loaded on the controller.
#### `register_slave` #### `register_host`
Slave handshake for establishing a new connection session. Host handshake for establishing a new connection session.
- `token: string` - Authentication token. - `token: string` - Authentication token.
- `agent: string` - Human readable string of the software connecting. - `agent: string` - Human readable string of the software connecting.
- `version: string` - The protocol version of the slave, e.g. "2.0.0". - `version: string` - The protocol version of the host, e.g. "2.0.0".
- `name: string` - Name of the slave. - `name: string` - Name of the host.
- `id: integer` - ID of the slave. - `id: integer` - ID of the host.
- `plugins: Object<string, string>` - - `plugins: Object<string, string>` -
Object mapping plugin names to plugin version for plugins that are available on the slave. Object mapping plugin names to plugin version for plugins that are available on the host.
#### `register_control` #### `register_control`
@@ -235,15 +235,15 @@ Server hello after opening the WebSocket.
} }
``` ```
Slave registering a new connection session. Host registering a new connection session.
```json ```json
{ {
"seq": null, "seq": null,
"type": "register_slave", "type": "register_host",
"data": { "data": {
"token": "xyzabc123", "token": "xyzabc123",
"agent": "Clusterio Slave", "agent": "Clusterio Host",
"version": "2.0.0", "version": "2.0.0",
"name": "Foo", "name": "Foo",
"id": 123 "id": 123
@@ -265,4 +265,4 @@ Controller reply signaling the session is established and ready.
} }
``` ```
Slave and controller can now send events and requests over the connection. Host and controller can now send events and requests over the connection.
+2 -2
View File
@@ -13,8 +13,8 @@
"include": [ "include": [
"packages/controller/src/", "packages/controller/src/",
"packages/controller/controller.js", "packages/controller/controller.js",
"packages/slave/src/", "packages/host/src/",
"packages/slave/slave.js", "packages/host/host.js",
"packages/ctl/src/", "packages/ctl/src/",
"packages/ctl/ctl.js", "packages/ctl/ctl.js",
"packages/lib/", "packages/lib/",
+11 -11
View File
@@ -8,25 +8,25 @@ Before `clusterioctl` can be used it needs to be configured for the cluster it w
The easiest way to do this is to run `npx clusteriocontroller bootstrap create-ctl-config <username>` on the controller, which creates the necessary `config-control.json` for managing the cluster as the given user. The easiest way to do this is to run `npx clusteriocontroller bootstrap create-ctl-config <username>` on the controller, which creates the necessary `config-control.json` for managing the cluster as the given user.
## Slaves ## Hosts
### List slaves ### List hosts
ctl> slave list ctl> host list
Lists all slaves. Lists all hosts.
### Generate token ### Generate token
ctl> slave generate-token [--id <slave id>] ctl> host generate-token [--id <host id>]
Generate a token for a slave. `--id` is the slave id. Generate a token for a host. `--id` is the host id.
### Create config ### Create config
ctl> slave create-config [--id <slave id>] [--name <slave name>] [--generate-token <bool>] [--output <path to output>] ctl> host create-config [--id <host id>] [--name <host name>] [--generate-token <bool>] [--output <path to output>]
Create a slave config. `--id` id the slave id. `--name` is the slave name. `--generate-token` decides whether to also generate a token, defaulting to `0`. `--output` is the filename for the output of the config; Default is "config-slave.json", "-" for stdout. Create a host config. `--id` id the host id. `--name` is the host name. `--generate-token` decides whether to also generate a token, defaulting to `0`. `--output` is the filename for the output of the config; Default is "config-host.json", "-" for stdout.
## Instances ## Instances
@@ -85,11 +85,11 @@ To be written.
Edit instance config in a text editor. Once file is saved and editor has been exited, config values will be read back in. `instance` is the instance to edit, `editor` is the editor to use. If `editor` is not given, clusterioctl will read from the `EDITOR` or `VISUAL` enviroment variables. Edit instance config in a text editor. Once file is saved and editor has been exited, config values will be read back in. `instance` is the instance to edit, `editor` is the editor to use. If `editor` is not given, clusterioctl will read from the `EDITOR` or `VISUAL` enviroment variables.
### Assign instance to slave ### Assign instance to host
ctl> instace assign <instance> [slave] ctl> instace assign <instance> [host]
Assigns instance to slave. `instance` is instance to assign, `slave` is slave to assign to. If `slave` is not provided, instance will be unassigned. Assigns instance to host. `instance` is instance to assign, `host` is host to assign to. If `host` is not provided, instance will be unassigned.
## Users ## Users
+2 -2
View File
@@ -63,9 +63,9 @@ This creates a private key file named `key.pem` and a public certificate file na
npx clusteriocontroller config set controller.tls_certificate cert.pem npx clusteriocontroller config set controller.tls_certificate cert.pem
npx clusteriocontroller config set controller.tls_private_key key.pem npx clusteriocontroller config set controller.tls_private_key key.pem
Additionally you will need copy the `cert.pem` file to all of the computers you want to set up as slaves and configure it as the `slave.tls_ca` option: Additionally you will need copy the `cert.pem` file to all of the computers you want to set up as hosts and configure it as the `host.tls_ca` option:
npx clusterioslave config set slave.tls_ca cert.pem npx clusteriohost config set host.tls_ca cert.pem
You will also need to copy the `cert.pem` file to all of the computers you want to remotely manage the cluster via clusterctl and configure it as `control.tls_ca` option: You will also need to copy the `cert.pem` file to all of the computers you want to remotely manage the cluster via clusterctl and configure it as `control.tls_ca` option:
+33 -33
View File
@@ -41,7 +41,7 @@ The usual guides for creating such packages apply.
At minimum the `package.json` file must contain a version entry. At minimum the `package.json` file must contain a version entry.
A possible workflow for developing plugins is to place the plugin in a sub-directory of where clusterio has been installed, and rely on Node.js searching up the folder heirarchy for it to find `@clusterio/lib`. A possible workflow for developing plugins is to place the plugin in a sub-directory of where clusterio has been installed, and rely on Node.js searching up the folder heirarchy for it to find `@clusterio/lib`.
To add it to `plugin-list.json` so that it gets loaded use the `plugin add <path>` sub-command to either clusteriocontroller, clusterioslave or clusterioctl. To add it to `plugin-list.json` so that it gets loaded use the `plugin add <path>` sub-command to either clusteriocontroller, clusteriohost or clusterioctl.
Note that it's important that the path starts with ./ or ../ (use .\ or ..\ on Windows). Note that it's important that the path starts with ./ or ../ (use .\ or ..\ on Windows).
For a plugin the most important file is the `info.js` file. For a plugin the most important file is the `info.js` file.
@@ -157,7 +157,7 @@ The async init method is always called immediatly after the constructor, so ther
## Logging Messages ## Logging Messages
The base plugin classes provide a winston logger for logging messages to the shared cluster log. The base plugin classes provide a winston logger for logging messages to the shared cluster log.
For instances a copy of the log is also stored on the slave the instance is on. For instances a copy of the log is also stored on the host the instance is on.
To use it, pass a string to one of the log levels functions, for example: To use it, pass a string to one of the log levels functions, for example:
```js ```js
@@ -209,7 +209,7 @@ For example in info.js:
const libConfig = require("@clusterio/lib/config"); const libConfig = require("@clusterio/lib/config");
class ControllerConfigGroup extends libConfig.PluginConfigGroup { } class ControllerConfigGroup extends libConfig.PluginConfigGroup { }
ControllerConfigGroup.defaultAccess = ["controller", "slave", "control"]; ControllerConfigGroup.defaultAccess = ["controller", "host", "control"];
ControllerConfigGroup.groupName = "foo_frobber"; ControllerConfigGroup.groupName = "foo_frobber";
ControllerConfigGroup.define({ ControllerConfigGroup.define({
name: "level", name: "level",
@@ -314,7 +314,7 @@ For RCON, commands longer than 50 characters may end up being executed after sho
## Defining Link Messages ## Defining Link Messages
You will most likely have to communicate with the controller or other instances in your plugin for it to do anything useful. You will most likely have to communicate with the controller or other instances in your plugin for it to do anything useful.
For this there's a WebSocket communication channel established between the slaves and the controller that plugins can define their own messages to send over it. For this there's a WebSocket communication channel established between the hosts and the controller that plugins can define their own messages to send over it.
This channel is bi-directional and all messages sent over it are validated with a JSON schema (see [this guide][guide] for an introduction to writing JSON schema). This channel is bi-directional and all messages sent over it are validated with a JSON schema (see [this guide][guide] for an introduction to writing JSON schema).
[guide]: https://json-schema.org/learn/getting-started-step-by-step.html [guide]: https://json-schema.org/learn/getting-started-step-by-step.html
@@ -335,7 +335,7 @@ The Event constructor takes an object of properties that define the event, for e
messages: { messages: {
startFrobnication: new libLink.Event({ startFrobnication: new libLink.Event({
type: "foo_frobber:start_frobnication", type: "foo_frobber:start_frobnication",
links: ["controller-slave", "slave-instance"], links: ["controller-host", "host-instance"],
forwardTo: "instance", forwardTo: "instance",
eventRequired: ["frobnication_type"], eventRequired: ["frobnication_type"],
eventProperties: { eventProperties: {
@@ -346,9 +346,9 @@ messages: {
}, },
``` ```
This specifies an event that can be sent from the controller to a slave, and from a slave to an instance. This specifies an event that can be sent from the controller to a host, and from a host to an instance.
It also specifies that the event must contain the property `frobnication_type`, with a string value in the data payload and that it may optionally contain a boolean `urgent` property. It also specifies that the event must contain the property `frobnication_type`, with a string value in the data payload and that it may optionally contain a boolean `urgent` property.
It will also be forwarded by slaves to a specific instance. It will also be forwarded by hosts to a specific instance.
The following properties are recognized by the Event constructor: The following properties are recognized by the Event constructor:
@@ -361,17 +361,17 @@ The type of the message sent over the socket will have the suffix `_event` appen
#### links #### links
An array of strings describing which links this event can be sent over. An array of strings describing which links this event can be sent over.
Direction matters, `"controller-slave"` means the event can be sent from the controller to the slave, but can't be sent back the other way, unless `"slave-controller"` is also present in the links array. Direction matters, `"controller-host"` means the event can be sent from the controller to the host, but can't be sent back the other way, unless `"host-controller"` is also present in the links array.
The available endpoints are `controller`, `slave`, `instance`, and `control`. The available endpoints are `controller`, `host`, `instance`, and `control`.
Controller talks with slave and control, and slave talks to instance. Controller talks with host and control, and host talks to instance.
The full chain must be specified as the individual links in order for a message to travers multiple hops, (i.e., for a message to go from controller to instance it must have both `"controller-slave"` and `"slave-instance"` in the links array). The full chain must be specified as the individual links in order for a message to travers multiple hops, (i.e., for a message to go from controller to instance it must have both `"controller-host"` and `"host-instance"` in the links array).
See `forwardTo` and `broadcastTo` for ways to forward an event to the next link in a chain. See `forwardTo` and `broadcastTo` for ways to forward an event to the next link in a chain.
#### forwardTo #### forwardTo
Target to forward an event to. Target to forward an event to.
Can either be `"controller"`, to indicate a slave should forward it to the controller, or `"instance"`, to indicate it should be forwarded to the instances specified by the `instance_id` event property. Can either be `"controller"`, to indicate a host should forward it to the controller, or `"instance"`, to indicate it should be forwarded to the instances specified by the `instance_id` event property.
This works by using a default handler for the event at the links that forward it. This works by using a default handler for the event at the links that forward it.
#### broadcastTo #### broadcastTo
@@ -379,7 +379,7 @@ This works by using a default handler for the event at the links that forward it
Target to broadcast this message towards. Target to broadcast this message towards.
A value of "instance" means the event will be broadcast to all instances downstream of the target it's sent to, but not back from where it came from. A value of "instance" means the event will be broadcast to all instances downstream of the target it's sent to, but not back from where it came from.
Currently, only "instance" is supported. Currently, only "instance" is supported.
This means that sending the event to a slave from an instance will cause it to be broadcast to all instances of that slave except for the instance it came from. This means that sending the event to a host from an instance will cause it to be broadcast to all instances of that host except for the instance it came from.
#### eventRequired #### eventRequired
@@ -394,7 +394,7 @@ This is equivalent to using the `properties` keyword in JSON schema, except that
See [this guide][guide] for an introduction to writing JSON schemas. See [this guide][guide] for an introduction to writing JSON schemas.
The forwardTo and broadcastTo can be combined such that specifying `"controller"` as the forwardTo value and `"instance"` as the broadcastTo value will cause the event to be broadcast to all instances in the cluster. The forwardTo and broadcastTo can be combined such that specifying `"controller"` as the forwardTo value and `"instance"` as the broadcastTo value will cause the event to be broadcast to all instances in the cluster.
For this to work, you will need to specify `instance-slave`, `slave-controller`, `controller-slave`, and `slave-instance` as the links. For this to work, you will need to specify `instance-host`, `host-controller`, `controller-host`, and `host-instance` as the links.
Keep in mind when forwarding events that if the target an event is being forwarded to is not online, the event will be dropped. Keep in mind when forwarding events that if the target an event is being forwarded to is not online, the event will be dropped.
Use a request if you need a confirmation that the message was received. Use a request if you need a confirmation that the message was received.
@@ -410,7 +410,7 @@ For example, the following could be defined in `info.js`:
messages: { messages: {
reportFrobnication: new libLink.Request({ reportFrobnication: new libLink.Request({
type: "foo_frobber:report_frobnication", type: "foo_frobber:report_frobnication",
links: ["controller-slave", "slave-instance"], links: ["controller-host", "host-instance"],
forwardTo: "instance", forwardTo: "instance",
requestRequired: ["verbosity"], requestRequired: ["verbosity"],
requestProperties: { requestProperties: {
@@ -427,10 +427,10 @@ messages: {
}, },
``` ```
This specifies a request that can be sent from the controller to a slave, and from a slave to an instance. This specifies a request that can be sent from the controller to a host, and from a host to an instance.
The request data must contain the property `verbosity` with an integer number as the value, as well as the `instance_id` property (implied by `forwardTo: "instance"`) and it may also contain a boolean `special` property. The request data must contain the property `verbosity` with an integer number as the value, as well as the `instance_id` property (implied by `forwardTo: "instance"`) and it may also contain a boolean `special` property.
It also defines that the response sent must contain a `report` property mapping to an array of strings. It also defines that the response sent must contain a `report` property mapping to an array of strings.
When received by a slave, it will also be forwarded to the instance specified by `instance_id`. When received by a host, it will also be forwarded to the instance specified by `instance_id`.
The following properties are recognized by the Request constructor: The following properties are recognized by the Request constructor:
@@ -443,17 +443,17 @@ The type of the message sent over the socket will have the suffix `_request` app
#### links #### links
An array of strings describing which links this request can be sent over. An array of strings describing which links this request can be sent over.
Direction matters; `"controller-slave"` means the request can be sent from the controller to the slave and the slave can reply to the controller, but the slave can't send a request to the controller unless `"slave-controller"` is also present in the links array. Direction matters; `"controller-host"` means the request can be sent from the controller to the host and the host can reply to the controller, but the host can't send a request to the controller unless `"host-controller"` is also present in the links array.
The available endpoints are `controller`, `slave`, `instance`, and `control`. The available endpoints are `controller`, `host`, `instance`, and `control`.
Controller talks with slave and control, and slave talks to instance. Controller talks with host and control, and host talks to instance.
The full chain must be specified as the individual links in order for a message to travers multiple hops, (i.e., for a message to go from controller to instance it must have both `"controller-slave"` and `"slave-instance"` in the links array). The full chain must be specified as the individual links in order for a message to travers multiple hops, (i.e., for a message to go from controller to instance it must have both `"controller-host"` and `"host-instance"` in the links array).
See `forwardTo` for ways to forward a request to the next link in a chain. See `forwardTo` for ways to forward a request to the next link in a chain.
#### forwardTo #### forwardTo
Target to forward the request to. Target to forward the request to.
Can either be `"controller"` to indicate a slave should forward it to the controller when receiving it from an instance, or `"instance"` to indicate it should be forwarded to the instances specified by the `instance_id` request property. Can either be `"controller"` to indicate a host should forward it to the controller when receiving it from an instance, or `"instance"` to indicate it should be forwarded to the instances specified by the `instance_id` request property.
This works by using a default handler for the request by the links that forward it. This works by using a default handler for the request by the links that forward it.
#### requestRequired #### requestRequired
@@ -480,7 +480,7 @@ Same as the requestProperties only for the response sent back by the target.
## Sending Link Messages ## Sending Link Messages
Link messages are sent by calling the `.send()` method on the Event/Request instance with the link you want to send it over and the data to send. Link messages are sent by calling the `.send()` method on the Event/Request instance with the link you want to send it over and the data to send.
For `InstancePlugin` code the link to the slave is the `instance` itself, which is accessible through the `.instance` property of the `InstancePlugin`. For `InstancePlugin` code the link to the host is the `instance` itself, which is accessible through the `.instance` property of the `InstancePlugin`.
The `.info` property of the plugin class exposes the data exported from the plugin's `info.js` module. The `.info` property of the plugin class exposes the data exported from the plugin's `info.js` module.
In other words: In other words:
@@ -497,26 +497,26 @@ For the Request class the send method is async and returns the response data rec
### Handling connection events ### Handling connection events
There are a few connection related events that plugins neeed to repsond to in order to avoid data loss and connection problems. There are a few connection related events that plugins neeed to repsond to in order to avoid data loss and connection problems.
The most important is the prepare disconnect for the link between controller and slave. The most important is the prepare disconnect for the link between controller and host.
This is signaled to `ControllerPlugin` classes via the `onPrepareSlaveDisconnect` hook and to `InstancePlugin` classes via the `onPrepareControllerDisconnect` hook. This is signaled to `ControllerPlugin` classes via the `onPrepareHostDisconnect` hook and to `InstancePlugin` classes via the `onPrepareControllerDisconnect` hook.
After the prepare disconnect the connection will be closed, which will result in pending requests and events being dropped. After the prepare disconnect the connection will be closed, which will result in pending requests and events being dropped.
Plugins must respond to the prepare disconnect by stopping any processess it does that send events or requests over the link in question. Plugins must respond to the prepare disconnect by stopping any processess it does that send events or requests over the link in question.
This can be accomplished either through listening for the prepare disconnect hook, or by checking the `connected` property of the `SlaveConnection` class and `Slave` class on the controller and slave respectively. This can be accomplished either through listening for the prepare disconnect hook, or by checking the `connected` property of the `HostConnection` class and `Host` class on the controller and host respectively.
For example the sending of an event from an `InstancePlugin` class can be stopped while the connection is not connected, not in the dropped state, and not in the process of discunnecting by using the following code: For example the sending of an event from an `InstancePlugin` class can be stopped while the connection is not connected, not in the dropped state, and not in the process of discunnecting by using the following code:
```js ```js
if (this.slave.connected) { if (this.host.connected) {
this.info.messages.frobnicate.send(this.instance, { foo: "bar" }); this.info.messages.frobnicate.send(this.instance, { foo: "bar" });
} }
``` ```
If the event or request needs to be sent to the controller it can be put into a queue stored on the plugin instance and sent out when the connection is established again. If the event or request needs to be sent to the controller it can be put into a queue stored on the plugin instance and sent out when the connection is established again.
The re-establishement of the connection is notified to plugins via the `connect` event to the `onControllerConnectionEvent` and `onSlaveConnectionEvent` hooks. The re-establishement of the connection is notified to plugins via the `connect` event to the `onControllerConnectionEvent` and `onHostConnectionEvent` hooks.
The second connection event which is of lesser importance to respond to is the `drop` connection event served through `onControllerConnectionEvent` for `InstancePlugin` classes and through `onSlaveConnectionEvent` for `ControllerPlugin` classes. The second connection event which is of lesser importance to respond to is the `drop` connection event served through `onControllerConnectionEvent` for `InstancePlugin` classes and through `onHostConnectionEvent` for `ControllerPlugin` classes.
This is raised when the connection between the controller and slave in question is lost, most likely due to networking issues. This is raised when the connection between the controller and host in question is lost, most likely due to networking issues.
When in the dropped state the slave will keep trying to reconnect to the controller in order to re-establish it, and if successful no events or requests will be lost. When in the dropped state the host will keep trying to reconnect to the controller in order to re-establish it, and if successful no events or requests will be lost.
However while in the dropped state any requests and events sent gets queued up in memory until the connection is either re-established or the session times out. However while in the dropped state any requests and events sent gets queued up in memory until the connection is either re-established or the session times out.
This means that if your plugin sends a lot of events or requests, they can end up being queued up in a buffer and sent out all at once the connection is re-estabished. This means that if your plugin sends a lot of events or requests, they can end up being queued up in a buffer and sent out all at once the connection is re-estabished.
To avoid this you should be throtteling and/or stopping your requests/events after `drop` has been raised, and continue back as normal when `resume` is raised. To avoid this you should be throtteling and/or stopping your requests/events after `drop` has been raised, and continue back as normal when `resume` is raised.
@@ -557,8 +557,8 @@ const barMetric = new Gauge(
barMetric.labels(String(this.instance.id)).set(someValue); barMetric.labels(String(this.instance.id)).set(someValue);
``` ```
Metrics are automatically registered to the default registry, and this default registry is automatically polled by the controller on slaves. Metrics are automatically registered to the default registry, and this default registry is automatically polled by the controller on hosts.
This means that it's important that you place the definition of the metric at module level so that it's not created more than once over the lifetime of a slave. This means that it's important that you place the definition of the metric at module level so that it's not created more than once over the lifetime of a host.
Since the metrics remember their values and would continue to be exported after an instance is shutdown, there's code at instance shutdown that removes all the values where the `instance_id` label matches the id of the instance shut down. Since the metrics remember their values and would continue to be exported after an instance is shutdown, there's code at instance shutdown that removes all the values where the `instance_id` label matches the id of the instance shut down.
For statistics you need to update on collection there's an `onMetrics` hook on both controller and instance plugins that is run before the metrics in the default registry are collected. For statistics you need to update on collection there's an `onMetrics` hook on both controller and instance plugins that is run before the metrics in the default registry are collected.
+1 -1
View File
@@ -24,7 +24,7 @@
"@clusterio/ctl": "workspace:*", "@clusterio/ctl": "workspace:*",
"@clusterio/lib": "workspace:*", "@clusterio/lib": "workspace:*",
"@clusterio/controller": "workspace:*", "@clusterio/controller": "workspace:*",
"@clusterio/slave": "workspace:*", "@clusterio/host": "workspace:*",
"eslint": "^8.18.0", "eslint": "^8.18.0",
"eslint-plugin-node": "^11.1.0", "eslint-plugin-node": "^11.1.0",
"express": "^4.16.2", "express": "^4.16.2",
+3 -3
View File
@@ -1,7 +1,7 @@
# Clusterio Controller # Clusterio Controller
Communication hub for Clusterio clusters. Communication hub for Clusterio clusters.
The controller forwards data between Clusterio slaves connected to it and allows the cluster to be remotely managed through WebSocket connections to it either by using the included web interface or the Clusterio ctl command line utility. The controller forwards data between Clusterio hosts connected to it and allows the cluster to be remotely managed through WebSocket connections to it either by using the included web interface or the Clusterio ctl command line utility.
# Usage # Usage
@@ -35,7 +35,7 @@ For example, installing the Subspace Storage plugin:
npm install @clusterio/plugin-subspace_storage npm install @clusterio/plugin-subspace_storage
npx clusteriocontroller plugin add @clusterio/plugin-subspace_storage npx clusteriocontroller plugin add @clusterio/plugin-subspace_storage
Since the `plugin-list.json` is shared between controller, slave and ctl you usually only need to do this once per machine. Since the `plugin-list.json` is shared between controller, host and ctl you usually only need to do this once per machine.
#### `plugin remove <name>` #### `plugin remove <name>`
@@ -49,7 +49,7 @@ For example, uninstalling the Subspace Storage plugin:
npx clusteriocontroller plugin remove subspace_storage npx clusteriocontroller plugin remove subspace_storage
npm uninstall @clusterio/plugin-subspace_storage npm uninstall @clusterio/plugin-subspace_storage
Since the `plugin-list.json` is shared between controller, slave and ctl you usually only need to do this once per machine. Since the `plugin-list.json` is shared between controller, host and ctl you usually only need to do this once per machine.
#### `plugin list` #### `plugin list`
+19 -19
View File
@@ -3,7 +3,7 @@
/** /**
* Clusterio controller * Clusterio controller
* *
* Facilitates communication between slaves and control of the cluster * Facilitates communication between hosts and control of the cluster
* through WebSocet connections, and hosts a webserver for browser * through WebSocet connections, and hosts a webserver for browser
* interfaces and Prometheus statistics export. It is remotely controlled * interfaces and Prometheus statistics export. It is remotely controlled
* by {@link module:ctl/ctl}. * by {@link module:ctl/ctl}.
@@ -45,19 +45,19 @@ let controller;
void new libPrometheus.Gauge( void new libPrometheus.Gauge(
"clusterio_controller_slave_mapping", "clusterio_controller_host_mapping",
"Mapping of Slave ID to name", "Mapping of Host ID to name",
{ {
labels: ["slave_id", "slave_name"], labels: ["host_id", "host_name"],
callback: function(gauge) { callback: function(gauge) {
gauge.clear(); gauge.clear();
if (!controller || !controller.slaves) { if (!controller || !controller.hosts) {
return; return;
} }
for (let [id, slave] of controller.slaves) { for (let [id, host] of controller.hosts) {
gauge.labels({ gauge.labels({
slave_id: String(id), host_id: String(id),
slave_name: slave.name, host_name: host.name,
}).set(1); }).set(1);
}; };
}, },
@@ -66,9 +66,9 @@ void new libPrometheus.Gauge(
void new libPrometheus.Gauge( void new libPrometheus.Gauge(
"clusterio_controller_instance_mapping", "clusterio_controller_instance_mapping",
"Mapping of Instance ID to name and slave", "Mapping of Instance ID to name and host",
{ {
labels: ["instance_id", "instance_name", "slave_id"], labels: ["instance_id", "instance_name", "host_id"],
callback: function(gauge) { callback: function(gauge) {
gauge.clear(); gauge.clear();
if (!controller || !controller.instances) { if (!controller || !controller.instances) {
@@ -78,7 +78,7 @@ void new libPrometheus.Gauge(
gauge.labels({ gauge.labels({
instance_id: String(id), instance_id: String(id),
instance_name: String(instance.config.get("instance.name")), instance_name: String(instance.config.get("instance.name")),
slave_id: String(instance.config.get("instance.assigned_slave")), host_id: String(instance.config.get("instance.assigned_host")),
}).set(1); }).set(1);
} }
}, },
@@ -92,16 +92,16 @@ void new libPrometheus.Gauge(
); );
void new libPrometheus.Gauge( void new libPrometheus.Gauge(
"clusterio_controller_active_slaves", "clusterio_controller_active_hosts",
"How many slaves are currently connected to the controller", "How many hosts are currently connected to the controller",
{ callback: function(gauge) { gauge.set(controller.wsServer.slaveConnections.size); }} { callback: function(gauge) { gauge.set(controller.wsServer.hostConnections.size); }}
); );
void new libPrometheus.Gauge( void new libPrometheus.Gauge(
"clusterio_controller_connected_clients_count", "How many clients are currently connected to this controller", "clusterio_controller_connected_clients_count", "How many clients are currently connected to this controller",
{ {
labels: ["type"], callback: async function(gauge) { labels: ["type"], callback: async function(gauge) {
gauge.labels("slave").set(controller.wsServer.slaveConnections.size); gauge.labels("host").set(controller.wsServer.hostConnections.size);
gauge.labels("control").set(controller.wsServer.controlConnections.length); gauge.labels("control").set(controller.wsServer.controlConnections.length);
}, },
}, },
@@ -139,10 +139,10 @@ async function handleBootstrapCommand(args, controllerConfig) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log(user.createToken(controllerConfig.get("controller.auth_secret"))); console.log(user.createToken(controllerConfig.get("controller.auth_secret")));
} else if (subCommand === "generate-slave-token") { } else if (subCommand === "generate-host-token") {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log(jwt.sign( console.log(jwt.sign(
{ aud: "slave", slave: args.id }, { aud: "host", host: args.id },
Buffer.from(controllerConfig.get("controller.auth_secret"), "base64") Buffer.from(controllerConfig.get("controller.auth_secret"), "base64")
)); ));
@@ -217,8 +217,8 @@ async function initialize() {
yargs yargs
.command("create-admin <name>", "Create a cluster admin") .command("create-admin <name>", "Create a cluster admin")
.command("generate-user-token <name>", "Generate authentication token for the given user") .command("generate-user-token <name>", "Generate authentication token for the given user")
.command("generate-slave-token <id>", "Generate authentication token for the given slave", yargs => { .command("generate-host-token <id>", "Generate authentication token for the given host", yargs => {
yargs.positional("id", { describe: "ID of the slave", type: "number" }); yargs.positional("id", { describe: "ID of the host", type: "number" });
}) })
.command("create-ctl-config <name>", "Create clusterioctl config for the given user", yargs => { .command("create-ctl-config <name>", "Create clusterioctl config for the given user", yargs => {
yargs.option("output", { yargs.option("output", {
+11 -11
View File
@@ -31,33 +31,33 @@ class BaseConnection extends libLink.Link {
let instance = this._controller.instances.get(message.data.instance_id); let instance = this._controller.instances.get(message.data.instance_id);
if (!instance) { return; } if (!instance) { return; }
let slaveId = instance.config.get("instance.assigned_slave"); let hostId = instance.config.get("instance.assigned_host");
if (slaveId === null) { return; } if (hostId === null) { return; }
let connection = this._controller.wsServer.slaveConnections.get(slaveId); let connection = this._controller.wsServer.hostConnections.get(hostId);
if (!connection || connection.closing) { return; } if (!connection || connection.closing) { return; }
if (event.plugin && !connection.plugins.has(event.plugin)) { return; } if (event.plugin && !connection.plugins.has(event.plugin)) { return; }
event.send(connection, message.data); event.send(connection, message.data);
} }
async broadcastEventToSlaves(message, event) { async broadcastEventToHosts(message, event) {
for (let slaveConnection of this._controller.wsServer.slaveConnections.values()) { for (let hostConnection of this._controller.wsServer.hostConnections.values()) {
// Do not broadcast back to the source // Do not broadcast back to the source
if (slaveConnection === this) { continue; } if (hostConnection === this) { continue; }
if (slaveConnection.connector.closing) { continue; } if (hostConnection.connector.closing) { continue; }
if (event.plugin && !slaveConnection.plugins.has(event.plugin)) { continue; } if (event.plugin && !hostConnection.plugins.has(event.plugin)) { continue; }
event.send(slaveConnection, message.data); event.send(hostConnection, message.data);
} }
} }
async broadcastEventToInstance(message, event) { async broadcastEventToInstance(message, event) {
await this.broadcastEventToSlaves(message, event); await this.broadcastEventToHosts(message, event);
} }
async prepareDisconnectRequestHandler(message, request) { async prepareDisconnectRequestHandler(message, request) {
await libPlugin.invokeHook(this._controller.plugins, "onPrepareSlaveDisconnect", this); await libPlugin.invokeHook(this._controller.plugins, "onPrepareHostDisconnect", this);
this._disconnecting = true; this._disconnecting = true;
this.connector.setClosing(); this.connector.setClosing();
return await super.prepareDisconnectRequestHandler(message, request); return await super.prepareDisconnectRequestHandler(message, request);
+91 -91
View File
@@ -44,9 +44,9 @@ class ControlConnection extends BaseConnection {
*/ */
this.user = user; this.user = user;
this.slaveSubscriptions = { this.hostSubscriptions = {
all: false, all: false,
slave_ids: [], host_ids: [],
}; };
this.instanceSubscriptions = { this.instanceSubscriptions = {
@@ -78,7 +78,7 @@ class ControlConnection extends BaseConnection {
this.logSubscriptions = { this.logSubscriptions = {
all: false, all: false,
controller: false, controller: false,
slave_ids: [], host_ids: [],
instance_ids: [], instance_ids: [],
}; };
@@ -118,65 +118,65 @@ class ControlConnection extends BaseConnection {
this._controller.config.setProp(field, prop, value, "control"); this._controller.config.setProp(field, prop, value, "control");
} }
async listSlavesRequestHandler(message) { async listHostsRequestHandler(message) {
let list = []; let list = [];
for (let slave of this._controller.slaves.values()) { for (let host of this._controller.hosts.values()) {
list.push({ list.push({
agent: slave.agent, agent: host.agent,
version: slave.version, version: host.version,
id: slave.id, id: host.id,
name: slave.name, name: host.name,
public_address: slave.public_address || null, public_address: host.public_address || null,
connected: this._controller.wsServer.slaveConnections.has(slave.id), connected: this._controller.wsServer.hostConnections.has(host.id),
}); });
} }
return { list }; return { list };
} }
async setSlaveSubscriptionsRequestHandler(message) { async setHostSubscriptionsRequestHandler(message) {
this.slaveSubscriptions = message.data; this.hostSubscriptions = message.data;
} }
slaveUpdated(slave, update) { hostUpdated(host, update) {
if ( if (
this.slaveSubscriptions.all this.hostSubscriptions.all
|| this.slaveSubscriptions.slave_ids.includes(slave.id) || this.hostSubscriptions.host_ids.includes(host.id)
) { ) {
libLink.messages.slaveUpdate.send(this, update); libLink.messages.hostUpdate.send(this, update);
} }
} }
generateSlaveToken(slaveId) { generateHostToken(hostId) {
return jwt.sign( return jwt.sign(
{ aud: "slave", slave: slaveId }, { aud: "host", host: hostId },
Buffer.from(this._controller.config.get("controller.auth_secret"), "base64") Buffer.from(this._controller.config.get("controller.auth_secret"), "base64")
); );
} }
async generateSlaveTokenRequestHandler(message) { async generateHostTokenRequestHandler(message) {
let slaveId = message.data.slave_id; let hostId = message.data.host_id;
if (slaveId === null) { if (hostId === null) {
slaveId = Math.random() * 2**31 | 0; hostId = Math.random() * 2**31 | 0;
} }
return { token: this.generateSlaveToken(slaveId) }; return { token: this.generateHostToken(hostId) };
} }
async createSlaveConfigRequestHandler(message) { async createHostConfigRequestHandler(message) {
let slaveConfig = new libConfig.SlaveConfig("control"); let hostConfig = new libConfig.HostConfig("control");
await slaveConfig.init(); await hostConfig.init();
slaveConfig.set("slave.controller_url", this._controller.getControllerUrl()); hostConfig.set("host.controller_url", this._controller.getControllerUrl());
if (message.data.id !== null) { if (message.data.id !== null) {
slaveConfig.set("slave.id", message.data.id); hostConfig.set("host.id", message.data.id);
} }
if (message.data.name !== null) { if (message.data.name !== null) {
slaveConfig.set("slave.name", message.data.name); hostConfig.set("host.name", message.data.name);
} }
if (message.data.generate_token) { if (message.data.generate_token) {
this.user.checkPermission("core.slave.generate_token"); this.user.checkPermission("core.host.generate_token");
slaveConfig.set("slave.controller_token", this.generateSlaveToken(slaveConfig.get("slave.id"))); hostConfig.set("host.controller_token", this.generateHostToken(hostConfig.get("host.id")));
} }
return { serialized_config: slaveConfig.serialize() }; return { serialized_config: hostConfig.serialize() };
} }
async getInstanceRequestHandler(message) { async getInstanceRequestHandler(message) {
@@ -186,7 +186,7 @@ class ControlConnection extends BaseConnection {
return { return {
id, id,
name: instance.config.get("instance.name"), name: instance.config.get("instance.name"),
assigned_slave: instance.config.get("instance.assigned_slave"), assigned_host: instance.config.get("instance.assigned_host"),
game_port: instance.game_port || null, game_port: instance.game_port || null,
status: instance.status, status: instance.status,
}; };
@@ -198,7 +198,7 @@ class ControlConnection extends BaseConnection {
list.push({ list.push({
id: instance.config.get("instance.id"), id: instance.config.get("instance.id"),
name: instance.config.get("instance.name"), name: instance.config.get("instance.name"),
assigned_slave: instance.config.get("instance.assigned_slave"), assigned_host: instance.config.get("instance.assigned_host"),
game_port: instance.game_port || null, game_port: instance.game_port || null,
status: instance.status, status: instance.status,
}); });
@@ -218,14 +218,14 @@ class ControlConnection extends BaseConnection {
libLink.messages.instanceUpdate.send(this, { libLink.messages.instanceUpdate.send(this, {
id: instance.config.get("instance.id"), id: instance.config.get("instance.id"),
name: instance.config.get("instance.name"), name: instance.config.get("instance.name"),
assigned_slave: instance.config.get("instance.assigned_slave"), assigned_host: instance.config.get("instance.assigned_host"),
game_port: instance.game_port || null, game_port: instance.game_port || null,
status: instance.status, status: instance.status,
}); });
} }
} }
// XXX should probably add a hook for slave reuqests? // XXX should probably add a hook for host reuqests?
async createInstanceRequestHandler(message) { async createInstanceRequestHandler(message) {
let instanceConfig = new libConfig.InstanceConfig("controller"); let instanceConfig = new libConfig.InstanceConfig("controller");
await instanceConfig.load(message.data.serialized_config); await instanceConfig.load(message.data.serialized_config);
@@ -269,7 +269,7 @@ class ControlConnection extends BaseConnection {
async deleteInstanceRequestHandler(message, request) { async deleteInstanceRequestHandler(message, request) {
let instance = this._controller.getRequestInstance(message.data.instance_id); let instance = this._controller.getRequestInstance(message.data.instance_id);
if (instance.config.get("instance.assigned_slave") !== null) { if (instance.config.get("instance.assigned_host") !== null) {
await this.forwardRequestToInstance(message, request); await this.forwardRequestToInstance(message, request);
} }
this._controller.instances.delete(message.data.instance_id); this._controller.instances.delete(message.data.instance_id);
@@ -288,13 +288,13 @@ class ControlConnection extends BaseConnection {
} }
async updateInstanceConfig(instance) { async updateInstanceConfig(instance) {
let slaveId = instance.config.get("instance.assigned_slave"); let hostId = instance.config.get("instance.assigned_host");
if (slaveId !== null) { if (hostId !== null) {
let connection = this._controller.wsServer.slaveConnections.get(slaveId); let connection = this._controller.wsServer.hostConnections.get(hostId);
if (connection) { if (connection) {
await libLink.messages.assignInstance.send(connection, { await libLink.messages.assignInstance.send(connection, {
instance_id: instance.config.get("instance.id"), instance_id: instance.config.get("instance.id"),
serialized_config: instance.config.serialize("slave"), serialized_config: instance.config.serialize("host"),
}); });
} }
} }
@@ -302,8 +302,8 @@ class ControlConnection extends BaseConnection {
async setInstanceConfigFieldRequestHandler(message) { async setInstanceConfigFieldRequestHandler(message) {
let instance = this._controller.getRequestInstance(message.data.instance_id); let instance = this._controller.getRequestInstance(message.data.instance_id);
if (message.data.field === "instance.assigned_slave") { if (message.data.field === "instance.assigned_host") {
throw new libErrors.RequestError("instance.assigned_slave must be set through the assign-slave interface"); throw new libErrors.RequestError("instance.assigned_host must be set through the assign-host interface");
} }
if (message.data.field === "instance.id") { if (message.data.field === "instance.id") {
@@ -323,38 +323,38 @@ class ControlConnection extends BaseConnection {
} }
async assignInstanceCommandRequestHandler(message, request) { async assignInstanceCommandRequestHandler(message, request) {
let { slave_id, instance_id } = message.data; let { host_id, instance_id } = message.data;
let instance = this._controller.getRequestInstance(instance_id); let instance = this._controller.getRequestInstance(instance_id);
// Check if target slave is connected // Check if target host is connected
let newSlaveConnection; let newHostConnection;
if (slave_id !== null) { if (host_id !== null) {
newSlaveConnection = this._controller.wsServer.slaveConnections.get(slave_id); newHostConnection = this._controller.wsServer.hostConnections.get(host_id);
if (!newSlaveConnection) { if (!newHostConnection) {
// The case of the slave not getting the assign instance message // The case of the host not getting the assign instance message
// still have to be handled, so it's not a requirement that the // still have to be handled, so it's not a requirement that the
// target slave be connected to the controller while doing the // target host be connected to the controller while doing the
// assignment, but it is IMHO a better user experience if this // assignment, but it is IMHO a better user experience if this
// is the case. // is the case.
throw new libErrors.RequestError("Target slave is not connected to the controller"); throw new libErrors.RequestError("Target host is not connected to the controller");
} }
} }
// Unassign from currently assigned slave if it is connected. // Unassign from currently assigned host if it is connected.
let currentAssignedSlave = instance.config.get("instance.assigned_slave"); let currentAssignedHost = instance.config.get("instance.assigned_host");
if (currentAssignedSlave !== null && slave_id !== currentAssignedSlave) { if (currentAssignedHost !== null && host_id !== currentAssignedHost) {
let oldSlaveConnection = this._controller.wsServer.slaveConnections.get(currentAssignedSlave); let oldHostConnection = this._controller.wsServer.hostConnections.get(currentAssignedHost);
if (oldSlaveConnection && !oldSlaveConnection.connector.closing) { if (oldHostConnection && !oldHostConnection.connector.closing) {
await libLink.messages.unassignInstance.send(oldSlaveConnection, { instance_id }); await libLink.messages.unassignInstance.send(oldHostConnection, { instance_id });
} }
} }
// Assign to target // Assign to target
instance.config.set("instance.assigned_slave", slave_id); instance.config.set("instance.assigned_host", host_id);
if (slave_id !== null) { if (host_id !== null) {
await libLink.messages.assignInstance.send(newSlaveConnection, { await libLink.messages.assignInstance.send(newHostConnection, {
instance_id, instance_id,
serialized_config: instance.config.serialize("slave"), serialized_config: instance.config.serialize("host"),
}); });
} else { } else {
instance.status = "unassigned"; instance.status = "unassigned";
@@ -383,7 +383,7 @@ class ControlConnection extends BaseConnection {
let ready = new Promise((resolve, reject) => { let ready = new Promise((resolve, reject) => {
stream.events.on("source", resolve); stream.events.on("source", resolve);
stream.events.on("timeout", () => reject( stream.events.on("timeout", () => reject(
new libErrors.RequestError("Timed out establishing stream from slave") new libErrors.RequestError("Timed out establishing stream from host")
)); ));
}); });
ready.catch(() => {}); ready.catch(() => {});
@@ -411,29 +411,29 @@ class ControlConnection extends BaseConnection {
} }
let sourceInstance = this._controller.getRequestInstance(instance_id); let sourceInstance = this._controller.getRequestInstance(instance_id);
let targetInstance = this._controller.getRequestInstance(target_instance_id); let targetInstance = this._controller.getRequestInstance(target_instance_id);
let sourceSlaveId = sourceInstance.config.get("instance.assigned_slave"); let sourceHostId = sourceInstance.config.get("instance.assigned_host");
let targetSlaveId = targetInstance.config.get("instance.assigned_slave"); let targetHostId = targetInstance.config.get("instance.assigned_host");
if (sourceSlaveId === null) { if (sourceHostId === null) {
throw new libErrors.RequestError("Source instance is not assigned a slave"); throw new libErrors.RequestError("Source instance is not assigned a host");
} }
if (targetSlaveId === null) { if (targetHostId === null) {
throw new libErrors.RequestError("Target instance is not assigned a slave"); throw new libErrors.RequestError("Target instance is not assigned a host");
} }
// Let slave handle request if source and target is on the same slave. // Let host handle request if source and target is on the same host.
if (sourceSlaveId === targetSlaveId) { if (sourceHostId === targetHostId) {
return await this.forwardRequestToInstance(message, request); return await this.forwardRequestToInstance(message, request);
} }
// Check connectivity // Check connectivity
let sourceSlaveConnection = this._controller.wsServer.slaveConnections.get(sourceSlaveId); let sourceHostConnection = this._controller.wsServer.hostConnections.get(sourceHostId);
if (!sourceSlaveConnection || sourceSlaveConnection.closing) { if (!sourceHostConnection || sourceHostConnection.closing) {
throw new libErrors.RequestError("Source slave is not connected to the controller"); throw new libErrors.RequestError("Source host is not connected to the controller");
} }
let targetSlaveConnection = this._controller.wsServer.slaveConnections.get(targetSlaveId); let targetHostConnection = this._controller.wsServer.hostConnections.get(targetHostId);
if (!targetSlaveConnection || targetSlaveConnection.closing) { if (!targetHostConnection || targetHostConnection.closing) {
throw new libErrors.RequestError("Target slave is not connected to the controller"); throw new libErrors.RequestError("Target host is not connected to the controller");
} }
// Create stream to proxy from target to source // Create stream to proxy from target to source
@@ -448,8 +448,8 @@ class ControlConnection extends BaseConnection {
// Ignore errors if not listening for them to avoid crash. // Ignore errors if not listening for them to avoid crash.
stream.events.on("error", () => { /* ignore */ }); stream.events.on("error", () => { /* ignore */ });
// Establish push from source slave to stream, this is done first to // Establish push from source host to stream, this is done first to
// ensure the file size is known prior to the target slave pull. // ensure the file size is known prior to the target host pull.
await Promise.all([ await Promise.all([
this._controller.forwardRequestToInstance(libLink.messages.pushSave, { this._controller.forwardRequestToInstance(libLink.messages.pushSave, {
instance_id, instance_id,
@@ -459,7 +459,7 @@ class ControlConnection extends BaseConnection {
events.once(stream.events, "source"), events.once(stream.events, "source"),
]); ]);
// Establish pull from target slave to stream and wait for completion. // Establish pull from target host to stream and wait for completion.
let result = await this._controller.forwardRequestToInstance(libLink.messages.pullSave, { let result = await this._controller.forwardRequestToInstance(libLink.messages.pullSave, {
instance_id: target_instance_id, instance_id: target_instance_id,
stream_id: stream.id, stream_id: stream.id,
@@ -668,8 +668,8 @@ class ControlConnection extends BaseConnection {
} }
updateLogSubscriptions() { updateLogSubscriptions() {
let { all, controller, slave_ids, instance_ids } = this.logSubscriptions; let { all, controller, host_ids, instance_ids } = this.logSubscriptions;
if (all || controller || slave_ids.length || instance_ids.length) { if (all || controller || host_ids.length || instance_ids.length) {
if (!this.logTransport) { if (!this.logTransport) {
this.logTransport = new libLoggingUtils.LinkTransport({ link: this }); this.logTransport = new libLoggingUtils.LinkTransport({ link: this });
this._controller.clusterLogger.add(this.logTransport); this._controller.clusterLogger.add(this.logTransport);
@@ -684,10 +684,10 @@ class ControlConnection extends BaseConnection {
async queryLogRequestHandler(message) { async queryLogRequestHandler(message) {
let observeDuration = queryLogTime.startTimer(); let observeDuration = queryLogTime.startTimer();
let { all, controller, slave_ids, instance_ids } = message.data; let { all, controller, host_ids, instance_ids } = message.data;
let log; let log;
if (!all && controller && !slave_ids.length && !instance_ids.length) { if (!all && controller && !host_ids.length && !instance_ids.length) {
log = await this._controller.queryControllerLog(message.data); log = await this._controller.queryControllerLog(message.data);
} else { } else {
log = await this._controller.queryClusterLog(message.data); log = await this._controller.queryClusterLog(message.data);
@@ -886,7 +886,7 @@ class ControlConnection extends BaseConnection {
user.isAdmin = admin; user.isAdmin = admin;
this._controller.userUpdated(user); this._controller.userUpdated(user);
this.broadcastEventToSlaves({ data: { name, admin }}, libLink.messages.adminlistUpdate); this.broadcastEventToHosts({ data: { name, admin }}, libLink.messages.adminlistUpdate);
} }
async setUserBannedRequestHandler(message) { async setUserBannedRequestHandler(message) {
@@ -904,7 +904,7 @@ class ControlConnection extends BaseConnection {
user.isBanned = banned; user.isBanned = banned;
user.banReason = reason; user.banReason = reason;
this._controller.userUpdated(user); this._controller.userUpdated(user);
this.broadcastEventToSlaves({ data: { name, banned, reason }}, libLink.messages.banlistUpdate); this.broadcastEventToHosts({ data: { name, banned, reason }}, libLink.messages.banlistUpdate);
} }
async setUserWhitelistedRequestHandler(message) { async setUserWhitelistedRequestHandler(message) {
@@ -921,7 +921,7 @@ class ControlConnection extends BaseConnection {
user.isWhitelisted = whitelisted; user.isWhitelisted = whitelisted;
this._controller.userUpdated(user); this._controller.userUpdated(user);
this.broadcastEventToSlaves({ data: { name, whitelisted }}, libLink.messages.whitelistUpdate); this.broadcastEventToHosts({ data: { name, whitelisted }}, libLink.messages.whitelistUpdate);
} }
async deleteUserRequestHandler(message) { async deleteUserRequestHandler(message) {
@@ -935,13 +935,13 @@ class ControlConnection extends BaseConnection {
this._controller.userUpdated(user); this._controller.userUpdated(user);
if (user.is_admin) { if (user.is_admin) {
this.broadcastEventToSlaves({ data: { name, admin: false }}, libLink.messages.adminlistUpdate); this.broadcastEventToHosts({ data: { name, admin: false }}, libLink.messages.adminlistUpdate);
} }
if (user.is_whitelisted) { if (user.is_whitelisted) {
this.broadcastEventToSlaves({ data: { name, whitelisted: false }}, libLink.messages.whitelistUpdate); this.broadcastEventToHosts({ data: { name, whitelisted: false }}, libLink.messages.whitelistUpdate);
} }
if (user.is_banned) { if (user.is_banned) {
this.broadcastEventToSlaves({ data: { name, banned: false, reason: "" }}, libLink.messages.banlistUpdate); this.broadcastEventToHosts({ data: { name, banned: false, reason: "" }}, libLink.messages.banlistUpdate);
} }
} }
+26 -26
View File
@@ -61,10 +61,10 @@ class Controller {
this.app.locals.streams = new Map(); this.app.locals.streams = new Map();
/** /**
* Mapping of slave id to slave info * Mapping of host id to host info
* @type {Map<number, Object>} * @type {Map<number, Object>}
*/ */
this.slaves = null; this.hosts = null;
/** /**
* Mapping of instance id to instance info * Mapping of instance id to instance info
@@ -145,7 +145,7 @@ class Controller {
return (await Promise.all([ return (await Promise.all([
libFileOps.directorySize(path.join(this.logDirectory, "cluster")), libFileOps.directorySize(path.join(this.logDirectory, "cluster")),
libFileOps.directorySize(path.join(this.logDirectory, "controller")), libFileOps.directorySize(path.join(this.logDirectory, "controller")),
libFileOps.directorySize(path.join(this.logDirectory, "slave")), libFileOps.directorySize(path.join(this.logDirectory, "host")),
])).reduce((a, v) => a + v, 0); ])).reduce((a, v) => a + v, 0);
} }
@@ -192,7 +192,7 @@ class Controller {
let databaseDirectory = this.config.get("controller.database_directory"); let databaseDirectory = this.config.get("controller.database_directory");
await fs.ensureDir(databaseDirectory); await fs.ensureDir(databaseDirectory);
this.slaves = await Controller.loadSlaves(path.join(databaseDirectory, "slaves.json")); this.hosts = await Controller.loadHosts(path.join(databaseDirectory, "hosts.json"));
this.instances = await Controller.loadInstances(path.join(databaseDirectory, "instances.json")); this.instances = await Controller.loadInstances(path.join(databaseDirectory, "instances.json"));
this.modPacks = await Controller.loadModPacks(path.join(databaseDirectory, "mod-packs.json")); this.modPacks = await Controller.loadModPacks(path.join(databaseDirectory, "mod-packs.json"));
this.userManager = new UserManager(this.config); this.userManager = new UserManager(this.config);
@@ -313,8 +313,8 @@ class Controller {
} }
let databaseDirectory = this.config.get("controller.database_directory"); let databaseDirectory = this.config.get("controller.database_directory");
if (this.slaves) { if (this.hosts) {
await Controller.saveSlaves(path.join(databaseDirectory, "slaves.json"), this.slaves); await Controller.saveHosts(path.join(databaseDirectory, "hosts.json"), this.hosts);
} }
if (this.instances) { if (this.instances) {
@@ -344,7 +344,7 @@ class Controller {
logger.info("Goodbye"); logger.info("Goodbye");
} }
static async loadSlaves(filePath) { static async loadHosts(filePath) {
let serialized; let serialized;
try { try {
serialized = JSON.parse(await fs.readFile(filePath)); serialized = JSON.parse(await fs.readFile(filePath));
@@ -365,8 +365,8 @@ class Controller {
return new Map(serialized); return new Map(serialized);
} }
static async saveSlaves(filePath, slaves) { static async saveHosts(filePath, hosts) {
await libFileOps.safeOutputFile(filePath, JSON.stringify([...slaves.entries()], null, 4)); await libFileOps.safeOutputFile(filePath, JSON.stringify([...hosts.entries()], null, 4));
} }
static async loadInstances(filePath) { static async loadInstances(filePath) {
@@ -378,7 +378,7 @@ class Controller {
for (let serializedConfig of serialized) { for (let serializedConfig of serialized) {
let instanceConfig = new libConfig.InstanceConfig("controller"); let instanceConfig = new libConfig.InstanceConfig("controller");
await instanceConfig.load(serializedConfig); await instanceConfig.load(serializedConfig);
let status = instanceConfig.get("instance.assigned_slave") === null ? "unassigned" : "unknown"; let status = instanceConfig.get("instance.assigned_host") === null ? "unassigned" : "unknown";
let instance = { config: instanceConfig, status }; let instance = { config: instanceConfig, status };
instances.set(instanceConfig.get("instance.id"), instance); instances.set(instanceConfig.get("instance.id"), instance);
} }
@@ -543,14 +543,14 @@ class Controller {
} }
} }
slaveUpdated(slave) { hostUpdated(host) {
let update = { let update = {
agent: slave.agent, agent: host.agent,
version: slave.version, version: host.version,
id: slave.id, id: host.id,
name: slave.name, name: host.name,
public_address: slave.public_address || null, public_address: host.public_address || null,
connected: this.wsServer.slaveConnections.has(slave.id), connected: this.wsServer.hostConnections.has(host.id),
}; };
for (let controlConnection of this.wsServer.controlConnections) { for (let controlConnection of this.wsServer.controlConnections) {
@@ -558,7 +558,7 @@ class Controller {
continue; continue;
} }
controlConnection.slaveUpdated(slave, update); controlConnection.hostUpdated(host, update);
} }
} }
@@ -801,9 +801,9 @@ class Controller {
} }
/** /**
* Forward the given request to the slave contaning the instance * Forward the given request to the host contaning the instance
* *
* Finds the slave which the instance with the given instance ID is * Finds the host which the instance with the given instance ID is
* currently assigned to and forwards the request to that instance. * currently assigned to and forwards the request to that instance.
* The request data must have an instance_id parameter. * The request data must have an instance_id parameter.
* *
@@ -815,17 +815,17 @@ class Controller {
*/ */
async forwardRequestToInstance(request, data) { async forwardRequestToInstance(request, data) {
let instance = this.getRequestInstance(data.instance_id); let instance = this.getRequestInstance(data.instance_id);
let slaveId = instance.config.get("instance.assigned_slave"); let hostId = instance.config.get("instance.assigned_host");
if (slaveId === null) { if (hostId === null) {
throw new libErrors.RequestError("Instance is not assigned to a slave"); throw new libErrors.RequestError("Instance is not assigned to a host");
} }
let connection = this.wsServer.slaveConnections.get(slaveId); let connection = this.wsServer.hostConnections.get(hostId);
if (!connection) { if (!connection) {
throw new libErrors.RequestError("Slave containing instance is not connected"); throw new libErrors.RequestError("Host containing instance is not connected");
} }
if (request.plugin && !connection.plugins.has(request.plugin)) { if (request.plugin && !connection.plugins.has(request.plugin)) {
throw new libErrors.RequestError(`Slave containing instance does not have ${request.plugin} plugin`); throw new libErrors.RequestError(`Host containing instance does not have ${request.plugin} plugin`);
} }
return await request.send(connection, data); return await request.send(connection, data);
@@ -9,14 +9,14 @@ const BaseConnection = require("./BaseConnection");
/** /**
* Represents the connection to a slave * Represents the connection to a host
* *
* @extends module:controller/src/BaseConnection * @extends module:controller/src/BaseConnection
* @alias module:controller/src/SlaveConnection * @alias module:controller/src/HostConnection
*/ */
class SlaveConnection extends BaseConnection { class HostConnection extends BaseConnection {
constructor(registerData, connector, controller) { constructor(registerData, connector, controller) {
super("slave", connector, controller); super("host", connector, controller);
this._agent = registerData.agent; this._agent = registerData.agent;
this._id = registerData.id; this._id = registerData.id;
@@ -25,7 +25,7 @@ class SlaveConnection extends BaseConnection {
this.plugins = new Map(Object.entries(registerData.plugins)); this.plugins = new Map(Object.entries(registerData.plugins));
this._checkPluginVersions(); this._checkPluginVersions();
this._controller.slaves.set(this._id, { this._controller.hosts.set(this._id, {
agent: this._agent, agent: this._agent,
id: this._id, id: this._id,
name: this._name, name: this._name,
@@ -38,15 +38,15 @@ class SlaveConnection extends BaseConnection {
// eslint-disable-next-line no-loop-func // eslint-disable-next-line no-loop-func
this.connector.on(event, () => { this.connector.on(event, () => {
for (let plugin of this._controller.plugins.values()) { for (let plugin of this._controller.plugins.values()) {
plugin.onSlaveConnectionEvent(this, event); plugin.onHostConnectionEvent(this, event);
} }
}); });
} }
this.connector.on("close", () => { this.connector.on("close", () => {
// Update status to unknown for instances on this slave. // Update status to unknown for instances on this host.
for (let instance of this._controller.instances.values()) { for (let instance of this._controller.instances.values()) {
if (instance.config.get("instance.assigned_slave") !== this._id) { if (instance.config.get("instance.assigned_host") !== this._id) {
continue; continue;
} }
@@ -63,13 +63,13 @@ class SlaveConnection extends BaseConnection {
for (let [name, version] of this.plugins) { for (let [name, version] of this.plugins) {
let info = pluginInfos.get(name); let info = pluginInfos.get(name);
if (!info) { if (!info) {
logger.warn(`Slave ${this._name} has plugin ${name} ${version} which the controller does not have`); logger.warn(`Host ${this._name} has plugin ${name} ${version} which the controller does not have`);
continue; continue;
} }
if (info.version !== version) { if (info.version !== version) {
logger.warn( logger.warn(
`Slave ${this._name} has plugin ${name} ${version} which does not match the version of this ` + `Host ${this._name} has plugin ${name} ${version} which does not match the version of this ` +
`plugin on the controller (${info.version})` `plugin on the controller (${info.version})`
); );
} }
@@ -77,15 +77,15 @@ class SlaveConnection extends BaseConnection {
for (let [name, info] of pluginInfos) { for (let [name, info] of pluginInfos) {
if (!this.plugins.has(name)) { if (!this.plugins.has(name)) {
logger.warn(`Slave ${this._name} is missing plugin ${name} ${info.version}`); logger.warn(`Host ${this._name} is missing plugin ${name} ${info.version}`);
} }
} }
} }
/** /**
* ID of the slave this connection is connected to * ID of the host this connection is connected to
* *
* @returns {number} slave ID. * @returns {number} host ID.
*/ */
get id() { get id() {
return this._id; return this._id;
@@ -95,17 +95,17 @@ class SlaveConnection extends BaseConnection {
let instance = this._controller.instances.get(message.data.instance_id); let instance = this._controller.instances.get(message.data.instance_id);
// It's possible to get updates from an instance that does not exist // It's possible to get updates from an instance that does not exist
// or is not assigned to the slave it originated from if it was // or is not assigned to the host it originated from if it was
// reassigned or deleted while the connection to the slave it was // reassigned or deleted while the connection to the host it was
// originally on was down at the time. // originally on was down at the time.
if (!instance || instance.config.get("instance.assigned_slave") !== this._id) { if (!instance || instance.config.get("instance.assigned_host") !== this._id) {
logger.warn(`Got bogus update for instance id ${message.data.instance_id}`); logger.warn(`Got bogus update for instance id ${message.data.instance_id}`);
return; return;
} }
// We may receive status changed where the status hasn't changed // We may receive status changed where the status hasn't changed
// from our perspective if the connection was down at the time it // from our perspective if the connection was down at the time it
// changed. Slaves also send status updates on assignInstance which // changed. Hosts also send status updates on assignInstance which
// for hacky reason is also used to push config changes and // for hacky reason is also used to push config changes and
// restablish status after a connection loss. // restablish status after a connection loss.
if (instance.status === message.data.status) { if (instance.status === message.data.status) {
@@ -123,23 +123,23 @@ class SlaveConnection extends BaseConnection {
async updateInstancesRequestHandler(message) { async updateInstancesRequestHandler(message) {
// Push updated instance configs // Push updated instance configs
for (let instance of this._controller.instances.values()) { for (let instance of this._controller.instances.values()) {
if (instance.config.get("instance.assigned_slave") === this._id) { if (instance.config.get("instance.assigned_host") === this._id) {
await libLink.messages.assignInstance.send(this, { await libLink.messages.assignInstance.send(this, {
instance_id: instance.config.get("instance.id"), instance_id: instance.config.get("instance.id"),
serialized_config: instance.config.serialize("slave"), serialized_config: instance.config.serialize("host"),
}); });
} }
} }
// Assign instances the slave has but controller does not // Assign instances the host has but controller does not
for (let instanceData of message.data.instances) { for (let instanceData of message.data.instances) {
let instanceConfig = new libConfig.InstanceConfig("controller"); let instanceConfig = new libConfig.InstanceConfig("controller");
await instanceConfig.load(instanceData.serialized_config, "slave"); await instanceConfig.load(instanceData.serialized_config, "host");
let controllerInstance = this._controller.instances.get(instanceConfig.get("instance.id")); let controllerInstance = this._controller.instances.get(instanceConfig.get("instance.id"));
if (controllerInstance) { if (controllerInstance) {
// Check if this instance is assigned somewhere else. // Check if this instance is assigned somewhere else.
if (controllerInstance.config.get("instance.assigned_slave") !== this._id) { if (controllerInstance.config.get("instance.assigned_host") !== this._id) {
await libLink.messages.unassignInstance.send(this, { await libLink.messages.unassignInstance.send(this, {
instance_id: controllerInstance.config.get("instance.id"), instance_id: controllerInstance.config.get("instance.id"),
}); });
@@ -159,13 +159,13 @@ class SlaveConnection extends BaseConnection {
continue; continue;
} }
instanceConfig.set("instance.assigned_slave", this._id); instanceConfig.set("instance.assigned_host", this._id);
let newInstance = { config: instanceConfig, status: instanceData.status }; let newInstance = { config: instanceConfig, status: instanceData.status };
this._controller.instances.set(instanceConfig.get("instance.id"), newInstance); this._controller.instances.set(instanceConfig.get("instance.id"), newInstance);
this._controller.addInstanceHooks(newInstance); this._controller.addInstanceHooks(newInstance);
await libLink.messages.assignInstance.send(this, { await libLink.messages.assignInstance.send(this, {
instance_id: instanceConfig.get("instance.id"), instance_id: instanceConfig.get("instance.id"),
serialized_config: instanceConfig.serialize("slave"), serialized_config: instanceConfig.serialize("host"),
}); });
await libPlugin.invokeHook(this._controller.plugins, "onInstanceStatusChanged", newInstance, null); await libPlugin.invokeHook(this._controller.plugins, "onInstanceStatusChanged", newInstance, null);
} }
@@ -197,8 +197,8 @@ class SlaveConnection extends BaseConnection {
async logMessageEventHandler(message) { async logMessageEventHandler(message) {
this._controller.clusterLogger.log({ this._controller.clusterLogger.log({
...message.data.info, ...message.data.info,
slave_id: this._id, host_id: this._id,
slave_name: this._name, host_name: this._name,
}); });
} }
@@ -224,4 +224,4 @@ class SlaveConnection extends BaseConnection {
} }
} }
module.exports = SlaveConnection; module.exports = HostConnection;
+21 -21
View File
@@ -9,7 +9,7 @@ const libSchema = require("@clusterio/lib/schema");
const ControlConnection = require("./ControlConnection"); const ControlConnection = require("./ControlConnection");
const packageVersion = require("../package").version; const packageVersion = require("../package").version;
const SlaveConnection = require("./SlaveConnection"); const HostConnection = require("./HostConnection");
const WsServerConnector = require("./WsServerConnector"); const WsServerConnector = require("./WsServerConnector");
@@ -41,8 +41,8 @@ class WsServer {
/** @type {Array<module:controller/src/ControlConnection>} */ /** @type {Array<module:controller/src/ControlConnection>} */
this.controlConnections = []; this.controlConnections = [];
/** @type {Map<number, module:controller/src/SlaveConnection>} */ /** @type {Map<number, module:controller/src/HostConnection>} */
this.slaveConnections = new Map(); this.hostConnections = new Map();
/** @type {Map<number, module:controller/src/WsServerConnector>} */ /** @type {Map<number, module:controller/src/WsServerConnector>} */
this.activeConnectors = new Map(); this.activeConnectors = new Map();
/** @type {Set<module:ws>} */ /** @type {Set<module:ws>} */
@@ -68,8 +68,8 @@ class WsServer {
disconnectTasks.push(controlConnection.disconnect(1001, "Server Quit")); disconnectTasks.push(controlConnection.disconnect(1001, "Server Quit"));
} }
for (let slaveConnection of this.slaveConnections.values()) { for (let hostConnection of this.hostConnections.values()) {
disconnectTasks.push(slaveConnection.disconnect(1001, "Server Quit")); disconnectTasks.push(hostConnection.disconnect(1001, "Server Quit"));
} }
logger.info(`WsServer | Waiting for ${disconnectTasks.length} connectors to close`); logger.info(`WsServer | Waiting for ${disconnectTasks.length} connectors to close`);
@@ -212,15 +212,15 @@ ${err.stack}`
// Validate token // Validate token
let user; let user;
try { try {
if (type === "register_slave") { if (type === "register_host") {
let tokenPayload = jwt.verify( let tokenPayload = jwt.verify(
data.token, data.token,
Buffer.from(this.controller.config.get("controller.auth_secret"), "base64"), Buffer.from(this.controller.config.get("controller.auth_secret"), "base64"),
{ audience: "slave" } { audience: "host" }
); );
if (tokenPayload.slave !== data.id) { if (tokenPayload.host !== data.id) {
throw new Error("missmatched slave id"); throw new Error("missmatched host id");
} }
} else if (type === "register_control") { } else if (type === "register_control") {
@@ -262,34 +262,34 @@ ${err.stack}`
}); });
let additionalReadyData = {}; let additionalReadyData = {};
if (type === "register_slave") { if (type === "register_host") {
let connection = this.slaveConnections.get(data.id); let connection = this.hostConnections.get(data.id);
if (connection) { if (connection) {
logger.verbose(`WsServer | disconnecting existing connection for slave ${data.id}`); logger.verbose(`WsServer | disconnecting existing connection for host ${data.id}`);
await connection.disconnect(1008, "Registered from another connection"); await connection.disconnect(1008, "Registered from another connection");
} }
logger.info( logger.info(
`WsServer | registered slave ${data.name} (${data.id}) using agent ${data.agent} ${data.version}` `WsServer | registered host ${data.name} (${data.id}) using agent ${data.agent} ${data.version}`
); );
if (data.agent === "Clusterio Slave" && data.version !== packageVersion) { if (data.agent === "Clusterio Host" && data.version !== packageVersion) {
logger.warn( logger.warn(
`Host ${data.name} (${data.id}) connected using version ${data.version} which does not match the ` + `Host ${data.name} (${data.id}) connected using version ${data.version} which does not match the ` +
`version of the controller is currently running (${packageVersion}). It may not work as expected.` `version of the controller is currently running (${packageVersion}). It may not work as expected.`
); );
} }
connection = new SlaveConnection(data, connector, this.controller); connection = new HostConnection(data, connector, this.controller);
connector.on("close", () => { connector.on("close", () => {
if (this.slaveConnections.get(data.id) === connection) { if (this.hostConnections.get(data.id) === connection) {
this.slaveConnections.delete(data.id); this.hostConnections.delete(data.id);
this.controller.slaveUpdated(this.controller.slaves.get(data.id)); this.controller.hostUpdated(this.controller.hosts.get(data.id));
} else { } else {
logger.warn("Unlisted SlaveConnection closed"); logger.warn("Unlisted HostConnection closed");
} }
}); });
this.slaveConnections.set(data.id, connection); this.hostConnections.set(data.id, connection);
this.controller.slaveUpdated(this.controller.slaves.get(data.id)); this.controller.hostUpdated(this.controller.hosts.get(data.id));
} else if (type === "register_control") { } else if (type === "register_control") {
+11 -11
View File
@@ -49,14 +49,14 @@ async function getMetrics(req, res, next) {
let requests = []; let requests = [];
let timeout = req.app.locals.controller.config.get("controller.metrics_timeout") * 1000; let timeout = req.app.locals.controller.config.get("controller.metrics_timeout") * 1000;
for (let slaveConnection of req.app.locals.controller.wsServer.slaveConnections.values()) { for (let hostConnection of req.app.locals.controller.wsServer.hostConnections.values()) {
if (!slaveConnection.connected) { if (!hostConnection.connected) {
continue; continue;
} }
requests.push(libHelpers.timeout( requests.push(libHelpers.timeout(
libLink.messages.getMetrics.send(slaveConnection).catch(err => { libLink.messages.getMetrics.send(hostConnection).catch(err => {
if (!(err instanceof libErrors.SessionLost)) { if (!(err instanceof libErrors.SessionLost)) {
logger.error(`Unexpected error gathering metrics from slave:\n${err.stack}`); logger.error(`Unexpected error gathering metrics from host:\n${err.stack}`);
} }
return null; return null;
}), }),
@@ -80,7 +80,7 @@ async function getMetrics(req, res, next) {
resultMap.set(result.metric.name, result); resultMap.set(result.metric.name, result);
} else { } else {
// Merge metrics received by multiple slaves // Merge metrics received by multiple hosts
mergeSamples(resultMap.get(result.metric.name), result); mergeSamples(resultMap.get(result.metric.name), result);
} }
} }
@@ -123,7 +123,7 @@ function getPlugins(req, res) {
res.send(plugins); res.send(plugins);
} }
function validateSlaveToken(req, res, next) { function validateHostToken(req, res, next) {
let token = req.header("x-access-token"); let token = req.header("x-access-token");
if (!token) { if (!token) {
res.sendStatus(401); res.sendStatus(401);
@@ -134,7 +134,7 @@ function validateSlaveToken(req, res, next) {
jwt.verify( jwt.verify(
token, token,
Buffer.from(req.app.locals.controller.config.get("controller.auth_secret"), "base64"), Buffer.from(req.app.locals.controller.config.get("controller.auth_secret"), "base64"),
{ audience: "slave" } { audience: "host" }
); );
} catch (err) { } catch (err) {
@@ -356,7 +356,7 @@ async function uploadSave(req, res) {
let timeout = new Promise((_, reject) => { let timeout = new Promise((_, reject) => {
proxyStream.events.on("timeout", () => { proxyStream.events.on("timeout", () => {
stream.resume(); stream.resume();
reject(new Error("Timed out establishing stream to slave")); reject(new Error("Timed out establishing stream to host"));
}); });
}); });
@@ -609,7 +609,7 @@ function addRouteHandlers(app) {
app.get("/metrics", (req, res, next) => getMetrics(req, res, next).catch(next)); app.get("/metrics", (req, res, next) => getMetrics(req, res, next).catch(next));
app.get("/api/plugins", getPlugins); app.get("/api/plugins", getPlugins);
app.put("/api/upload-export", app.put("/api/upload-export",
validateSlaveToken, validateHostToken,
(req, res, next) => uploadExport(req, res).catch(next) (req, res, next) => uploadExport(req, res).catch(next)
); );
app.put("/api/stream/:id", (req, res, next) => putStream(req, res).catch(next)); app.put("/api/stream/:id", (req, res, next) => putStream(req, res).catch(next));
@@ -628,8 +628,8 @@ function addRouteHandlers(app) {
const webRoutes = [ const webRoutes = [
"/", "/",
"/controller", "/controller",
"/slaves", "/hosts",
"/slaves/:id/view", "/hosts/:id/view",
"/instances", "/instances",
"/instances/:id/view", "/instances/:id/view",
"/mods", "/mods",
+51 -51
View File
@@ -117,11 +117,11 @@ async function execController(args) {
return await execFile(path.join("node_modules", ".bin", `clusteriocontroller${scriptExt}`), args); return await execFile(path.join("node_modules", ".bin", `clusteriocontroller${scriptExt}`), args);
} }
async function execSlave(args) { async function execHost(args) {
if (dev) { if (dev) {
return await execFile("node", [path.join("packages", "slave"), ...args]); return await execFile("node", [path.join("packages", "host"), ...args]);
} }
return await execFile(path.join("node_modules", ".bin", `clusterioslave${scriptExt}`), args); return await execFile(path.join("node_modules", ".bin", `clusteriohost${scriptExt}`), args);
} }
async function execCtl(args) { async function execCtl(args) {
@@ -131,7 +131,7 @@ async function execCtl(args) {
return await execFile(path.join("node_modules", ".bin", `clusterioctl${scriptExt}`), args); return await execFile(path.join("node_modules", ".bin", `clusterioctl${scriptExt}`), args);
} }
function validateSlaveToken(token) { function validateHostToken(token) {
let parts = token.split("."); let parts = token.split(".");
if (parts.length !== 3) { if (parts.length !== 3) {
throw new InstallError("Invalid token"); throw new InstallError("Invalid token");
@@ -144,7 +144,7 @@ function validateSlaveToken(token) {
throw new InstallError("Invalid token"); throw new InstallError("Invalid token");
} }
if (parsed.aud !== "slave" || !Number.isInteger(parsed.slave)) { if (parsed.aud !== "host" || !Number.isInteger(parsed.host)) {
throw new InstallError("Invalid token"); throw new InstallError("Invalid token");
} }
} }
@@ -197,8 +197,8 @@ async function installClusterio(mode, plugins) {
if (["standalone", "controller"].includes(mode)) { if (["standalone", "controller"].includes(mode)) {
components.push("@clusterio/controller"); components.push("@clusterio/controller");
} }
if (["standalone", "slave"].includes(mode)) { if (["standalone", "host"].includes(mode)) {
components.push("@clusterio/slave"); components.push("@clusterio/host");
} }
if (mode === "ctl") { if (mode === "ctl") {
components.push("@clusterio/ctl"); components.push("@clusterio/ctl");
@@ -281,22 +281,22 @@ WantedBy=multi-user.target
} }
} }
if (["standalone", "slave"].includes(mode)) { if (["standalone", "host"].includes(mode)) {
if (process.platform === "win32") { if (process.platform === "win32") {
await safeOutputFile( await safeOutputFile(
"run-slave.cmd", "run-host.cmd",
"@echo off\n.\\node_modules\\.bin\\clusterioslave.cmd run\n" "@echo off\n.\\node_modules\\.bin\\clusteriohost.cmd run\n"
); );
} else { } else {
await safeOutputFile( await safeOutputFile(
"run-slave.sh", "run-host.sh",
"#!/bin/sh\nexec ./node_modules/.bin/clusterioslave run\n", "#!/bin/sh\nexec ./node_modules/.bin/clusteriohost run\n",
{ mode: 0o755 }, { mode: 0o755 },
); );
await safeOutputFile( await safeOutputFile(
"systemd/clusterioslave.service", "systemd/clusteriohost.service",
`[Unit] `[Unit]
Description=Clusterio Slave Description=Clusterio Host
[Service] [Service]
User=${os.userInfo().username} User=${os.userInfo().username}
@@ -304,7 +304,7 @@ Group=${await groupIdToName(os.userInfo().gid)}
WorkingDirectory=${process.cwd()} WorkingDirectory=${process.cwd()}
KillMode=mixed KillMode=mixed
KillSignal=SIGINT KillSignal=SIGINT
ExecStart=${process.cwd()}/node_modules/.bin/clusterioslave run --log-level=warn ExecStart=${process.cwd()}/node_modules/.bin/clusteriohost run --log-level=warn
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target
@@ -324,9 +324,9 @@ async function inquirerMissingArgs(args) {
message: "Operating mode to install", message: "Operating mode to install",
default: "standalone", default: "standalone",
choices: [ choices: [
{ name: "Standalone (install both controller and slave on this computer)", value: "standalone" }, { name: "Standalone (install both controller and host on this computer)", value: "standalone" },
{ name: "Controller only", value: "controller" }, { name: "Controller only", value: "controller" },
{ name: "Slave only", value: "slave" }, { name: "Host only", value: "host" },
{ name: "Ctl only", value: "ctl" }, { name: "Ctl only", value: "ctl" },
{ name: "Plugins only", value: "plugins" }, { name: "Plugins only", value: "plugins" },
], ],
@@ -350,18 +350,18 @@ async function inquirerMissingArgs(args) {
], answers); ], answers);
} }
if (answers.mode === "slave") { if (answers.mode === "host") {
if (args.slaveName) { answers.slaveName = args.slaveName; } if (args.hostName) { answers.hostName = args.hostName; }
answers = await inquirer.prompt([ answers = await inquirer.prompt([
{ {
type: "input", type: "input",
name: "slaveName", name: "hostName",
message: "Name of slave", message: "Name of host",
}, },
], answers); ], answers);
} }
if (["slave", "ctl"].includes(answers.mode)) { if (["host", "ctl"].includes(answers.mode)) {
if (args.controllerUrl) { answers.controllerUrl = args.controllerUrl; } if (args.controllerUrl) { answers.controllerUrl = args.controllerUrl; }
answers = await inquirer.prompt([ answers = await inquirer.prompt([
{ {
@@ -381,11 +381,11 @@ async function inquirerMissingArgs(args) {
], answers); ], answers);
} }
if (answers.mode === "slave") { if (answers.mode === "host") {
validateSlaveToken(answers.controllerToken); validateHostToken(answers.controllerToken);
} }
if (["standalone", "slave"].includes(answers.mode)) { if (["standalone", "host"].includes(answers.mode)) {
let myIp = "localhost"; let myIp = "localhost";
if (args.publicAddress) { if (args.publicAddress) {
answers.publicAddress = args.publicAddress; answers.publicAddress = args.publicAddress;
@@ -532,25 +532,25 @@ async function main() {
}) })
.option("mode", { .option("mode", {
nargs: 1, describe: "Operating mode to install", nargs: 1, describe: "Operating mode to install",
choices: ["standalone", "controller", "slave", "ctl", "plugins"], choices: ["standalone", "controller", "host", "ctl", "plugins"],
}) })
.option("admin", { .option("admin", {
nargs: 1, describe: "Admin account name [standalone/controller]", type: "string", nargs: 1, describe: "Admin account name [standalone/controller]", type: "string",
}) })
.option("slave-name", { .option("host-name", {
nargs: 1, describe: "Slave name [slave]", type: "string", nargs: 1, describe: "Host name [host]", type: "string",
}) })
.option("controller-url", { .option("controller-url", {
nargs: 1, describe: "Controller URL [slave/ctl]", type: "string", nargs: 1, describe: "Controller URL [host/ctl]", type: "string",
}) })
.option("controller-token", { .option("controller-token", {
nargs: 1, describe: "Controller authentication token [slave/ctl]", type: "string", nargs: 1, describe: "Controller authentication token [host/ctl]", type: "string",
}) })
.option("public-address", { .option("public-address", {
nargs: 1, describe: "DNS/IP Address to connect to this server [standalone/slave]", type: "string", nargs: 1, describe: "DNS/IP Address to connect to this server [standalone/host]", type: "string",
}) })
.option("factorio-dir", { .option("factorio-dir", {
nargs: 1, describe: "Path to Factorio installation [standalone/slave]", type: "string", nargs: 1, describe: "Path to Factorio installation [standalone/host]", type: "string",
}) })
.option("plugins", { .option("plugins", {
array: true, describe: "Plugins to install", type: "string", array: true, describe: "Plugins to install", type: "string",
@@ -602,32 +602,32 @@ async function main() {
} }
if (answers.mode === "standalone") { if (answers.mode === "standalone") {
logger.info("Setting up slave"); logger.info("Setting up host");
await execSlave(["config", "set", "slave.name", "local"]); await execHost(["config", "set", "host.name", "local"]);
let result = await execSlave(["config", "show", "slave.id"]); let result = await execHost(["config", "show", "host.id"]);
let slaveId = Number.parseInt(result.stdout.split("\n").slice(-2)[0], 10); let hostId = Number.parseInt(result.stdout.split("\n").slice(-2)[0], 10);
result = await execController(["bootstrap", "generate-slave-token", slaveId]); result = await execController(["bootstrap", "generate-host-token", hostId]);
let slaveToken = result.stdout.split("\n").slice(-2)[0]; let hostToken = result.stdout.split("\n").slice(-2)[0];
await execSlave(["config", "set", "slave.controller_token", slaveToken]); await execHost(["config", "set", "host.controller_token", hostToken]);
await execSlave(["config", "set", "slave.public_address", answers.publicAddress]); await execHost(["config", "set", "host.public_address", answers.publicAddress]);
await execSlave(["config", "set", "slave.factorio_directory", answers.factorioDir]); await execHost(["config", "set", "host.factorio_directory", answers.factorioDir]);
} }
if (answers.mode === "slave") { if (answers.mode === "host") {
logger.info("Setting up slave"); logger.info("Setting up host");
let slaveId = JSON.parse(Buffer.from(answers.controllerToken.split(".")[1], "base64")).slave; let hostId = JSON.parse(Buffer.from(answers.controllerToken.split(".")[1], "base64")).host;
await execSlave(["config", "set", "slave.id", slaveId]); await execHost(["config", "set", "host.id", hostId]);
await execSlave(["config", "set", "slave.name", answers.slaveName]); await execHost(["config", "set", "host.name", answers.hostName]);
await execSlave(["config", "set", "slave.controller_url", answers.controllerUrl]); await execHost(["config", "set", "host.controller_url", answers.controllerUrl]);
await execSlave(["config", "set", "slave.controller_token", answers.controllerToken]); await execHost(["config", "set", "host.controller_token", answers.controllerToken]);
await execSlave(["config", "set", "slave.public_address", answers.publicAddress]); await execHost(["config", "set", "host.public_address", answers.publicAddress]);
await execSlave(["config", "set", "slave.factorio_directory", answers.factorioDir]); await execHost(["config", "set", "host.factorio_directory", answers.factorioDir]);
} }
if (!dev && ["standalone", "controller", "slave"].includes(answers.mode)) { if (!dev && ["standalone", "controller", "host"].includes(answers.mode)) {
logger.info("Writing run scripts"); logger.info("Writing run scripts");
await writeScripts(answers.mode); await writeScripts(answers.mode);
} }
+2 -2
View File
@@ -37,7 +37,7 @@ For example, installing the Subspace Storage plugin:
npm install @clusterio/plugin-subspace_storage npm install @clusterio/plugin-subspace_storage
npx clusterioctl plugin add @clusterio/plugin-subspace_storage npx clusterioctl plugin add @clusterio/plugin-subspace_storage
Since the `plugin-list.json` is shared between controller, slave and ctl you usually only need to do this once per machine. Since the `plugin-list.json` is shared between controller, host and ctl you usually only need to do this once per machine.
#### `plugin remove <name>` #### `plugin remove <name>`
@@ -51,7 +51,7 @@ For example, uninstalling the Subspace Storage plugin:
npx clusterioctl plugin remove subspace_storage npx clusterioctl plugin remove subspace_storage
npm uninstall @clusterio/plugin-subspace_storage npm uninstall @clusterio/plugin-subspace_storage
Since the `plugin-list.json` is shared between controller, slave and ctl you usually only need to do this once per machine. Since the `plugin-list.json` is shared between controller, host and ctl you usually only need to do this once per machine.
#### `plugin list` #### `plugin list`
+3 -3
View File
@@ -83,7 +83,7 @@ class Control extends libLink.Link {
async accountUpdateEventHandler() { } async accountUpdateEventHandler() { }
async slaveUpdateEventHandler() { } async hostUpdateEventHandler() { }
async instanceUpdateEventHandler() { } async instanceUpdateEventHandler() { }
@@ -98,12 +98,12 @@ class Control extends libLink.Link {
async setLogSubscriptions({ async setLogSubscriptions({
all = false, all = false,
controller = false, controller = false,
slave_ids = [], host_ids = [],
instance_ids = [], instance_ids = [],
max_level = null, max_level = null,
}) { }) {
await libLink.messages.setLogSubscriptions.send(this, { await libLink.messages.setLogSubscriptions.send(this, {
all, controller, slave_ids, instance_ids, max_level, all, controller, host_ids, instance_ids, max_level,
}); });
} }
+31 -31
View File
@@ -237,39 +237,39 @@ controllerPluginCommands.add(new libCommand.Command({
controllerCommands.add(controllerPluginCommands); controllerCommands.add(controllerPluginCommands);
const slaveCommands = new libCommand.CommandTree({ name: "slave", description: "Slave management" }); const hostCommands = new libCommand.CommandTree({ name: "host", description: "Host management" });
slaveCommands.add(new libCommand.Command({ hostCommands.add(new libCommand.Command({
definition: [["list", "l"], "List slaves connected to the controller"], definition: [["list", "l"], "List hosts connected to the controller"],
handler: async function(args, control) { handler: async function(args, control) {
let response = await libLink.messages.listSlaves.send(control); let response = await libLink.messages.listHosts.send(control);
print(asTable(response.list)); print(asTable(response.list));
}, },
})); }));
slaveCommands.add(new libCommand.Command({ hostCommands.add(new libCommand.Command({
definition: ["generate-token", "Generate token for a slave", (yargs) => { definition: ["generate-token", "Generate token for a host", (yargs) => {
yargs.option("id", { type: "number", nargs: 1, describe: "Slave id" }); yargs.option("id", { type: "number", nargs: 1, describe: "Host id" });
}], }],
handler: async function(args, control) { handler: async function(args, control) {
let slaveId = typeof args.id === "number" ? args.id : null; let hostId = typeof args.id === "number" ? args.id : null;
let response = await libLink.messages.generateSlaveToken.send(control, { slave_id: slaveId }); let response = await libLink.messages.generateHostToken.send(control, { host_id: hostId });
print(response.token); print(response.token);
}, },
})); }));
slaveCommands.add(new libCommand.Command({ hostCommands.add(new libCommand.Command({
definition: ["create-config", "Create slave config", (yargs) => { definition: ["create-config", "Create host config", (yargs) => {
yargs.option("id", { type: "number", nargs: 1, describe: "Slave id", default: null }); yargs.option("id", { type: "number", nargs: 1, describe: "Host id", default: null });
yargs.option("name", { type: "string", nargs: 1, describe: "Slave name", default: null }); yargs.option("name", { type: "string", nargs: 1, describe: "Host name", default: null });
yargs.option("generate-token", { yargs.option("generate-token", {
type: "boolean", nargs: 0, describe: "Generate authentication token", default: false, type: "boolean", nargs: 0, describe: "Generate authentication token", default: false,
}); });
yargs.option("output", { yargs.option("output", {
type: "string", nargs: 1, describe: "Path to output config (- for stdout)", default: "config-slave.json", type: "string", nargs: 1, describe: "Path to output config (- for stdout)", default: "config-host.json",
}); });
}], }],
handler: async function(args, control) { handler: async function(args, control) {
let response = await libLink.messages.createSlaveConfig.send(control, { let response = await libLink.messages.createHostConfig.send(control, {
id: args.id, name: args.name, generate_token: args.generateToken, id: args.id, name: args.name, generate_token: args.generateToken,
}); });
@@ -430,7 +430,7 @@ instanceConfigCommands.add(new libCommand.Command({
throw new libErrors.CommandError(`No editor avalible. Checked CLI input, EDITOR and VISUAL env vars throw new libErrors.CommandError(`No editor avalible. Checked CLI input, EDITOR and VISUAL env vars
Try "ctl controller config edit <editor of choice>"`); Try "ctl controller config edit <editor of choice>"`);
} }
let disallowedList = {"instance.id": 0, "instance.assigned_slave": 0, "factorio.settings": 0}; let disallowedList = {"instance.id": 0, "instance.assigned_host": 0, "factorio.settings": 0};
let allConfigElements = await serializedConfigToString( let allConfigElements = await serializedConfigToString(
response.serialized_config, response.serialized_config,
libConfig.InstanceConfig, libConfig.InstanceConfig,
@@ -487,16 +487,16 @@ instanceConfigCommands.add(new libCommand.Command({
instanceCommands.add(instanceConfigCommands); instanceCommands.add(instanceConfigCommands);
instanceCommands.add(new libCommand.Command({ instanceCommands.add(new libCommand.Command({
definition: ["assign <instance> [slave]", "Assign instance to a slave", (yargs) => { definition: ["assign <instance> [host]", "Assign instance to a host", (yargs) => {
yargs.positional("instance", { describe: "Instance to assign", type: "string" }); yargs.positional("instance", { describe: "Instance to assign", type: "string" });
yargs.positional("slave", { describe: "Slave to assign to or unassign if none", type: "string" }); yargs.positional("host", { describe: "Host to assign to or unassign if none", type: "string" });
}], }],
handler: async function(args, control) { handler: async function(args, control) {
let instanceId = await libCommand.resolveInstance(control, args.instance); let instanceId = await libCommand.resolveInstance(control, args.instance);
let slaveId = args.slave ? await libCommand.resolveSlave(control, args.slave) : null; let hostId = args.host ? await libCommand.resolveHost(control, args.host) : null;
await libLink.messages.assignInstanceCommand.send(control, { await libLink.messages.assignInstanceCommand.send(control, {
instance_id: instanceId, instance_id: instanceId,
slave_id: slaveId, host_id: hostId,
}); });
}, },
})); }));
@@ -1586,19 +1586,19 @@ logCommands.add(new libCommand.Command({
yargs.options({ yargs.options({
"all": { describe: "Follow the whole cluster log", nargs: 0, type: "boolean", default: false }, "all": { describe: "Follow the whole cluster log", nargs: 0, type: "boolean", default: false },
"controller": { describe: "Follow log of the controller", nargs: 0, type: "boolean", default: false }, "controller": { describe: "Follow log of the controller", nargs: 0, type: "boolean", default: false },
"slave": { describe: "Follow log of given slave", nargs: 1, type: "string", default: null }, "host": { describe: "Follow log of given host", nargs: 1, type: "string", default: null },
"instance": { describe: "Follow log of given instance", nargs: 1, type: "string", default: null }, "instance": { describe: "Follow log of given instance", nargs: 1, type: "string", default: null },
}); });
}], }],
handler: async function(args, control) { handler: async function(args, control) {
if (!args.all && !args.controller && !args.slave && !args.instance) { if (!args.all && !args.controller && !args.host && !args.instance) {
logger.error("At least one of --all, --controller, --slave and --instance must be passed"); logger.error("At least one of --all, --controller, --host and --instance must be passed");
process.exitCode = 1; process.exitCode = 1;
return; return;
} }
let instance_ids = args.instance ? [await libCommand.resolveInstance(control, args.instance)] : []; let instance_ids = args.instance ? [await libCommand.resolveInstance(control, args.instance)] : [];
let slave_ids = args.slave ? [await libCommand.resolveSlave(control, args.slave)] : []; let host_ids = args.host ? [await libCommand.resolveHost(control, args.host)] : [];
await control.setLogSubscriptions({ all: args.all, controller: args.controller, slave_ids, instance_ids }); await control.setLogSubscriptions({ all: args.all, controller: args.controller, host_ids, instance_ids });
control.keepOpen = true; control.keepOpen = true;
}, },
})); }));
@@ -1608,7 +1608,7 @@ logCommands.add(new libCommand.Command({
yargs.options({ yargs.options({
"all": { describe: "Query the whole cluster log", nargs: 0, type: "boolean", default: false }, "all": { describe: "Query the whole cluster log", nargs: 0, type: "boolean", default: false },
"controller": { describe: "Query log of the controller", nargs: 0, type: "boolean", default: false }, "controller": { describe: "Query log of the controller", nargs: 0, type: "boolean", default: false },
"slave": { describe: "Query log of given slave", nargs: 1, type: "string", default: null }, "host": { describe: "Query log of given host", nargs: 1, type: "string", default: null },
"instance": { describe: "Query log of given instance", nargs: 1, type: "string", default: null }, "instance": { describe: "Query log of given instance", nargs: 1, type: "string", default: null },
"max-level": { describe: "Maximum log level to return", nargs: 1, type: "string", default: null }, "max-level": { describe: "Maximum log level to return", nargs: 1, type: "string", default: null },
"limit": { describe: "Max number of entries to return", nargs: 1, type: "number", default: 1000 }, "limit": { describe: "Max number of entries to return", nargs: 1, type: "number", default: 1000 },
@@ -1616,17 +1616,17 @@ logCommands.add(new libCommand.Command({
}); });
}], }],
handler: async function(args, control) { handler: async function(args, control) {
if (!args.all && !args.controller && !args.slave && !args.instance) { if (!args.all && !args.controller && !args.host && !args.instance) {
logger.error("At least one of --all, --controller, --slave and --instance must be passed"); logger.error("At least one of --all, --controller, --host and --instance must be passed");
process.exitCode = 1; process.exitCode = 1;
return; return;
} }
let instance_ids = args.instance ? [await libCommand.resolveInstance(control, args.instance)] : []; let instance_ids = args.instance ? [await libCommand.resolveInstance(control, args.instance)] : [];
let slave_ids = args.slave ? [await libCommand.resolveSlave(control, args.slave)] : []; let host_ids = args.host ? [await libCommand.resolveHost(control, args.host)] : [];
let result = await libLink.messages.queryLog.send(control, { let result = await libLink.messages.queryLog.send(control, {
all: args.all, all: args.all,
controller: args.controller, controller: args.controller,
slave_ids, host_ids,
instance_ids, instance_ids,
max_level: args.maxLevel, max_level: args.maxLevel,
limit: args.limit, limit: args.limit,
@@ -1663,7 +1663,7 @@ debugCommands.add(new libCommand.Command({
async function registerCommands(controlPlugins, yargs) { async function registerCommands(controlPlugins, yargs) {
const rootCommands = new libCommand.CommandTree({ name: "clusterioctl", description: "Manage cluster" }); const rootCommands = new libCommand.CommandTree({ name: "clusterioctl", description: "Manage cluster" });
rootCommands.add(controllerCommands); rootCommands.add(controllerCommands);
rootCommands.add(slaveCommands); rootCommands.add(hostCommands);
rootCommands.add(instanceCommands); rootCommands.add(instanceCommands);
rootCommands.add(modPackCommands); rootCommands.add(modPackCommands);
rootCommands.add(modCommands); rootCommands.add(modCommands);
@@ -1,28 +1,28 @@
# Clusterio Slave # Clusterio Host
Node hosting Factorio servers in a Clusterio cluster. Node hosting Factorio servers in a Clusterio cluster.
Clusterio slaves connect to the controller and waits for commands from the controller to start up and stop instances. Clusterio hosts connect to the controller and waits for commands from the controller to start up and stop instances.
A cluster can have any number of slaves in it located on different computers, and each slave can host any number of instances each of which is a Factorio server that talks with the rest of the cluster. A cluster can have any number of hosts in it located on different computers, and each host can host any number of instances each of which is a Factorio server that talks with the rest of the cluster.
## Usage ## Usage
npx clusterioslave <command> npx clusteriohost <command>
Common options: Common options:
* `--plugin-list <file>` JSON file to use for storing the list of plugins that are available to the slave. * `--plugin-list <file>` JSON file to use for storing the list of plugins that are available to the host.
Defaults to `plugin-list.json` and will be created if it does not exist. Defaults to `plugin-list.json` and will be created if it does not exist.
See the `plugin` command for managing this list. See the `plugin` command for managing this list.
* `--config <file>` JSON file to use for storing configuration for the slave. * `--config <file>` JSON file to use for storing configuration for the host.
Defaults to `config-slave.json` and will be created if it does not exist. Defaults to `config-host.json` and will be created if it does not exist.
See the `config` command for inspecting and modifying the configuration. See the `config` command for inspecting and modifying the configuration.
### `plugin <command>` ### `plugin <command>`
Configure plugins available to be loaded by the slave. Configure plugins available to be loaded by the host.
The available plugins will be loaded unless they have been disabled in the configuration, see the config command for disabling plugins. The available plugins will be loaded unless they have been disabled in the configuration, see the config command for disabling plugins.
@@ -34,9 +34,9 @@ A relative path must start with ./ or ../ (or .\ and ..\ on Windows) otherwise i
For example, installing the Subspace Storage plugin: For example, installing the Subspace Storage plugin:
npm install @clusterio/plugin-subspace_storage npm install @clusterio/plugin-subspace_storage
npx clusterioslave plugin add @clusterio/plugin-subspace_storage npx clusteriohost plugin add @clusterio/plugin-subspace_storage
Since the `plugin-list.json` is shared between controller, slave and ctl you usually only need to do this once per machine. Since the `plugin-list.json` is shared between controller, host and ctl you usually only need to do this once per machine.
#### `plugin remove <name>` #### `plugin remove <name>`
@@ -47,10 +47,10 @@ Removing and unistalling a plugin is usually not neccessary as the functions pro
For example, uninstalling the Subspace Storage plugin: For example, uninstalling the Subspace Storage plugin:
npx clusterioslave plugin remove subspace_storage npx clusteriohost plugin remove subspace_storage
npm uninstall @clusterio/plugin-subspace_storage npm uninstall @clusterio/plugin-subspace_storage
Since the `plugin-list.json` is shared between controller, slave and ctl you usually only need to do this once per machine. Since the `plugin-list.json` is shared between controller, host and ctl you usually only need to do this once per machine.
#### `plugin list` #### `plugin list`
@@ -60,8 +60,8 @@ Lists the plugins set up to be available by name followed by path.
### `config` ### `config`
Manage the slave configuration offline. Manage the host configuration offline.
This should only be used when the slave is stopped, otherwise the config read might be out of date and config changes will be overwritten when the slave shuts down. This should only be used when the host is stopped, otherwise the config read might be out of date and config changes will be overwritten when the host shuts down.
#### `config set <config-entry> [value]` #### `config set <config-entry> [value]`
@@ -85,7 +85,7 @@ Lists up all configuration entries with their currently configured values.
### `run` ### `run`
Runs the slave. Runs the host.
## See Also ## See Also
@@ -1,16 +1,16 @@
#!/usr/bin/env node #!/usr/bin/env node
/** /**
* Clusterio slave * Clusterio host
* *
* Connects to the controller and hosts Factorio servers that can * Connects to the controller and hosts Factorio servers that can
* communicate with the cluster. It is remotely controlled by {@link * communicate with the cluster. It is remotely controlled by {@link
* module:controller/controller}. * module:controller/controller}.
* *
* @module slave/slave * @module host/host
* @author Danielv123, Hornwitser * @author Danielv123, Hornwitser
* @example * @example
* npx clusterioslave run * npx clusteriohost run
*/ */
"use strict"; "use strict";
const fs = require("fs-extra"); const fs = require("fs-extra");
@@ -30,44 +30,44 @@ const libSharedCommands = require("@clusterio/lib/shared_commands");
const { ConsoleTransport, FilteredTransport, levels, logger } = require("@clusterio/lib/logging"); const { ConsoleTransport, FilteredTransport, levels, logger } = require("@clusterio/lib/logging");
const libLoggingUtils = require("@clusterio/lib/logging_utils"); const libLoggingUtils = require("@clusterio/lib/logging_utils");
const Slave = require("./src/Slave"); const Host = require("./src/Host");
class SlaveConnector extends libLink.WebSocketClientConnector { class HostConnector extends libLink.WebSocketClientConnector {
constructor(slaveConfig, tlsCa, pluginInfos) { constructor(hostConfig, tlsCa, pluginInfos) {
super( super(
slaveConfig.get("slave.controller_url"), hostConfig.get("host.controller_url"),
slaveConfig.get("slave.max_reconnect_delay"), hostConfig.get("host.max_reconnect_delay"),
tlsCa tlsCa
); );
this.slaveConfig = slaveConfig; this.hostConfig = hostConfig;
this.pluginInfos = pluginInfos; this.pluginInfos = pluginInfos;
} }
register() { register() {
logger.info("Connector | registering slave"); logger.info("Connector | registering host");
let plugins = {}; let plugins = {};
for (let pluginInfo of this.pluginInfos) { for (let pluginInfo of this.pluginInfos) {
plugins[pluginInfo.name] = pluginInfo.version; plugins[pluginInfo.name] = pluginInfo.version;
} }
this.sendHandshake("register_slave", { this.sendHandshake("register_host", {
token: this.slaveConfig.get("slave.controller_token"), token: this.hostConfig.get("host.controller_token"),
agent: "Clusterio Slave", agent: "Clusterio Host",
version, version,
id: this.slaveConfig.get("slave.id"), id: this.hostConfig.get("host.id"),
name: this.slaveConfig.get("slave.name"), name: this.hostConfig.get("host.name"),
public_address: this.slaveConfig.get("slave.public_address"), public_address: this.hostConfig.get("host.public_address"),
plugins, plugins,
}); });
} }
} }
async function startSlave() { async function startHost() {
// argument parsing // argument parsing
const args = yargs const args = yargs
.scriptName("slave") .scriptName("host")
.usage("$0 <command> [options]") .usage("$0 <command> [options]")
.option("log-level", { .option("log-level", {
nargs: 1, nargs: 1,
@@ -84,8 +84,8 @@ async function startSlave() {
}) })
.option("config", { .option("config", {
nargs: 1, nargs: 1,
describe: "slave config file to use", describe: "host config file to use",
default: "config-slave.json", default: "config-host.json",
type: "string", type: "string",
}) })
.option("plugin-list", { .option("plugin-list", {
@@ -95,8 +95,8 @@ async function startSlave() {
type: "string", type: "string",
}) })
.command("plugin", "Manage available plugins", libSharedCommands.pluginCommand) .command("plugin", "Manage available plugins", libSharedCommands.pluginCommand)
.command("config", "Manage Slave config", libSharedCommands.configCommand) .command("config", "Manage Host config", libSharedCommands.configCommand)
.command("run", "Run slave") .command("run", "Run host")
.demandCommand(1, "You need to specify a command to run") .demandCommand(1, "You need to specify a command to run")
.strict() .strict()
.argv .argv
@@ -106,21 +106,21 @@ async function startSlave() {
// Migration from alpha-10 single file logs, note that we can't use // Migration from alpha-10 single file logs, note that we can't use
// the logger here as it's not initalized yet. // the logger here as it's not initalized yet.
/* eslint-disable no-console */ /* eslint-disable no-console */
let slaveLogDirectory = path.join(args.logDirectory, "slave"); let hostLogDirectory = path.join(args.logDirectory, "host");
if (!await fs.pathExists(slaveLogDirectory) && await fs.pathExists("slave.log")) { if (!await fs.pathExists(hostLogDirectory) && await fs.pathExists("host.log")) {
console.log("Migrating slave log..."); console.log("Migrating host log...");
await fs.ensureDir(slaveLogDirectory); await fs.ensureDir(hostLogDirectory);
await libLoggingUtils.migrateLogs("slave.log", slaveLogDirectory, "slave-%DATE%.log"); await libLoggingUtils.migrateLogs("host.log", hostLogDirectory, "host-%DATE%.log");
console.log("Migration complete, you should delete slave.log now"); console.log("Migration complete, you should delete host.log now");
} }
/* eslint-enable no-console */ /* eslint-enable no-console */
} }
logger.add(new winston.transports.DailyRotateFile({ logger.add(new winston.transports.DailyRotateFile({
format: winston.format.json(), format: winston.format.json(),
filename: "slave-%DATE%.log", filename: "host-%DATE%.log",
utc: true, utc: true,
dirname: path.join(args.logDirectory, "slave"), dirname: path.join(args.logDirectory, "host"),
})); }));
if (args.logLevel !== "none") { if (args.logLevel !== "none") {
logger.add(new ConsoleTransport({ logger.add(new ConsoleTransport({
@@ -133,7 +133,7 @@ async function startSlave() {
let command = args._[0]; let command = args._[0];
if (command === "run") { if (command === "run") {
logger.info(`Starting Clusterio slave ${version}`); logger.info(`Starting Clusterio host ${version}`);
} }
logger.info(`Loading available plugins from ${args.pluginList}`); logger.info(`Loading available plugins from ${args.pluginList}`);
@@ -158,14 +158,14 @@ async function startSlave() {
libConfig.finalizeConfigs(); libConfig.finalizeConfigs();
logger.info(`Loading config from ${args.config}`); logger.info(`Loading config from ${args.config}`);
let slaveConfig = new libConfig.SlaveConfig("slave"); let hostConfig = new libConfig.HostConfig("host");
try { try {
await slaveConfig.load(JSON.parse(await fs.readFile(args.config))); await hostConfig.load(JSON.parse(await fs.readFile(args.config)));
} catch (err) { } catch (err) {
if (err.code === "ENOENT") { if (err.code === "ENOENT") {
logger.info("Config not found, initializing new config"); logger.info("Config not found, initializing new config");
await slaveConfig.init(); await hostConfig.init();
} else { } else {
throw new libErrors.StartupError(`Failed to load ${args.config}: ${err.message}`); throw new libErrors.StartupError(`Failed to load ${args.config}: ${err.message}`);
@@ -173,49 +173,49 @@ async function startSlave() {
} }
if (command === "config") { if (command === "config") {
await libSharedCommands.handleConfigCommand(args, slaveConfig, args.config); await libSharedCommands.handleConfigCommand(args, hostConfig, args.config);
return; return;
} }
// If we get here the command was run // If we get here the command was run
await fs.ensureDir(slaveConfig.get("slave.instances_directory")); await fs.ensureDir(hostConfig.get("host.instances_directory"));
await fs.ensureDir(slaveConfig.get("slave.mods_directory")); await fs.ensureDir(hostConfig.get("host.mods_directory"));
await fs.ensureDir("modules"); await fs.ensureDir("modules");
// Set the process title, shows up as the title of the CMD window on windows // Set the process title, shows up as the title of the CMD window on windows
// and as the process name in ps/top on linux. // and as the process name in ps/top on linux.
process.title = "clusterioSlave"; process.title = "clusterioHost";
// make sure we have the controller access token // make sure we have the controller access token
if (slaveConfig.get("slave.controller_token") === "enter token here") { if (hostConfig.get("host.controller_token") === "enter token here") {
logger.fatal("ERROR invalid config!"); logger.fatal("ERROR invalid config!");
logger.fatal( logger.fatal(
"Controller requires an access token for socket operations. As clusterio\n"+ "Controller requires an access token for socket operations. As clusterio\n"+
"slaves depends upon this, please set your token using the command npx\n"+ "hosts depends upon this, please set your token using the command npx\n"+
"clusterioslave config set slave.controller_token <token>. You can generate an\n"+ "clusteriohost config set host.controller_token <token>. You can generate an\n"+
"auth token using npx clusterioctl generate-slave-token." "auth token using npx clusterioctl generate-host-token."
); );
process.exitCode = 1; process.exitCode = 1;
return; return;
} }
// make sure url ends with / // make sure url ends with /
if (!slaveConfig.get("slave.controller_url").endsWith("/")) { if (!hostConfig.get("host.controller_url").endsWith("/")) {
logger.fatal("ERROR invalid config!"); logger.fatal("ERROR invalid config!");
logger.fatal("slave.controller_url must end with '/'"); logger.fatal("host.controller_url must end with '/'");
process.exitCode = 1; process.exitCode = 1;
return; return;
} }
let tlsCa = null; let tlsCa = null;
let tlsCaPath = slaveConfig.get("slave.tls_ca"); let tlsCaPath = hostConfig.get("host.tls_ca");
if (tlsCaPath) { if (tlsCaPath) {
tlsCa = await fs.readFile(tlsCaPath); tlsCa = await fs.readFile(tlsCaPath);
} }
let slaveConnector = new SlaveConnector(slaveConfig, tlsCa, pluginInfos); let hostConnector = new HostConnector(hostConfig, tlsCa, pluginInfos);
let slave = new Slave(slaveConnector, slaveConfig, tlsCa, pluginInfos); let host = new Host(hostConnector, hostConfig, tlsCa, pluginInfos);
// Handle interrupts // Handle interrupts
let secondSigint = false; let secondSigint = false;
@@ -229,7 +229,7 @@ async function startSlave() {
secondSigint = true; secondSigint = true;
logger.info("Caught interrupt signal, shutting down"); logger.info("Caught interrupt signal, shutting down");
slave.shutdown(); host.shutdown();
}); });
let secondSigterm = false; let secondSigterm = false;
process.on("SIGTERM", () => { process.on("SIGTERM", () => {
@@ -242,19 +242,19 @@ async function startSlave() {
secondSigterm = true; secondSigterm = true;
logger.info("Caught termination signal, shutting down"); logger.info("Caught termination signal, shutting down");
slave.shutdown(); host.shutdown();
}); });
process.on("SIGHUP", () => { process.on("SIGHUP", () => {
logger.info("Terminal closed, shutting down"); logger.info("Terminal closed, shutting down");
slave.shutdown(); host.shutdown();
}); });
slaveConnector.once("connect", () => { hostConnector.once("connect", () => {
logger.add(new libLoggingUtils.LinkTransport({ link: slave })); logger.add(new libLoggingUtils.LinkTransport({ link: host }));
}); });
await slaveConnector.connect(); await hostConnector.connect();
logger.info("Started slave"); logger.info("Started host");
} }
if (module === require.main) { if (module === require.main) {
@@ -266,15 +266,15 @@ I version of clusterio. Expect things to break. I
+==========================================================+ +==========================================================+
` `
); );
startSlave().catch(err => { startHost().catch(err => {
if (err instanceof libErrors.AuthenticationFailed) { if (err instanceof libErrors.AuthenticationFailed) {
logger.fatal(err.message); logger.fatal(err.message);
} else if (err instanceof libErrors.StartupError) { } else if (err instanceof libErrors.StartupError) {
logger.fatal(` logger.fatal(`
+-----------------------------------+ +----------------------------------+
| Unable to to start clusterioslave | | Unable to to start clusteriohost |
+-----------------------------------+ +----------------------------------+
${err.stack}` ${err.stack}`
); );
@@ -289,7 +289,7 @@ ${err.original.stack}`
} else { } else {
logger.fatal(` logger.fatal(`
+------------------------------------------------------------+ +------------------------------------------------------------+
| Unexpected error occured while starting slave, please | | Unexpected error occured while starting host, please |
| report it to https://github.com/clusterio/clusterio/issues | | report it to https://github.com/clusterio/clusterio/issues |
+------------------------------------------------------------+ +------------------------------------------------------------+
${err.stack}` ${err.stack}`
File renamed without changes.
File renamed without changes.
@@ -1,15 +1,15 @@
{ {
"name": "@clusterio/slave", "name": "@clusterio/host",
"description": "Implementation of Clusterio slave server", "description": "Implementation of Clusterio host server",
"author": "Hornwitser", "author": "Hornwitser",
"license": "MIT", "license": "MIT",
"version": "2.0.0-alpha.13", "version": "2.0.0-alpha.13",
"main": "slave.js", "main": "host.js",
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1"
}, },
"bin": { "bin": {
"clusterioslave": "./slave.js" "clusteriohost": "./host.js"
}, },
"keywords": [ "keywords": [
"factorio" "factorio"
@@ -49,7 +49,7 @@ async function discoverInstances(instancesDir) {
let instanceInfos = new Map(); let instanceInfos = new Map();
for (let entry of await fs.readdir(instancesDir, { withFileTypes: true })) { for (let entry of await fs.readdir(instancesDir, { withFileTypes: true })) {
if (entry.isDirectory()) { if (entry.isDirectory()) {
let instanceConfig = new libConfig.InstanceConfig("slave"); let instanceConfig = new libConfig.InstanceConfig("host");
let configPath = path.join(instancesDir, entry.name, "instance.json"); let configPath = path.join(instancesDir, entry.name, "instance.json");
try { try {
@@ -81,16 +81,16 @@ async function discoverInstances(instancesDir) {
} }
/** /**
* Handles running the slave * Handles running the host
* *
* Connects to the controller over the WebSocket and manages intsances. * Connects to the controller over the WebSocket and manages intsances.
* @alias module:slave/src/Slave * @alias module:host/src/Host
*/ */
class Slave extends libLink.Link { class Host extends libLink.Link {
// I don't like God classes, but the alternative of putting all this state // I don't like God classes, but the alternative of putting all this state
// into global variables is not much better. // into global variables is not much better.
constructor(connector, slaveConfig, tlsCa, pluginInfos) { constructor(connector, hostConfig, tlsCa, pluginInfos) {
super("slave", "controller", connector); super("host", "controller", connector);
libLink.attachAllMessages(this); libLink.attachAllMessages(this);
this.pluginInfos = pluginInfos; this.pluginInfos = pluginInfos;
@@ -98,7 +98,7 @@ class Slave extends libLink.Link {
libPlugin.attachPluginMessages(this, { info: pluginInfo }); libPlugin.attachPluginMessages(this, { info: pluginInfo });
} }
this.config = slaveConfig; this.config = hostConfig;
/** /**
* Certificate authority used to validate TLS connections to the controller. * Certificate authority used to validate TLS connections to the controller.
@@ -173,7 +173,7 @@ class Slave extends libLink.Link {
throw new Error(`Instance folder was unepectedly invalid: name ${err.message}`); throw new Error(`Instance folder was unepectedly invalid: name ${err.message}`);
} }
let instancesDir = this.config.get("slave.instances_directory"); let instancesDir = this.config.get("host.instances_directory");
for (let i = 0; i < 10; i++) { // Limit attempts in case this is somehow an infinite loop for (let i = 0; i < 10; i++) { // Limit attempts in case this is somehow an infinite loop
let candidateDir = path.join(instancesDir, await libFileOps.findUnusedName(instancesDir, name)); let candidateDir = path.join(instancesDir, await libFileOps.findUnusedName(instancesDir, name));
try { try {
@@ -315,13 +315,13 @@ class Slave extends libLink.Link {
instanceInfo.config.update(serialized_config, true, "controller"); instanceInfo.config.update(serialized_config, true, "controller");
} else { } else {
let instanceConfig = new libConfig.InstanceConfig("slave"); let instanceConfig = new libConfig.InstanceConfig("host");
await instanceConfig.load(serialized_config, "controller"); await instanceConfig.load(serialized_config, "controller");
let instanceDir = await this._createNewInstanceDir(instanceConfig.get("instance.name")); let instanceDir = await this._createNewInstanceDir(instanceConfig.get("instance.name"));
logger.info(`Creating ${instanceDir}`); logger.info(`Creating ${instanceDir}`);
await Instance.create(instanceDir, this.config.get("slave.factorio_directory")); await Instance.create(instanceDir, this.config.get("host.factorio_directory"));
instanceInfo = { instanceInfo = {
path: instanceDir, path: instanceDir,
config: instanceConfig, config: instanceConfig,
@@ -336,7 +336,7 @@ class Slave extends libLink.Link {
// Somewhat hacky, but in the event of a lost session the status is // Somewhat hacky, but in the event of a lost session the status is
// resent on assigment since the controller sends an assigment // resent on assigment since the controller sends an assigment
// request for all the instances it knows should be on this slave. // request for all the instances it knows should be on this host.
let instanceConnection = this.instanceConnections.get(instance_id); let instanceConnection = this.instanceConnections.get(instance_id);
libLink.messages.instanceStatusChanged.send(this, { libLink.messages.instanceStatusChanged.send(this, {
instance_id, instance_id,
@@ -388,7 +388,7 @@ class Slave extends libLink.Link {
* Initialize and connect an unloaded instance * Initialize and connect an unloaded instance
* *
* @param {number} instanceId - ID of instance to initialize. * @param {number} instanceId - ID of instance to initialize.
* @returns {Promise<module:slave/slave~InstanceConnection>} connection to instance. * @returns {Promise<module:host/host~InstanceConnection>} connection to instance.
*/ */
async _connectInstance(instanceId) { async _connectInstance(instanceId) {
let instanceInfo = this.getRequestInstanceInfo(instanceId); let instanceInfo = this.getRequestInstanceInfo(instanceId);
@@ -399,7 +399,7 @@ class Slave extends libLink.Link {
let [connectionClient, connectionServer] = libLink.VirtualConnector.makePair(); let [connectionClient, connectionServer] = libLink.VirtualConnector.makePair();
let instanceConnection = new InstanceConnection(connectionServer, this, instanceId); let instanceConnection = new InstanceConnection(connectionServer, this, instanceId);
let instance = new Instance( let instance = new Instance(
this, connectionClient, instanceInfo.path, this.config.get("slave.factorio_directory"), instanceInfo.config this, connectionClient, instanceInfo.path, this.config.get("host.factorio_directory"), instanceInfo.config
); );
this.instanceConnections.set(instanceId, instanceConnection); this.instanceConnections.set(instanceId, instanceConnection);
@@ -422,8 +422,8 @@ class Slave extends libLink.Link {
for await (let result of libPrometheus.defaultRegistry.collect()) { for await (let result of libPrometheus.defaultRegistry.collect()) {
if (result.metric.name.startsWith("process_")) { if (result.metric.name.startsWith("process_")) {
results.push(libPrometheus.serializeResult(result, { results.push(libPrometheus.serializeResult(result, {
addLabels: { "slave_id": String(this.config.get("slave.id")) }, addLabels: { "host_id": String(this.config.get("host.id")) },
metricName: result.metric.name.replace("process_", "clusterio_slave_"), metricName: result.metric.name.replace("process_", "clusterio_host_"),
})); }));
} else { } else {
@@ -601,7 +601,7 @@ class Slave extends libLink.Link {
checkRequestSaveName(filename); checkRequestSaveName(filename);
let instanceInfo = this.getRequestInstanceInfo(instance_id); let instanceInfo = this.getRequestInstanceInfo(instance_id);
let url = new URL(this.config.get("slave.controller_url")); let url = new URL(this.config.get("host.controller_url"));
url.pathname += `api/stream/${stream_id}`; url.pathname += `api/stream/${stream_id}`;
let response = await phin({ let response = await phin({
url, method: "GET", url, method: "GET",
@@ -656,7 +656,7 @@ class Slave extends libLink.Link {
throw err; throw err;
} }
let url = new URL(this.config.get("slave.controller_url")); let url = new URL(this.config.get("host.controller_url"));
url.pathname += `api/stream/${stream_id}`; url.pathname += `api/stream/${stream_id}`;
phin({ phin({
url, method: "PUT", url, method: "PUT",
@@ -706,10 +706,10 @@ class Slave extends libLink.Link {
* Discover available instances * Discover available instances
* *
* Looks through the instances directory for instances and updates * Looks through the instances directory for instances and updates
* the slave and controller with the new list of instances. * the host and controller with the new list of instances.
*/ */
async updateInstances() { async updateInstances() {
this.discoveredInstanceInfos = await discoverInstances(this.config.get("slave.instances_directory")); this.discoveredInstanceInfos = await discoverInstances(this.config.get("host.instances_directory"));
let list = []; let list = [];
for (let [instanceId, instanceInfo] of this.discoveredInstanceInfos) { for (let [instanceId, instanceInfo] of this.discoveredInstanceInfos) {
let instanceConnection = this.instanceConnections.get(instanceId); let instanceConnection = this.instanceConnections.get(instanceId);
@@ -773,7 +773,7 @@ class Slave extends libLink.Link {
for (let instanceId of this.instanceConnections.keys()) { for (let instanceId of this.instanceConnections.keys()) {
await this.stopInstance(instanceId); await this.stopInstance(instanceId);
} }
await this.connector.close(1000, "Slave Shutdown"); await this.connector.close(1000, "Host Shutdown");
// Clear silly interval in pidfile library. // Clear silly interval in pidfile library.
pidusage.clear(); pidusage.clear();
@@ -781,7 +781,7 @@ class Slave extends libLink.Link {
setBlocking(true); setBlocking(true);
logger.error(` logger.error(`
+------------------------------------------------------------+ +------------------------------------------------------------+
| Unexpected error occured while shutting down slave, please | | Unexpected error occured while shutting down host, please |
| report it to https://github.com/clusterio/clusterio/issues | | report it to https://github.com/clusterio/clusterio/issues |
+------------------------------------------------------------+ +------------------------------------------------------------+
${err.stack}` ${err.stack}`
@@ -801,7 +801,7 @@ ${err.stack}`
} }
} }
module.exports = Slave; module.exports = Host;
// For testing only // For testing only
module.exports._discoverInstances = discoverInstances; module.exports._discoverInstances = discoverInstances;
@@ -98,13 +98,13 @@ const serverSettingsActions = {
/** /**
* Keeps track of the runtime parameters of an instance * Keeps track of the runtime parameters of an instance
* @alias module:slave/src/Instance * @alias module:host/src/Instance
*/ */
class Instance extends libLink.Link { class Instance extends libLink.Link {
constructor(slave, connector, dir, factorioDir, instanceConfig) { constructor(host, connector, dir, factorioDir, instanceConfig) {
super("instance", "slave", connector); super("instance", "host", connector);
libLink.attachAllMessages(this); libLink.attachAllMessages(this);
this._slave = slave; this._host = host;
this._dir = dir; this._dir = dir;
this.plugins = new Map(); this.plugins = new Map();
@@ -461,10 +461,10 @@ rcon.print(game.table_to_json(players))`.replace(/\r?\n/g, " ");
this._saveStats().catch(err => this.logger.error(`Error saving stats:\n${err.stack}`)); this._saveStats().catch(err => this.logger.error(`Error saving stats:\n${err.stack}`));
} }
async _loadPlugin(pluginInfo, slave) { async _loadPlugin(pluginInfo, host) {
let pluginLoadStarted = Date.now(); let pluginLoadStarted = Date.now();
let InstancePluginClass = await libPluginLoader.loadInstancePluginClass(pluginInfo); let InstancePluginClass = await libPluginLoader.loadInstancePluginClass(pluginInfo);
let instancePlugin = new InstancePluginClass(pluginInfo, this, slave); let instancePlugin = new InstancePluginClass(pluginInfo, this, host);
this.plugins.set(pluginInfo.name, instancePlugin); this.plugins.set(pluginInfo.name, instancePlugin);
await instancePlugin.init(); await instancePlugin.init();
libPlugin.attachPluginMessages(this, instancePlugin); libPlugin.attachPluginMessages(this, instancePlugin);
@@ -509,14 +509,14 @@ rcon.print(game.table_to_json(players))`.replace(/\r?\n/g, " ");
for (let pluginInfo of pluginInfos) { for (let pluginInfo of pluginInfos) {
if ( if (
!pluginInfo.instanceEntrypoint !pluginInfo.instanceEntrypoint
|| !this._slave.serverPlugins.has(pluginInfo.name) || !this._host.serverPlugins.has(pluginInfo.name)
|| !this.config.group(pluginInfo.name).get("load_plugin") || !this.config.group(pluginInfo.name).get("load_plugin")
) { ) {
continue; continue;
} }
try { try {
await this._loadPlugin(pluginInfo, this._slave); await this._loadPlugin(pluginInfo, this._host);
} catch (err) { } catch (err) {
this.notifyExit(); this.notifyExit();
await this.sendSaveListUpdate(); await this.sendSaveListUpdate();
@@ -593,7 +593,7 @@ rcon.print(game.table_to_json(players))`.replace(/\r?\n/g, " ");
* a vanilla mods folder. If the instance mods directory does't exist * a vanilla mods folder. If the instance mods directory does't exist
* then it will be created. * then it will be created.
* *
* On Linux this creates symlinks to mods in the slave's mods folder, on * On Linux this creates symlinks to mods in the host's mods folder, on
* Windows hard links are used instead due to symlinks being privileged. * Windows hard links are used instead due to symlinks being privileged.
*/ */
async syncMods() { async syncMods() {
@@ -608,7 +608,7 @@ rcon.print(game.table_to_json(players))`.replace(/\r?\n/g, " ");
// TODO validate factorioVersion // TODO validate factorioVersion
if (!this._slave.config.get("slave.mods_directory_is_shared")) { if (!this._host.config.get("host.mods_directory_is_shared")) {
throw new Error("Fetching mods is not implemented"); throw new Error("Fetching mods is not implemented");
} }
@@ -629,7 +629,7 @@ rcon.print(game.table_to_json(players))`.replace(/\r?\n/g, " ");
} }
// Add mods from mod the pack // Add mods from mod the pack
const modsDir = this._slave.config.get("slave.mods_directory"); const modsDir = this._host.config.get("host.mods_directory");
for (let mod of this.activeModPack.mods.values()) { for (let mod of this.activeModPack.mods.values()) {
const modFile = `${mod.name}_${mod.version}.zip`; const modFile = `${mod.name}_${mod.version}.zip`;
const target = path.join(modsDir, modFile); const target = path.join(modsDir, modFile);
@@ -668,7 +668,7 @@ rcon.print(game.table_to_json(players))`.replace(/\r?\n/g, " ");
this.logger.verbose("Writing server-adminlist.json"); this.logger.verbose("Writing server-adminlist.json");
libFileOps.safeOutputFile( libFileOps.safeOutputFile(
this.server.writePath("server-adminlist.json"), this.server.writePath("server-adminlist.json"),
JSON.stringify([...this._slave.adminlist], null, 4) JSON.stringify([...this._host.adminlist], null, 4)
); );
} }
@@ -676,7 +676,7 @@ rcon.print(game.table_to_json(players))`.replace(/\r?\n/g, " ");
this.logger.verbose("Writing server-banlist.json"); this.logger.verbose("Writing server-banlist.json");
libFileOps.safeOutputFile( libFileOps.safeOutputFile(
this.server.writePath("server-banlist.json"), this.server.writePath("server-banlist.json"),
JSON.stringify([...this._slave.banlist].map( JSON.stringify([...this._host.banlist].map(
([username, reason]) => ({ username, reason }) ([username, reason]) => ({ username, reason })
), null, 4), ), null, 4),
); );
@@ -686,7 +686,7 @@ rcon.print(game.table_to_json(players))`.replace(/\r?\n/g, " ");
this.logger.verbose("Writing server-whitelist.json"); this.logger.verbose("Writing server-whitelist.json");
libFileOps.safeOutputFile( libFileOps.safeOutputFile(
this.server.writePath("server-whitelist.json"), this.server.writePath("server-whitelist.json"),
JSON.stringify([...this._slave.whitelist], null, 4) JSON.stringify([...this._host.whitelist], null, 4)
); );
} }
@@ -897,7 +897,7 @@ rcon.print(game.table_to_json(players))`.replace(/\r?\n/g, " ");
if (this.config.get("factorio.sync_whitelist")) { if (this.config.get("factorio.sync_whitelist")) {
await this.sendRcon("/whitelist clear"); await this.sendRcon("/whitelist clear");
for (let player of this._slave.whitelist) { for (let player of this._host.whitelist) {
await this.sendRcon(`/whitelist ${player}`); await this.sendRcon(`/whitelist ${player}`);
} }
} }
@@ -1072,16 +1072,16 @@ rcon.print(game.table_to_json(players))`.replace(/\r?\n/g, " ");
let zip = await libFactorio.exportData(this.server); let zip = await libFactorio.exportData(this.server);
let content = await zip.generateAsync({ type: "nodebuffer" }); let content = await zip.generateAsync({ type: "nodebuffer" });
let url = new URL(this._slave.config.get("slave.controller_url")); let url = new URL(this._host.config.get("host.controller_url"));
url.pathname += "api/upload-export"; url.pathname += "api/upload-export";
url.searchParams.set("mod_pack_id", this.activeModPack.id); url.searchParams.set("mod_pack_id", this.activeModPack.id);
let response = await phin({ let response = await phin({
url, method: "PUT", url, method: "PUT",
data: content, data: content,
core: { ca: this._slave.tlsCa }, core: { ca: this._host.tlsCa },
headers: { headers: {
"Content-Type": "application/zip", "Content-Type": "application/zip",
"x-access-token": this._slave.config.get("slave.controller_token"), "x-access-token": this._host.config.get("host.controller_token"),
}, },
}); });
if (response.statusCode !== 200) { if (response.statusCode !== 200) {
@@ -5,28 +5,28 @@ const libErrors = require("@clusterio/lib/errors");
class InstanceConnection extends libLink.Link { class InstanceConnection extends libLink.Link {
constructor(connector, slave, instanceId) { constructor(connector, host, instanceId) {
super("slave", "instance", connector); super("host", "instance", connector);
this.slave = slave; this.host = host;
this.instanceId = instanceId; this.instanceId = instanceId;
this.plugins = new Map(); this.plugins = new Map();
this.status = "stopped"; this.status = "stopped";
libLink.attachAllMessages(this); libLink.attachAllMessages(this);
for (let pluginInfo of slave.pluginInfos) { for (let pluginInfo of host.pluginInfos) {
libPlugin.attachPluginMessages(this, { info: pluginInfo }); libPlugin.attachPluginMessages(this, { info: pluginInfo });
} }
} }
async forwardRequestToController(message, request) { async forwardRequestToController(message, request) {
return await request.send(this.slave, message.data); return await request.send(this.host, message.data);
} }
async forwardRequestToInstance(message, request) { async forwardRequestToInstance(message, request) {
let instanceId = message.data.instance_id; let instanceId = message.data.instance_id;
let instanceConnection = this.slave.instanceConnections.get(instanceId); let instanceConnection = this.host.instanceConnections.get(instanceId);
if (!instanceConnection) { if (!instanceConnection) {
// Instance is probably on another slave // Instance is probably on another host
return await this.forwardRequestToController(message, request); return await this.forwardRequestToController(message, request);
} }
@@ -40,9 +40,9 @@ class InstanceConnection extends libLink.Link {
async forwardEventToInstance(message, event) { async forwardEventToInstance(message, event) {
let instanceId = message.data.instance_id; let instanceId = message.data.instance_id;
let instanceConnection = this.slave.instanceConnections.get(instanceId); let instanceConnection = this.host.instanceConnections.get(instanceId);
if (!instanceConnection) { if (!instanceConnection) {
// Instance is probably on another slave // Instance is probably on another host
await this.forwardEventToController(message, event); await this.forwardEventToController(message, event);
return; return;
} }
@@ -52,15 +52,15 @@ class InstanceConnection extends libLink.Link {
} }
async forwardEventToController(message, event) { async forwardEventToController(message, event) {
if (!this.slave.connector.hasSession) { if (!this.host.connector.hasSession) {
return; return;
} }
event.send(this.slave, message.data); event.send(this.host, message.data);
} }
async broadcastEventToInstance(message, event) { async broadcastEventToInstance(message, event) {
for (let instanceConnection of this.slave.instanceConnections.values()) { for (let instanceConnection of this.host.instanceConnections.values()) {
// Do not broadcast back to the source // Do not broadcast back to the source
if (instanceConnection === this) { continue; } if (instanceConnection === this) { continue; }
if (event.plugin && !instanceConnection.plugins.has(event.plugin)) { continue; } if (event.plugin && !instanceConnection.plugins.has(event.plugin)) { continue; }
@@ -76,7 +76,7 @@ class InstanceConnection extends libLink.Link {
async instanceStatusChangedEventHandler(message, event) { async instanceStatusChangedEventHandler(message, event) {
this.status = message.data.status; this.status = message.data.status;
if (this.status === "stopped") { if (this.status === "stopped") {
this.slave.instanceConnections.delete(this.instanceId); this.host.instanceConnections.delete(this.instanceId);
} }
this.forwardEventToController(message, event); this.forwardEventToController(message, event);
} }
+1 -1
View File
@@ -1,5 +1,5 @@
# Common Library for Clusterio # Common Library for Clusterio
This package contains functionality shared between controller, slave, ctl and/or plugins of Clusterio. This package contains functionality shared between controller, host, ctl and/or plugins of Clusterio.
See [the Clusterio repository](https://github.com/clusterio/clusterio) for more information on Clusterio. See [the Clusterio repository](https://github.com/clusterio/clusterio) for more information on Clusterio.
+18 -18
View File
@@ -135,35 +135,35 @@ class CommandTree {
/** /**
* Resolve a string into a slave ID * Resolve a string into a host ID
* *
* Resolves a string with either an slave name or an id into an integer with * Resolves a string with either an host name or an id into an integer with
* the slave ID. * the host ID.
* *
* @param {module:lib/link.Link} client - link to controller to query slave on. * @param {module:lib/link.Link} client - link to controller to query host on.
* @param {string} slaveName - string with name or id of slave. * @param {string} hostName - string with name or id of host.
* @returns {Promise<number>} slave ID. * @returns {Promise<number>} host ID.
* @static * @static
*/ */
async function resolveSlave(client, slaveName) { async function resolveHost(client, hostName) {
let slaveId; let hostId;
if (/^-?\d+$/.test(slaveName)) { if (/^-?\d+$/.test(hostName)) {
slaveId = parseInt(slaveName, 10); hostId = parseInt(hostName, 10);
} else { } else {
let response = await libLink.messages.listSlaves.send(client); let response = await libLink.messages.listHosts.send(client);
for (let slave of response.list) { for (let host of response.list) {
if (slave.name === slaveName) { if (host.name === hostName) {
slaveId = slave.id; hostId = host.id;
break; break;
} }
} }
if (slaveId === undefined) { if (hostId === undefined) {
throw new libErrors.CommandError(`No slave named ${slaveName}`); throw new libErrors.CommandError(`No host named ${hostName}`);
} }
} }
return slaveId; return hostId;
} }
/** /**
@@ -275,7 +275,7 @@ module.exports = {
Command, Command,
CommandTree, CommandTree,
resolveSlave, resolveHost,
resolveInstance, resolveInstance,
resolveModPack, resolveModPack,
retrieveRole, retrieveRole,
+34 -34
View File
@@ -10,7 +10,7 @@ const classes = require("./classes");
* @memberof module:lib/config * @memberof module:lib/config
*/ */
class ControllerGroup extends classes.ConfigGroup { } class ControllerGroup extends classes.ConfigGroup { }
ControllerGroup.defaultAccess = ["controller", "slave", "control"]; ControllerGroup.defaultAccess = ["controller", "host", "control"];
ControllerGroup.groupName = "controller"; ControllerGroup.groupName = "controller";
ControllerGroup.define({ ControllerGroup.define({
name: "name", name: "name",
@@ -110,7 +110,7 @@ ControllerGroup.define({
ControllerGroup.define({ ControllerGroup.define({
name: "metrics_timeout", name: "metrics_timeout",
title: "Metrics Timeout", title: "Metrics Timeout",
description: "Timeout in seconds for metrics gathering from slaves.", description: "Timeout in seconds for metrics gathering from hosts.",
type: "number", type: "number",
initial_value: 8, initial_value: 8,
}); });
@@ -148,95 +148,95 @@ ControllerConfig.registerGroup(ControllerGroup);
/** /**
* Slave config group for {@link module:lib/config.SlaveConfig} * Host config group for {@link module:lib/config.HostConfig}
* @extends module:lib/config.ConfigGroup * @extends module:lib/config.ConfigGroup
* @memberof module:lib/config * @memberof module:lib/config
*/ */
class SlaveGroup extends classes.ConfigGroup {} class HostGroup extends classes.ConfigGroup {}
SlaveGroup.defaultAccess = ["controller", "slave", "control"]; HostGroup.defaultAccess = ["controller", "host", "control"];
SlaveGroup.groupName = "slave"; HostGroup.groupName = "host";
SlaveGroup.define({ HostGroup.define({
name: "name", name: "name",
description: "Name of the slave", description: "Name of the host",
type: "string", type: "string",
initial_value: "New Slave", initial_value: "New Host",
}); });
SlaveGroup.define({ HostGroup.define({
name: "id", name: "id",
description: "ID of the slave", description: "ID of the host",
type: "number", type: "number",
initial_value: () => Math.random() * 2**31 | 0, initial_value: () => Math.random() * 2**31 | 0,
}); });
SlaveGroup.define({ HostGroup.define({
name: "factorio_directory", name: "factorio_directory",
description: "Path to directory to look for factorio installs", description: "Path to directory to look for factorio installs",
type: "string", type: "string",
initial_value: "factorio", initial_value: "factorio",
}); });
SlaveGroup.define({ HostGroup.define({
name: "mods_directory", name: "mods_directory",
title: "Mods Directory", title: "Mods Directory",
description: "Path to directory where mods for instances are cached.", description: "Path to directory where mods for instances are cached.",
type: "string", type: "string",
initial_value: "mods", initial_value: "mods",
}); });
SlaveGroup.define({ HostGroup.define({
name: "mods_directory_is_shared", name: "mods_directory_is_shared",
title: "Mods Directory is Shared", title: "Mods Directory is Shared",
description: "True if the mods directory is shared with the controller.", description: "True if the mods directory is shared with the controller.",
type: "boolean", type: "boolean",
initial_value: true, initial_value: true,
}); });
SlaveGroup.define({ HostGroup.define({
name: "instances_directory", name: "instances_directory",
description: "Path to directory to store instances in.", description: "Path to directory to store instances in.",
restartRequired: true, restartRequired: true,
type: "string", type: "string",
initial_value: "instances", initial_value: "instances",
}); });
SlaveGroup.define({ HostGroup.define({
name: "controller_url", name: "controller_url",
description: "URL to connect to the controller at", description: "URL to connect to the controller at",
restartRequired: true, restartRequired: true,
type: "string", type: "string",
initial_value: "http://localhost:8080/", initial_value: "http://localhost:8080/",
}); });
SlaveGroup.define({ HostGroup.define({
name: "controller_token", name: "controller_token",
description: "Token to authenticate to controller with.", description: "Token to authenticate to controller with.",
restartRequired: true, restartRequired: true,
type: "string", type: "string",
initial_value: "enter token here", initial_value: "enter token here",
}); });
SlaveGroup.define({ HostGroup.define({
name: "tls_ca", name: "tls_ca",
description: "Path to Certificate Authority to validate TLS connection to controller against.", description: "Path to Certificate Authority to validate TLS connection to controller against.",
restartRequired: true, restartRequired: true,
type: "string", type: "string",
optional: true, optional: true,
}); });
SlaveGroup.define({ HostGroup.define({
name: "public_address", name: "public_address",
description: "Public facing address players should connect to in order to join instances on this slave", description: "Public facing address players should connect to in order to join instances on this host",
type: "string", type: "string",
initial_value: "localhost", initial_value: "localhost",
}); });
SlaveGroup.define({ HostGroup.define({
name: "max_reconnect_delay", name: "max_reconnect_delay",
title: "Max Reconnect Delay", title: "Max Reconnect Delay",
description: "Maximum delay to wait before attempting to reconnect WebSocket", description: "Maximum delay to wait before attempting to reconnect WebSocket",
type: "number", type: "number",
initial_value: 60, initial_value: 60,
}); });
SlaveGroup.finalize(); HostGroup.finalize();
/** /**
* Slave Config class * Host Config class
* @extends module:lib/config.Config * @extends module:lib/config.Config
* @memberof module:lib/config * @memberof module:lib/config
*/ */
class SlaveConfig extends classes.Config { } class HostConfig extends classes.Config { }
SlaveConfig.registerGroup(SlaveGroup); HostConfig.registerGroup(HostGroup);
/** /**
@@ -245,7 +245,7 @@ SlaveConfig.registerGroup(SlaveGroup);
* @memberof module:lib/config * @memberof module:lib/config
*/ */
class InstanceGroup extends classes.ConfigGroup { } class InstanceGroup extends classes.ConfigGroup { }
InstanceGroup.defaultAccess = ["controller", "slave", "control"]; InstanceGroup.defaultAccess = ["controller", "host", "control"];
InstanceGroup.groupName = "instance"; InstanceGroup.groupName = "instance";
InstanceGroup.define({ InstanceGroup.define({
name: "name", name: "name",
@@ -259,13 +259,13 @@ InstanceGroup.define({
initial_value: () => Math.random() * 2**31 | 0, initial_value: () => Math.random() * 2**31 | 0,
}); });
InstanceGroup.define({ InstanceGroup.define({
name: "assigned_slave", name: "assigned_host",
type: "number", type: "number",
optional: true, optional: true,
}); });
InstanceGroup.define({ InstanceGroup.define({
name: "auto_start", name: "auto_start",
description: "Automatically start this instance when the slave hosting it is started up", description: "Automatically start this instance when the host hosting it is started up",
type: "boolean", type: "boolean",
initial_value: false, initial_value: false,
}); });
@@ -277,7 +277,7 @@ InstanceGroup.finalize();
* @memberof module:lib/config * @memberof module:lib/config
*/ */
class FactorioGroup extends classes.ConfigGroup { } class FactorioGroup extends classes.ConfigGroup { }
FactorioGroup.defaultAccess = ["controller", "slave", "control"]; FactorioGroup.defaultAccess = ["controller", "host", "control"];
FactorioGroup.groupName = "factorio"; FactorioGroup.groupName = "factorio";
FactorioGroup.define({ FactorioGroup.define({
name: "version", name: "version",
@@ -478,7 +478,7 @@ function registerPluginConfigGroups(pluginInfos) {
} else { } else {
class ControllerConfigGroup extends classes.PluginConfigGroup { } class ControllerConfigGroup extends classes.PluginConfigGroup { }
ControllerConfigGroup.defaultAccess = ["controller", "slave", "control"]; ControllerConfigGroup.defaultAccess = ["controller", "host", "control"];
ControllerConfigGroup.groupName = pluginInfo.name; ControllerConfigGroup.groupName = pluginInfo.name;
ControllerConfigGroup.finalize(); ControllerConfigGroup.finalize();
ControllerConfig.registerGroup(ControllerConfigGroup); ControllerConfig.registerGroup(ControllerConfigGroup);
@@ -491,7 +491,7 @@ function registerPluginConfigGroups(pluginInfos) {
} else { } else {
class InstanceConfigGroup extends classes.PluginConfigGroup { } class InstanceConfigGroup extends classes.PluginConfigGroup { }
InstanceConfigGroup.defaultAccess = ["controller", "slave", "control"]; InstanceConfigGroup.defaultAccess = ["controller", "host", "control"];
InstanceConfigGroup.groupName = pluginInfo.name; InstanceConfigGroup.groupName = pluginInfo.name;
InstanceConfigGroup.finalize(); InstanceConfigGroup.finalize();
InstanceConfig.registerGroup(InstanceConfigGroup); InstanceConfig.registerGroup(InstanceConfigGroup);
@@ -507,20 +507,20 @@ function registerPluginConfigGroups(pluginInfos) {
*/ */
function finalizeConfigs() { function finalizeConfigs() {
ControllerConfig.finalize(); ControllerConfig.finalize();
SlaveConfig.finalize(); HostConfig.finalize();
InstanceConfig.finalize(); InstanceConfig.finalize();
ControlConfig.finalize(); ControlConfig.finalize();
} }
module.exports = { module.exports = {
ControllerGroup, ControllerGroup,
SlaveGroup, HostGroup,
InstanceGroup, InstanceGroup,
FactorioGroup, FactorioGroup,
ControlGroup, ControlGroup,
ControllerConfig, ControllerConfig,
SlaveConfig, HostConfig,
InstanceConfig, InstanceConfig,
ControlConfig, ControlConfig,
+2 -2
View File
@@ -34,8 +34,8 @@ async function generateExportMod(server) {
clean: false, clean: false,
build: true, build: true,
pack: true, pack: true,
// XXX fix path when moving lib/factorio to slave // XXX fix path when moving lib/factorio to host
sourceDir: path.join(__dirname, "..", "..", "slave", "lua", "export"), sourceDir: path.join(__dirname, "..", "..", "host", "lua", "export"),
outputDir: server.writePath("mods"), outputDir: server.writePath("mods"),
bumpPatch: false, bumpPatch: false,
factorioVersion: server.version.replace(/\.\d+$/, ""), factorioVersion: server.version.replace(/\.\d+$/, ""),
+61 -61
View File
@@ -278,7 +278,7 @@ class Request extends Message {
let messages = {}; let messages = {};
// Connection requests // Connection requests
let wsLinks = ["controller-control", "control-controller", "controller-slave", "slave-controller"]; let wsLinks = ["controller-control", "control-controller", "controller-host", "host-controller"];
messages.prepareDisconnect = new Request({ messages.prepareDisconnect = new Request({
type: "prepare_disconnect", type: "prepare_disconnect",
links: wsLinks, links: wsLinks,
@@ -287,7 +287,7 @@ messages.prepareDisconnect = new Request({
messages.prepareControllerDisconnect = new Request({ messages.prepareControllerDisconnect = new Request({
type: "prepare_controller_disconnect", type: "prepare_controller_disconnect",
links: ["slave-instance"], links: ["host-instance"],
}); });
messages.ping = new Request({ messages.ping = new Request({
@@ -335,7 +335,7 @@ messages.setControllerConfigProp = new Request({
}, },
}); });
let slaveProperties = { let hostProperties = {
"agent": { type: "string" }, "agent": { type: "string" },
"version": { type: "string" }, "version": { type: "string" },
"name": { type: "string" }, "name": { type: "string" },
@@ -344,29 +344,29 @@ let slaveProperties = {
"public_address": { type: ["null", "string"] }, "public_address": { type: ["null", "string"] },
}; };
messages.listSlaves = new Request({ messages.listHosts = new Request({
type: "list_slaves", type: "list_hosts",
links: ["control-controller"], links: ["control-controller"],
permission: "core.slave.list", permission: "core.host.list",
responseProperties: { responseProperties: {
"list": { "list": {
type: "array", type: "array",
items: { items: {
additionalProperties: false, additionalProperties: false,
required: ["agent", "version", "name", "id", "connected"], required: ["agent", "version", "name", "id", "connected"],
properties: slaveProperties, properties: hostProperties,
}, },
}, },
}, },
}); });
messages.setSlaveSubscriptions = new Request({ messages.setHostSubscriptions = new Request({
type: "set_slave_subscriptions", type: "set_host_subscriptions",
links: ["control-controller"], links: ["control-controller"],
permission: "core.slave.subscribe", permission: "core.host.subscribe",
requestProperties: { requestProperties: {
"all": { type: "boolean" }, "all": { type: "boolean" },
"slave_ids": { "host_ids": {
type: "array", type: "array",
items: { type: "integer" }, items: { type: "integer" },
}, },
@@ -376,7 +376,7 @@ messages.setSlaveSubscriptions = new Request({
let instanceProperties = { let instanceProperties = {
"name": { type: "string" }, "name": { type: "string" },
"id": { type: "integer" }, "id": { type: "integer" },
"assigned_slave": { type: ["null", "integer"] }, "assigned_host": { type: ["null", "integer"] },
"game_port": { type: ["null", "integer"] }, "game_port": { type: ["null", "integer"] },
"status": { enum: [ "status": { enum: [
"unknown", "unassigned", "stopped", "starting", "running", "stopping", "unknown", "unassigned", "stopped", "starting", "running", "stopping",
@@ -403,7 +403,7 @@ messages.listInstances = new Request({
type: "array", type: "array",
items: { items: {
additionalProperties: false, additionalProperties: false,
required: ["name", "id", "assigned_slave", "status"], required: ["name", "id", "assigned_host", "status"],
properties: instanceProperties, properties: instanceProperties,
}, },
}, },
@@ -423,22 +423,22 @@ messages.setInstanceSubscriptions = new Request({
}, },
}); });
messages.generateSlaveToken = new Request({ messages.generateHostToken = new Request({
type: "generate_slave_token", type: "generate_host_token",
links: ["control-controller"], links: ["control-controller"],
permission: "core.slave.generate_token", permission: "core.host.generate_token",
requestProperties: { requestProperties: {
"slave_id": { type: ["integer", "null"] }, "host_id": { type: ["integer", "null"] },
}, },
responseProperties: { responseProperties: {
"token": { type: "string" }, "token": { type: "string" },
}, },
}); });
messages.createSlaveConfig = new Request({ messages.createHostConfig = new Request({
type: "create_slave_config", type: "create_host_config",
links: ["control-controller"], links: ["control-controller"],
permission: "core.slave.create_config", permission: "core.host.create_config",
requestProperties: { requestProperties: {
"id": { type: ["integer", "null"] }, "id": { type: ["integer", "null"] },
"name": { type: ["string", "null"] }, "name": { type: ["string", "null"] },
@@ -500,13 +500,13 @@ messages.assignInstanceCommand = new Request({
permission: "core.instance.assign", permission: "core.instance.assign",
requestProperties: { requestProperties: {
"instance_id": { type: "integer" }, "instance_id": { type: "integer" },
"slave_id": { type: ["integer", "null"] }, "host_id": { type: ["integer", "null"] },
}, },
}); });
messages.startInstance = new Request({ messages.startInstance = new Request({
type: "start_instance", type: "start_instance",
links: ["control-controller", "controller-slave", "slave-instance"], links: ["control-controller", "controller-host", "host-instance"],
permission: "core.instance.start", permission: "core.instance.start",
requestProperties: { requestProperties: {
"save": { type: ["string", "null"] }, "save": { type: ["string", "null"] },
@@ -533,7 +533,7 @@ let saveList = {
messages.listSaves = new Request({ messages.listSaves = new Request({
type: "list_saves", type: "list_saves",
links: ["control-controller", "controller-slave", "slave-instance"], links: ["control-controller", "controller-host", "host-instance"],
permission: "core.instance.save.list", permission: "core.instance.save.list",
forwardTo: "instance", forwardTo: "instance",
responseProperties: { responseProperties: {
@@ -543,7 +543,7 @@ messages.listSaves = new Request({
messages.createSave = new Request({ messages.createSave = new Request({
type: "create_save", type: "create_save",
links: ["control-controller", "controller-slave", "slave-instance"], links: ["control-controller", "controller-host", "host-instance"],
permission: "core.instance.save.create", permission: "core.instance.save.create",
forwardTo: "instance", forwardTo: "instance",
requestProperties: { requestProperties: {
@@ -556,7 +556,7 @@ messages.createSave = new Request({
messages.renameSave = new Request({ messages.renameSave = new Request({
type: "rename_save", type: "rename_save",
links: ["control-controller", "controller-slave"], links: ["control-controller", "controller-host"],
permission: "core.instance.save.rename", permission: "core.instance.save.rename",
forwardTo: "instance", forwardTo: "instance",
requestProperties: { requestProperties: {
@@ -567,7 +567,7 @@ messages.renameSave = new Request({
messages.copySave = new Request({ messages.copySave = new Request({
type: "copy_save", type: "copy_save",
links: ["control-controller", "controller-slave"], links: ["control-controller", "controller-host"],
permission: "core.instance.save.copy", permission: "core.instance.save.copy",
forwardTo: "instance", forwardTo: "instance",
requestProperties: { requestProperties: {
@@ -578,7 +578,7 @@ messages.copySave = new Request({
messages.deleteSave = new Request({ messages.deleteSave = new Request({
type: "delete_save", type: "delete_save",
links: ["control-controller", "controller-slave"], links: ["control-controller", "controller-host"],
permission: "core.instance.save.delete", permission: "core.instance.save.delete",
forwardTo: "instance", forwardTo: "instance",
requestProperties: { requestProperties: {
@@ -601,7 +601,7 @@ messages.downloadSave = new Request({
messages.transferSave = new Request({ messages.transferSave = new Request({
type: "transfer_save", type: "transfer_save",
links: ["control-controller", "controller-slave"], links: ["control-controller", "controller-host"],
permission: "core.instance.save.transfer", permission: "core.instance.save.transfer",
requestProperties: { requestProperties: {
"instance_id": { type: "integer" }, "instance_id": { type: "integer" },
@@ -617,7 +617,7 @@ messages.transferSave = new Request({
messages.pullSave = new Request({ messages.pullSave = new Request({
type: "pull_save", type: "pull_save",
links: ["controller-slave"], links: ["controller-host"],
requestProperties: { requestProperties: {
"instance_id": { type: "integer" }, "instance_id": { type: "integer" },
"stream_id": { type: "string" }, "stream_id": { type: "string" },
@@ -630,7 +630,7 @@ messages.pullSave = new Request({
messages.pushSave = new Request({ messages.pushSave = new Request({
type: "push_save", type: "push_save",
links: ["controller-slave"], links: ["controller-host"],
requestProperties: { requestProperties: {
"instance_id": { type: "integer" }, "instance_id": { type: "integer" },
"stream_id": { type: "string" }, "stream_id": { type: "string" },
@@ -653,7 +653,7 @@ messages.setSaveListSubscriptions = new Request({
messages.loadScenario = new Request({ messages.loadScenario = new Request({
type: "load_scenario", type: "load_scenario",
links: ["control-controller", "controller-slave", "slave-instance"], links: ["control-controller", "controller-host", "host-instance"],
permission: "core.instance.load_scenario", permission: "core.instance.load_scenario",
forwardTo: "instance", forwardTo: "instance",
requestProperties: { requestProperties: {
@@ -666,42 +666,42 @@ messages.loadScenario = new Request({
messages.exportData = new Request({ messages.exportData = new Request({
type: "export_data", type: "export_data",
links: ["control-controller", "controller-slave", "slave-instance"], links: ["control-controller", "controller-host", "host-instance"],
permission: "core.instance.export_data", permission: "core.instance.export_data",
forwardTo: "instance", forwardTo: "instance",
}); });
messages.extractPlayers = new Request({ messages.extractPlayers = new Request({
type: "extract_players", type: "extract_players",
links: ["control-controller", "controller-slave", "slave-instance"], links: ["control-controller", "controller-host", "host-instance"],
permission: "core.instance.extract_players", permission: "core.instance.extract_players",
forwardTo: "instance", forwardTo: "instance",
}); });
messages.stopInstance = new Request({ messages.stopInstance = new Request({
type: "stop_instance", type: "stop_instance",
links: ["control-controller", "controller-slave", "slave-instance"], links: ["control-controller", "controller-host", "host-instance"],
permission: "core.instance.stop", permission: "core.instance.stop",
forwardTo: "instance", forwardTo: "instance",
}); });
messages.killInstance = new Request({ messages.killInstance = new Request({
type: "kill_instance", type: "kill_instance",
links: ["control-controller", "controller-slave", "slave-instance"], links: ["control-controller", "controller-host", "host-instance"],
permission: "core.instance.kill", permission: "core.instance.kill",
forwardTo: "instance", forwardTo: "instance",
}); });
messages.deleteInstance = new Request({ messages.deleteInstance = new Request({
type: "delete_instance", type: "delete_instance",
links: ["control-controller", "controller-slave"], links: ["control-controller", "controller-host"],
permission: "core.instance.delete", permission: "core.instance.delete",
forwardTo: "instance", forwardTo: "instance",
}); });
messages.sendRcon = new Request({ messages.sendRcon = new Request({
type: "send_rcon", type: "send_rcon",
links: ["control-controller", "controller-slave", "slave-instance"], links: ["control-controller", "controller-host", "host-instance"],
permission: "core.instance.send_rcon", permission: "core.instance.send_rcon",
forwardTo: "instance", forwardTo: "instance",
requestProperties: { requestProperties: {
@@ -714,7 +714,7 @@ messages.sendRcon = new Request({
messages.getModPack = new Request({ messages.getModPack = new Request({
type: "get_mod_pack", type: "get_mod_pack",
links: ["control-controller", "instance-slave", "slave-controller"], links: ["control-controller", "instance-host", "host-controller"],
permission: "core.mod_pack.get", permission: "core.mod_pack.get",
forwardTo: "controller", forwardTo: "controller",
requestProperties: { requestProperties: {
@@ -727,7 +727,7 @@ messages.getModPack = new Request({
messages.getDefaultModPack = new Request({ messages.getDefaultModPack = new Request({
type: "get_default_mod_pack", type: "get_default_mod_pack",
links: ["control-controller", "instance-slave", "slave-controller"], links: ["control-controller", "instance-host", "host-controller"],
permission: "core.mod_pack.get", permission: "core.mod_pack.get",
forwardTo: "controller", forwardTo: "controller",
responseProperties: { responseProperties: {
@@ -1107,7 +1107,7 @@ messages.setLogSubscriptions = new Request({
requestProperties: { requestProperties: {
"all": { type: "boolean" }, "all": { type: "boolean" },
"controller": { type: "boolean" }, "controller": { type: "boolean" },
"slave_ids": { "host_ids": {
type: "array", type: "array",
items: { type: "integer" }, items: { type: "integer" },
}, },
@@ -1126,7 +1126,7 @@ messages.queryLog = new Request({
requestProperties: { requestProperties: {
"all": { type: "boolean" }, "all": { type: "boolean" },
"controller": { type: "boolean" }, "controller": { type: "boolean" },
"slave_ids": { "host_ids": {
type: "array", type: "array",
items: { type: "integer" }, items: { type: "integer" },
}, },
@@ -1150,7 +1150,7 @@ messages.queryLog = new Request({
// Internal requests // Internal requests
messages.updateInstances = new Request({ messages.updateInstances = new Request({
type: "update_instances", type: "update_instances",
links: ["slave-controller"], links: ["host-controller"],
requestProperties: { requestProperties: {
"instances": { "instances": {
type: "array", type: "array",
@@ -1171,7 +1171,7 @@ messages.updateInstances = new Request({
messages.assignInstance = new Request({ messages.assignInstance = new Request({
type: "assign_instance", type: "assign_instance",
links: ["controller-slave"], links: ["controller-host"],
requestProperties: { requestProperties: {
"serialized_config": { type: "object" }, "serialized_config": { type: "object" },
}, },
@@ -1180,13 +1180,13 @@ messages.assignInstance = new Request({
messages.unassignInstance = new Request({ messages.unassignInstance = new Request({
type: "unassigne_instance", type: "unassigne_instance",
links: ["controller-slave"], links: ["controller-host"],
forwardTo: "instance", forwardTo: "instance",
}); });
messages.getMetrics = new Request({ messages.getMetrics = new Request({
type: "get_metrics", type: "get_metrics",
links: ["controller-slave", "slave-instance"], links: ["controller-host", "host-instance"],
responseProperties: { responseProperties: {
"results": { type: "array" }, "results": { type: "array" },
}, },
@@ -1221,8 +1221,8 @@ class Event extends Message {
* Optional target to broadcast this event to. Only 'instance' is * Optional target to broadcast this event to. Only 'instance' is
* currently supported. An event will only be broadcast towards * currently supported. An event will only be broadcast towards
* links that are downstream, so sending an event set to instance * links that are downstream, so sending an event set to instance
* broadcast that's sent to a slave will only be broadcast to that * broadcast that's sent to a host will only be broadcast to that
* slave's instances. * host's instances.
* @param {?Array<string>} eventRequired - * @param {?Array<string>} eventRequired -
* List of properties required to be present in the event payload. * List of properties required to be present in the event payload.
* Defaults to all. * Defaults to all.
@@ -1307,7 +1307,7 @@ class Event extends Message {
if ( if (
this.broadcastTo === "instance" this.broadcastTo === "instance"
&& [ && [
"instance-slave", "slave-controller", "control-controller", "controller-slave", "instance-host", "host-controller", "control-controller", "controller-host",
].includes(`${link.target}-${link.source}`) ].includes(`${link.target}-${link.source}`)
) { ) {
if (!handler) { if (!handler) {
@@ -1387,7 +1387,7 @@ messages.accountUpdate = new Event({
messages.logMessage = new Event({ messages.logMessage = new Event({
type: "log_message", type: "log_message",
links: ["slave-controller", "controller-control"], links: ["host-controller", "controller-control"],
eventProperties: { eventProperties: {
"info": { "info": {
type: "object", type: "object",
@@ -1400,16 +1400,16 @@ messages.logMessage = new Event({
}, },
}); });
messages.slaveUpdate = new Event({ messages.hostUpdate = new Event({
type: "slave_update", type: "host_update",
links: ["controller-control"], links: ["controller-control"],
eventProperties: slaveProperties, eventProperties: hostProperties,
}); });
messages.instanceInitialized = new Event({ messages.instanceInitialized = new Event({
type: "instance_initialized", type: "instance_initialized",
links: ["instance-slave"], links: ["instance-host"],
eventProperties: { eventProperties: {
"instance_id": { type: "integer" }, "instance_id": { type: "integer" },
"plugins": { "plugins": {
@@ -1420,7 +1420,7 @@ messages.instanceInitialized = new Event({
}); });
messages.instanceStatusChanged = new Event({ messages.instanceStatusChanged = new Event({
type: "instance_status_changed", type: "instance_status_changed",
links: ["instance-slave", "slave-controller"], links: ["instance-host", "host-controller"],
eventRequired: ["instance_id", "status"], eventRequired: ["instance_id", "status"],
eventProperties: { eventProperties: {
"instance_id": { type: "integer" }, "instance_id": { type: "integer" },
@@ -1439,7 +1439,7 @@ messages.instanceUpdate = new Event({
messages.saveListUpdate = new Event({ messages.saveListUpdate = new Event({
type: "save_list_update", type: "save_list_update",
links: ["instance-slave", "slave-controller", "controller-control"], links: ["instance-host", "host-controller", "controller-control"],
forwardTo: "controller", forwardTo: "controller",
eventProperties: { eventProperties: {
"instance_id": { type: "integer" }, "instance_id": { type: "integer" },
@@ -1472,7 +1472,7 @@ messages.userUpdate = new Event({
messages.controllerConnectionEvent = new Event({ messages.controllerConnectionEvent = new Event({
type: "controller_connection_event", type: "controller_connection_event",
links: ["slave-instance"], links: ["host-instance"],
eventProperties: { eventProperties: {
"event": { type: "string" }, "event": { type: "string" },
}, },
@@ -1480,7 +1480,7 @@ messages.controllerConnectionEvent = new Event({
messages.syncUserLists = new Event({ messages.syncUserLists = new Event({
type: "sync_user_lists", type: "sync_user_lists",
links: ["controller-slave"], links: ["controller-host"],
eventProperties: { eventProperties: {
"adminlist": { "adminlist": {
type: "array", type: "array",
@@ -1503,7 +1503,7 @@ messages.syncUserLists = new Event({
messages.banlistUpdate = new Event({ messages.banlistUpdate = new Event({
type: "banlist_update", type: "banlist_update",
links: ["controller-slave", "slave-instance"], links: ["controller-host", "host-instance"],
broadcastTo: "instance", broadcastTo: "instance",
eventProperties: { eventProperties: {
"name": { type: "string" }, "name": { type: "string" },
@@ -1514,7 +1514,7 @@ messages.banlistUpdate = new Event({
messages.adminlistUpdate = new Event({ messages.adminlistUpdate = new Event({
type: "adminlist_update", type: "adminlist_update",
links: ["controller-slave", "slave-instance"], links: ["controller-host", "host-instance"],
eventProperties: { eventProperties: {
"name": { type: "string" }, "name": { type: "string" },
"admin": { type: "boolean" }, "admin": { type: "boolean" },
@@ -1524,7 +1524,7 @@ messages.adminlistUpdate = new Event({
messages.whitelistUpdate = new Event({ messages.whitelistUpdate = new Event({
type: "whitelist_update", type: "whitelist_update",
links: ["controller-slave", "slave-instance"], links: ["controller-host", "host-instance"],
eventProperties: { eventProperties: {
"name": { type: "string" }, "name": { type: "string" },
"whitelisted": { type: "boolean" }, "whitelisted": { type: "boolean" },
@@ -1534,7 +1534,7 @@ messages.whitelistUpdate = new Event({
messages.playerEvent = new Event({ messages.playerEvent = new Event({
type: "player_event", type: "player_event",
links: ["instance-slave", "slave-controller"], links: ["instance-host", "host-controller"],
forwardTo: "controller", forwardTo: "controller",
eventRequired: ["instance_id", "type", "name"], eventRequired: ["instance_id", "type", "name"],
eventProperties: { eventProperties: {
+11 -11
View File
@@ -63,8 +63,8 @@ class WebConsoleFormat {
transform(info, options) { transform(info, options) {
let src = " "; let src = " ";
if (info.slave_id !== undefined) { if (info.host_id !== undefined) {
src += `s:${info.slave_name} - `; src += `s:${info.host_name} - `;
} }
if (info.instance_id !== undefined) { if (info.instance_id !== undefined) {
src += `i:${info.instance_name}`; src += `i:${info.instance_name}`;
@@ -88,12 +88,12 @@ class WebConsoleFormat {
* @property {string} [max_level] - * @property {string} [max_level] -
* Maximum log level to include. Higher levels are more verbose. * Maximum log level to include. Higher levels are more verbose.
* @property {boolean} [all] - * @property {boolean} [all] -
* Include log entries from controller, all slaves and all instances. * Include log entries from controller, all hosts and all instances.
* @property {boolean} [controller] - * @property {boolean} [controller] -
* Include log entries from the controller. * Include log entries from the controller.
* @property {Array<number>} [slave_ids] - * @property {Array<number>} [host_ids] -
* Include log entries for the given slaves and instances of those * Include log entries for the given hosts and instances of those
* slaves by id. * hosts by id.
* @property {Array<number>} [instance_ids] - * @property {Array<number>} [instance_ids] -
* Include log entries for the given instances by id. * Include log entries for the given instances by id.
*/ */
@@ -107,7 +107,7 @@ class WebConsoleFormat {
* filter returning true for log entries that match it. * filter returning true for log entries that match it.
* @static * @static
*/ */
function logFilter({ all, controller, slave_ids, instance_ids, max_level }) { function logFilter({ all, controller, host_ids, instance_ids, max_level }) {
return info => { return info => {
// Note: reversed to filter out undefined levels // Note: reversed to filter out undefined levels
if (max_level && !(levels[info.level] <= levels[max_level])) { if (max_level && !(levels[info.level] <= levels[max_level])) {
@@ -117,14 +117,14 @@ function logFilter({ all, controller, slave_ids, instance_ids, max_level }) {
if (all) { if (all) {
return true; return true;
} }
if (controller && info.slave_id === undefined) { if (controller && info.host_id === undefined) {
return true; return true;
} }
if ( if (
slave_ids host_ids
&& info.slave_id !== undefined && info.host_id !== undefined
&& info.instance_id === undefined && info.instance_id === undefined
&& slave_ids.includes(info.slave_id) && host_ids.includes(info.host_id)
) { ) {
return true; return true;
} }
+13 -13
View File
@@ -90,8 +90,8 @@ class TerminalFormat {
if (info.instance_name) { if (info.instance_name) {
src += `i:${info.instance_name} `; src += `i:${info.instance_name} `;
} else if (info.slave_name) { } else if (info.host_name) {
src += `s:${info.slave_name} `; src += `s:${info.host_name} `;
} }
if (info.plugin) { if (info.plugin) {
src += `${info.plugin}: `; src += `${info.plugin}: `;
@@ -158,7 +158,7 @@ class LogIndex {
this.index.set(file, { this.index.set(file, {
levels: new Set(serializedEntry.levels), levels: new Set(serializedEntry.levels),
controller: serializedEntry.controller, controller: serializedEntry.controller,
slave_ids: new Set(serializedEntry.slave_ids), host_ids: new Set(serializedEntry.host_ids),
instance_ids: new Set(serializedEntry.instance_ids), instance_ids: new Set(serializedEntry.instance_ids),
}); });
} }
@@ -174,7 +174,7 @@ class LogIndex {
serialized.files[file] = { serialized.files[file] = {
levels: [...entry.levels], levels: [...entry.levels],
controller: entry.controller, controller: entry.controller,
slave_ids: [...entry.slave_ids], host_ids: [...entry.host_ids],
instance_ids: [...entry.instance_ids], instance_ids: [...entry.instance_ids],
}; };
} }
@@ -225,7 +225,7 @@ class LogIndex {
let entry = { let entry = {
levels: new Set(), levels: new Set(),
controller: false, controller: false,
slave_ids: new Set(), host_ids: new Set(),
instance_ids: new Set(), instance_ids: new Set(),
}; };
for await (let line of lineStream) { for await (let line of lineStream) {
@@ -240,10 +240,10 @@ class LogIndex {
if (info.level) { if (info.level) {
entry.levels.add(info.level); entry.levels.add(info.level);
} }
if (info.slave_id === undefined) { if (info.host_id === undefined) {
entry.controller = true; entry.controller = true;
} else if (info.instance_id === undefined) { } else if (info.instance_id === undefined) {
entry.slave_ids.add(info.slave_id); entry.host_ids.add(info.host_id);
} }
if (info.instance_id !== undefined) { if (info.instance_id !== undefined) {
entry.instance_ids.add(info.instance_id); entry.instance_ids.add(info.instance_id);
@@ -279,7 +279,7 @@ class LogIndex {
* @returns {boolean} * @returns {boolean}
* true if the file may contain entries included by the filter. * true if the file may contain entries included by the filter.
*/ */
filterIncludesFile(file, { max_level, all, controller, instance_ids, slave_ids }) { filterIncludesFile(file, { max_level, all, controller, instance_ids, host_ids }) {
let entry = this.index.get(file); let entry = this.index.get(file);
if (!entry) { if (!entry) {
return true; return true;
@@ -301,7 +301,7 @@ class LogIndex {
if (controller && entry.controller) { if (controller && entry.controller) {
return true; return true;
} }
if (slave_ids && slave_ids.some(id => entry.slave_ids.has(id))) { if (host_ids && host_ids.some(id => entry.host_ids.has(id))) {
return true; return true;
} }
if (instance_ids && instance_ids.some(id => entry.instance_ids.has(id))) { if (instance_ids && instance_ids.some(id => entry.instance_ids.has(id))) {
@@ -321,12 +321,12 @@ class LogIndex {
* @property {string} [max_level] - * @property {string} [max_level] -
* Maximum log level to include. Higher levels are more verbose. * Maximum log level to include. Higher levels are more verbose.
* @property {boolean} [all] - * @property {boolean} [all] -
* Include log entries from controller, all slaves and all instances. * Include log entries from controller, all hosts and all instances.
* @property {boolean} [controller] - * @property {boolean} [controller] -
* Include log entries from the controller. * Include log entries from the controller.
* @property {Array<number>} [slave_ids] - * @property {Array<number>} [host_ids] -
* Include log entries for the given slaves and instances of those * Include log entries for the given hosts and instances of those
* slaves by id. * hosts by id.
* @property {Array<number>} [instance_ids] - * @property {Array<number>} [instance_ids] -
* Include log entries for the given instances by id. * Include log entries for the given instances by id.
*/ */
+49 -49
View File
@@ -19,7 +19,7 @@ const helpers = require("./helpers");
* Base class for instance plugins * Base class for instance plugins
* *
* Instance plugins are subclasses of this class which get instantiated by * Instance plugins are subclasses of this class which get instantiated by
* the slave when it brings up an instance with the plugin enabled in the * the host when it brings up an instance with the plugin enabled in the
* config. To be discovered the class must be exported under the name * config. To be discovered the class must be exported under the name
* `InstancePlugin` in the module specified by the `instanceEntrypoint` in * `InstancePlugin` in the module specified by the `instanceEntrypoint` in
* the plugin's info.js file. * the plugin's info.js file.
@@ -31,7 +31,7 @@ const helpers = require("./helpers");
* @static * @static
*/ */
class BaseInstancePlugin { class BaseInstancePlugin {
constructor(info, instance, slave) { constructor(info, instance, host) {
/** /**
* The plugin's own info module * The plugin's own info module
*/ */
@@ -39,19 +39,19 @@ class BaseInstancePlugin {
/** /**
* Instance the plugin started for * Instance the plugin started for
* @type {module:slave/src/Instance} * @type {module:host/src/Instance}
*/ */
this.instance = instance; this.instance = instance;
/** /**
* Slave running the instance * Host running the instance
* *
* With the exepction of accessing the slave's config you should * With the exepction of accessing the host's config you should
* avoid ineracting with the slave object directly. * avoid ineracting with the host object directly.
* *
* @type {module:slave/src/Slave} * @type {module:host/src/Host}
*/ */
this.slave = slave; this.host = host;
/** /**
* Logger for this plugin * Logger for this plugin
@@ -161,12 +161,12 @@ class BaseInstancePlugin {
* *
* ##### drop * ##### drop
* *
* Invoked when a connection loss is detected between the slave and the * Invoked when a connection loss is detected between the host and the
* controller. Plugins should respond to this event by throtteling messages * controller. Plugins should respond to this event by throtteling messages
* it is sending to the controller to an absolute minimum. * it is sending to the controller to an absolute minimum.
* *
* Messages sent over a dropped controller connection will get queued up in * Messages sent over a dropped controller connection will get queued up in
* memory on the slave and sent all in one go when the connection is * memory on the host and sent all in one go when the connection is
* re-established again. * re-established again.
* *
* ##### resume * ##### resume
@@ -186,17 +186,17 @@ class BaseInstancePlugin {
onControllerConnectionEvent(event) { } onControllerConnectionEvent(event) { }
/** /**
* Called when the controller is preparing to disconnect from the slave * Called when the controller is preparing to disconnect from the host
* *
* Invoked when the controller has requested the disconnection from * Invoked when the controller has requested the disconnection from
* the slave. This typically happens when it is in the process of * the host. This typically happens when it is in the process of
* shutting down. * shutting down.
* *
* Plugins must stop sending messages to the controller, or are forwarded * Plugins must stop sending messages to the controller, or are forwarded
* via the controller after the prepare disconnect has been handled. * via the controller after the prepare disconnect has been handled.
* *
* @param {module:controller/src/SlaveConnection} connection - * @param {module:controller/src/HostConnection} connection -
* The connection to the slave preparing to disconnect. * The connection to the host preparing to disconnect.
*/ */
async onPrepareControllerDisconnect(connection) { } async onPrepareControllerDisconnect(connection) { }
@@ -324,12 +324,12 @@ class BaseControllerPlugin {
* Called when the status of an instance changes * Called when the status of an instance changes
* *
* Invoked when the controller has changed the status of an instance * Invoked when the controller has changed the status of an instance
* or received notice from a slave that the status of an instance has * or received notice from a host that the status of an instance has
* changed. The possible statuses that can be notified about are: * changed. The possible statuses that can be notified about are:
* - `unassigned:`: Instance is no longer asssigned to a slave. * - `unassigned:`: Instance is no longer asssigned to a host.
* - `unknown`: Slave assigned to instance is offline. * - `unknown`: Host assigned to instance is offline.
* - `stopped`: Instance is no longer running or was just assigned to a * - `stopped`: Instance is no longer running or was just assigned to a
* slave. * host.
* - `starting`: Instance is in the process of starting up. * - `starting`: Instance is in the process of starting up.
* - `running`: Instance startup completed and is now running. * - `running`: Instance startup completed and is now running.
* - `stopping`: Instance is in the processing stopping. * - `stopping`: Instance is in the processing stopping.
@@ -339,20 +339,20 @@ class BaseControllerPlugin {
* - `deleted`: Instance was deleted. * - `deleted`: Instance was deleted.
* *
* On controller startup all known instances gets the status `unknown` if * On controller startup all known instances gets the status `unknown` if
* they are assigned to a slave, when the slave then connects to the * they are assigned to a host, when the host then connects to the
* controller and informs of the current status of its instances this hook * controller and informs of the current status of its instances this hook
* is invoked for all those instances. You can detect this situation by * is invoked for all those instances. You can detect this situation by
* checking if prev equals `unknown`. If the slave disconnects the * checking if prev equals `unknown`. If the host disconnects the
* instances will again get the `unknown` status. * instances will again get the `unknown` status.
* *
* When instances are created on the controller they will notify of a status * When instances are created on the controller they will notify of a status
* change with prev set to null. While the status of new instances is * change with prev set to null. While the status of new instances is
* in most cases `unassigned` it's possible for the created instance to * in most cases `unassigned` it's possible for the created instance to
* start with any state in some slave connection corner cases. * start with any state in some host connection corner cases.
* *
* Note that it's possible for status change notification to get lost in * Note that it's possible for status change notification to get lost in
* the case of network outages if reconnect fails to re-establish the * the case of network outages if reconnect fails to re-establish the
* session between the controller and the slave. * session between the controller and the host.
* *
* @param {Object} instance - the instance that changed. * @param {Object} instance - the instance that changed.
* @param {?string} prev - the previous status of the instance. * @param {?string} prev - the previous status of the instance.
@@ -409,22 +409,22 @@ class BaseControllerPlugin {
async onShutdown() { } async onShutdown() { }
/** /**
* Called when an event on a slave connection happens * Called when an event on a host connection happens
* *
* The event param may be one of connect, drop, resume and close and has * The event param may be one of connect, drop, resume and close and has
* the following meaning: * the following meaning:
* *
* ##### connect * ##### connect
* *
* Invoked when a new connection to the slave has been established. * Invoked when a new connection to the host has been established.
* *
* ##### drop * ##### drop
* *
* Invoked when a connection loss is detected between the controller and a * Invoked when a connection loss is detected between the controller and a
* slave. Plugins should respond to this event by throtteling messages * host. Plugins should respond to this event by throtteling messages
* it is sending to the given slave connection to an absolute minimum. * it is sending to the given host connection to an absolute minimum.
* *
* Messages sent over a dropped slave connection will get queued up in * Messages sent over a dropped host connection will get queued up in
* memory on the controller and sent all in one go when the connection is * memory on the controller and sent all in one go when the connection is
* re-established again. * re-established again.
* *
@@ -435,13 +435,13 @@ class BaseControllerPlugin {
* *
* ##### close * ##### close
* *
* Invoked when the connection to the slave has been closed. * Invoked when the connection to the host has been closed.
* *
* @param {module:controller/src/SlaveConnection} connection - * @param {module:controller/src/HostConnection} connection -
* The connection the event occured on. * The connection the event occured on.
* @param {string} event - one of connect, drop, resume and close * @param {string} event - one of connect, drop, resume and close
*/ */
onSlaveConnectionEvent(connection, event) { } onHostConnectionEvent(connection, event) { }
/** /**
* Called when an avent on a control connection happens * Called when an avent on a control connection happens
@@ -480,18 +480,18 @@ class BaseControllerPlugin {
onControlConnectionEvent(connection, event) { } onControlConnectionEvent(connection, event) { }
/** /**
* Called when a slave is preparing to disconnect from the controller * Called when a host is preparing to disconnect from the controller
* *
* Invoked when a slave has requested the disconnection from the controller. * Invoked when a host has requested the disconnection from the controller.
* This typically happens when it is in the process of shutting down. * This typically happens when it is in the process of shutting down.
* *
* Plugins must stop sending messages to the slave in question after the * Plugins must stop sending messages to the host in question after the
* prepare disconnect has been handled. * prepare disconnect has been handled.
* *
* @param {module:controller/src/SlaveConnection} connection - * @param {module:controller/src/HostConnection} connection -
* The connection to the slave preparing to disconnect. * The connection to the host preparing to disconnect.
*/ */
async onPrepareSlaveDisconnect(connection) { } async onPrepareHostDisconnect(connection) { }
/** /**
* Called when a mod pack is updated * Called when a mod pack is updated
@@ -536,22 +536,22 @@ class BaseControllerPlugin {
async onPlayerEvent(instance, event) { } async onPlayerEvent(instance, event) { }
/** /**
* Broadcast event to all connected slaves * Broadcast event to all connected hosts
* *
* Sends the given event to all slaves connected to the controller. * Sends the given event to all hosts connected to the controller.
* This does not include slaves that are in the process of closing the * This does not include hosts that are in the process of closing the
* connection, which typically happens when they are shutting down. * connection, which typically happens when they are shutting down.
* *
* @param {module:lib/link.Event} event - Event to send * @param {module:lib/link.Event} event - Event to send
* @param {Object} data - Data ta pass with the event. * @param {Object} data - Data ta pass with the event.
*/ */
broadcastEventToSlaves(event, data={}) { broadcastEventToHosts(event, data={}) {
for (let slaveConnection of this.controller.wsServer.slaveConnections.values()) { for (let hostConnection of this.controller.wsServer.hostConnections.values()) {
if ( if (
!slaveConnection.connector.closing !hostConnection.connector.closing
&& (!event.plugin || slaveConnection.plugins.has(event.plugin)) && (!event.plugin || hostConnection.plugins.has(event.plugin))
) { ) {
event.send(slaveConnection, data); event.send(hostConnection, data);
} }
} }
} }
@@ -702,11 +702,11 @@ class BaseWebPlugin {
* @type {object} * @type {object}
* @property {React.ComponentType} ControllerPage - * @property {React.ComponentType} ControllerPage -
* Placed at the end of the controller page. * Placed at the end of the controller page.
* @property {React.ComponentType} SlavesPage - * @property {React.ComponentType} HostsPage -
* Placed at the end of the slaves list page. * Placed at the end of the hosts list page.
* @property {React.ComponentType} SlaveViewPage - * @property {React.ComponentType} HostViewPage -
* Placed at the end of each slave page. Takes a `slave` param * Placed at the end of each host page. Takes a `host` param
* which is the slave the page is displayed for. * which is the host the page is displayed for.
* @property {React.ComponentType} InstancesPage - * @property {React.ComponentType} InstancesPage -
* Placed at the end of the instance list page. * Placed at the end of the instance list page.
* @property {React.ComponentType} InstanceViewPage - * @property {React.ComponentType} InstanceViewPage -
+1 -1
View File
@@ -168,7 +168,7 @@ const clientHandshake = ajv.compile({
anyOf: [ anyOf: [
{ {
properties: { properties: {
"type": { const: "register_slave" }, "type": { const: "register_host" },
"data": { "data": {
additionalProperties: false, additionalProperties: false,
required: ["token", "agent", "version", "name", "id", "plugins"], required: ["token", "agent", "version", "name", "id", "plugins"],
+1 -1
View File
@@ -1,5 +1,5 @@
/** /**
* Implementation of commands shared between controller/slave/ctl. * Implementation of commands shared between controller/host/ctl.
* @module lib/shared_commands * @module lib/shared_commands
*/ */
"use strict"; "use strict";
+13 -13
View File
@@ -365,26 +365,26 @@ definePermission({
description: "Modify the controller config or entries of the controller config.", description: "Modify the controller config or entries of the controller config.",
}); });
definePermission({ definePermission({
name: "core.slave.list", name: "core.host.list",
title: "List slaves", title: "List hosts",
description: "Get the full list of slaves in the cluster.", description: "Get the full list of hosts in the cluster.",
grantByDefault: true, grantByDefault: true,
}); });
definePermission({ definePermission({
name: "core.slave.subscribe", name: "core.host.subscribe",
title: "Subscribe to slave updates", title: "Subscribe to host updates",
description: "Subscribe to be notified on updates on the details of slaves.", description: "Subscribe to be notified on updates on the details of hosts.",
grantByDefault: true, grantByDefault: true,
}); });
definePermission({ definePermission({
name: "core.slave.generate_token", name: "core.host.generate_token",
title: "Generate slave token", title: "Generate host token",
description: "Generate tokens for slaves to connect to the cluster with.", description: "Generate tokens for hosts to connect to the cluster with.",
}); });
definePermission({ definePermission({
name: "core.slave.create_config", name: "core.host.create_config",
title: "Create slave config", title: "Create host config",
description: "Create slave configs via the controller.", description: "Create host configs via the controller.",
}); });
definePermission({ definePermission({
@@ -423,7 +423,7 @@ definePermission({
definePermission({ definePermission({
name: "core.instance.assign", name: "core.instance.assign",
title: "Assign instance", title: "Assign instance",
description: "Assign or reassign instances to a slave.", description: "Assign or reassign instances to a host.",
}); });
definePermission({ definePermission({
name: "core.instance.save.list", name: "core.instance.save.list",
@@ -5,14 +5,14 @@ import { libLink } from "@clusterio/lib";
import ControlContext from "./ControlContext"; import ControlContext from "./ControlContext";
import { notifyErrorHandler } from "../util/notify"; import { notifyErrorHandler } from "../util/notify";
import { useSlaveList } from "../model/slave"; import { useHostList } from "../model/host";
const { Paragraph } = Typography; const { Paragraph } = Typography;
export default function AssignInstanceModal(props) { export default function AssignInstanceModal(props) {
let [visible, setVisible] = useState(false); let [visible, setVisible] = useState(false);
let [slaveList] = useSlaveList(); let [hostList] = useHostList();
let [applying, setApplying] = useState(false); let [applying, setApplying] = useState(false);
let [form] = Form.useForm(); let [form] = Form.useForm();
let control = useContext(ControlContext); let control = useContext(ControlContext);
@@ -22,20 +22,20 @@ export default function AssignInstanceModal(props) {
} }
function handleAssign() { function handleAssign() {
let slaveId = form.getFieldValue("slave"); let hostId = form.getFieldValue("host");
if (slaveId === undefined) { if (hostId === undefined) {
setVisible(false); setVisible(false);
return; return;
} }
if (slaveId === "null") { if (hostId === "null") {
slaveId = null; hostId = null;
} }
setApplying(true); setApplying(true);
libLink.messages.assignInstanceCommand.send(control, { libLink.messages.assignInstanceCommand.send(control, {
instance_id: props.id, instance_id: props.id,
slave_id: slaveId, host_id: hostId,
}).then(() => { }).then(() => {
setVisible(false); setVisible(false);
if (props.onFinish) { if (props.onFinish) {
@@ -66,24 +66,24 @@ export default function AssignInstanceModal(props) {
destroyOnClose destroyOnClose
> >
<Paragraph style={{ maxWidth: "30em" }}> <Paragraph style={{ maxWidth: "30em" }}>
Select a Slave to assign this instance to. Assignment Select a Host to assign this instance to. Assignment
creates the necessary files on the slave to start up creates the necessary files on the host to start up
the instance. Note that reassigning an instance from the instance. Note that reassigning an instance from
one slave to another will not move the server save over. one host to another will not move the server save over.
</Paragraph> </Paragraph>
<Form form={form} initialValues={{ slave: props.slaveId === null ? "null" : props.slaveId }}> <Form form={form} initialValues={{ host: props.hostId === null ? "null" : props.hostId }}>
<Form.Item name="slave" label="Slave"> <Form.Item name="host" label="Host">
<Select> <Select>
<Select.Option value={"null"}> <Select.Option value={"null"}>
<Typography.Text italic>Unassigned</Typography.Text> <Typography.Text italic>Unassigned</Typography.Text>
</Select.Option> </Select.Option>
{slaveList.map((slave) => <Select.Option {hostList.map((host) => <Select.Option
key={slave["id"]} key={host["id"]}
value={slave["id"]} value={host["id"]}
disabled={!slave["connected"]} disabled={!host["connected"]}
> >
{slave["name"]} {host["name"]}
{!slave["connected"] && " (offline)"} {!host["connected"] && " (offline)"}
</Select.Option>)} </Select.Option>)}
</Select> </Select>
</Form.Item> </Form.Item>
@@ -6,61 +6,61 @@ import InstanceList from "./InstanceList";
import LogConsole from "./LogConsole"; import LogConsole from "./LogConsole";
import { useAccount } from "../model/account"; import { useAccount } from "../model/account";
import { useInstanceList } from "../model/instance"; import { useInstanceList } from "../model/instance";
import { useSlave } from "../model/slave"; import { useHost } from "../model/host";
import PageLayout from "./PageLayout"; import PageLayout from "./PageLayout";
import PluginExtra from "./PluginExtra"; import PluginExtra from "./PluginExtra";
const { Title } = Typography; const { Title } = Typography;
export default function SlaveViewPage(props) { export default function HostViewPage(props) {
let params = useParams(); let params = useParams();
let slaveId = Number(params.id); let hostId = Number(params.id);
let account = useAccount(); let account = useAccount();
let [instanceList] = useInstanceList(); let [instanceList] = useInstanceList();
let [slave] = useSlave(slaveId); let [host] = useHost(hostId);
instanceList = instanceList.filter(instance => instance["assigned_slave"] === slaveId); instanceList = instanceList.filter(instance => instance["assigned_host"] === hostId);
let nav = [{ name: "Slaves", path: "/slaves" }, { name: slave.name || slaveId }]; let nav = [{ name: "Hosts", path: "/hosts" }, { name: host.name || hostId }];
if (slave.loading) { if (host.loading) {
return <PageLayout nav={nav}><Spin size="large" /></PageLayout>; return <PageLayout nav={nav}><Spin size="large" /></PageLayout>;
} }
if (slave.missing) { if (host.missing) {
return <PageLayout nav={nav}> return <PageLayout nav={nav}>
<PageHeader <PageHeader
className="site-page-header" className="site-page-header"
title={slaveId} title={hostId}
/> />
<p>Slave with id {slaveId} was not found on the controller.</p> <p>Host with id {hostId} was not found on the controller.</p>
</PageLayout>; </PageLayout>;
} }
return <PageLayout nav={nav}> return <PageLayout nav={nav}>
<PageHeader <PageHeader
className="site-page-header" className="site-page-header"
title={slave.name || slaveId} title={host.name || hostId}
/> />
<Descriptions bordered size="small" column={{ xs: 1, sm: 2, xl: 4 }}> <Descriptions bordered size="small" column={{ xs: 1, sm: 2, xl: 4 }}>
<Descriptions.Item label="Name">{slave["name"]}</Descriptions.Item> <Descriptions.Item label="Name">{host["name"]}</Descriptions.Item>
<Descriptions.Item label="Connected"> <Descriptions.Item label="Connected">
<Tag color={slave["connected"] ? "#389e0d" : "#cf1322"}> <Tag color={host["connected"] ? "#389e0d" : "#cf1322"}>
{slave["connected"] ? "Connected" : "Disconnected"} {host["connected"] ? "Connected" : "Disconnected"}
</Tag> </Tag>
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label="Agent">{slave["agent"]}</Descriptions.Item> <Descriptions.Item label="Agent">{host["agent"]}</Descriptions.Item>
<Descriptions.Item label="Version">{slave["version"]}</Descriptions.Item> <Descriptions.Item label="Version">{host["version"]}</Descriptions.Item>
</Descriptions> </Descriptions>
{account.hasPermission("core.instance.list") && <> {account.hasPermission("core.instance.list") && <>
<Title level={5} style={{ marginTop: 16 }}>Instances</Title> <Title level={5} style={{ marginTop: 16 }}>Instances</Title>
<InstanceList instances={instanceList} size="small" hideAssignedSlave /> <InstanceList instances={instanceList} size="small" hideAssignedHost />
</>} </>}
{account.hasPermission("core.log.follow") && <> {account.hasPermission("core.log.follow") && <>
<Title level={5} style={{ marginTop: 16 }}>Console</Title> <Title level={5} style={{ marginTop: 16 }}>Console</Title>
<LogConsole slaves={[slaveId]} /> <LogConsole hosts={[hostId]} />
</>} </>}
<PluginExtra component="SlaveViewPage" slave={slave} /> <PluginExtra component="HostViewPage" host={host} />
</PageLayout>; </PageLayout>;
} }
@@ -9,17 +9,17 @@ import { useAccount } from "../model/account";
import ControlContext from "./ControlContext"; import ControlContext from "./ControlContext";
import PageLayout from "./PageLayout"; import PageLayout from "./PageLayout";
import PluginExtra from "./PluginExtra"; import PluginExtra from "./PluginExtra";
import { useSlaveList } from "../model/slave"; import { useHostList } from "../model/host";
import notify, { notifyErrorHandler } from "../util/notify"; import notify, { notifyErrorHandler } from "../util/notify";
const strcmp = new Intl.Collator(undefined, { numerice: "true", sensitivity: "base" }).compare; const strcmp = new Intl.Collator(undefined, { numerice: "true", sensitivity: "base" }).compare;
function GenerateSlaveTokenButton(props) { function GenerateHostTokenButton(props) {
let control = useContext(ControlContext); let control = useContext(ControlContext);
let [visible, setVisible] = useState(false); let [visible, setVisible] = useState(false);
let [token, setToken] = useState(null); let [token, setToken] = useState(null);
let [slaveId, setSlaveId] = useState(null); let [hostId, setHostId] = useState(null);
let [form] = Form.useForm(); let [form] = Form.useForm();
let [pluginList, setPluginList] = useState([]); let [pluginList, setPluginList] = useState([]);
useEffect(() => { useEffect(() => {
@@ -37,18 +37,18 @@ function GenerateSlaveTokenButton(props) {
async function generateToken() { async function generateToken() {
let id; let id;
let values = form.getFieldsValue(); let values = form.getFieldsValue();
if (values.slaveId) { if (values.hostId) {
id = Number.parseInt(values.slaveId, 10); id = Number.parseInt(values.hostId, 10);
if (Number.isNaN(id)) { if (Number.isNaN(id)) {
form.setFields([{ name: "slaveId", errors: ["Must be an integer"] }]); form.setFields([{ name: "hostId", errors: ["Must be an integer"] }]);
return; return;
} }
form.setFields([{ name: "slaveId", errors: [] }]); form.setFields([{ name: "hostId", errors: [] }]);
} }
let result = await libLink.messages.generateSlaveToken.send(control, { slave_id: id || null }); let result = await libLink.messages.generateHostToken.send(control, { host_id: id || null });
setToken(result.token); setToken(result.token);
setSlaveId(id); setHostId(id);
} }
// Generate a new random // Generate a new random
@@ -65,7 +65,7 @@ function GenerateSlaveTokenButton(props) {
onClick={() => { setVisible(true); }} onClick={() => { setVisible(true); }}
>Generate Token</Button> >Generate Token</Button>
<Modal <Modal
title="Generate Slave Token" title="Generate Host Token"
visible={visible} visible={visible}
footer={null} footer={null}
onCancel={() => { onCancel={() => {
@@ -76,7 +76,7 @@ function GenerateSlaveTokenButton(props) {
width="700px" width="700px"
> >
<Form form={form} layout="vertical" requiredMark="optional"> <Form form={form} layout="vertical" requiredMark="optional">
<Form.Item name="slaveId" label="Slave ID"> <Form.Item name="hostId" label="Host ID">
<InputNumber onChange={() => { <InputNumber onChange={() => {
generateToken().catch(notifyErrorHandler("Error generating token")); generateToken().catch(notifyErrorHandler("Error generating token"));
}} /> }} />
@@ -84,7 +84,7 @@ function GenerateSlaveTokenButton(props) {
</Form> </Form>
{token !== null && <> {token !== null && <>
<Typography.Paragraph> <Typography.Paragraph>
Slave auth token: Host auth token:
</Typography.Paragraph> </Typography.Paragraph>
<div className="codeblock"> <div className="codeblock">
<CopyButton <CopyButton
@@ -94,43 +94,43 @@ function GenerateSlaveTokenButton(props) {
{token} {token}
</div> </div>
<Typography.Paragraph> <Typography.Paragraph>
You can set the token on an existing slave with the following command: You can set the token on an existing host with the following command:
</Typography.Paragraph> </Typography.Paragraph>
<div className="codeblock"> <div className="codeblock">
<CopyButton <CopyButton
message={"Copied configuration commands to clipboard"} message={"Copied configuration commands to clipboard"}
text={`npx clusterioslave config set slave.controller_token ${token} text={`npx clusteriohost config set host.controller_token ${token}
npx clusterioslave config set slave.id ${slaveId}`} npx clusteriohost config set host.id ${hostId}`}
/> />
<p>npx clusterioslave config set slave.controller_token &lt;token&gt;</p> <p>npx clusteriohost config set host.controller_token &lt;token&gt;</p>
<p>npx clusterioslave config set slave.id &lt;slaveId&gt;</p> <p>npx clusteriohost config set host.id &lt;hostId&gt;</p>
</div> </div>
<Typography.Paragraph> <Typography.Paragraph>
Example slave setup commands: Example host setup commands:
</Typography.Paragraph> </Typography.Paragraph>
<div className="codeblock"> <div className="codeblock">
<CopyButton <CopyButton
message={"Copied slave setup commands to clipboard"} message={"Copied host setup commands to clipboard"}
text={` text={`
mkdir clusterio mkdir clusterio
cd clusterio cd clusterio
`+ `+
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
`npm init "@clusterio" -- --controller-token ${token} --mode "slave" --download-headless --controller-url ${document.location.origin}/ --slave-name "Slave ${slaveId || "?"}" --public-address localhost ${pluginString.length ? "--plugins" : ""} ${pluginString}` `npm init "@clusterio" -- --controller-token ${token} --mode "host" --download-headless --controller-url ${document.location.origin}/ --host-name "Host ${hostId || "?"}" --public-address localhost ${pluginString.length ? "--plugins" : ""} ${pluginString}`
}/> }/>
<p>&gt; mkdir clusterio</p> <p>&gt; mkdir clusterio</p>
<p>&gt; cd clusterio</p> <p>&gt; cd clusterio</p>
<p> <p>
&gt; npm init "@clusterio" -- &gt; npm init "@clusterio" --
--controller-token <span className="highlight">{token} </span> --controller-token <span className="highlight">{token} </span>
--mode "slave" --mode "host"
--download-headless --download-headless
--controller-url <span className="highlight">{document.location.origin}/ </span> --controller-url <span className="highlight">{document.location.origin}/ </span>
--slave-name <span className="highlight">"Slave {slaveId || "?"}" </span> --host-name <span className="highlight">"Host {hostId || "?"}" </span>
--public-address <span className="highlight">localhost </span> --public-address <span className="highlight">localhost </span>
{pluginString.length ? "--plugins" : ""} <span className="highlight">{pluginString}</span> {pluginString.length ? "--plugins" : ""} <span className="highlight">{pluginString}</span>
</p> </p>
<p>&gt; ./run-slave</p> <p>&gt; ./run-host</p>
</div> </div>
</>} </>}
</Modal> </Modal>
@@ -175,16 +175,16 @@ function CopyButton({ text, message }) {
} }
export default function SlavesPage() { export default function HostsPage() {
let account = useAccount(); let account = useAccount();
let history = useHistory(); let history = useHistory();
let [slaveList] = useSlaveList(); let [hostList] = useHostList();
return <PageLayout nav={[{ name: "Slaves" }]}> return <PageLayout nav={[{ name: "Hosts" }]}>
<PageHeader <PageHeader
className="site-page-header" className="site-page-header"
title="Slaves" title="Hosts"
extra={account.hasPermission("core.slave.generate_token") && <GenerateSlaveTokenButton />} extra={account.hasPermission("core.host.generate_token") && <GenerateHostTokenButton />}
/> />
<Table <Table
columns={[ columns={[
@@ -213,23 +213,23 @@ export default function SlavesPage() {
{ {
title: "Connected", title: "Connected",
key: "connected", key: "connected",
render: slave => <Tag render: host => <Tag
color={slave["connected"] ? "#389e0d" : "#cf1322"} color={host["connected"] ? "#389e0d" : "#cf1322"}
> >
{slave["connected"] ? "Connected" : "Disconnected"} {host["connected"] ? "Connected" : "Disconnected"}
</Tag>, </Tag>,
sorter: (a, b) => a["connected"] - b["connected"], sorter: (a, b) => a["connected"] - b["connected"],
}, },
]} ]}
dataSource={slaveList} dataSource={hostList}
rowKey={slave => slave["id"]} rowKey={host => host["id"]}
pagination={false} pagination={false}
onRow={(record, rowIndex) => ({ onRow={(record, rowIndex) => ({
onClick: event => { onClick: event => {
history.push(`/slaves/${record.id}/view`); history.push(`/hosts/${record.id}/view`);
}, },
})} })}
/> />
<PluginExtra component="SlavesPage" /> <PluginExtra component="HostsPage" />
</PageLayout>; </PageLayout>;
}; };
+18 -18
View File
@@ -4,7 +4,7 @@ import { message, Button, Space, Table } from "antd";
import CopyOutlined from "@ant-design/icons/CopyOutlined"; import CopyOutlined from "@ant-design/icons/CopyOutlined";
import { useAccount } from "../model/account"; import { useAccount } from "../model/account";
import { useSlaveList } from "../model/slave"; import { useHostList } from "../model/host";
import InstanceStatusTag from "./InstanceStatusTag"; import InstanceStatusTag from "./InstanceStatusTag";
import StartStopInstanceButton from "./StartStopInstanceButton"; import StartStopInstanceButton from "./StartStopInstanceButton";
@@ -13,26 +13,26 @@ const strcmp = new Intl.Collator(undefined, { numerice: "true", sensitivity: "ba
export default function InstanceList(props) { export default function InstanceList(props) {
let account = useAccount(); let account = useAccount();
let history = useHistory(); let history = useHistory();
let [slaveList] = useSlaveList(); let [hostList] = useHostList();
function slaveName(slaveId) { function hostName(hostId) {
if (slaveId === null) { if (hostId === null) {
return ""; return "";
} }
let slave = slaveList.find(s => s.id === slaveId); let host = hostList.find(s => s.id === hostId);
if (slave) { if (host) {
return slave.name; return host.name;
} }
return String(slaveId); return String(hostId);
} }
function instancePublicAddress(instance) { function instancePublicAddress(instance) {
let slave = slaveList.find(s => s.id === instance.assigned_slave); let host = hostList.find(s => s.id === instance.assigned_host);
if (slave && slave.public_address) { if (host && host.public_address) {
if (instance.game_port) { if (instance.game_port) {
return `${slave.public_address}:${instance.game_port}`; return `${host.public_address}:${instance.game_port}`;
} }
return slave.public_address; return host.public_address;
} }
return null; return null;
} }
@@ -45,10 +45,10 @@ export default function InstanceList(props) {
sorter: (a, b) => strcmp(a["name"], b["name"]), sorter: (a, b) => strcmp(a["name"], b["name"]),
}, },
{ {
title: "Assigned Slave", title: "Assigned Host",
key: "assigned_slave", key: "assigned_host",
render: instance => slaveName(instance["assigned_slave"]), render: instance => hostName(instance["assigned_host"]),
sorter: (a, b) => strcmp(slaveName(a["assigned_slave"]), slaveName(b["assigned_slave"])), sorter: (a, b) => strcmp(hostName(a["assigned_host"]), hostName(b["assigned_host"])),
responsive: ["sm"], responsive: ["sm"],
}, },
{ {
@@ -69,7 +69,7 @@ export default function InstanceList(props) {
/> />
</> : ""; </> : "";
}, },
sorter: (a, b) => strcmp(slavePublicAddress(a["assigned_slave"]), slavePublicAddress(b["assigned_slave"])), sorter: (a, b) => strcmp(hostPublicAddress(a["assigned_host"]), hostPublicAddress(b["assigned_host"])),
responsive: ["lg"], responsive: ["lg"],
}, },
{ {
@@ -90,7 +90,7 @@ export default function InstanceList(props) {
}] : []), }] : []),
]; ];
if (props.hideAssignedSlave) { if (props.hideAssignedHost) {
columns.splice(1, 1); columns.splice(1, 1);
} }
@@ -19,7 +19,7 @@ import LoadScenarioModal from "./LoadScenarioModal";
import SavesList from "./SavesList"; import SavesList from "./SavesList";
import { notifyErrorHandler } from "../util/notify"; import { notifyErrorHandler } from "../util/notify";
import { useInstance } from "../model/instance"; import { useInstance } from "../model/instance";
import { useSlave } from "../model/slave"; import { useHost } from "../model/host";
import InstanceStatusTag from "./InstanceStatusTag"; import InstanceStatusTag from "./InstanceStatusTag";
const { Title } = Typography; const { Title } = Typography;
@@ -28,20 +28,20 @@ const { Title } = Typography;
function InstanceDescription(props) { function InstanceDescription(props) {
let account = useAccount(); let account = useAccount();
const { slave, instance } = props; const { host, instance } = props;
let assigned = instance["assigned_slave"] !== null; let assigned = instance["assigned_host"] !== null;
return <Descriptions return <Descriptions
bordered bordered
size="small" size="small"
> >
<Descriptions.Item label="Slave"> <Descriptions.Item label="Host">
{!assigned {!assigned
? <em>Unassigned</em> ? <em>Unassigned</em>
: slave["name"] || instance["assigned_slave"] : host["name"] || instance["assigned_host"]
} }
{account.hasPermission("core.instance.assign") && <AssignInstanceModal {account.hasPermission("core.instance.assign") && <AssignInstanceModal
id={instance["id"]} id={instance["id"]}
slaveId={instance["assigned_slave"]} hostId={instance["assigned_host"]}
buttonProps={{ buttonProps={{
size: "small", size: "small",
style: { float: "Right" }, style: { float: "Right" },
@@ -64,7 +64,7 @@ export default function InstanceViewPage(props) {
let account = useAccount(); let account = useAccount();
let control = useContext(ControlContext); let control = useContext(ControlContext);
let [instance] = useInstance(instanceId); let [instance] = useInstance(instanceId);
let [slave] = useSlave(Number(instance["assigned_slave"])); let [host] = useHost(Number(instance["assigned_host"]));
let [exportingData, setExportingData] = useState(false); let [exportingData, setExportingData] = useState(false);
@@ -188,7 +188,7 @@ export default function InstanceViewPage(props) {
title={instance["name"]} title={instance["name"]}
extra={instanceButtons} extra={instanceButtons}
/> />
<InstanceDescription slave={slave} instance={instance} /> <InstanceDescription host={host} instance={instance} />
{ {
account.hasAllPermission("core.instance.save.list", "core.instance.save.list_subscribe") account.hasAllPermission("core.instance.save.list", "core.instance.save.list_subscribe")
@@ -60,7 +60,7 @@ export default function LogConsole(props) {
let logFilter = { let logFilter = {
all: props.all || false, all: props.all || false,
controller: props.controller || false, controller: props.controller || false,
slave_ids: props.slaves || [], host_ids: props.hosts || [],
instance_ids: props.instances || [], instance_ids: props.instances || [],
}; };
@@ -94,7 +94,7 @@ export default function LogConsole(props) {
return () => { return () => {
control.offLog(logFilter, logHandler); control.offLog(logFilter, logHandler);
}; };
}, [props.all, props.controller, (props.slaves || []).join(), (props.instances || []).join()]); }, [props.all, props.controller, (props.hosts || []).join(), (props.instances || []).join()]);
return <> return <>
<Paragraph code className="instance-console"> <Paragraph code className="instance-console">
+8 -8
View File
@@ -186,7 +186,7 @@ export default function SavesList(props) {
let [starting, setStarting] = useState(false); let [starting, setStarting] = useState(false);
let [uploadingFiles, setUploadingFiles] = useState([]); let [uploadingFiles, setUploadingFiles] = useState([]);
let slaveOffline = ["unassigned", "unknown"].includes(props.instance.status); let hostOffline = ["unassigned", "unknown"].includes(props.instance.status);
const saveTable = <Table const saveTable = <Table
size="small" size="small"
columns={[ columns={[
@@ -237,13 +237,13 @@ export default function SavesList(props) {
}} }}
>Load save</Button>} >Load save</Button>}
{account.hasPermission("core.instance.save.rename") && <RenameModal {account.hasPermission("core.instance.save.rename") && <RenameModal
disabled={slaveOffline} instanceId={props.instance.id} save={save} disabled={hostOffline} instanceId={props.instance.id} save={save}
/>} />}
{account.hasPermission("core.instance.save.copy") && <CopyModal {account.hasPermission("core.instance.save.copy") && <CopyModal
disabled={slaveOffline} instanceId={props.instance.id} save={save} disabled={hostOffline} instanceId={props.instance.id} save={save}
/>} />}
{account.hasPermission("core.instance.save.download") && <Button {account.hasPermission("core.instance.save.download") && <Button
disabled={slaveOffline} disabled={hostOffline}
onClick={() => { onClick={() => {
libLink.messages.downloadSave.send( libLink.messages.downloadSave.send(
control, { instance_id: props.instance.id, save: save.name } control, { instance_id: props.instance.id, save: save.name }
@@ -257,7 +257,7 @@ export default function SavesList(props) {
}} }}
>Download</Button>} >Download</Button>}
{account.hasPermission("core.instance.save.transfer") && <TransferModal {account.hasPermission("core.instance.save.transfer") && <TransferModal
disabled={slaveOffline} instanceId={props.instance.id} save={save} disabled={hostOffline} instanceId={props.instance.id} save={save}
/>} />}
{account.hasPermission("core.instance.save.delete") && <Popconfirm {account.hasPermission("core.instance.save.delete") && <Popconfirm
title="Permanently delete save?" title="Permanently delete save?"
@@ -270,7 +270,7 @@ export default function SavesList(props) {
).catch(notifyErrorHandler("Error deleting save")); ).catch(notifyErrorHandler("Error deleting save"));
}} }}
> >
<Button danger disabled={slaveOffline}>Delete</Button> <Button danger disabled={hostOffline}>Delete</Button>
</Popconfirm>} </Popconfirm>}
</Space>, </Space>,
}} }}
@@ -309,7 +309,7 @@ export default function SavesList(props) {
} }
let uploadProps = { let uploadProps = {
disabled: slaveOffline, disabled: hostOffline,
name: "file", name: "file",
accept: ".zip", accept: ".zip",
headers: { headers: {
@@ -326,7 +326,7 @@ export default function SavesList(props) {
return <div> return <div>
<SectionHeader title="Saves" extra={<Space> <SectionHeader title="Saves" extra={<Space>
{account.hasPermission("core.instance.save.upload") && <Upload {...uploadProps} > {account.hasPermission("core.instance.save.upload") && <Upload {...uploadProps} >
<Button disabled={slaveOffline}>Upload save</Button> <Button disabled={hostOffline}>Upload save</Button>
</Upload>} </Upload>}
{account.hasPermission("core.instance.save.create") && <CreateSaveModal instance={props.instance} />} {account.hasPermission("core.instance.save.create") && <CreateSaveModal instance={props.instance} />}
</Space>} /> </Space>} />
+1 -1
View File
@@ -2,7 +2,7 @@ export { default as bootstrap } from "./bootstrap.jsx";
export { default as notify, notifyErrorHandler } from "./util/notify.jsx"; export { default as notify, notifyErrorHandler } from "./util/notify.jsx";
export * from "./model/instance.jsx"; export * from "./model/instance.jsx";
export * from "./model/slave.jsx"; export * from "./model/host.jsx";
export * from "./model/locale.jsx"; export * from "./model/locale.jsx";
export * from "./model/item_metadata.jsx"; export * from "./model/item_metadata.jsx";
export * from "./model/account.jsx"; export * from "./model/account.jsx";
+77
View File
@@ -0,0 +1,77 @@
import { useEffect, useContext, useState } from "react";
import ControlContext from "../components/ControlContext";
import { libLink } from "@clusterio/lib";
export function useHost(id) {
let control = useContext(ControlContext);
let [host, setHost] = useState({ loading: true });
function updateHost() {
// XXX optimize by requesting only the host in question
libLink.messages.listHosts.send(control).then(result => {
let match = result.list.find(i => i.id === id);
if (!match) {
setHost({ missing: true });
} else {
setHost({ ...match, present: true });
}
});
}
useEffect(() => {
if (!Number.isInteger(id)) {
setHost({ missing: true });
return undefined;
}
updateHost();
function updateHandler(newHost) {
setHost({ ...newHost, present: true });
}
control.onHostUpdate(id, updateHandler);
return () => {
control.offHostUpdate(id, updateHandler);
};
}, [id]);
return [host, updateHost];
}
export function useHostList() {
let control = useContext(ControlContext);
let [hostList, setHostList] = useState([]);
function updateHostList() {
libLink.messages.listHosts.send(control).then(result => {
setHostList(result.list);
});
}
useEffect(() => {
updateHostList();
function updateHandler(newHost) {
setHostList(oldList => {
let newList = oldList.concat();
let index = newList.findIndex(s => s.id === newHost.id);
if (index !== -1) {
newList[index] = newHost;
} else {
newList.push(newHost);
}
return newList;
});
}
control.onHostUpdate(null, updateHandler);
return () => {
control.offHostUpdate(null, updateHandler);
};
}, []);
return [hostList];
}
-77
View File
@@ -1,77 +0,0 @@
import { useEffect, useContext, useState } from "react";
import ControlContext from "../components/ControlContext";
import { libLink } from "@clusterio/lib";
export function useSlave(id) {
let control = useContext(ControlContext);
let [slave, setSlave] = useState({ loading: true });
function updateSlave() {
// XXX optimize by requesting only the slave in question
libLink.messages.listSlaves.send(control).then(result => {
let match = result.list.find(i => i.id === id);
if (!match) {
setSlave({ missing: true });
} else {
setSlave({ ...match, present: true });
}
});
}
useEffect(() => {
if (!Number.isInteger(id)) {
setSlave({ missing: true });
return undefined;
}
updateSlave();
function updateHandler(newSlave) {
setSlave({ ...newSlave, present: true });
}
control.onSlaveUpdate(id, updateHandler);
return () => {
control.offSlaveUpdate(id, updateHandler);
};
}, [id]);
return [slave, updateSlave];
}
export function useSlaveList() {
let control = useContext(ControlContext);
let [slaveList, setSlaveList] = useState([]);
function updateSlaveList() {
libLink.messages.listSlaves.send(control).then(result => {
setSlaveList(result.list);
});
}
useEffect(() => {
updateSlaveList();
function updateHandler(newSlave) {
setSlaveList(oldList => {
let newList = oldList.concat();
let index = newList.findIndex(s => s.id === newSlave.id);
if (index !== -1) {
newList[index] = newSlave;
} else {
newList.push(newSlave);
}
return newList;
});
}
control.onSlaveUpdate(null, updateHandler);
return () => {
control.offSlaveUpdate(null, updateHandler);
};
}, []);
return [slaveList];
}
+9 -9
View File
@@ -1,8 +1,8 @@
import React from "react"; import React from "react";
import ControllerPage from "./components/ControllerPage"; import ControllerPage from "./components/ControllerPage";
import SlavesPage from "./components/SlavesPage"; import HostsPage from "./components/HostsPage";
import SlaveViewPage from "./components/SlaveViewPage"; import HostViewPage from "./components/HostViewPage";
import InstancesPage from "./components/InstancesPage"; import InstancesPage from "./components/InstancesPage";
import InstanceViewPage from "./components/InstanceViewPage"; import InstanceViewPage from "./components/InstanceViewPage";
import ModPackViewPage from "./components/ModPackViewPage"; import ModPackViewPage from "./components/ModPackViewPage";
@@ -22,15 +22,15 @@ export const pages = [
content: <ControllerPage />, content: <ControllerPage />,
}, },
{ {
path: "/slaves", path: "/hosts",
sidebarName: "Slaves", sidebarName: "Hosts",
permission: "core.slave.list", permission: "core.host.list",
content: <SlavesPage />, content: <HostsPage />,
}, },
{ {
path: "/slaves/:id/view", path: "/hosts/:id/view",
sidebarPath: "/slaves", sidebarPath: "/hosts",
content: <SlaveViewPage />, content: <HostViewPage />,
}, },
{ {
path: "/instances", path: "/instances",
+25 -25
View File
@@ -67,7 +67,7 @@ export class Control extends libLink.Link {
this.accountRoles = null; this.accountRoles = null;
this.accountUpdateHandlers = []; this.accountUpdateHandlers = [];
this.slaveUpdateHandlers = new Map(); this.hostUpdateHandlers = new Map();
this.instanceUpdateHandlers = new Map(); this.instanceUpdateHandlers = new Map();
this.saveListUpdateHandlers = new Map(); this.saveListUpdateHandlers = new Map();
this.modPackUpdateHandlers = new Map(); this.modPackUpdateHandlers = new Map();
@@ -79,8 +79,8 @@ export class Control extends libLink.Link {
this.accountName = data.account.name; this.accountName = data.account.name;
this.accountRoles = data.account.roles; this.accountRoles = data.account.roles;
this.emitAccountUpdate(); this.emitAccountUpdate();
this.updateSlaveSubscriptions().catch(err => logger.error( this.updateHostSubscriptions().catch(err => logger.error(
`Unexpected error updating slave subscriptions:\n${err.stack}` `Unexpected error updating host subscriptions:\n${err.stack}`
)); ));
this.updateInstanceSubscriptions().catch(err => logger.error( this.updateInstanceSubscriptions().catch(err => logger.error(
`Unexpected error updating instance subscriptions:\n${err.stack}` `Unexpected error updating instance subscriptions:\n${err.stack}`
@@ -143,60 +143,60 @@ export class Control extends libLink.Link {
this.accountUpdateHandlers.splice(index, 1); this.accountUpdateHandlers.splice(index, 1);
} }
async slaveUpdateEventHandler(message) { async hostUpdateEventHandler(message) {
let handlers = [].concat( let handlers = [].concat(
this.slaveUpdateHandlers.get(null) || [], this.hostUpdateHandlers.get(null) || [],
this.slaveUpdateHandlers.get(message.data.id) || [], this.hostUpdateHandlers.get(message.data.id) || [],
); );
for (let handler of handlers) { for (let handler of handlers) {
handler(message.data); handler(message.data);
} }
} }
async onSlaveUpdate(id, handler) { async onHostUpdate(id, handler) {
if (id !== null && !Number.isInteger(id)) { if (id !== null && !Number.isInteger(id)) {
throw new Error("Invalid slave id"); throw new Error("Invalid host id");
} }
let handlers = this.slaveUpdateHandlers.get(id); let handlers = this.hostUpdateHandlers.get(id);
if (!handlers) { if (!handlers) {
handlers = []; handlers = [];
this.slaveUpdateHandlers.set(id, handlers); this.hostUpdateHandlers.set(id, handlers);
} }
handlers.push(handler); handlers.push(handler);
if (handlers.length === 1) { if (handlers.length === 1) {
await this.updateSlaveSubscriptions(); await this.updateHostSubscriptions();
} }
} }
async offSlaveUpdate(id, handler) { async offHostUpdate(id, handler) {
let handlers = this.slaveUpdateHandlers.get(id); let handlers = this.hostUpdateHandlers.get(id);
if (!handlers || !handlers.length) { if (!handlers || !handlers.length) {
throw new Error(`No handlers for slave ${id} exists`); throw new Error(`No handlers for host ${id} exists`);
} }
let index = handlers.lastIndexOf(handler); let index = handlers.lastIndexOf(handler);
if (index === -1) { if (index === -1) {
throw new Error(`Given handler is not registered for slave ${id}`); throw new Error(`Given handler is not registered for host ${id}`);
} }
handlers.splice(index, 1); handlers.splice(index, 1);
if (!handlers.length) { if (!handlers.length) {
this.slaveUpdateHandlers.delete(id); this.hostUpdateHandlers.delete(id);
await this.updateSlaveSubscriptions(); await this.updateHostSubscriptions();
} }
} }
async updateSlaveSubscriptions() { async updateHostSubscriptions() {
if (!this.connector.connected) { if (!this.connector.connected) {
return; return;
} }
await libLink.messages.setSlaveSubscriptions.send(this, { await libLink.messages.setHostSubscriptions.send(this, {
all: this.slaveUpdateHandlers.has(null), all: this.hostUpdateHandlers.has(null),
slave_ids: [...this.slaveUpdateHandlers.keys()].filter(e => e !== null), host_ids: [...this.hostUpdateHandlers.keys()].filter(e => e !== null),
}); });
} }
@@ -536,16 +536,16 @@ export class Control extends libLink.Link {
let combinedFilter = { let combinedFilter = {
all: false, all: false,
controller: false, controller: false,
slave_ids: [], host_ids: [],
instance_ids: [], instance_ids: [],
}; };
for (let filter of this.logHandlers.keys()) { for (let filter of this.logHandlers.keys()) {
if (filter.all) { combinedFilter.all = true; } if (filter.all) { combinedFilter.all = true; }
if (filter.controller) { combinedFilter.controller = true; } if (filter.controller) { combinedFilter.controller = true; }
for (let slaveId of filter.slave_ids || []) { for (let hostId of filter.host_ids || []) {
if (!combinedFilter.slave_ids.includes(slaveId)) { if (!combinedFilter.host_ids.includes(hostId)) {
combinedFilter.slave_ids.push(slaveId); combinedFilter.host_ids.push(hostId);
} }
} }
for (let instanceId of filter.instance_ids || []) { for (let instanceId of filter.instance_ids || []) {
+1 -1
View File
@@ -10,4 +10,4 @@ Run the following commands in the folder Clusterio is installed to:
npm install @clusterio/plugin-global_chat npm install @clusterio/plugin-global_chat
npx clusteriocontroller plugin add @clusterio/plugin-global_chat npx clusteriocontroller plugin add @clusterio/plugin-global_chat
Substitute clusteriocontroller with clusterioslave or clusterioctl if this a dedicate slave or ctl installation respectively. Substitute clusteriocontroller with clusteriohost or clusterioctl if this a dedicate host or ctl installation respectively.
+1 -1
View File
@@ -11,7 +11,7 @@ module.exports = {
messages: { messages: {
chat: new libLink.Event({ chat: new libLink.Event({
type: "global_chat:chat", type: "global_chat:chat",
links: ["instance-slave", "slave-controller", "controller-slave", "slave-instance", "control-controller"], links: ["instance-host", "host-controller", "controller-host", "host-instance", "control-controller"],
forwardTo: "controller", forwardTo: "controller",
broadcastTo: "instance", broadcastTo: "instance",
eventProperties: { eventProperties: {
+1 -1
View File
@@ -45,7 +45,7 @@ class InstancePlugin extends libPlugin.BaseInstancePlugin {
async onOutput(output) { async onOutput(output) {
if (output.type === "action" && output.action === "CHAT") { if (output.type === "action" && output.action === "CHAT") {
if (this.slave.connector.connected) { if (this.host.connector.connected) {
this.sendChat(output.message); this.sendChat(output.message);
} else { } else {
this.messageQueue.push(output.message); this.messageQueue.push(output.message);
+1 -1
View File
@@ -24,7 +24,7 @@ describe("global_chat plugin", function() {
let instancePlugin; let instancePlugin;
before(async function() { before(async function() {
instancePlugin = new instance.InstancePlugin(info, new mock.MockInstance(), new mock.MockSlave()); instancePlugin = new instance.InstancePlugin(info, new mock.MockInstance(), new mock.MockHost());
await instancePlugin.init(); await instancePlugin.init();
}); });
+1 -1
View File
@@ -15,7 +15,7 @@ Run the following commands in the folder Clusterio is installed to:
npm install @clusterio/plugin-inventory_sync npm install @clusterio/plugin-inventory_sync
npx clusteriocontroller plugin add @clusterio/plugin-inventory_sync npx clusteriocontroller plugin add @clusterio/plugin-inventory_sync
Substitute clusteriocontroller with clusterioslave or clusterioctl if this a dedicated slave or ctl installation respectively. Substitute clusteriocontroller with clusteriohost or clusterioctl if this a dedicated host or ctl installation respectively.
## Method of operation ## Method of operation
+6 -6
View File
@@ -2,7 +2,7 @@
const { libConfig, libLink, libUsers } = require("@clusterio/lib"); const { libConfig, libLink, libUsers } = require("@clusterio/lib");
class ControllerConfigGroup extends libConfig.PluginConfigGroup { } class ControllerConfigGroup extends libConfig.PluginConfigGroup { }
ControllerConfigGroup.defaultAccess = ["controller", "slave", "control"]; ControllerConfigGroup.defaultAccess = ["controller", "host", "control"];
ControllerConfigGroup.groupName = "inventory_sync"; ControllerConfigGroup.groupName = "inventory_sync";
ControllerConfigGroup.define({ ControllerConfigGroup.define({
name: "autosave_interval", name: "autosave_interval",
@@ -22,7 +22,7 @@ ControllerConfigGroup.define({
ControllerConfigGroup.finalize(); ControllerConfigGroup.finalize();
class InstanceConfigGroup extends libConfig.PluginConfigGroup { } class InstanceConfigGroup extends libConfig.PluginConfigGroup { }
InstanceConfigGroup.defaultAccess = ["controller", "slave", "control"]; InstanceConfigGroup.defaultAccess = ["controller", "host", "control"];
InstanceConfigGroup.groupName = "inventory_sync"; InstanceConfigGroup.groupName = "inventory_sync";
InstanceConfigGroup.define({ InstanceConfigGroup.define({
name: "rcon_chunk_size", name: "rcon_chunk_size",
@@ -57,7 +57,7 @@ module.exports = {
messages: { messages: {
acquire: new libLink.Request({ acquire: new libLink.Request({
type: "inventory_sync:acquire", type: "inventory_sync:acquire",
links: ["instance-slave", "slave-controller"], links: ["instance-host", "host-controller"],
forwardTo: "controller", forwardTo: "controller",
requestProperties: { requestProperties: {
"instance_id": { type: "integer" }, "instance_id": { type: "integer" },
@@ -73,7 +73,7 @@ module.exports = {
}), }),
release: new libLink.Request({ release: new libLink.Request({
type: "inventory_sync:release", type: "inventory_sync:release",
links: ["instance-slave", "slave-controller"], links: ["instance-host", "host-controller"],
forwardTo: "controller", forwardTo: "controller",
requestProperties: { requestProperties: {
"instance_id": { type: "integer" }, "instance_id": { type: "integer" },
@@ -82,7 +82,7 @@ module.exports = {
}), }),
upload: new libLink.Request({ upload: new libLink.Request({
type: "inventory_sync:upload", type: "inventory_sync:upload",
links: ["instance-slave", "slave-controller"], links: ["instance-host", "host-controller"],
forwardTo: "controller", forwardTo: "controller",
requestProperties: { requestProperties: {
"instance_id": { type: "integer" }, "instance_id": { type: "integer" },
@@ -92,7 +92,7 @@ module.exports = {
}), }),
download: new libLink.Request({ download: new libLink.Request({
type: "inventory_sync:download", type: "inventory_sync:download",
links: ["instance-slave", "slave-controller"], links: ["instance-host", "host-controller"],
forwardTo: "controller", forwardTo: "controller",
requestProperties: { requestProperties: {
"instance_id": { type: "integer" }, "instance_id": { type: "integer" },
+4 -4
View File
@@ -41,7 +41,7 @@ class InstancePlugin extends libPlugin.BaseInstancePlugin {
this.disconnecting = false; this.disconnecting = false;
(async () => { (async () => {
for (let player_name of this.playersToRelease) { for (let player_name of this.playersToRelease) {
if (!this.slave.connector.connected || this.disconnecting) { if (!this.host.connector.connected || this.disconnecting) {
return; return;
} }
this.playersToRelease.delete(player); this.playersToRelease.delete(player);
@@ -63,7 +63,7 @@ class InstancePlugin extends libPlugin.BaseInstancePlugin {
message: "Controller is temporarily unavailable", message: "Controller is temporarily unavailable",
}; };
if (this.slave.connector.connected && !this.disconnecting) { if (this.host.connector.connected && !this.disconnecting) {
try { try {
response = { response = {
player_name: request.player_name, player_name: request.player_name,
@@ -85,7 +85,7 @@ class InstancePlugin extends libPlugin.BaseInstancePlugin {
} }
async handleRelease(request) { async handleRelease(request) {
if (!this.slave.connector.connected) { if (!this.host.connector.connected) {
this.playersToRelease.set(request.player_name); this.playersToRelease.set(request.player_name);
} }
@@ -104,7 +104,7 @@ class InstancePlugin extends libPlugin.BaseInstancePlugin {
} }
async handleUpload(player_data) { async handleUpload(player_data) {
if (!this.slave.connector.connected || this.disconnecting) { if (!this.host.connector.connected || this.disconnecting) {
return; return;
} }
+1 -1
View File
@@ -12,7 +12,7 @@ Run the following commands in the folder Clusterio is installed to:
npm install @clusterio/plugin-player_auth npm install @clusterio/plugin-player_auth
npx clusteriocontroller plugin add @clusterio/plugin-player_auth npx clusteriocontroller plugin add @clusterio/plugin-player_auth
Substitute clusteriocontroller with clusterioslave or clusterioctl if this a dedicate slave or ctl installation respectively. Substitute clusteriocontroller with clusteriohost or clusterioctl if this a dedicate host or ctl installation respectively.
## Controller Configuration ## Controller Configuration
+3 -3
View File
@@ -3,7 +3,7 @@ let { libConfig, libLink } = require("@clusterio/lib");
class ControllerConfigGroup extends libConfig.PluginConfigGroup {} class ControllerConfigGroup extends libConfig.PluginConfigGroup {}
ControllerConfigGroup.defaultAccess = ["controller", "slave", "control"]; ControllerConfigGroup.defaultAccess = ["controller", "host", "control"];
ControllerConfigGroup.groupName = "player_auth"; ControllerConfigGroup.groupName = "player_auth";
ControllerConfigGroup.define({ ControllerConfigGroup.define({
name: "code_length", name: "code_length",
@@ -33,7 +33,7 @@ module.exports = {
messages: { messages: {
fetchPlayerCode: new libLink.Request({ fetchPlayerCode: new libLink.Request({
type: "player_auth:fetch_player_code", type: "player_auth:fetch_player_code",
links: ["instance-slave", "slave-controller"], links: ["instance-host", "host-controller"],
forwardTo: "controller", forwardTo: "controller",
requestProperties: { requestProperties: {
"player": { type: "string" }, "player": { type: "string" },
@@ -45,7 +45,7 @@ module.exports = {
}), }),
setVerifyCode: new libLink.Request({ setVerifyCode: new libLink.Request({
type: "player_auth:set_verify_code", type: "player_auth:set_verify_code",
links: ["instance-slave", "slave-controller"], links: ["instance-host", "host-controller"],
forwardTo: "controller", forwardTo: "controller",
requestProperties: { requestProperties: {
"player": { type: "string" }, "player": { type: "string" },
+1 -1
View File
@@ -18,7 +18,7 @@ class InstancePlugin extends libPlugin.BaseInstancePlugin {
async handleEvent(event) { async handleEvent(event) {
if (event.type === "open_dialog") { if (event.type === "open_dialog") {
if (!this.slave.connector.connected) { if (!this.host.connector.connected) {
await this.sendRcon(`/web-login error ${event.player} login is temporarily unavailable`); await this.sendRcon(`/web-login error ${event.player} login is temporarily unavailable`);
return; return;
} }
+2 -2
View File
@@ -363,10 +363,10 @@ describe("player_auth", function() {
describe("open_dialog", async function() { describe("open_dialog", async function() {
it("should call /web-login error if not connected to controller", async function() { it("should call /web-login error if not connected to controller", async function() {
instancePlugin.instance.server.reset(); instancePlugin.instance.server.reset();
instancePlugin.slave.connector.connected = false; instancePlugin.host.connector.connected = false;
await instancePlugin.handleEvent({ type: "open_dialog", player: "test" }); await instancePlugin.handleEvent({ type: "open_dialog", player: "test" });
let command = instancePlugin.instance.server.rconCommands[0]; let command = instancePlugin.instance.server.rconCommands[0];
instancePlugin.slave.connector.connected = true; instancePlugin.host.connector.connected = true;
assert.equal(command, "/web-login error test login is temporarily unavailable"); assert.equal(command, "/web-login error test login is temporarily unavailable");
}); });
it("should call /web-login error after error from the controller", async function() { it("should call /web-login error after error from the controller", async function() {
+1 -1
View File
@@ -10,4 +10,4 @@ Run the following commands in the folder Clusterio is installed to:
npm install @clusterio/plugin-research_sync npm install @clusterio/plugin-research_sync
npx clusteriocontroller plugin add @clusterio/plugin-research_sync npx clusteriocontroller plugin add @clusterio/plugin-research_sync
Substitute clusteriocontroller with clusterioslave or clusterioctl if this a dedicate slave or ctl installation respectively. Substitute clusteriocontroller with clusteriohost or clusterioctl if this a dedicate host or ctl installation respectively.
+4 -4
View File
@@ -59,7 +59,7 @@ class ControllerPlugin extends libPlugin.BaseControllerPlugin {
this.lastProgressBroadcast = Date.now(); this.lastProgressBroadcast = Date.now();
if (techs.length) { if (techs.length) {
this.broadcastEventToSlaves(this.info.messages.progress, { technologies: techs }); this.broadcastEventToHosts(this.info.messages.progress, { technologies: techs });
} }
} }
@@ -97,7 +97,7 @@ class ControllerPlugin extends libPlugin.BaseControllerPlugin {
tech.progress = null; tech.progress = null;
this.progressToBroadcast.delete(name); this.progressToBroadcast.delete(name);
this.broadcastEventToSlaves(this.info.messages.finished, { name, level: tech.level }); this.broadcastEventToHosts(this.info.messages.finished, { name, level: tech.level });
} }
} }
@@ -127,7 +127,7 @@ class ControllerPlugin extends libPlugin.BaseControllerPlugin {
if (progress) { if (progress) {
this.progressToBroadcast.add(name); this.progressToBroadcast.add(name);
} else if (researched || baseLevel(name) !== level) { } else if (researched || baseLevel(name) !== level) {
this.broadcastEventToSlaves(this.info.messages.finished, { name, level }); this.broadcastEventToHosts(this.info.messages.finished, { name, level });
} }
} else { } else {
@@ -138,7 +138,7 @@ class ControllerPlugin extends libPlugin.BaseControllerPlugin {
if (tech.level < level || researched) { if (tech.level < level || researched) {
// Send update if the unlocked level is greater // Send update if the unlocked level is greater
if (level - !researched > tech.level - !tech.researched) { if (level - !researched > tech.level - !tech.researched) {
this.broadcastEventToSlaves(this.info.messages.finished, { name, level: level - !researched }); this.broadcastEventToHosts(this.info.messages.finished, { name, level: level - !researched });
} }
tech.level = level; tech.level = level;
tech.progress = progress; tech.progress = progress;
+4 -4
View File
@@ -28,7 +28,7 @@ module.exports = {
messages: { messages: {
contribution: new libLink.Event({ contribution: new libLink.Event({
type: "research_sync:contribution", type: "research_sync:contribution",
links: ["instance-slave", "slave-controller"], links: ["instance-host", "host-controller"],
forwardTo: "controller", forwardTo: "controller",
eventProperties: { eventProperties: {
"name": { type: "string" }, "name": { type: "string" },
@@ -38,7 +38,7 @@ module.exports = {
}), }),
progress: new libLink.Event({ progress: new libLink.Event({
type: "research_sync:progress", type: "research_sync:progress",
links: ["controller-slave", "slave-instance"], links: ["controller-host", "host-instance"],
broadcastTo: "instance", broadcastTo: "instance",
eventProperties: { eventProperties: {
"technologies": { "technologies": {
@@ -57,7 +57,7 @@ module.exports = {
}), }),
finished: new libLink.Event({ finished: new libLink.Event({
type: "research_sync:finished", type: "research_sync:finished",
links: ["instance-slave", "slave-controller", "controller-slave", "slave-instance"], links: ["instance-host", "host-controller", "controller-host", "host-instance"],
forwardTo: "controller", forwardTo: "controller",
broadcastTo: "instance", broadcastTo: "instance",
eventProperties: { eventProperties: {
@@ -67,7 +67,7 @@ module.exports = {
}), }),
syncTechnologies: new libLink.Request({ syncTechnologies: new libLink.Request({
type: "research_sync:sync_technologies", type: "research_sync:sync_technologies",
links: ["instance-slave", "slave-controller"], links: ["instance-host", "host-controller"],
forwardTo: "controller", forwardTo: "controller",
requestProperties: { requestProperties: {
"technologies": technologies, "technologies": technologies,
+1 -1
View File
@@ -10,7 +10,7 @@ Run the following commands in the folder Clusterio is installed to:
npm install @clusterio/plugin-statistics_exporter npm install @clusterio/plugin-statistics_exporter
npx clusteriocontroller plugin add @clusterio/plugin-statistics_exporter npx clusteriocontroller plugin add @clusterio/plugin-statistics_exporter
Substitute clusteriocontroller with clusterioslave or clusterioctl if this a dedicate slave or ctl installation respectively. Substitute clusteriocontroller with clusteriohost or clusterioctl if this a dedicate host or ctl installation respectively.
## Instance Configuration ## Instance Configuration
+1 -1
View File
@@ -2,7 +2,7 @@
const { libConfig } = require("@clusterio/lib"); const { libConfig } = require("@clusterio/lib");
class InstanceConfigGroup extends libConfig.PluginConfigGroup {} class InstanceConfigGroup extends libConfig.PluginConfigGroup {}
InstanceConfigGroup.defaultAccess = ["controller", "slave", "control"]; InstanceConfigGroup.defaultAccess = ["controller", "host", "control"];
InstanceConfigGroup.groupName = "statistics_exporter"; InstanceConfigGroup.groupName = "statistics_exporter";
InstanceConfigGroup.define({ InstanceConfigGroup.define({
name: "command_timeout", name: "command_timeout",
+1 -1
View File
@@ -10,4 +10,4 @@ Run the following commands in the folder Clusterio is installed to:
npm install @clusterio/plugin-subspace_storage npm install @clusterio/plugin-subspace_storage
npx clusteriocontroller plugin add @clusterio/plugin-subspace_storage npx clusteriocontroller plugin add @clusterio/plugin-subspace_storage
Substitute clusteriocontroller with clusterioslave or clusterioctl if this a dedicate slave or ctl installation respectively. Substitute clusteriocontroller with clusteriohost or clusterioctl if this a dedicate host or ctl installation respectively.
+2 -2
View File
@@ -107,7 +107,7 @@ class ControllerPlugin extends libPlugin.BaseControllerPlugin {
} }
itemsToUpdate = [...itemsToUpdate.entries()]; itemsToUpdate = [...itemsToUpdate.entries()];
this.broadcastEventToSlaves(this.info.messages.updateStorage, { items: itemsToUpdate }); this.broadcastEventToHosts(this.info.messages.updateStorage, { items: itemsToUpdate });
for (let link of this.subscribedControlLinks) { for (let link of this.subscribedControlLinks) {
this.info.messages.updateStorage.send(link, { items: itemsToUpdate }); this.info.messages.updateStorage.send(link, { items: itemsToUpdate });
} }
@@ -142,7 +142,7 @@ class ControllerPlugin extends libPlugin.BaseControllerPlugin {
let itemsRemoved = []; let itemsRemoved = [];
if (method === "simple") { if (method === "simple") {
// Give out as much items as possible until there are 0 left. This // Give out as much items as possible until there are 0 left. This
// might lead to one slave getting all the items and the rest nothing. // might lead to one host getting all the items and the rest nothing.
for (let item of message.data.items) { for (let item of message.data.items) {
let count = this.items.getItemCount(item[0]); let count = this.items.getItemCount(item[0]);
let toRemove = Math.min(count, item[1]); let toRemove = Math.min(count, item[1]);
+1 -1
View File
@@ -84,7 +84,7 @@ class NeuralDole {
this.lastRequest[`${object.name}_${object.instanceID}_${object.instanceName}`] = object.count; this.lastRequest[`${object.name}_${object.instanceID}_${object.instanceName}`] = object.count;
// 0. Number of items to give in that dose // 0. Number of items to give in that dose
// 1. New dole for item X // 1. New dole for item X
// 2. New carry for item X slave Y // 2. New carry for item X instance Y
this.dole[object.name] = magicData[1]; this.dole[object.name] = magicData[1];
this.carry[`${object.name} ${object.instanceID}`] = magicData[2]; this.carry[`${object.name} ${object.instanceID}`] = magicData[2];
this.debt[`${object.name} ${object.instanceID}`] = magicData[3]; this.debt[`${object.name} ${object.instanceID}`] = magicData[3];
+10 -10
View File
@@ -23,23 +23,23 @@ function sigmoid3(x) {
return x; return x;
} }
// DOSE - Doses items for item X slave Y // DOSE - Doses items for item X instance Y
// Numreq - number of items that are requested for item X for slave Y // Numreq - number of items that are requested for item X for instance Y
// Instore - number of items for item X in controller gauge (currently) // Instore - number of items for item X in controller gauge (currently)
// store_last_tick - // store_last_tick -
// Number of items for item X in controller gauge at time of last tick // Number of items for item X in controller gauge at time of last tick
// dole - Current dole for item X // dole - Current dole for item X
// carry - carry for item X slave Y // carry - carry for item X instance Y
// prev_req - Numreq for previous request for item X slave Y // prev_req - Numreq for previous request for item X instance Y
// * doleinfo_last - // * doleinfo_last -
// Object with 2 values for last "tick period": numreq - total number or // Object with 2 values for last "tick period": numreq - total number or
// requests for item, numslave - number of slaves requesting that item // requests for item, numinstance - number of instances requesting that item
// debt - debt for item X slave Y // debt - debt for item X instance Y
// Returns: Array of 3 elements // Returns: Array of 3 elements
// 0.Number of items to give in that dose // 0.Number of items to give in that dose
// 1.New dole for item X // 1.New dole for item X
// 2.New carry for item X slave Y // 2.New carry for item X instance Y
// 3.New debt // 3.New debt
function dose(numreq, instore, store_last_tick, dole, carry, prev_req, numreq_total_adj, debt) { function dose(numreq, instore, store_last_tick, dole, carry, prev_req, numreq_total_adj, debt) {
numreq = Number(numreq); numreq = Number(numreq);
@@ -98,18 +98,18 @@ function dose(numreq, instore, store_last_tick, dole, carry, prev_req, numreq_to
// TICK - // TICK -
// Updates values for item X, should be called once for each item for // Updates values for item X, should be called once for each item for
// each round of all slaves requesting that item Instore - number of // each round of all instances requesting that item Instore - number of
// items for item X in controller gauge (currently) // items for item X in controller gauge (currently)
// dole - Current dole for item X // dole - Current dole for item X
// store_last_tick - // store_last_tick -
// Number of items for item X in controller gauge at time of last tick (not this one) // Number of items for item X in controller gauge at time of last tick (not this one)
// * doleinfo_last - // * doleinfo_last -
// Object with 2 values for last "tick period": numreq - total number or // Object with 2 values for last "tick period": numreq - total number or
// requests for item, numslave - number of slaves requesting that item // requests for item, numinstance - number of instances requesting that item
// //
// * doleinfo_new - // * doleinfo_new -
// Object with 2 values for new "tick period": numreq - total number or // Object with 2 values for new "tick period": numreq - total number or
// requests for item, numslave - number of slaves requesting that item // requests for item, numinstance - number of instances requesting that item
// Returns: Array of 2 elements // Returns: Array of 2 elements
+7 -7
View File
@@ -2,7 +2,7 @@
const { libConfig, libLink, libUsers } = require("@clusterio/lib"); const { libConfig, libLink, libUsers } = require("@clusterio/lib");
class ControllerConfigGroup extends libConfig.PluginConfigGroup {} class ControllerConfigGroup extends libConfig.PluginConfigGroup {}
ControllerConfigGroup.defaultAccess = ["controller", "slave", "control"]; ControllerConfigGroup.defaultAccess = ["controller", "host", "control"];
ControllerConfigGroup.groupName = "subspace_storage"; ControllerConfigGroup.groupName = "subspace_storage";
ControllerConfigGroup.define({ ControllerConfigGroup.define({
name: "autosave_interval", name: "autosave_interval",
@@ -29,12 +29,12 @@ ControllerConfigGroup.define({
ControllerConfigGroup.finalize(); ControllerConfigGroup.finalize();
class InstanceConfigGroup extends libConfig.PluginConfigGroup {} class InstanceConfigGroup extends libConfig.PluginConfigGroup {}
InstanceConfigGroup.defaultAccess = ["controller", "slave", "control"]; InstanceConfigGroup.defaultAccess = ["controller", "host", "control"];
InstanceConfigGroup.groupName = "subspace_storage"; InstanceConfigGroup.groupName = "subspace_storage";
InstanceConfigGroup.define({ InstanceConfigGroup.define({
name: "log_item_transfers", name: "log_item_transfers",
title: "Log Item Transfers", title: "Log Item Transfers",
description: "Spam slave console with item transfers done.", description: "Spam host console with item transfers done.",
type: "boolean", type: "boolean",
initial_value: false, initial_value: false,
}); });
@@ -78,7 +78,7 @@ module.exports = {
// XXX this should be a request to be reliable // XXX this should be a request to be reliable
place: new libLink.Event({ place: new libLink.Event({
type: "subspace_storage:place", type: "subspace_storage:place",
links: ["instance-slave", "slave-controller"], links: ["instance-host", "host-controller"],
forwardTo: "controller", forwardTo: "controller",
eventProperties: { eventProperties: {
"instance_id": { type: "integer" }, "instance_id": { type: "integer" },
@@ -87,7 +87,7 @@ module.exports = {
}), }),
remove: new libLink.Request({ remove: new libLink.Request({
type: "subspace_storage:remove", type: "subspace_storage:remove",
links: ["instance-slave", "slave-controller"], links: ["instance-host", "host-controller"],
forwardTo: "controller", forwardTo: "controller",
requestProperties: { requestProperties: {
"instance_id": { type: "integer" }, "instance_id": { type: "integer" },
@@ -99,7 +99,7 @@ module.exports = {
}), }),
getStorage: new libLink.Request({ getStorage: new libLink.Request({
type: "subspace_storage:get_storage", type: "subspace_storage:get_storage",
links: ["instance-slave", "slave-controller", "control-controller"], links: ["instance-host", "host-controller", "control-controller"],
permission: "subspace_storage.storage.view", permission: "subspace_storage.storage.view",
forwardTo: "controller", forwardTo: "controller",
responseProperties: { responseProperties: {
@@ -108,7 +108,7 @@ module.exports = {
}), }),
updateStorage: new libLink.Event({ updateStorage: new libLink.Event({
type: "subspace_storage:update_storage", type: "subspace_storage:update_storage",
links: ["controller-slave", "slave-instance", "controller-control"], links: ["controller-host", "host-instance", "controller-control"],
broadcastTo: "instance", broadcastTo: "instance",
eventProperties: { eventProperties: {
"items": items, "items": items,
+3 -3
View File
@@ -13,7 +13,7 @@ class InstancePlugin extends libPlugin.BaseInstancePlugin {
this.provideItems(output).catch(err => this.unexpectedError(err)); this.provideItems(output).catch(err => this.unexpectedError(err));
}); });
this.instance.server.on("ipc-subspace_storage:orders", (orders) => { this.instance.server.on("ipc-subspace_storage:orders", (orders) => {
if (this.instance.status !== "running" || !this.slave.connected) { if (this.instance.status !== "running" || !this.host.connected) {
return; return;
} }
@@ -25,7 +25,7 @@ class InstancePlugin extends libPlugin.BaseInstancePlugin {
async onStart() { async onStart() {
this.pingId = setInterval(() => { this.pingId = setInterval(() => {
if (!this.slave.connected) { if (!this.host.connected) {
return; // Only ping if we are actually connected to the controller. return; // Only ping if we are actually connected to the controller.
} }
this.sendRcon( this.sendRcon(
@@ -50,7 +50,7 @@ class InstancePlugin extends libPlugin.BaseInstancePlugin {
// provide items -------------------------------------------------------------- // provide items --------------------------------------------------------------
async provideItems(items) { async provideItems(items) {
if (!this.slave.connector.hasSession) { if (!this.host.connector.hasSession) {
// For now the items are voided if the controller connection is // For now the items are voided if the controller connection is
// down, which is no different from the previous behaviour. // down, which is no different from the previous behaviour.
if (this.instance.config.get("subspace_storage.log_item_transfers")) { if (this.instance.config.get("subspace_storage.log_item_transfers")) {
+1 -1
View File
@@ -307,7 +307,7 @@ describe("controller/src/routes", function() {
}); });
assert.equal(response.statusCode, 500); assert.equal(response.statusCode, 500);
assert.deepEqual( assert.deepEqual(
JSON.parse(response.body), { errors: ["Timed out establishing stream to slave"], request_errors: [] } JSON.parse(response.body), { errors: ["Timed out establishing stream to host"], request_errors: [] }
); );
}); });
+6 -6
View File
@@ -1,9 +1,9 @@
{"timestamp":"2021-12-10T10:00:00.000Z","level":"verbose","message":"Controller start"} {"timestamp":"2021-12-10T10:00:00.000Z","level":"verbose","message":"Controller start"}
{"timestamp":"2021-12-10T10:00:01.000Z","level":"verbose","message":"Loading"} {"timestamp":"2021-12-10T10:00:01.000Z","level":"verbose","message":"Loading"}
{"timestamp":"2021-12-10T10:00:03.000Z","level":"info","message":"Ready"} {"timestamp":"2021-12-10T10:00:03.000Z","level":"info","message":"Ready"}
{"timestamp":"2021-12-10T10:00:10.000Z","slave_id":1,"level":"verbose","message":"Slave connected"} {"timestamp":"2021-12-10T10:00:10.000Z","host_id":1,"level":"verbose","message":"Host connected"}
{"timestamp":"2021-12-10T10:00:11.000Z","slave_id":1,"level":"verbose","message":"Found instance 10"} {"timestamp":"2021-12-10T10:00:11.000Z","host_id":1,"level":"verbose","message":"Found instance 10"}
{"timestamp":"2021-12-10T10:00:13.000Z","slave_id":1,"instance_id":10,"level":"server","message":"Starting up"} {"timestamp":"2021-12-10T10:00:13.000Z","host_id":1,"instance_id":10,"level":"server","message":"Starting up"}
{"timestamp":"2021-12-10T10:00:14.000Z","slave_id":1,"instance_id":10,"level":"server","message":"Loading map"} {"timestamp":"2021-12-10T10:00:14.000Z","host_id":1,"instance_id":10,"level":"server","message":"Loading map"}
{"timestamp":"2021-12-10T10:00:15.000Z","slave_id":1,"instance_id":10,"level":"server","message":"Game ready"} {"timestamp":"2021-12-10T10:00:15.000Z","host_id":1,"instance_id":10,"level":"server","message":"Game ready"}
{"timestamp":"2021-12-10T10:01:00.000Z","slave_id":1,"instance_id":10,"level":"error","message":"Unexpected crash"} {"timestamp":"2021-12-10T10:01:00.000Z","host_id":1,"instance_id":10,"level":"error","message":"Unexpected crash"}
+26 -26
View File
@@ -3,9 +3,9 @@ const assert = require("assert").strict;
const path = require("path"); const path = require("path");
const libConfig = require("@clusterio/lib/config"); const libConfig = require("@clusterio/lib/config");
const Slave = require("@clusterio/slave/src/Slave"); const Host = require("@clusterio/host/src/Host");
describe("Slave testing", function() { describe("Host testing", function() {
before(function() { before(function() {
libConfig.InstanceConfig.finalize(); libConfig.InstanceConfig.finalize();
}); });
@@ -13,9 +13,9 @@ describe("Slave testing", function() {
describe("discoverInstances()", function() { describe("discoverInstances()", function() {
it("should discover test instance", async function() { it("should discover test instance", async function() {
let instancePath = path.join("test", "file", "instances"); let instancePath = path.join("test", "file", "instances");
let instanceInfos = await Slave._discoverInstances(instancePath); let instanceInfos = await Host._discoverInstances(instancePath);
let referenceConfig = new libConfig.InstanceConfig("slave"); let referenceConfig = new libConfig.InstanceConfig("host");
await referenceConfig.init(); await referenceConfig.init();
referenceConfig.set("instance.id", 1); referenceConfig.set("instance.id", 1);
referenceConfig.set("instance.name", "test"); referenceConfig.set("instance.name", "test");
@@ -29,11 +29,11 @@ describe("Slave testing", function() {
}); });
}); });
describe("class Slave", function() { describe("class Host", function() {
describe(".syncUserListsEventHandler()", function() { describe(".syncUserListsEventHandler()", function() {
let mockSlave; let mockHost;
beforeEach(function() { beforeEach(function() {
mockSlave = { mockHost = {
adminlist: new Set(), adminlist: new Set(),
whitelist: new Set(), whitelist: new Set(),
banlist: new Map(), banlist: new Map(),
@@ -41,7 +41,7 @@ describe("Slave testing", function() {
broadcastEventToInstance(message, event) { broadcastEventToInstance(message, event) {
this.broadcasts.push(message["data"]); this.broadcasts.push(message["data"]);
}, },
syncUserListsEventHandler: Slave.prototype.syncUserListsEventHandler, syncUserListsEventHandler: Host.prototype.syncUserListsEventHandler,
syncLists(adminlist, banlist, whitelist) { syncLists(adminlist, banlist, whitelist) {
return this.syncUserListsEventHandler({ "data": { return this.syncUserListsEventHandler({ "data": {
"adminlist": adminlist, "adminlist": adminlist,
@@ -53,57 +53,57 @@ describe("Slave testing", function() {
}); });
it("should broadcast new entries to adminlist", async function() { it("should broadcast new entries to adminlist", async function() {
await mockSlave.syncLists(["admin1"], [], []); await mockHost.syncLists(["admin1"], [], []);
await mockSlave.syncLists(["admin1", "admin2"], [], []); await mockHost.syncLists(["admin1", "admin2"], [], []);
assert.deepEqual(mockSlave.broadcasts, [ assert.deepEqual(mockHost.broadcasts, [
{ "name": "admin1", "admin": true }, { "name": "admin1", "admin": true },
{ "name": "admin2", "admin": true }, { "name": "admin2", "admin": true },
]); ]);
}); });
it("should broadcast removals from adminlist", async function() { it("should broadcast removals from adminlist", async function() {
mockSlave.adminlist.add("admin1").add("admin2"); mockHost.adminlist.add("admin1").add("admin2");
await mockSlave.syncLists(["admin1"], [], []); await mockHost.syncLists(["admin1"], [], []);
assert.deepEqual(mockSlave.broadcasts, [ assert.deepEqual(mockHost.broadcasts, [
{ "name": "admin2", "admin": false }, { "name": "admin2", "admin": false },
]); ]);
}); });
it("should broadcast new entries to whitelist", async function() { it("should broadcast new entries to whitelist", async function() {
await mockSlave.syncLists([], [], ["player1"]); await mockHost.syncLists([], [], ["player1"]);
await mockSlave.syncLists([], [], ["player1", "player2"]); await mockHost.syncLists([], [], ["player1", "player2"]);
assert.deepEqual(mockSlave.broadcasts, [ assert.deepEqual(mockHost.broadcasts, [
{ "name": "player1", "whitelisted": true }, { "name": "player1", "whitelisted": true },
{ "name": "player2", "whitelisted": true }, { "name": "player2", "whitelisted": true },
]); ]);
}); });
it("should broadcast removals from whitelist", async function() { it("should broadcast removals from whitelist", async function() {
mockSlave.whitelist.add("player1").add("player2"); mockHost.whitelist.add("player1").add("player2");
await mockSlave.syncLists([], [], ["player1"]); await mockHost.syncLists([], [], ["player1"]);
assert.deepEqual(mockSlave.broadcasts, [ assert.deepEqual(mockHost.broadcasts, [
{ "name": "player2", "whitelisted": false }, { "name": "player2", "whitelisted": false },
]); ]);
}); });
it("should broadcast new entries to banlist", async function() { it("should broadcast new entries to banlist", async function() {
await mockSlave.syncLists([], [["badie1", "greifing"]], []); await mockHost.syncLists([], [["badie1", "greifing"]], []);
await mockSlave.syncLists([], [["badie1", "greifing"], ["badie2", "annoying"]], []); await mockHost.syncLists([], [["badie1", "greifing"], ["badie2", "annoying"]], []);
assert.deepEqual(mockSlave.broadcasts, [ assert.deepEqual(mockHost.broadcasts, [
{ "name": "badie1", "banned": true, "reason": "greifing" }, { "name": "badie1", "banned": true, "reason": "greifing" },
{ "name": "badie2", "banned": true, "reason": "annoying" }, { "name": "badie2", "banned": true, "reason": "annoying" },
]); ]);
}); });
it("should broadcast removals to banlist", async function() { it("should broadcast removals to banlist", async function() {
mockSlave.banlist.set("badie1", "greifing").set("badie2", "annoying"); mockHost.banlist.set("badie1", "greifing").set("badie2", "annoying");
await mockSlave.syncLists([], [["badie1", "greifing"]], []); await mockHost.syncLists([], [["badie1", "greifing"]], []);
assert.deepEqual(mockSlave.broadcasts, [ assert.deepEqual(mockHost.broadcasts, [
{ "name": "badie2", "banned": false, "reason": "" }, { "name": "badie2", "banned": false, "reason": "" },
]); ]);
}); });
@@ -4,7 +4,7 @@ const path = require("path");
const libConfig = require("@clusterio/lib/config"); const libConfig = require("@clusterio/lib/config");
const { wait } = require("@clusterio/lib/helpers"); const { wait } = require("@clusterio/lib/helpers");
const Instance = require("@clusterio/slave/src/Instance"); const Instance = require("@clusterio/host/src/Instance");
const { MockConnector, MockServer } = require("../mock"); const { MockConnector, MockServer } = require("../mock");
@@ -16,7 +16,7 @@ describe("class Instance", function() {
let instance; let instance;
let connector; let connector;
beforeEach(async function() { beforeEach(async function() {
let instanceConfig = new libConfig.InstanceConfig("slave"); let instanceConfig = new libConfig.InstanceConfig("host");
await instanceConfig.init(); await instanceConfig.init();
instanceConfig.set("instance.name", "foo"); instanceConfig.set("instance.name", "foo");
connector = new MockConnector(); connector = new MockConnector();
+44 -44
View File
@@ -92,7 +92,7 @@ describe("Integration of Clusterio", function() {
let result = await libLink.messages.queryLog.send(getControl(), { let result = await libLink.messages.queryLog.send(getControl(), {
all: true, all: true,
controller: false, controller: false,
slave_ids: [], host_ids: [],
instance_ids: [], instance_ids: [],
max_level: null, max_level: null,
limit: 10, limit: 10,
@@ -104,7 +104,7 @@ describe("Integration of Clusterio", function() {
let first = await libLink.messages.queryLog.send(getControl(), { let first = await libLink.messages.queryLog.send(getControl(), {
all: true, all: true,
controller: false, controller: false,
slave_ids: [], host_ids: [],
instance_ids: [], instance_ids: [],
max_level: null, max_level: null,
limit: 1, limit: 1,
@@ -113,7 +113,7 @@ describe("Integration of Clusterio", function() {
let last = await libLink.messages.queryLog.send(getControl(), { let last = await libLink.messages.queryLog.send(getControl(), {
all: true, all: true,
controller: false, controller: false,
slave_ids: [], host_ids: [],
instance_ids: [], instance_ids: [],
max_level: null, max_level: null,
limit: 1, limit: 1,
@@ -124,41 +124,41 @@ describe("Integration of Clusterio", function() {
}); });
}); });
describe("clusterioslave", function() { describe("clusteriohost", function() {
describe("slaveUpdateEventHandler()", function() { describe("hostUpdateEventHandler()", function() {
it("should trigger when a new slave is added", async function() { it("should trigger when a new host is added", async function() {
// On windows there's currently no way to automate graceful shutdown of the slave // On windows there's currently no way to automate graceful shutdown of the host
// process as CTRL+C is some weird terminal thing and SIGINT isn't a thing. // process as CTRL+C is some weird terminal thing and SIGINT isn't a thing.
if (process.platform === "win32") { if (process.platform === "win32") {
this.skip(); this.skip();
} }
slowTest(this); slowTest(this);
getControl().slaveUpdates = []; getControl().hostUpdates = [];
let config = "alt-slave-config.json"; let config = "alt-host-config.json";
let configPath = path.join("temp", "test", config); let configPath = path.join("temp", "test", config);
await fs.remove(configPath); await fs.remove(configPath);
await fs.remove(path.join("temp", "test", "alt-instances")); await fs.remove(path.join("temp", "test", "alt-instances"));
await execCtl(`slave create-config --id 5 --name alt-slave --generate-token --output ${config}`); await execCtl(`host create-config --id 5 --name alt-host --generate-token --output ${config}`);
await exec( await exec(
`node ../../packages/slave --config ${config} config set slave.tls_ca ../../test/file/tls/cert.pem` `node ../../packages/host --config ${config} config set host.tls_ca ../../test/file/tls/cert.pem`
); );
await exec( await exec(
`node ../../packages/slave --config ${config} config set slave.instances_directory alt-instances` `node ../../packages/host --config ${config} config set host.instances_directory alt-instances`
); );
let slaveProcess; let hostProcess;
try { try {
slaveProcess = await spawn( hostProcess = await spawn(
"alt-slave:", `node ../../packages/slave run --config ${config}`, /Started slave/ "alt-host:", `node ../../packages/host run --config ${config}`, /Started host/
); );
// Add instance to test the unknown status afterwards // Add instance to test the unknown status afterwards
await execCtl("instance create alt-test --id 99"); await execCtl("instance create alt-test --id 99");
await execCtl("instance assign alt-test 5"); await execCtl("instance assign alt-test 5");
} finally { } finally {
if (slaveProcess) { if (hostProcess) {
slaveProcess.kill("SIGINT"); hostProcess.kill("SIGINT");
await events.once(slaveProcess, "exit"); await events.once(hostProcess, "exit");
} }
} }
@@ -166,8 +166,8 @@ describe("Integration of Clusterio", function() {
let sawConnected = false; let sawConnected = false;
let sawDisconnected = false; let sawDisconnected = false;
for (let update of getControl().slaveUpdates) { for (let update of getControl().hostUpdates) {
if (update.name !== "alt-slave") { if (update.name !== "alt-host") {
continue; continue;
} }
@@ -179,13 +179,13 @@ describe("Integration of Clusterio", function() {
} }
} }
assert(sawUpdate, "No slave update was sent"); assert(sawUpdate, "No host update was sent");
assert(sawConnected, "No slave update with status connected was sent"); assert(sawConnected, "No host update with status connected was sent");
assert(sawDisconnected, "No slave update with status disconnected was sent"); assert(sawDisconnected, "No host update with status disconnected was sent");
let result = await libLink.messages.listSlaves.send(getControl()); let result = await libLink.messages.listHosts.send(getControl());
let slaves = new Map(result.list.map(instance => [instance.id, instance])); let hosts = new Map(result.list.map(instance => [instance.id, instance]));
assert(slaves.has(5), "Slave list was not updated"); assert(hosts.has(5), "Host list was not updated");
}); });
}); });
}); });
@@ -233,17 +233,17 @@ describe("Integration of Clusterio", function() {
}); });
}); });
describe("slave list", function() { describe("host list", function() {
it("runs", async function() { it("runs", async function() {
await execCtl("slave list"); await execCtl("host list");
}); });
}); });
describe("slave generate-token", function() { describe("host generate-token", function() {
it("runs", async function() { it("runs", async function() {
await execCtl("slave generate-token --id 42"); await execCtl("host generate-token --id 42");
}); });
it("runs without an id", async function() { it("runs without an id", async function() {
await execCtl("slave generate-token"); await execCtl("host generate-token");
}); });
}); });
describe("instance list", function() { describe("instance list", function() {
@@ -370,7 +370,7 @@ describe("Integration of Clusterio", function() {
let { log } = await libLink.messages.queryLog.send(getControl(), { let { log } = await libLink.messages.queryLog.send(getControl(), {
all: false, all: false,
controller: false, controller: false,
slave_ids: [], host_ids: [],
instance_ids: [44], instance_ids: [44],
max_level: null, max_level: null,
limit: 10, limit: 10,
@@ -683,24 +683,24 @@ describe("Integration of Clusterio", function() {
; ;
describe(remote ? "remote" : "local", function() { describe(remote ? "remote" : "local", function() {
if (remote) { if (remote) {
let slaveProcess; let hostProcess;
before(async function() { before(async function() {
// On windows there's currently no way to automate graceful shutdown of the slave // On windows there's currently no way to automate graceful shutdown of the host
// process as CTRL+C is some weird terminal thing and SIGINT isn't a thing. // process as CTRL+C is some weird terminal thing and SIGINT isn't a thing.
if (process.platform === "win32") { if (process.platform === "win32") {
this.skip(); this.skip();
} }
slowTest(this); slowTest(this);
// Reuse from the clusterioslave test // Reuse from the clusteriohost test
let config = "alt-slave-config.json"; let config = "alt-host-config.json";
slaveProcess = await spawn( hostProcess = await spawn(
"alt-slave:", `node ../../packages/slave run --config ${config}`, /Started slave/ "alt-host:", `node ../../packages/host run --config ${config}`, /Started host/
); );
}); });
after(async function() { after(async function() {
if (slaveProcess) { if (hostProcess) {
slaveProcess.kill("SIGINT"); hostProcess.kill("SIGINT");
await events.once(slaveProcess, "exit"); await events.once(hostProcess, "exit");
} }
}); });
} }
@@ -1036,14 +1036,14 @@ describe("Integration of Clusterio", function() {
assert.deepEqual(newRole, { id: 5, name: "new", description: "A new role", permissions: [] }); assert.deepEqual(newRole, { id: 5, name: "new", description: "A new role", permissions: [] });
}); });
it("should add permissions with --add-perms", async function() { it("should add permissions with --add-perms", async function() {
let args = "--name new --add-perms core.slave.list core.instance.list"; let args = "--name new --add-perms core.host.list core.instance.list";
await execCtl(`role edit new ${args}`); await execCtl(`role edit new ${args}`);
let result = await libLink.messages.listRoles.send(getControl()); let result = await libLink.messages.listRoles.send(getControl());
let newRole = result.list.find(role => role.name === "new"); let newRole = result.list.find(role => role.name === "new");
assert.deepEqual(newRole.permissions, ["core.slave.list", "core.instance.list"]); assert.deepEqual(newRole.permissions, ["core.host.list", "core.instance.list"]);
}); });
it("should remove permissions with --remove-perms", async function() { it("should remove permissions with --remove-perms", async function() {
let args = "--name new --remove-perms core.slave.list"; let args = "--name new --remove-perms core.host.list";
await execCtl(`role edit new ${args}`); await execCtl(`role edit new ${args}`);
let result = await libLink.messages.listRoles.send(getControl()); let result = await libLink.messages.listRoles.send(getControl());
let newRole = result.list.find(role => role.name === "new"); let newRole = result.list.find(role => role.name === "new");
+19 -19
View File
@@ -26,7 +26,7 @@ class TestControl extends libLink.Link {
constructor(connector) { constructor(connector) {
super("control", "controller", connector); super("control", "controller", connector);
libLink.attachAllMessages(this); libLink.attachAllMessages(this);
this.slaveUpdates = []; this.hostUpdates = [];
this.instanceUpdates = []; this.instanceUpdates = [];
this.saveListUpdates = []; this.saveListUpdates = [];
this.modUpdates = []; this.modUpdates = [];
@@ -34,9 +34,9 @@ class TestControl extends libLink.Link {
this.userUpdates = []; this.userUpdates = [];
this.connector.on("connect", () => { this.connector.on("connect", () => {
libLink.messages.setSlaveSubscriptions.send( libLink.messages.setHostSubscriptions.send(
this, { all: true, slave_ids: [] } this, { all: true, host_ids: [] }
).catch(err => logger.error(`Error setting slave subscriptions:\n${err.stack}`)); ).catch(err => logger.error(`Error setting host subscriptions:\n${err.stack}`));
libLink.messages.setInstanceSubscriptions.send( libLink.messages.setInstanceSubscriptions.send(
this, { all: true, instance_ids: [] } this, { all: true, instance_ids: [] }
).catch(err => logger.error(`Error setting instance subscriptions:\n${err.stack}`)); ).catch(err => logger.error(`Error setting instance subscriptions:\n${err.stack}`));
@@ -61,8 +61,8 @@ class TestControl extends libLink.Link {
async accountUpdateEventHandler() { } async accountUpdateEventHandler() { }
async slaveUpdateEventHandler(message) { async hostUpdateEventHandler(message) {
this.slaveUpdates.push(message.data); this.hostUpdates.push(message.data);
} }
async instanceUpdateEventHandler(message) { async instanceUpdateEventHandler(message) {
@@ -118,7 +118,7 @@ async function get(urlPath) {
} }
let controllerProcess; let controllerProcess;
let slaveProcess; let hostProcess;
let control; let control;
let url = "https://localhost:4443/"; let url = "https://localhost:4443/";
@@ -127,7 +127,7 @@ let instancesDir = path.join("temp", "test", "instances");
let databaseDir = path.join("temp", "test", "database"); let databaseDir = path.join("temp", "test", "database");
let pluginListPath = path.join("temp", "test", "plugin-list.json"); let pluginListPath = path.join("temp", "test", "plugin-list.json");
let controllerConfigPath = path.join("temp", "test", "config-controller.json"); let controllerConfigPath = path.join("temp", "test", "config-controller.json");
let slaveConfigPath = path.join("temp", "test", "config-slave.json"); let hostConfigPath = path.join("temp", "test", "config-host.json");
let controlConfigPath = path.join("temp", "test", "config-control.json"); let controlConfigPath = path.join("temp", "test", "config-control.json");
async function exec(command, options = {}) { async function exec(command, options = {}) {
@@ -185,7 +185,7 @@ before(async function() {
await fs.remove(pluginListPath); await fs.remove(pluginListPath);
await fs.remove(controllerConfigPath); await fs.remove(controllerConfigPath);
await fs.remove(slaveConfigPath); await fs.remove(hostConfigPath);
await fs.remove(controlConfigPath); await fs.remove(controlConfigPath);
await fs.ensureDir(path.join("temp", "test")); await fs.ensureDir(path.join("temp", "test"));
@@ -210,11 +210,11 @@ before(async function() {
controllerProcess = await spawn("controller:", "node ../../packages/controller run", /Started controller/); controllerProcess = await spawn("controller:", "node ../../packages/controller run", /Started controller/);
await execCtl("slave create-config --id 4 --name slave --generate-token"); await execCtl("host create-config --id 4 --name host --generate-token");
await exec(`node ../../packages/slave config set slave.factorio_directory ${path.join("..", "..", "factorio")}`); await exec(`node ../../packages/host config set host.factorio_directory ${path.join("..", "..", "factorio")}`);
await exec("node ../../packages/slave config set slave.tls_ca ../../test/file/tls/cert.pem"); await exec("node ../../packages/host config set host.tls_ca ../../test/file/tls/cert.pem");
slaveProcess = await spawn("slave:", "node ../../packages/slave run", /Started slave/); hostProcess = await spawn("host:", "node ../../packages/host run", /Started host/);
let tlsCa = await fs.readFile("test/file/tls/cert.pem"); let tlsCa = await fs.readFile("test/file/tls/cert.pem");
let controlConnector = new TestControlConnector(url, 2, tlsCa); let controlConnector = new TestControlConnector(url, 2, tlsCa);
@@ -236,10 +236,10 @@ before(async function() {
after(async function() { after(async function() {
this.timeout(20000); this.timeout(20000);
if (slaveProcess) { if (hostProcess) {
console.log("Shutting down slave"); console.log("Shutting down host");
slaveProcess.kill("SIGINT"); hostProcess.kill("SIGINT");
await events.once(slaveProcess, "exit"); await events.once(hostProcess, "exit");
} }
if (controllerProcess) { if (controllerProcess) {
console.log("Shutting down controller"); console.log("Shutting down controller");
@@ -253,7 +253,7 @@ after(async function() {
// Ensure the test processes are stopped. // Ensure the test processes are stopped.
process.on("exit", () => { process.on("exit", () => {
if (slaveProcess) { slaveProcess.kill(); } if (hostProcess) { hostProcess.kill(); }
if (controllerProcess) { controllerProcess.kill(); } if (controllerProcess) { controllerProcess.kill(); }
}); });
@@ -274,6 +274,6 @@ module.exports = {
instancesDir, instancesDir,
databaseDir, databaseDir,
controllerConfigPath, controllerConfigPath,
slaveConfigPath, hostConfigPath,
controlConfigPath, controlConfigPath,
}; };
+12 -12
View File
@@ -11,12 +11,12 @@ describe("lib/command", function() {
let testRole = { id: 28, name: "Test Role", description: "Test", permissions: [] }; let testRole = { id: 28, name: "Test Role", description: "Test", permissions: [] };
let mockConnector = new mock.MockConnector(); let mockConnector = new mock.MockConnector();
mockConnector.on("send", function(message) { mockConnector.on("send", function(message) {
if (message.type === "list_slaves_request") { if (message.type === "list_hosts_request") {
this.emit("message", { this.emit("message", {
seq: 1, type: "list_slaves_response", seq: 1, type: "list_hosts_response",
data: { data: {
seq: message.seq, seq: message.seq,
list: [{ agent: "test", version: "0.1", id: 11, name: "Test Slave", connected: false }], list: [{ agent: "test", version: "0.1", id: 11, name: "Test Host", connected: false }],
}, },
}); });
} else if (message.type === "list_instances_request") { } else if (message.type === "list_instances_request") {
@@ -24,7 +24,7 @@ describe("lib/command", function() {
seq: 1, type: "list_instances_response", seq: 1, type: "list_instances_response",
data: { data: {
seq: message.seq, seq: message.seq,
list: [{ id: 57, assigned_slave: 4, name: "Test Instance", status: "stopped" }], list: [{ id: 57, assigned_host: 4, name: "Test Instance", status: "stopped" }],
}, },
}); });
} else if (message.type === "list_roles_request") { } else if (message.type === "list_roles_request") {
@@ -39,21 +39,21 @@ describe("lib/command", function() {
}); });
let testControl = new mock.MockControl(mockConnector); let testControl = new mock.MockControl(mockConnector);
libLink.messages.listSlaves.attach(testControl); libLink.messages.listHosts.attach(testControl);
libLink.messages.listInstances.attach(testControl); libLink.messages.listInstances.attach(testControl);
libLink.messages.listRoles.attach(testControl); libLink.messages.listRoles.attach(testControl);
describe("resolveSlave", function() { describe("resolveHost", function() {
it("should pass an integer like string back", async function() { it("should pass an integer like string back", async function() {
assert.equal(await libCommand.resolveSlave(null, "123"), 123); assert.equal(await libCommand.resolveHost(null, "123"), 123);
}); });
it("should resolve a slave name with the controller", async function() { it("should resolve a host name with the controller", async function() {
assert.equal(await libCommand.resolveSlave(testControl, "Test Slave"), 11); assert.equal(await libCommand.resolveHost(testControl, "Test Host"), 11);
}); });
it("should throw if slave is not found", async function() { it("should throw if host is not found", async function() {
await assert.rejects( await assert.rejects(
libCommand.resolveSlave(testControl, "invalid"), libCommand.resolveHost(testControl, "invalid"),
new libErrors.CommandError("No slave named invalid") new libErrors.CommandError("No host named invalid")
); );
}); });
}); });
+4 -4
View File
@@ -111,13 +111,13 @@ describe("lib/logging_utils.js", function() {
it("should filter by controller", async function() { it("should filter by controller", async function() {
let log = await libLoggingUtils.queryLog(logDir, { controller: true }, index()); let log = await libLoggingUtils.queryLog(logDir, { controller: true }, index());
assert.deepEqual(log, allLines.filter( assert.deepEqual(log, allLines.filter(
info => info.slave_id === undefined && info.instance_id === undefined info => info.host_id === undefined && info.instance_id === undefined
)); ));
}); });
it("should filter by slave_ids", async function() { it("should filter by host_ids", async function() {
let log = await libLoggingUtils.queryLog(logDir, { slave_ids: [1, 2] }, index()); let log = await libLoggingUtils.queryLog(logDir, { host_ids: [1, 2] }, index());
assert.deepEqual(log, allLines.filter( assert.deepEqual(log, allLines.filter(
info => [1, 2].includes(info.slave_id) && info.instance_id === undefined info => [1, 2].includes(info.host_id) && info.instance_id === undefined
)); ));
}); });
it("should filter by instance_ids", async function() { it("should filter by instance_ids", async function() {
+2 -2
View File
@@ -66,8 +66,8 @@ describe("lib/plugin", function() {
await controllerPlugin.onInstanceConfigFieldChanged({}, {}, "foo", null); await controllerPlugin.onInstanceConfigFieldChanged({}, {}, "foo", null);
await controllerPlugin.onMetrics(); await controllerPlugin.onMetrics();
await controllerPlugin.onShutdown(); await controllerPlugin.onShutdown();
controllerPlugin.onSlaveConnectionEvent({}, "connect"); controllerPlugin.onHostConnectionEvent({}, "connect");
await controllerPlugin.onPrepareSlaveDisconnect({}); await controllerPlugin.onPrepareHostDisconnect({});
}); });
}); });
Loaded 100 of 101 files, more files were not shown because too many files have changed in this diff. Show more