mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-11 15:04:52 +09:00
Rename Master to Controller
The role of the master server as the party controlling every part of the cluster is not well reflected in its name and has caused confusion among users on several occasions. Rename master to controller to better reflects its role.
This commit is contained in:
1 parent
6ae4c8ef46
commit
70ad295a89
119 files changed
+1394
-1376
No files matched your search
+1
-1
@@ -35,7 +35,7 @@ Factorio*
|
||||
/mods
|
||||
/database
|
||||
/plugin-list.json
|
||||
/config-master.json
|
||||
/config-controller.json
|
||||
/config-control.json
|
||||
/config-slave.json
|
||||
pnpm-lock.yaml
|
||||
|
||||
+12
-11
@@ -10,9 +10,9 @@
|
||||
Regular freeplay games can now be used with Clusterio and will be compatible without having to convert them to Hotpatch.
|
||||
- Daemonized slaves.
|
||||
Slaves 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 master server that can be accessed through the clusterioctl cli tool.
|
||||
- Rewritten the communication between slaves and the master from scratch.
|
||||
The new system is based on a WebSocket connection between the slaves and the master server and provides efficient validated bi-directonal communication.
|
||||
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.
|
||||
The new system is based on a WebSocket connection between the slaves and the controller and provides efficient validated bi-directonal communication.
|
||||
- Rewritten the plugin system from scratch.
|
||||
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.
|
||||
@@ -24,9 +24,9 @@
|
||||
### Features
|
||||
|
||||
- Added export of pollution statitics.
|
||||
- Reconnection logic that esures no data is dropped talking to the master server provided the session can be resumed.
|
||||
- Reconnection logic that esures no data is dropped talking to the controller provided the session can be resumed.
|
||||
- Added support for having multiple Factorio installs and selecting which version to use on a per instance basis.
|
||||
- Added config to specify which path the master interface is accessed under, allowing it to be proxied behind web-server.
|
||||
- Added config to specify which path the controller interface is accessed under, allowing it to be proxied behind web-server.
|
||||
- Added WebSocket usage statistics.
|
||||
- Added commands sent statistic.
|
||||
- Added Factorio server CPU and memory usage statistic.
|
||||
@@ -42,13 +42,13 @@
|
||||
- Added option to configure timeout for exported Prometheus metrics.
|
||||
- Added option to set address to bind HTTP(S) port to.
|
||||
- Added an overview of the installed plugins to the web interface.
|
||||
- Added clusterioctl command to list plugins on the master server.
|
||||
- Added clusterioctl command to list plugins on the controller.
|
||||
- Added Player Auth plugin for logging in to the web interface by proving the ability to log in to a server as a given user.
|
||||
- Added Node.js based installer to simplify setting up a cluster.
|
||||
|
||||
### Changes
|
||||
|
||||
- Added error handling during master startup.
|
||||
- Added error handling during controller startup.
|
||||
- Factorio game and rcon port now defaults to a random port above 49151.
|
||||
- Removed unimplemented mods update command.
|
||||
- Fixed rcon password being generated with Math.random().
|
||||
@@ -76,7 +76,8 @@
|
||||
- Removed info and shout command from globalChat plugin - Removed mirrorAllChat and enableCrossServerShout configuration options for globalChat plugin.
|
||||
- Removed UPSdisplay plugin.
|
||||
UPS statistics is exported by the statistics exporter plugin.
|
||||
- Master server now defaults to hosting on https on port 8443.
|
||||
- Controller now defaults to hosting on https on port 8443.
|
||||
- Renamed master to controller.
|
||||
- Renamed client to slave.
|
||||
- Removed rotation of factorio-current.log files.
|
||||
|
||||
@@ -134,13 +135,13 @@
|
||||
- Removed msBetweenCommands config option.
|
||||
The RCON is instead limited to 5 concurrent commands.
|
||||
- Removed allowRemoteCommandExecution config option.
|
||||
Remote commands are always allowed with the move to master managing slaves/instances.
|
||||
- Removed `--databaseDirectory`, `--masterPort`, and `--sslPort` command line arguments from the master server.
|
||||
Remote commands are always allowed with the move to controller managing slaves/instances.
|
||||
- Removed `--databaseDirectory`, `--controllerPort`, and `--sslPort` command line arguments from the controller.
|
||||
- Implemented a new config system that replaces the old.
|
||||
Breaks all plugins.
|
||||
- Removed usage of socket.io entirely in favor of a plain WebSocket connection.
|
||||
- Renamed clusterioMod plugin to subspace_storage.
|
||||
- Master no longer creates secret-api-token.txt.
|
||||
- Controller no longer creates secret-api-token.txt.
|
||||
- Removed automatic creation of self-signed TLS certificate.
|
||||
- Node.js versions below 12 are no longer supported.
|
||||
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ RUN pnpm install
|
||||
# Install plugins. This is intended as a reasonable default, enabling plugins to make for fun gameplay.
|
||||
# If you want a different set of plugins, consider using this as the base image for your own.
|
||||
#RUN pnpm install @clusterio/plugin-subspace_storage
|
||||
#RUN npx clusteriomaster plugin add @clusterio/plugin-subspace_storage
|
||||
#RUN npx clusteriocontroller plugin add @clusterio/plugin-subspace_storage
|
||||
|
||||
COPY --from=subspace_storage_builder /subspace_storage/dist/ /clusterio/mods/
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ If you are starting a new cluster, it's highly recommended to use the 2.0 alpha.
|
||||
* [Windows setup](#windows-setup)
|
||||
* [MacOS setup](#macos-setup)
|
||||
* [Installing Plugins](#installing-plugins)
|
||||
* [Configure Master Server](#configure-master-server)
|
||||
* [Configure Controller](#configure-controller)
|
||||
* [Managing Factorio mods](#managing-factorio-mods)
|
||||
* [Setting up shared storage](#setting-up-shared-storage)
|
||||
* [Running Clusterio](#running-clusterio)
|
||||
@@ -71,7 +71,7 @@ These are the plugins supported and maintained by the Clusterio developers:
|
||||
|
||||
- [Global Chat](/plugins/global_chat/README.md): share the in-game chat between servers.
|
||||
- [Research Sync](/plugins/research_sync/README.md): synchronize research progress and technologies unlocked between servers.
|
||||
- [Statistics Exporter](/plugins/statistics_exporter/README.md): collect in-game statistics from all the servers and makes it available to the Prometheus endpoint on the master server.
|
||||
- [Statistics Exporter](/plugins/statistics_exporter/README.md): collect in-game statistics from all the servers and makes it available to the Prometheus endpoint on the controller.
|
||||
- [Subspace Storage](https://github.com/clusterio/subspace_storage): Provide shared storage that can transport items between servers via teleport chests.
|
||||
- [Player Auth](/plugins/player_auth/README.md): Provides authentication to the cluster via logging into a Factorio server.
|
||||
- [Inventory Sync](/plugins/inventory_sync/README.md): Synchronizes your inventory between servers.
|
||||
@@ -126,7 +126,7 @@ Clusterio has *very* limited support for using docker.
|
||||
|
||||
sudo docker build -t clusterio --no-cache --force-rm clusterio
|
||||
|
||||
sudo docker run --name master -e MODE=master -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
|
||||
|
||||
@@ -173,37 +173,37 @@ First install the package the plugin is provided by via npm, for example
|
||||
|
||||
Then tell clusterio that this plugin exists by adding it as a plugin.
|
||||
|
||||
npx clusteriomaster 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 master, slave and ctl.
|
||||
This adds it to the default `plugin-list.json` file which in the shared folder setup is loaded by controller, slave 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.
|
||||
The clusteriomaster, clusterioslave and clusterioctl commands has the plugin sub-command so you do not need to install clusteriomaster to add plugins, instead use the clusterio command you have available.
|
||||
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.
|
||||
|
||||
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).
|
||||
|
||||
|
||||
## Configure Master Server
|
||||
## Configure Controller
|
||||
|
||||
By default the master server will listen for HTTP on port 8080.
|
||||
By default the controller will listen for HTTP on port 8080.
|
||||
You can change the port used with the command
|
||||
|
||||
npx clusteriomaster config set master.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.
|
||||
For the standalone installation mode you can use
|
||||
|
||||
npx clusterioslave config set slave.master_url http://localhost:1234/
|
||||
npx clusterioslave config set slave.controller_url http://localhost:1234/
|
||||
|
||||
If you plan to make your cluster available externally set the address
|
||||
that it will be accessible under with, for example
|
||||
|
||||
npx clusteriomaster config set master.external_address http://203.0.113.4:1234/
|
||||
npx clusteriocontroller config set controller.external_address http://203.0.113.4:1234/
|
||||
|
||||
Change the url to reflect the IP, protocol, and port the master server is accessible under, dns names are also supported.
|
||||
If you're planning on making the master server accessible on the internet it's recommended to set up TLS, see the [Setting Up TLS](/docs/setting-up-tls.md) document for more details.
|
||||
Change the url to reflect the IP, protocol, and port the controller is accessible under, dns names are also supported.
|
||||
If you're planning on making the controller accessible on the internet it's recommended to set up TLS, see the [Setting Up TLS](/docs/setting-up-tls.md) document for more details.
|
||||
|
||||
You can list the config of the master server with the `npx clusteriomaster config list` command.
|
||||
See the [readme for @clusterio/master](/packages/master/README.md) for more information.
|
||||
You can list the config of the controller with the `npx clusteriocontroller config list` command.
|
||||
See the [readme for @clusterio/controller](/packages/controller/README.md) for more information.
|
||||
|
||||
|
||||
## Managing Factorio mods
|
||||
@@ -218,7 +218,7 @@ Better remote management of mods is a planned feature.
|
||||
|
||||
## Setting up shared storage
|
||||
|
||||
The chests teleporting items to and from the shared storage on the master server 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.
|
||||
|
||||
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.
|
||||
@@ -226,43 +226,43 @@ Do not install the [Clusterio mod](https://mods.factorio.com/mod/clusterio), thi
|
||||
|
||||
## Running Clusterio
|
||||
|
||||
After completing the installation start up the master server and at least one slave separately.
|
||||
The installer provides the `run-master` and `run-slave` scripts to make this simple.
|
||||
Once the master 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.
|
||||
After completing the installation start up the controller and at least one slave separately.
|
||||
The installer provides the `run-controller` and `run-slave` 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.
|
||||
|
||||
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.
|
||||
|
||||
### Running via Systemd
|
||||
|
||||
The install script creates systemd service scripts for clusteriomaster and clusterioslave (if applicable) to start up as the user than ran the installer.
|
||||
The install script creates systemd service scripts for clusteriocontroller and clusterioslave (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:
|
||||
|
||||
#### To start as a service run:
|
||||
|
||||
sudo systemctl start clusteriomaster # for the master server
|
||||
sudo systemctl start clusteriocontroller # for the controller
|
||||
sudo systemctl start clusterioslave # for each server hosting a slave
|
||||
|
||||
#### To automatically get it to start on boot:
|
||||
|
||||
sudo systemctl enable clusteriomaster # for the master server
|
||||
sudo systemctl enable clusteriocontroller # for the controller
|
||||
sudo systemctl enable clusterioslave # for each server hosting a slave
|
||||
|
||||
|
||||
## Setting up remote slaves
|
||||
|
||||
Run the installer as described in the installation section and choose "Slave only" as the operating mode to install, it'll ask for a master URL and an authentication token.
|
||||
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.
|
||||
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.
|
||||
Once you start up the slave it should show up in the Slaves list and be available for assigning and running instances on.
|
||||
|
||||
|
||||
## Setting up clusterioctl
|
||||
|
||||
There's a command line interface available for Clusterio which is installed separately with the same installer as for the master and slave.
|
||||
There's a command line interface available for Clusterio which is installed separately with the same installer as for the controller and slave.
|
||||
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 master 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
|
||||
|
||||
npx clusteriomaster bootstrap generate-user-token <username>
|
||||
npx clusteriocontroller bootstrap generate-user-token <username>
|
||||
|
||||
|
||||
## Common problems
|
||||
@@ -276,7 +276,7 @@ According to [this link](https://askubuntu.com/questions/839520/open-port-443-fo
|
||||
|
||||
sudo setcap 'cap_net_bind_service=+ep' $(readlink -f $(which node))
|
||||
|
||||
### Port forwarding doesn't work on the master server when running under WSL
|
||||
### Port forwarding doesn't work on the controller when running under WSL
|
||||
|
||||
If you follow the ubuntu guide on WSL (Windows Subsystem for Linux, Bash on Ubuntu on Windows specifically), you will find that the website works on localhost and on your local ip, but not on the global ip.
|
||||
This is also true when you correctly port-forwarded the correct ports.
|
||||
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
master:
|
||||
controller:
|
||||
build: .
|
||||
command: npx clusteriomaster run
|
||||
command: npx clusteriocontroller run
|
||||
slave:
|
||||
build: .
|
||||
command: npx clusterioslave run
|
||||
@@ -11,7 +11,7 @@ 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 also knows what the endpoint types it's connected to are via the source and target attributes.
|
||||
|
||||
The endpoint types currently implemented are master, slave, instance, and control.
|
||||
The endpoint types currently implemented are controller, slave, 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.
|
||||
|
||||
Upon creation the link will register with the connector in order to receive and process messages from it.
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
<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 master server, instances, slaves, and the clusterioctl utility.
|
||||
Plugins get their own config group for the master server and instances which they can add their own fields to.
|
||||
Clusterio uses a group based configuration system to manage settings for the controller, instances, slaves, and the clusterioctl utility.
|
||||
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 built-in config groups and their fields are defined in [packages/lib/config/definitions.js](/packages/lib/config/definitions.js) while plugins can add their own config groups to the `MasterConfigGroup` and `InstanceConfigGroup` properties of the `info.js` export using subclasses of `PluginConfigGroup`.
|
||||
The built-in config groups and their fields are defined in [packages/lib/config/definitions.js](/packages/lib/config/definitions.js) while plugins can add their own config groups to the `ControllerConfigGroup` and `InstanceConfigGroup` properties of the `info.js` export using subclasses of `PluginConfigGroup`.
|
||||
The plugin group has the enabled field pre-defined on it, and it's used to enable/disable plugins.
|
||||
|
||||
|
||||
@@ -17,10 +17,10 @@ The `define` method takes an object as argument with the following properties:
|
||||
|
||||
**access**:
|
||||
The locations this config field can be read and modified from.
|
||||
This is an array of strings which can contain the values `"master"`, `"slave"` and `"control"`.
|
||||
This is an array of strings which can contain the values `"controller"`, `"slave"` 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.
|
||||
Takes the value of `defaultAccess` set on the config group class if not passed.
|
||||
Note that instance config groups should have both `"master"` and `"slave"` for all of its fields or unexpected behaviour may occur.
|
||||
Note that instance config groups should have both `"controller"` and `"slave"` for all of its fields or unexpected behaviour may occur.
|
||||
|
||||
**type**:
|
||||
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.
|
||||
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 master server 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 slaves on startup.
|
||||
This makes it possible to edit instance configs when the slave 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 master server 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 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.
|
||||
+44
-44
@@ -1,9 +1,9 @@
|
||||
# Configuration
|
||||
|
||||
|
||||
## Master Configuration
|
||||
## Controller Configuration
|
||||
|
||||
### master.name
|
||||
### controller.name
|
||||
|
||||
Name of the cluster.
|
||||
Used to distinguish it from other clusters and is prepended to the name of servers in the server list.
|
||||
@@ -11,37 +11,37 @@ Used to distinguish it from other clusters and is prepended to the name of serve
|
||||
Defaluts to "Your Cluster".
|
||||
|
||||
|
||||
### master.database_directory
|
||||
### controller.database_directory
|
||||
|
||||
Directory used to store cluster data to.
|
||||
Needs to be writeable by the master server.
|
||||
Needs to be writeable by the controller.
|
||||
This is also used by plugins to store its data.
|
||||
|
||||
Defaults to "database".
|
||||
|
||||
|
||||
### master.http_port
|
||||
### controller.http_port
|
||||
|
||||
Port to host HTTP server on.
|
||||
If set to null no HTTP server will be exposed.
|
||||
At least one of this and master.https_port needs to be set to a port.
|
||||
At least one of this and controller.https_port needs to be set to a port.
|
||||
|
||||
Defaults to 8080.
|
||||
|
||||
|
||||
### master.https_port
|
||||
### controller.https_port
|
||||
|
||||
Port to host HTTPS server on.
|
||||
Uses master.tls_certificate and master.tls_private_key as the certificate and private key for the HTTPS server.
|
||||
Uses controller.tls_certificate and controller.tls_private_key as the certificate and private key for the HTTPS server.
|
||||
If set to null no HTTPS server will be exposed.
|
||||
At least one of this and master.http_port needs to be set to a port.
|
||||
At least one of this and controller.http_port needs to be set to a port.
|
||||
|
||||
See the [Setting up TLS](/docs/setting-up-tls.md) document for a guide to setting up HTTPS with Clusterio.
|
||||
|
||||
Defaults to null.
|
||||
|
||||
|
||||
### master.bind_address
|
||||
### controller.bind_address
|
||||
|
||||
IP address to bind the HTTP and HTTPS ports on.
|
||||
Useful to limit which interface to accept connections from.
|
||||
@@ -51,30 +51,30 @@ interfaces.
|
||||
Defaults to null.
|
||||
|
||||
|
||||
### master.external_address
|
||||
### controller.external_address
|
||||
|
||||
External address the master server is accessible on.
|
||||
Currently only used for `clusteriomaster bootstrap create-ctl-config` in order to give the right url to connect to.
|
||||
External address the controller is accessible on.
|
||||
Currently only used for `clusteriocontroller bootstrap create-ctl-config` in order to give the right url to connect to.
|
||||
This should be a full URL ending with a /.
|
||||
|
||||
Defaults to null meaning assume localhost.
|
||||
|
||||
|
||||
### master.tls_certificate
|
||||
### controller.tls_certificate
|
||||
|
||||
Path to TLS certificate to use for the HTTPS server when master.https_port is configured.
|
||||
Path to TLS certificate to use for the HTTPS server when controller.https_port is configured.
|
||||
|
||||
Defaults to "database/certificates/cert.crt".
|
||||
|
||||
|
||||
### master.tls_private_key
|
||||
### controller.tls_private_key
|
||||
|
||||
Path to TLS private key to use for the HTTPS server when master.https_port is configured.
|
||||
Path to TLS private key to use for the HTTPS server when controller.https_port is configured.
|
||||
|
||||
Defaults to "database/certificates/cert.key".
|
||||
|
||||
|
||||
### master.auth_secret
|
||||
### controller.auth_secret
|
||||
|
||||
Base64 encoded authentication secret used to verify tokens issued to slaves, users, and sessions.
|
||||
This should contain 256 bytes of random data, and if changed will cause all tokens become invalid.
|
||||
@@ -83,7 +83,7 @@ Keep this secret if leaked an attacker could easily compromise the cluster.
|
||||
Defaults to null which means generate a secure secret on startup.
|
||||
|
||||
|
||||
### master.heartbeat_interval
|
||||
### controller.heartbeat_interval
|
||||
|
||||
Interval in seconds heartbeats are sent out at for WebSocket connections.
|
||||
If a WebSocket connection hasn't received a heartbeat in 2 times the heartbeat interval it will be considered stale and closed.
|
||||
@@ -92,17 +92,17 @@ A lower value means less time between connections going stale and them being clo
|
||||
Defaults to 15.
|
||||
|
||||
|
||||
### master.session_timeout
|
||||
### controller.session_timeout
|
||||
|
||||
Time in seconds from the connection is closed following a heartbeat timeout until the session is invalidated and data loss occurs.
|
||||
A higher timeout gives more time for a client to recconect and resume an active session over a connection that was dropped, but also leads to stale connections taking longer to clear out.
|
||||
In the case of the master or client crashing the data loss is unavoidable.
|
||||
In the case of the controller or client crashing the data loss is unavoidable.
|
||||
|
||||
Value should not be less than the configured `max_reconnect_delay` of the clients and it should be greater than 2 times `master.heartbeat_interval`.
|
||||
Value should not be less than the configured `max_reconnect_delay` of the clients and it should be greater than 2 times `controller.heartbeat_interval`.
|
||||
Defaults to 60.
|
||||
|
||||
|
||||
### master.metrics_timeout
|
||||
### controller.metrics_timeout
|
||||
|
||||
Timeout in seconds before a call to gather metrics from a slave times out.
|
||||
This should be less than both the timeout and collection interval configured for the collection job in Prometheus.
|
||||
@@ -110,15 +110,15 @@ This should be less than both the timeout and collection interval configured for
|
||||
Defaults to 8.
|
||||
|
||||
|
||||
### master.stream_timeout
|
||||
### controller.stream_timeout
|
||||
|
||||
Timeout in seconds for proxy streams through the master that has been created but is not yet sending data to be automatically closed.
|
||||
Timeout in seconds for proxy streams through the controller that has been created but is not yet sending data to be automatically closed.
|
||||
Streams that have started sending data before this timeout is not affected.
|
||||
|
||||
Defaults to 15.
|
||||
|
||||
|
||||
### master.default_role_id
|
||||
### controller.default_role_id
|
||||
|
||||
Role to automatically grant to new users.
|
||||
If null no role is granted.
|
||||
@@ -128,8 +128,8 @@ Defaults to 1 which correspond to the default Player role.
|
||||
|
||||
### <plugin_name>.load_plugin
|
||||
|
||||
Whether to load the plugin on the master server.
|
||||
Plugins not loaded on the master server will not be loaded on instances.
|
||||
Whether to load the plugin on the controller.
|
||||
Plugins not loaded on the controller will not be loaded on instances.
|
||||
|
||||
Defaults to true.
|
||||
|
||||
@@ -167,16 +167,16 @@ One sub-directory will be created in it for each instance assigned to this slave
|
||||
Defaults to "instances".
|
||||
|
||||
|
||||
### slave.master_url
|
||||
### slave.controller_url
|
||||
|
||||
URL to connect to the master server to.
|
||||
URL to connect to the controller to.
|
||||
|
||||
Defaults to "http://localhost:8080/".
|
||||
|
||||
|
||||
### slave.master_token
|
||||
### slave.controller_token
|
||||
|
||||
Access token used for authenticating with the master server.
|
||||
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.
|
||||
|
||||
Defaults to "enter token here".
|
||||
@@ -184,8 +184,8 @@ Defaults to "enter token here".
|
||||
|
||||
### slave.tls_ca
|
||||
|
||||
Path to certificate in PEM format to use for validating a TLS connection to the master server.
|
||||
If you have a self signed certificate on the master server you will need to copy the certificate to your slaves and set it as the certificate authority with this option.
|
||||
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.
|
||||
|
||||
Defaults to null meaning use Node.js's default set of trusted certificate authorities.
|
||||
|
||||
@@ -200,7 +200,7 @@ Defaults to "localhost".
|
||||
|
||||
### slave.max_reconnect_delay
|
||||
|
||||
Maximum delay in seconds to wait after connection to the master server 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.
|
||||
|
||||
Defaults to 60.
|
||||
@@ -383,39 +383,39 @@ Defaults to 5.
|
||||
### <plugin_name>.load_plugin
|
||||
|
||||
Whether to load the given plugin on the instance.
|
||||
Note that plugins need to be loaded on the master server in order for them to be loaded on instances.
|
||||
Note that plugins need to be loaded on the controller in order for them to be loaded on instances.
|
||||
|
||||
Defaults to true.
|
||||
|
||||
|
||||
## Control Configuration
|
||||
|
||||
### control.master_url
|
||||
### control.controller_url
|
||||
|
||||
URL to connect to the master server to.
|
||||
URL to connect to the controller to.
|
||||
|
||||
Defaults to null meaning complain about it not being set and exit.
|
||||
|
||||
|
||||
### control.master_token
|
||||
### control.controller_token
|
||||
|
||||
Access token used for authenticating with the master server.
|
||||
You can generate an access token with `clusteriomaster bootstrap generate-user-token <username>`, or use the `clusteriomaster bootstrap create-ctl-config <username>` to create a new ctl config with the correct url and token in it.
|
||||
Access token used for authenticating with the controller.
|
||||
You can generate an access token with `clusteriocontroller bootstrap generate-user-token <username>`, or use the `clusteriocontroller bootstrap create-ctl-config <username>` to create a new ctl config with the correct url and token in it.
|
||||
|
||||
Defaults to null meaning complain about it not being set and exit.
|
||||
|
||||
|
||||
### control.tls_ca
|
||||
|
||||
Path to certificate in PEM format to use for validating a TLS connection to the master server.
|
||||
If you have a self signed certificate on the master server you will need to copy the certificate to the computer you run clusterctl from and set it as the certificate authority with this option.
|
||||
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 the computer you run clusterctl from and set it as the certificate authority with this option.
|
||||
|
||||
Defaults to null meaning use Node.js's default set of trusted certificate authorities.
|
||||
|
||||
|
||||
### control.max_reconnect_delay
|
||||
|
||||
Max duration in seconds to wait before attempting to reconnect with the master server after the connection is dropped.
|
||||
Max duration in seconds to wait before attempting to reconnect with the controller after the connection is dropped.
|
||||
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.
|
||||
|
||||
Defaults to 60.
|
||||
@@ -68,17 +68,17 @@ 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:
|
||||
|
||||
npx clusteriomaster -> node packages/master
|
||||
npx clusteriocontroller -> node packages/controller
|
||||
npx clusterioslave -> node packages/slave
|
||||
npx clusterioctl -> node packages/ctl
|
||||
|
||||
Note that to use `clusterioctl` you will have to create a control config first:
|
||||
|
||||
node packages/master 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/master run` to run the master server and `node packages/slave run` to run the slave which connects to the master server.
|
||||
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.
|
||||
|
||||
For web development on the master there are also the following flags:
|
||||
For web development on the controller there are also the following flags:
|
||||
|
||||
`--dev` - Recompile the `web_ui` package on the fly providing live reloading of it.
|
||||
|
||||
@@ -86,7 +86,7 @@ For web development on the master there are also the following flags:
|
||||
|
||||
These options can be combined and multiple plugins can be actived for dev mode.
|
||||
Keep in mind if you're working on external plugins the `--dev-plugin` option is likely to only work if the plugin is placed inside the `external_plugins` directory and its dependencies are installed with pnpm as part of the clusterio repository workspace.
|
||||
Note that webpack is configured to delete the web interface build when using these options, so you will have to run `pnpm run -r --if-present prepare` before you start the master without these flags again.
|
||||
Note that webpack is configured to delete the web interface build when using these options, so you will have to run `pnpm run -r --if-present prepare` before you start the controller without these flags again.
|
||||
|
||||
|
||||
### Starting a Feature Branch
|
||||
|
||||
+12
-12
@@ -12,9 +12,9 @@ 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.
|
||||
|
||||
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 master server there's a `Link` instance for each slave and control connection stored in the `WsServer` class.
|
||||
For slaves the `Slave` instance is a link to the master server, 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 ctl and web_ui there's a `Control` instance that acts as the link to the master server.
|
||||
For the controller there's a `Link` instance for each slave 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 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.
|
||||
These handlers are named after the name of the property the message is stored under with `EventHandler` appended for events and `RequestHandler` appended for requests.
|
||||
@@ -96,11 +96,11 @@ The following messages are the only valid messages that can be sent during the h
|
||||
|
||||
#### `hello`
|
||||
|
||||
Sent by the master server when the WebSocket connection has been opened.
|
||||
Sent by the controller when the WebSocket connection has been opened.
|
||||
|
||||
- `version: string` - The version of the master, e.g. "2.0.0".
|
||||
- `version: string` - The version of the controller, e.g. "2.0.0".
|
||||
- `plugins: Object<string, string>` -
|
||||
Object mapping plugin names to plugin version for plugins that are loaded on the master server.
|
||||
Object mapping plugin names to plugin version for plugins that are loaded on the controller.
|
||||
|
||||
#### `register_slave`
|
||||
|
||||
@@ -131,12 +131,12 @@ Handshake for resuming an existing connection session where the connection had d
|
||||
|
||||
#### `invalidate`
|
||||
|
||||
Sent by the master server in reply to a resume for a session that cannot be resumed.
|
||||
Sent by the controller in reply to a resume for a session that cannot be resumed.
|
||||
The client should start a new session with a register\_\* message in response.
|
||||
|
||||
#### `continue`
|
||||
|
||||
Sent by the master server to signal resuming the session was successful.
|
||||
Sent by the controller to signal resuming the session was successful.
|
||||
|
||||
- `last_seq: integer | null` - Last message seq received by the server.
|
||||
- `session_timeout: integer` - Session timeout in seconds.
|
||||
@@ -144,7 +144,7 @@ Sent by the master server to signal resuming the session was successful.
|
||||
|
||||
#### `ready`
|
||||
|
||||
Sent by the master server to signal the connection has been established with a new session.
|
||||
Sent by the controller to signal the connection has been established with a new session.
|
||||
|
||||
- `session_token: string` - Token to resume the session with.
|
||||
- `session_timeout: integer` - Session timeout in seconds.
|
||||
@@ -159,7 +159,7 @@ The heartbeat should contain the `seq` of the last received message, and receivi
|
||||
|
||||
#### `heartbeat`
|
||||
|
||||
Sent by both the master server and clients at the interval given in the continue/ready messages.
|
||||
Sent by both the controller and clients at the interval given in the continue/ready messages.
|
||||
|
||||
- `last_seq: integer` - Last message seq received over the link.
|
||||
|
||||
@@ -251,7 +251,7 @@ Slave registering a new connection session.
|
||||
}
|
||||
```
|
||||
|
||||
Master server reply signaling the session is established and ready.
|
||||
Controller reply signaling the session is established and ready.
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -265,4 +265,4 @@ Master server reply signaling the session is established and ready.
|
||||
}
|
||||
```
|
||||
|
||||
Slave and master can now send events and requests over the connection.
|
||||
Slave and controller can now send events and requests over the connection.
|
||||
@@ -11,6 +11,6 @@ This can be disabled with the `factorio.enable_save_patching` config on a per in
|
||||
|
||||
## User Accounts
|
||||
|
||||
The master server creates and stores accounts for all Factorio users that log into instances under its control.
|
||||
The controller creates and stores accounts for all Factorio users that log into instances under its control.
|
||||
These accounts are used to store per player data and authorize management of the cluster.
|
||||
By default users accounts are assigned a Player role that gives a limited read access to the cluster resources, and admins of the cluster can be assigned a Cluster Admin role to get access to manage it.
|
||||
+2
-2
@@ -11,8 +11,8 @@
|
||||
},
|
||||
"source": {
|
||||
"include": [
|
||||
"packages/master/src/",
|
||||
"packages/master/master.js",
|
||||
"packages/controller/src/",
|
||||
"packages/controller/controller.js",
|
||||
"packages/slave/src/",
|
||||
"packages/slave/slave.js",
|
||||
"packages/ctl/src/",
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# Managing a Cluster
|
||||
|
||||
Clusterio clusters are managed through the master server by using the `clusterioctl` command line interface which is invoked by running `npx clusterioctl <command>` in the clusterio directory.
|
||||
Clusterio clusters are managed through the controller by using the `clusterioctl` command line interface which is invoked by running `npx clusterioctl <command>` in the clusterio directory.
|
||||
This document uses the shorthand `ctl> foo` to indicate `npx clusterioctl foo` should be executed.
|
||||
Mandatory parameters are shown in `<angles bracket>` and optional pameters are in `[square brackets]`.
|
||||
|
||||
Before `clusterioctl` can be used it needs to be configured for the cluster it will connect to.
|
||||
The easiest way to do this is to run `npx clusteriomaster bootstrap create-ctl-config <username>` on the master server, 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
|
||||
@@ -189,7 +189,7 @@ By default there's a roled named Cluster Admin which grants access to everything
|
||||
|
||||
ctl> user delete <name>
|
||||
|
||||
Deletes everything stored on the master server for this user.
|
||||
Deletes everything stored on the controller for this user.
|
||||
|
||||
Note: If the player is banned from the cluster this will effectively unban them, as the ban status is stored with the user account.
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Setting up TLS
|
||||
|
||||
By default Clusterio will listen for connections over HTTP which is fine for local networks but over the Internet it should idealy be over HTTPS as there are some credentials that could be exposed to eavesdroppers.
|
||||
The recommended setup is to have an ordinary HTTPS server software like Apache or Nginx to act as a reverse proxy in front of the Clusterio master server.
|
||||
The recommended setup is to have an ordinary HTTPS server software like Apache or Nginx to act as a reverse proxy in front of the Clusterio controller.
|
||||
The reverse proxy would then be set up with a TLS certificate from a Certificate Authoritiy like [Let's Encrypt](https://letsencrypt.org).
|
||||
Alternatively you can have Clusterio listen for connections over HTTPS directly by providing it with a TLS certificate and private key.
|
||||
|
||||
@@ -11,17 +11,17 @@ See the last section for how to create and use a self-signed certificate for an
|
||||
|
||||
## Using a reverse proxy
|
||||
|
||||
Configure the proxy to forward requests over HTTP to the port the master server is listening to so that for example a request to `https://example.com/instances/` is forwarded to `http://localhost:8080/instances/`.
|
||||
It is possible to host the interface under a sub-path, in that case a request under the sub-path should be mapped to the master server with the sub-path stripped out.
|
||||
Configure the proxy to forward requests over HTTP to the port the controller is listening to so that for example a request to `https://example.com/instances/` is forwarded to `http://localhost:8080/instances/`.
|
||||
It is possible to host the interface under a sub-path, in that case a request under the sub-path should be mapped to the controller with the sub-path stripped out.
|
||||
For example `https://example.com/sub/path/instances/` would be mapped to `http://localhost:8080/instances`.
|
||||
|
||||
Additianlly WebSocket connections to `/api/socket` also needs to be forwarded.
|
||||
Typically this requires explicit configuration and support for forwarding WebSocket connections.
|
||||
If the interface hosted under a sub-path the `/api/socket` address is relative to the sub-path and the sub-path also needs to be stripped out when forwarded.
|
||||
|
||||
You can also optionally set the master server to listen only on the loopback interface to prevent it being directly connected to from the outside.
|
||||
You can also optionally set the controller to listen only on the loopback interface to prevent it being directly connected to from the outside.
|
||||
|
||||
npx clusteriomaster config set master.bind_address 127.0.0.1
|
||||
npx clusteriocontroller config set controller.bind_address 127.0.0.1
|
||||
|
||||
|
||||
### Apache Config
|
||||
@@ -44,7 +44,7 @@ Pay attention to the use of trailing slashes, as they are important.
|
||||
|
||||
## Hosting HTTPS directly from Node.js
|
||||
|
||||
Listening on HTTPS is supported by the Clusterio master server via the `master.https_port` option provided paths to a certificate file and a private key file is configured for the `master.tls_certificate` and `master.tls_private_key` options.
|
||||
Listening on HTTPS is supported by the Clusterio controller via the `controller.https_port` option provided paths to a certificate file and a private key file is configured for the `controller.tls_certificate` and `controller.tls_private_key` options.
|
||||
Both of these are expected to be unencrypted PEM files.
|
||||
|
||||
|
||||
@@ -58,10 +58,10 @@ substituting `<ip>` with the IP address the server will be accessed under, for e
|
||||
Multiple addresses can be given by supplying a comma sepparated list of `IP:address` pairs and DNS names can also be specified using the `DNS:example.com` format.
|
||||
You need to supply every name you want to connect to the server under the subjectAltName field, for example to connect to the HTTPS port locally via an address like `https://localhost:4443/` you need to add `DNS:localhost`.
|
||||
|
||||
This creates a private key file named `key.pem` and a public certificate file named `cert.pem` and these are used for the `master.tls_certificate` and `master.tls_private_key` config options respectively on the master server:
|
||||
This creates a private key file named `key.pem` and a public certificate file named `cert.pem` and these are used for the `controller.tls_certificate` and `controller.tls_private_key` config options respectively on the controller:
|
||||
|
||||
npx clusteriomaster config set master.tls_certificate cert.pem
|
||||
npx clusteriomaster config set master.tls_private_key key.pem
|
||||
npx clusteriocontroller config set controller.tls_certificate cert.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:
|
||||
|
||||
|
||||
+56
-56
@@ -27,7 +27,7 @@ The basic file structure of a plugin is the following.
|
||||
plugin_name/
|
||||
+- info.js
|
||||
+- package.json
|
||||
+- master.js
|
||||
+- controller.js
|
||||
+- instance.js
|
||||
+- control.js
|
||||
+- test/
|
||||
@@ -41,7 +41,7 @@ The usual guides for creating such packages apply.
|
||||
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`.
|
||||
To add it to `plugin-list.json` so that it gets loaded use the `plugin add <path>` sub-command to either clusteriomaster, 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, clusterioslave or clusterioctl.
|
||||
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.
|
||||
@@ -56,7 +56,7 @@ module.exports = {
|
||||
title: "Foo Frobber",
|
||||
description: "Does advanced frobnication",
|
||||
instanceEntrypoint: "instance",
|
||||
masterEntrypoint: "master",
|
||||
controllerEntrypoint: "controller",
|
||||
messages: {
|
||||
/* See below */
|
||||
},
|
||||
@@ -80,19 +80,19 @@ The following properties are recognized:
|
||||
**instanceEntrypoint**:
|
||||
Path to a Node.js module relative to the plugin directory which contains the InstancePlugin class definition for this plugin.
|
||||
This is an optional paramater.
|
||||
A plugin may have code only for instances but it must still be loaded on the master in order for it to be possible to load it on an instance.
|
||||
A plugin may have code only for instances but it must still be loaded on the controller in order for it to be possible to load it on an instance.
|
||||
|
||||
**InstanceConfigGroup**:
|
||||
Subclass of `PluginConfigGroup` for defining the per instance configuration fields for this plugin.
|
||||
See [Plugin Configuration](#plugin-configuration)
|
||||
|
||||
**masterEntrypoint**:
|
||||
Path to a Node.js module relative to the plugin directory which contains the MasterPlugin class definiton for this plugin.
|
||||
**controllerEntrypoint**:
|
||||
Path to a Node.js module relative to the plugin directory which contains the ControllerPlugin class definiton for this plugin.
|
||||
This is an optional parameter.
|
||||
A plugin can be made that only runs on the master server.
|
||||
A plugin can be made that only runs on the controller.
|
||||
|
||||
**MasterConfigGroup**:
|
||||
Subclass of `PluginConfigGroup` for defining the master server configuration fields for this plugin.
|
||||
**ControllerConfigGroup**:
|
||||
Subclass of `PluginConfigGroup` for defining the controller configuration fields for this plugin.
|
||||
See [Plugin Configuration](#plugin-configuration)
|
||||
|
||||
**controlEntrypoint**:
|
||||
@@ -108,21 +108,21 @@ The optional module folder contains a Clusterio module that will be patched into
|
||||
See the section on [Clusterio Modules](developing-for-clusterio.md) in the Developing for Clusterio document.
|
||||
The only restriction imposed on modules embedded into plugins is that they must be named the same as the plugin.
|
||||
|
||||
While there is no standard for how to organize a plugin it's recommended to put the MasterPlugin class definition into master.js and the InstancePlugin class definition into instance.js.
|
||||
While there is no standard for how to organize a plugin it's recommended to put the ControllerPlugin class definition into controller.js and the InstancePlugin class definition into instance.js.
|
||||
You can put them into whatever file you want (even the same one for both).
|
||||
|
||||
For both instanceEntrypoint and masterEntrypoint the path should not end with .js and it should use forward slashes for directory separators if any.
|
||||
For both instanceEntrypoint and controllerEntrypoint the path should not end with .js and it should use forward slashes for directory separators if any.
|
||||
|
||||
|
||||
## Defining the plugin class
|
||||
|
||||
The plugin class should derive from its respective base class defined in `lib/plugin`.
|
||||
For example, to define a MasterPlugin class the following code can be used:
|
||||
For example, to define a ControllerPlugin class the following code can be used:
|
||||
|
||||
```js
|
||||
const libPlugin = require("@clusterio/lib/plugin");
|
||||
|
||||
class MasterPlugin extends libPlugin.BaseMasterPlugin {
|
||||
class ControllerPlugin extends libPlugin.BaseControllerPlugin {
|
||||
async init() {
|
||||
this.foo = 42;
|
||||
await this.startFrobnication();
|
||||
@@ -132,11 +132,11 @@ class MasterPlugin extends libPlugin.BaseMasterPlugin {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
MasterPlugin,
|
||||
ControllerPlugin,
|
||||
}
|
||||
```
|
||||
|
||||
For the instance plugin it's exactly the same except "Master" is replaced with "Instance", and for the clusterioctl plugin "Control" is used.
|
||||
For the instance plugin it's exactly the same except "Controller" is replaced with "Instance", and for the clusterioctl plugin "Control" is used.
|
||||
The available hooks that you can override are documented in the base class [in lib/plugin.js](/packages/lib/plugin.js).
|
||||
|
||||
It's best to avoid defining a constructor, but if you insist on defining one, forward all arguments to the base class.
|
||||
@@ -202,39 +202,39 @@ For plugin hooks expections thrown are automatically catched and logged, but for
|
||||
## Plugin Configuration
|
||||
|
||||
Clusterio provides a configuration system that handles storing, distributing, editing and validating config fields for you.
|
||||
You can take advantage of it by subclassing `PluginConfigGroup`, setting `defaultAccess` to where config entries can be accessed from, setting the `groupName` to your plugin name, defining fields on it, finalizing it, and passing it as either `MasterConfigGroup` or `InstanceConfigGroup` in the `info.js` export.
|
||||
You can take advantage of it by subclassing `PluginConfigGroup`, setting `defaultAccess` to where config entries can be accessed from, setting the `groupName` to your plugin name, defining fields on it, finalizing it, and passing it as either `ControllerConfigGroup` or `InstanceConfigGroup` in the `info.js` export.
|
||||
For example in info.js:
|
||||
|
||||
```js
|
||||
const libConfig = require("@clusterio/lib/config");
|
||||
|
||||
class MasterConfigGroup extends libConfig.PluginConfigGroup { }
|
||||
MasterConfigGroup.defaultAccess = ["master", "slave", "control"];
|
||||
MasterConfigGroup.groupName = "foo_frobber";
|
||||
MasterConfigGroup.define({
|
||||
class ControllerConfigGroup extends libConfig.PluginConfigGroup { }
|
||||
ControllerConfigGroup.defaultAccess = ["controller", "slave", "control"];
|
||||
ControllerConfigGroup.groupName = "foo_frobber";
|
||||
ControllerConfigGroup.define({
|
||||
name: "level",
|
||||
description: "Level of frobnication done",
|
||||
type: "number",
|
||||
initial_value: 2,
|
||||
});
|
||||
MasterConfigGroup.finalize();
|
||||
ControllerConfigGroup.finalize();
|
||||
|
||||
module.exports = {
|
||||
...
|
||||
MasterConfigGroup: MasterConfigGroup,
|
||||
ControllerConfigGroup: ControllerConfigGroup,
|
||||
};
|
||||
```
|
||||
|
||||
Code inside the `MasterPlugin` class will then be able to access the level config field through the `Config` object at `this.master.config`, for example in the MasterPluginClass:
|
||||
Code inside the `ControllerPlugin` class will then be able to access the level config field through the `Config` object at `this.controller.config`, for example in the ControllerPluginClass:
|
||||
|
||||
```js
|
||||
async init() {
|
||||
let level = this.master.config.get("foo_frobber.level");
|
||||
let level = this.controller.config.get("foo_frobber.level");
|
||||
this.logger.info(`I got a frobnication level of ${level}`);
|
||||
}
|
||||
```
|
||||
|
||||
The same applies for instance configs, replace "master" with "instance" where appropriate.
|
||||
The same applies for instance configs, replace "controller" with "instance" where appropriate.
|
||||
See [Configuration System](config-system.md) for more details on how this system works.
|
||||
|
||||
|
||||
@@ -313,8 +313,8 @@ For RCON, commands longer than 50 characters may end up being executed after sho
|
||||
|
||||
## Defining Link Messages
|
||||
|
||||
You will most likely have to communicate with the master 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 master server that plugins can define their own messages to send over it.
|
||||
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.
|
||||
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
|
||||
@@ -335,7 +335,7 @@ The Event constructor takes an object of properties that define the event, for e
|
||||
messages: {
|
||||
startFrobnication: new libLink.Event({
|
||||
type: "foo_frobber:start_frobnication",
|
||||
links: ["master-slave", "slave-instance"],
|
||||
links: ["controller-slave", "slave-instance"],
|
||||
forwardTo: "instance",
|
||||
eventRequired: ["frobnication_type"],
|
||||
eventProperties: {
|
||||
@@ -346,7 +346,7 @@ messages: {
|
||||
},
|
||||
```
|
||||
|
||||
This specifies an event that can be sent from the master to a slave, and from a slave to an instance.
|
||||
This specifies an event that can be sent from the controller to a slave, and from a slave 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 will also be forwarded by slaves to a specific instance.
|
||||
|
||||
@@ -361,17 +361,17 @@ The type of the message sent over the socket will have the suffix `_event` appen
|
||||
#### links
|
||||
|
||||
An array of strings describing which links this event can be sent over.
|
||||
Direction matters, `"master-slave"` means the event can be sent from the master to the slave, but can't be sent back the other way, unless `"slave-master"` is also present in the links array.
|
||||
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.
|
||||
|
||||
The available endpoints are `master`, `slave`, `instance`, and `control`.
|
||||
Master talks with slave and control, and slave 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 master to instance it must have both `"master-slave"` and `"slave-instance"` in the links array).
|
||||
The available endpoints are `controller`, `slave`, `instance`, and `control`.
|
||||
Controller talks with slave and control, and slave 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).
|
||||
See `forwardTo` and `broadcastTo` for ways to forward an event to the next link in a chain.
|
||||
|
||||
#### forwardTo
|
||||
|
||||
Target to forward an event to.
|
||||
Can either be `"master"`, to indicate a slave should forward it to the master server, 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 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.
|
||||
This works by using a default handler for the event at the links that forward it.
|
||||
|
||||
#### broadcastTo
|
||||
@@ -393,8 +393,8 @@ Object with properties mapping to a JSON schema of that property that specifies
|
||||
This is equivalent to using the `properties` keyword in JSON schema, except that the properties specified are by default required and additional properties are not allowed.
|
||||
See [this guide][guide] for an introduction to writing JSON schemas.
|
||||
|
||||
The forwardTo and broadcastTo can be combined such that specifying `"master"` 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-master`, `master-slave`, and `slave-instance` as the links.
|
||||
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.
|
||||
|
||||
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.
|
||||
@@ -410,7 +410,7 @@ For example, the following could be defined in `info.js`:
|
||||
messages: {
|
||||
reportFrobnication: new libLink.Request({
|
||||
type: "foo_frobber:report_frobnication",
|
||||
links: ["master-slave", "slave-instance"],
|
||||
links: ["controller-slave", "slave-instance"],
|
||||
forwardTo: "instance",
|
||||
requestRequired: ["verbosity"],
|
||||
requestProperties: {
|
||||
@@ -427,7 +427,7 @@ messages: {
|
||||
},
|
||||
```
|
||||
|
||||
This specifies a request that can be sent from the master to a slave, and from a slave to an instance.
|
||||
This specifies a request that can be sent from the controller to a slave, and from a slave 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.
|
||||
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`.
|
||||
@@ -443,17 +443,17 @@ The type of the message sent over the socket will have the suffix `_request` app
|
||||
#### links
|
||||
|
||||
An array of strings describing which links this request can be sent over.
|
||||
Direction matters; `"master-slave"` means the request can be sent from the master to the slave and the slave can reply to the master, but the slave can't send a request to the master unless `"slave-master"` is also present in the links array.
|
||||
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.
|
||||
|
||||
The available endpoints are `master`, `slave`, `instance`, and `control`.
|
||||
Master talks with slave and control, and slave 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 master to instance it must have both `"master-slave"` and `"slave-instance"` in the links array).
|
||||
The available endpoints are `controller`, `slave`, `instance`, and `control`.
|
||||
Controller talks with slave and control, and slave 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).
|
||||
See `forwardTo` for ways to forward a request to the next link in a chain.
|
||||
|
||||
#### forwardTo
|
||||
|
||||
Target to forward the request to.
|
||||
Can either be `"master"` to indicate a slave should forward it to the master server 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 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.
|
||||
This works by using a default handler for the request by the links that forward it.
|
||||
|
||||
#### requestRequired
|
||||
@@ -497,12 +497,12 @@ For the Request class the send method is async and returns the response data rec
|
||||
### 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.
|
||||
The most important is the prepare disconnect for the link between master and slave.
|
||||
This is signaled to `MasterPlugin` classes via the `onPrepareSlaveDisconnect` hook and to `InstancePlugin` classes via the `onPrepareMasterDisconnect` hook.
|
||||
The most important is the prepare disconnect for the link between controller and slave.
|
||||
This is signaled to `ControllerPlugin` classes via the `onPrepareSlaveDisconnect` 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.
|
||||
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 master and slave respectively.
|
||||
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.
|
||||
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
|
||||
@@ -511,12 +511,12 @@ if (this.slave.connected) {
|
||||
}
|
||||
```
|
||||
|
||||
If the event or request needs to be sent to the master 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 `onMasterConnectionEvent` and `onSlaveConnectionEvent` hooks.
|
||||
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 second connection event which is of lesser importance to respond to is the `drop` connection event served through `onMasterConnectionEvent` for `InstancePlugin` classes and through `onSlaveConnectionEvent` for `MasterPlugin` classes.
|
||||
This is raised when the connection between the master and slave in question is lost, most likely due to networking issues.
|
||||
When in the dropped state the slave will keep trying to reconnect to the master server in order to re-establish it, and if successful no events or requests will be lost.
|
||||
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.
|
||||
This is raised when the connection between the controller and slave 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.
|
||||
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.
|
||||
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.
|
||||
@@ -536,11 +536,11 @@ const fooMetric = new Counter(
|
||||
"clusterio_foo_frobber_foo_metric", "Measures the level of foo",
|
||||
);
|
||||
|
||||
// Somewhere in the master plugin code
|
||||
// Somewhere in the controller plugin code
|
||||
fooMetric.inc();
|
||||
```
|
||||
|
||||
This works for master plugins, and the metric will be automatically available through the /metric HTTP endpoint.
|
||||
This works for controller plugins, and the metric will be automatically available through the /metric HTTP endpoint.
|
||||
It's recommended that plugin metrics follow `clusterio_<plugin_name>_<metric_name>` as the naming scheme.
|
||||
|
||||
For metrics that are per-instance, you must define an `instance_id` label and set it accordingly, for example:
|
||||
@@ -557,11 +557,11 @@ const barMetric = new Gauge(
|
||||
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 master server on slaves.
|
||||
Metrics are automatically registered to the default registry, and this default registry is automatically polled by the controller on slaves.
|
||||
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.
|
||||
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 master 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.
|
||||
|
||||
|
||||
## Adding Custom Commands to clusterioctl
|
||||
@@ -600,7 +600,7 @@ For a command the `definition` is the arguments to pass to [yargs.command](http:
|
||||
The `handler` is an async function that's invoked when the command is executed and it's passed the parsed command line arguments and a reference to the `Control` class of clusterioctl.
|
||||
It's possible to optain a reference to the plugin class with `control.plugins.get(info.name)`.
|
||||
|
||||
Note that messages sent from clusterioctl needs to have `"control-master"` as a part of the links array for it to be accepted by the master server, see [Defining Link Messages](#defining-link-messages) for how to define the messages that can be sent to the master.
|
||||
Note that messages sent from clusterioctl needs to have `"control-controller"` as a part of the links array for it to be accepted by the controller, see [Defining Link Messages](#defining-link-messages) for how to define the messages that can be sent to the controller.
|
||||
|
||||
To have the command tree become part of clusterioctl it needs to be added to the rootCommand tree in `addCommands` callback of the Control plugin:
|
||||
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@
|
||||
"devDependencies": {
|
||||
"@clusterio/ctl": "workspace:*",
|
||||
"@clusterio/lib": "workspace:*",
|
||||
"@clusterio/master": "workspace:*",
|
||||
"@clusterio/controller": "workspace:*",
|
||||
"@clusterio/slave": "workspace:*",
|
||||
"eslint": "^8.18.0",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
|
||||
File renamed without changes.
@@ -1,27 +1,27 @@
|
||||
# Clusterio Master Server
|
||||
# Clusterio Controller
|
||||
|
||||
Communication hub for Clusterio clusters.
|
||||
The master server 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 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.
|
||||
|
||||
|
||||
# Usage
|
||||
|
||||
npx clusteriomaster <command>
|
||||
npx clusteriocontroller <command>
|
||||
|
||||
Common options:
|
||||
|
||||
* `--plugin-list <file>` JSON file to use for storing the list of plugins that are available to the master server.
|
||||
* `--plugin-list <file>` JSON file to use for storing the list of plugins that are available to the controller.
|
||||
Defaults to `plugin-list.json` and will be created if it does not exist.
|
||||
See the `plugin` command for managing this list.
|
||||
|
||||
* `--config <file>` JSON file to use for storing configuration for the master server.
|
||||
Defaults to `config-master.json` and will be created if it does not exist.
|
||||
* `--config <file>` JSON file to use for storing configuration for the controller.
|
||||
Defaults to `config-controller.json` and will be created if it does not exist.
|
||||
See the `config` command for inspecting and modifying the configuration.
|
||||
|
||||
|
||||
### `plugin <command>`
|
||||
|
||||
Configure plugins available to be loaded by the master server.
|
||||
Configure plugins available to be loaded by the controller.
|
||||
The available plugins will be loaded unless they have been disabled in the configuration, see the config command for disabling plugins.
|
||||
|
||||
|
||||
@@ -33,9 +33,9 @@ A relative path must start with ./ or ../ (or .\ and ..\ on Windows) otherwise i
|
||||
For example, installing the Subspace Storage plugin:
|
||||
|
||||
npm install @clusterio/plugin-subspace_storage
|
||||
npx clusteriomaster plugin add @clusterio/plugin-subspace_storage
|
||||
npx clusteriocontroller plugin add @clusterio/plugin-subspace_storage
|
||||
|
||||
Since the `plugin-list.json` is shared between master, slave and ctl you usually only need to do this once per machine.
|
||||
Since the `plugin-list.json` is shared between controller, slave and ctl you usually only need to do this once per machine.
|
||||
|
||||
|
||||
#### `plugin remove <name>`
|
||||
@@ -46,10 +46,10 @@ Removing and unistalling a plugin is usually not neccessary as the functions pro
|
||||
|
||||
For example, uninstalling the Subspace Storage plugin:
|
||||
|
||||
npx clusteriomaster plugin remove subspace_storage
|
||||
npx clusteriocontroller plugin remove subspace_storage
|
||||
npm uninstall @clusterio/plugin-subspace_storage
|
||||
|
||||
Since the `plugin-list.json` is shared between master, slave and ctl you usually only need to do this once per machine.
|
||||
Since the `plugin-list.json` is shared between controller, slave and ctl you usually only need to do this once per machine.
|
||||
|
||||
|
||||
#### `plugin list`
|
||||
@@ -59,8 +59,8 @@ Lists the plugins set up to be available by name followed by path.
|
||||
|
||||
### `config`
|
||||
|
||||
Manage the master server configuration offline.
|
||||
This should only be used when the master server is stopped, otherwise the config read might be out of date and config changes will be overwritten when the master server shuts down.
|
||||
Manage the controller configuration offline.
|
||||
This should only be used when the controller is stopped, otherwise the config read might be out of date and config changes will be overwritten when the controller shuts down.
|
||||
|
||||
|
||||
#### `config set <config-entry> [value]`
|
||||
@@ -96,7 +96,7 @@ You should be using your own Factorio multiplayer username here.
|
||||
#### `bootstrap generate-user-token <name>`
|
||||
|
||||
Generate an access token for logging in and possibly managing the cluster from the given user account.
|
||||
This can be used both in the web interface and as the `master_token` to connect with clusterioctl.
|
||||
This can be used both in the web interface and as the `controller_token` to connect with clusterioctl.
|
||||
|
||||
|
||||
#### `bootstrap create-ctl-config <name>`
|
||||
@@ -106,7 +106,7 @@ Creates a clusterioctl config for the given user with url and token set up for c
|
||||
|
||||
### `run`
|
||||
|
||||
Runs the master server.
|
||||
Runs the controller.
|
||||
|
||||
|
||||
## See Also
|
||||
@@ -1,17 +1,17 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Clusterio master server
|
||||
* Clusterio controller
|
||||
*
|
||||
* Facilitates communication between slaves and control of the cluster
|
||||
* through WebSocet connections, and hosts a webserver for browser
|
||||
* interfaces and Prometheus statistics export. It is remotely controlled
|
||||
* by {@link module:ctl/ctl}.
|
||||
*
|
||||
* @module master/master
|
||||
* @module controller/controller
|
||||
* @author Danielv123, Hornwitser
|
||||
* @example
|
||||
* npx clusteriomaster run
|
||||
* npx clusteriocontroller run
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
@@ -36,25 +36,25 @@ const libSharedCommands = require("@clusterio/lib/shared_commands");
|
||||
const { ConsoleTransport, levels, logger } = require("@clusterio/lib/logging");
|
||||
const libLoggingUtils = require("@clusterio/lib/logging_utils");
|
||||
|
||||
const Master = require("./src/Master");
|
||||
const Controller = require("./src/Controller");
|
||||
const UserManager = require("./src/UserManager");
|
||||
const version = require("./package").version;
|
||||
|
||||
// globals
|
||||
let master;
|
||||
let controller;
|
||||
|
||||
|
||||
void new libPrometheus.Gauge(
|
||||
"clusterio_master_slave_mapping",
|
||||
"clusterio_controller_slave_mapping",
|
||||
"Mapping of Slave ID to name",
|
||||
{
|
||||
labels: ["slave_id", "slave_name"],
|
||||
callback: function(gauge) {
|
||||
gauge.clear();
|
||||
if (!master || !master.slaves) {
|
||||
if (!controller || !controller.slaves) {
|
||||
return;
|
||||
}
|
||||
for (let [id, slave] of master.slaves) {
|
||||
for (let [id, slave] of controller.slaves) {
|
||||
gauge.labels({
|
||||
slave_id: String(id),
|
||||
slave_name: slave.name,
|
||||
@@ -65,16 +65,16 @@ void new libPrometheus.Gauge(
|
||||
);
|
||||
|
||||
void new libPrometheus.Gauge(
|
||||
"clusterio_master_instance_mapping",
|
||||
"clusterio_controller_instance_mapping",
|
||||
"Mapping of Instance ID to name and slave",
|
||||
{
|
||||
labels: ["instance_id", "instance_name", "slave_id"],
|
||||
callback: function(gauge) {
|
||||
gauge.clear();
|
||||
if (!master || !master.instances) {
|
||||
if (!controller || !controller.instances) {
|
||||
return;
|
||||
}
|
||||
for (let [id, instance] of master.instances) {
|
||||
for (let [id, instance] of controller.instances) {
|
||||
gauge.labels({
|
||||
instance_id: String(id),
|
||||
instance_name: String(instance.config.get("instance.name")),
|
||||
@@ -86,32 +86,32 @@ void new libPrometheus.Gauge(
|
||||
);
|
||||
|
||||
void new libPrometheus.Gauge(
|
||||
"clusterio_master_websocket_active_connections",
|
||||
"How many WebSocket connections are currently open to the master server",
|
||||
{ callback: function(gauge) { gauge.set(master.wsServer.activeConnectors.size); }}
|
||||
"clusterio_controller_websocket_active_connections",
|
||||
"How many WebSocket connections are currently open to the controller",
|
||||
{ callback: function(gauge) { gauge.set(controller.wsServer.activeConnectors.size); }}
|
||||
);
|
||||
|
||||
void new libPrometheus.Gauge(
|
||||
"clusterio_master_active_slaves",
|
||||
"How many slaves are currently connected to the master",
|
||||
{ callback: function(gauge) { gauge.set(master.wsServer.slaveConnections.size); }}
|
||||
"clusterio_controller_active_slaves",
|
||||
"How many slaves are currently connected to the controller",
|
||||
{ callback: function(gauge) { gauge.set(controller.wsServer.slaveConnections.size); }}
|
||||
);
|
||||
|
||||
void new libPrometheus.Gauge(
|
||||
"clusterio_master_connected_clients_count", "How many clients are currently connected to this master server",
|
||||
"clusterio_controller_connected_clients_count", "How many clients are currently connected to this controller",
|
||||
{
|
||||
labels: ["type"], callback: async function(gauge) {
|
||||
gauge.labels("slave").set(master.wsServer.slaveConnections.size);
|
||||
gauge.labels("control").set(master.wsServer.controlConnections.length);
|
||||
gauge.labels("slave").set(controller.wsServer.slaveConnections.size);
|
||||
gauge.labels("control").set(controller.wsServer.controlConnections.length);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
async function handleBootstrapCommand(args, masterConfig) {
|
||||
async function handleBootstrapCommand(args, controllerConfig) {
|
||||
let subCommand = args._[1];
|
||||
let userManager = new UserManager(masterConfig);
|
||||
await userManager.load(path.join(masterConfig.get("master.database_directory"), "users.json"));
|
||||
let userManager = new UserManager(controllerConfig);
|
||||
await userManager.load(path.join(controllerConfig.get("controller.database_directory"), "users.json"));
|
||||
if (subCommand === "create-admin") {
|
||||
if (!args.name) {
|
||||
logger.error("name cannot be blank");
|
||||
@@ -127,7 +127,7 @@ async function handleBootstrapCommand(args, masterConfig) {
|
||||
let adminRole = libUsers.ensureDefaultAdminRole(userManager.roles);
|
||||
admin.roles.add(adminRole);
|
||||
admin.isAdmin = true;
|
||||
await userManager.save(path.join(masterConfig.get("master.database_directory"), "users.json"));
|
||||
await userManager.save(path.join(controllerConfig.get("controller.database_directory"), "users.json"));
|
||||
|
||||
} else if (subCommand === "generate-user-token") {
|
||||
let user = userManager.users.get(args.name);
|
||||
@@ -137,13 +137,13 @@ async function handleBootstrapCommand(args, masterConfig) {
|
||||
return;
|
||||
}
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(user.createToken(masterConfig.get("master.auth_secret")));
|
||||
console.log(user.createToken(controllerConfig.get("controller.auth_secret")));
|
||||
|
||||
} else if (subCommand === "generate-slave-token") {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(jwt.sign(
|
||||
{ aud: "slave", slave: args.id },
|
||||
Buffer.from(masterConfig.get("master.auth_secret"), "base64")
|
||||
Buffer.from(controllerConfig.get("controller.auth_secret"), "base64")
|
||||
));
|
||||
|
||||
} else if (subCommand === "create-ctl-config") {
|
||||
@@ -156,8 +156,10 @@ async function handleBootstrapCommand(args, masterConfig) {
|
||||
let controlConfig = new libConfig.ControlConfig("control");
|
||||
await controlConfig.init();
|
||||
|
||||
controlConfig.set("control.master_url", Master.calculateMasterUrl(masterConfig));
|
||||
controlConfig.set("control.master_token", admin.createToken(masterConfig.get("master.auth_secret")));
|
||||
controlConfig.set("control.controller_url", Controller.calculateControllerUrl(controllerConfig));
|
||||
controlConfig.set(
|
||||
"control.controller_token", admin.createToken(controllerConfig.get("controller.auth_secret"))
|
||||
);
|
||||
|
||||
let content = JSON.stringify(controlConfig.serialize(), null, 4);
|
||||
if (args.output === "-") {
|
||||
@@ -176,13 +178,13 @@ async function initialize() {
|
||||
shouldRun: false,
|
||||
clusterLogger: null,
|
||||
pluginInfos: null,
|
||||
masterConfigPath: null,
|
||||
masterConfig: null,
|
||||
controllerConfigPath: null,
|
||||
controllerConfig: null,
|
||||
};
|
||||
|
||||
// argument parsing
|
||||
parameters.args = yargs
|
||||
.scriptName("master")
|
||||
.scriptName("controller")
|
||||
.usage("$0 <command> [options]")
|
||||
.option("log-level", {
|
||||
nargs: 1,
|
||||
@@ -199,8 +201,8 @@ async function initialize() {
|
||||
})
|
||||
.option("config", {
|
||||
nargs: 1,
|
||||
describe: "master config file to use",
|
||||
default: "config-master.json",
|
||||
describe: "controller config file to use",
|
||||
default: "config-controller.json",
|
||||
type: "string",
|
||||
})
|
||||
.option("plugin-list", {
|
||||
@@ -210,7 +212,7 @@ async function initialize() {
|
||||
type: "string",
|
||||
})
|
||||
.command("plugin", "Manage available plugins", libSharedCommands.pluginCommand)
|
||||
.command("config", "Manage Master config", libSharedCommands.configCommand)
|
||||
.command("config", "Manage Controller config", libSharedCommands.configCommand)
|
||||
.command("bootstrap", "Bootstrap access to cluster", yargs => {
|
||||
yargs
|
||||
.command("create-admin <name>", "Create a cluster admin")
|
||||
@@ -226,7 +228,7 @@ async function initialize() {
|
||||
})
|
||||
.demandCommand(1, "You need to specify a command to run");
|
||||
})
|
||||
.command("run", "Run master server", yargs => {
|
||||
.command("run", "Run controller", yargs => {
|
||||
yargs.option("dev", { hidden: true, type: "boolean", nargs: 0 });
|
||||
yargs.option("dev-plugin", { hidden: true, type: "array" });
|
||||
})
|
||||
@@ -247,12 +249,12 @@ async function initialize() {
|
||||
console.log("Migration complete, you should delete cluster.log now");
|
||||
}
|
||||
|
||||
let masterLogDirectory = path.join(parameters.args.logDirectory, "master");
|
||||
if (!await fs.pathExists(masterLogDirectory) && await fs.pathExists("master.log")) {
|
||||
console.log("Migrating master log...");
|
||||
await fs.ensureDir(masterLogDirectory);
|
||||
await libLoggingUtils.migrateLogs("master.log", masterLogDirectory, "master-%DATE%.log");
|
||||
console.log("Migration complete, you should delete master.log now");
|
||||
let controllerLogDirectory = path.join(parameters.args.logDirectory, "controller");
|
||||
if (!await fs.pathExists(controllerLogDirectory) && await fs.pathExists("controller.log")) {
|
||||
console.log("Migrating controller log...");
|
||||
await fs.ensureDir(controllerLogDirectory);
|
||||
await libLoggingUtils.migrateLogs("controller.log", controllerLogDirectory, "controller-%DATE%.log");
|
||||
console.log("Migration complete, you should delete controller.log now");
|
||||
}
|
||||
/* eslint-enable no-console */
|
||||
}
|
||||
@@ -269,11 +271,11 @@ async function initialize() {
|
||||
dirname: path.join(parameters.args.logDirectory, "cluster"),
|
||||
}));
|
||||
|
||||
// Log stream for the master server.
|
||||
// Log stream for the controller.
|
||||
logger.add(new winston.transports.DailyRotateFile({
|
||||
format: winston.format.json(),
|
||||
filename: "master-%DATE%.log",
|
||||
dirname: path.join(parameters.args.logDirectory, "master"),
|
||||
filename: "controller-%DATE%.log",
|
||||
dirname: path.join(parameters.args.logDirectory, "controller"),
|
||||
}));
|
||||
logger.add(new winston.transports.Stream({
|
||||
stream: parameters.clusterLogger,
|
||||
@@ -288,7 +290,7 @@ async function initialize() {
|
||||
|
||||
let command = parameters.args._[0];
|
||||
if (command === "run") {
|
||||
logger.info(`Starting Clusterio master ${version}`);
|
||||
logger.info(`Starting Clusterio controller ${version}`);
|
||||
parameters.shouldRun = true;
|
||||
}
|
||||
|
||||
@@ -313,39 +315,39 @@ async function initialize() {
|
||||
libConfig.registerPluginConfigGroups(parameters.pluginInfos);
|
||||
libConfig.finalizeConfigs();
|
||||
|
||||
parameters.masterConfigPath = parameters.args.config;
|
||||
logger.info(`Loading config from ${parameters.masterConfigPath}`);
|
||||
parameters.masterConfig = new libConfig.MasterConfig("master");
|
||||
parameters.controllerConfigPath = parameters.args.config;
|
||||
logger.info(`Loading config from ${parameters.controllerConfigPath}`);
|
||||
parameters.controllerConfig = new libConfig.ControllerConfig("controller");
|
||||
try {
|
||||
await parameters.masterConfig.load(JSON.parse(await fs.readFile(parameters.masterConfigPath)));
|
||||
await parameters.controllerConfig.load(JSON.parse(await fs.readFile(parameters.controllerConfigPath)));
|
||||
|
||||
} catch (err) {
|
||||
if (err.code === "ENOENT") {
|
||||
logger.info("Config not found, initializing new config");
|
||||
await parameters.masterConfig.init();
|
||||
await parameters.controllerConfig.init();
|
||||
|
||||
} else {
|
||||
throw new libErrors.StartupError(`Failed to load ${parameters.masterConfigPath}: ${err.message}`);
|
||||
throw new libErrors.StartupError(`Failed to load ${parameters.controllerConfigPath}: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!parameters.masterConfig.get("master.auth_secret")) {
|
||||
logger.info("Generating new master authentication secret");
|
||||
if (!parameters.controllerConfig.get("controller.auth_secret")) {
|
||||
logger.info("Generating new controller authentication secret");
|
||||
let asyncRandomBytes = util.promisify(crypto.randomBytes);
|
||||
let bytes = await asyncRandomBytes(256);
|
||||
parameters.masterConfig.set("master.auth_secret", bytes.toString("base64"));
|
||||
parameters.controllerConfig.set("controller.auth_secret", bytes.toString("base64"));
|
||||
await libFileOps.safeOutputFile(
|
||||
parameters.masterConfigPath, JSON.stringify(parameters.masterConfig.serialize(), null, 4)
|
||||
parameters.controllerConfigPath, JSON.stringify(parameters.controllerConfig.serialize(), null, 4)
|
||||
);
|
||||
}
|
||||
|
||||
if (command === "config") {
|
||||
await libSharedCommands.handleConfigCommand(
|
||||
parameters.args, parameters.masterConfig, parameters.masterConfigPath
|
||||
parameters.args, parameters.controllerConfig, parameters.controllerConfigPath
|
||||
);
|
||||
|
||||
} else if (command === "bootstrap") {
|
||||
await handleBootstrapCommand(parameters.args, parameters.masterConfig);
|
||||
await handleBootstrapCommand(parameters.args, parameters.controllerConfig);
|
||||
}
|
||||
|
||||
return parameters;
|
||||
@@ -354,14 +356,14 @@ async function initialize() {
|
||||
async function startup() {
|
||||
// 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.
|
||||
process.title = "clusterioMaster";
|
||||
process.title = "clusterioController";
|
||||
|
||||
let { args, shouldRun, clusterLogger, pluginInfos, masterConfigPath, masterConfig } = await initialize();
|
||||
let { args, shouldRun, clusterLogger, pluginInfos, controllerConfigPath, controllerConfig } = await initialize();
|
||||
if (!shouldRun) {
|
||||
return;
|
||||
}
|
||||
|
||||
master = new Master(clusterLogger, pluginInfos, masterConfigPath, masterConfig);
|
||||
controller = new Controller(clusterLogger, pluginInfos, controllerConfigPath, controllerConfig);
|
||||
|
||||
let secondSigint = false;
|
||||
process.on("SIGINT", () => {
|
||||
@@ -374,7 +376,7 @@ async function startup() {
|
||||
|
||||
secondSigint = true;
|
||||
logger.info("Caught interrupt signal, shutting down");
|
||||
master.stop();
|
||||
controller.stop();
|
||||
});
|
||||
let secondSigterm = false;
|
||||
process.on("SIGTERM", () => {
|
||||
@@ -387,15 +389,15 @@ async function startup() {
|
||||
|
||||
secondSigterm = true;
|
||||
logger.info("Caught termination signal, shutting down");
|
||||
master.stop();
|
||||
controller.stop();
|
||||
});
|
||||
|
||||
process.on("SIGHUP", () => {
|
||||
logger.info("Terminal closed, shutting down");
|
||||
master.stop();
|
||||
controller.stop();
|
||||
});
|
||||
|
||||
await master.start(args);
|
||||
await controller.start(args);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
@@ -413,9 +415,9 @@ I version of clusterio. Expect things to break. I
|
||||
startup().catch(err => {
|
||||
if (err instanceof libErrors.StartupError) {
|
||||
logger.fatal(`
|
||||
+----------------------------------+
|
||||
| Unable to to start master server |
|
||||
+----------------------------------+
|
||||
+-------------------------------+
|
||||
| Unable to to start controller |
|
||||
+-------------------------------+
|
||||
${err.stack}`
|
||||
);
|
||||
} else if (err instanceof libErrors.PluginError) {
|
||||
@@ -428,7 +430,7 @@ ${err.original.stack}`
|
||||
} else {
|
||||
logger.fatal(`
|
||||
+------------------------------------------------------------+
|
||||
| Unexpected error occured while starting master, please |
|
||||
| Unexpected error occured while starting controller, please |
|
||||
| report it to https://github.com/clusterio/clusterio/issues |
|
||||
+------------------------------------------------------------+
|
||||
${err.stack}`
|
||||
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"name": "@clusterio/master",
|
||||
"description": "Implementation of Clusterio master server",
|
||||
"name": "@clusterio/controller",
|
||||
"description": "Implementation of Clusterio controller",
|
||||
"author": "Hornwitser",
|
||||
"license": "MIT",
|
||||
"version": "2.0.0-alpha.13",
|
||||
"main": "master.js",
|
||||
"main": "controller.js",
|
||||
"scripts": {
|
||||
"prepare": "webpack-cli --env production",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"bin": {
|
||||
"clusteriomaster": "./master.js"
|
||||
"clusteriocontroller": "./controller.js"
|
||||
},
|
||||
"keywords": [
|
||||
"factorio"
|
||||
@@ -6,35 +6,35 @@ const libPlugin = require("@clusterio/lib/plugin");
|
||||
|
||||
|
||||
/**
|
||||
* Base class for master server connections
|
||||
* Base class for controller connections
|
||||
*
|
||||
* @extends module:lib/link.Link
|
||||
* @alias module:master/src/BaseConnection
|
||||
* @alias module:controller/src/BaseConnection
|
||||
*/
|
||||
class BaseConnection extends libLink.Link {
|
||||
constructor(target, connector, master) {
|
||||
/** @member {module:master/src/WsServerConnector} module:master/src/BaseConnection#connector */
|
||||
super("master", target, connector);
|
||||
this._master = master;
|
||||
constructor(target, connector, controller) {
|
||||
/** @member {module:controller/src/WsServerConnector} module:controller/src/BaseConnection#connector */
|
||||
super("controller", target, connector);
|
||||
this._controller = controller;
|
||||
this._disconnecting = false;
|
||||
libLink.attachAllMessages(this);
|
||||
for (let masterPlugin of this._master.plugins.values()) {
|
||||
libPlugin.attachPluginMessages(this, masterPlugin);
|
||||
for (let controllerPlugin of this._controller.plugins.values()) {
|
||||
libPlugin.attachPluginMessages(this, controllerPlugin);
|
||||
}
|
||||
}
|
||||
|
||||
async forwardRequestToInstance(message, request) {
|
||||
return await this._master.forwardRequestToInstance(request, message.data);
|
||||
return await this._controller.forwardRequestToInstance(request, message.data);
|
||||
}
|
||||
|
||||
async forwardEventToInstance(message, event) {
|
||||
let instance = this._master.instances.get(message.data.instance_id);
|
||||
let instance = this._controller.instances.get(message.data.instance_id);
|
||||
if (!instance) { return; }
|
||||
|
||||
let slaveId = instance.config.get("instance.assigned_slave");
|
||||
if (slaveId === null) { return; }
|
||||
|
||||
let connection = this._master.wsServer.slaveConnections.get(slaveId);
|
||||
let connection = this._controller.wsServer.slaveConnections.get(slaveId);
|
||||
if (!connection || connection.closing) { return; }
|
||||
if (event.plugin && !connection.plugins.has(event.plugin)) { return; }
|
||||
|
||||
@@ -42,7 +42,7 @@ class BaseConnection extends libLink.Link {
|
||||
}
|
||||
|
||||
async broadcastEventToSlaves(message, event) {
|
||||
for (let slaveConnection of this._master.wsServer.slaveConnections.values()) {
|
||||
for (let slaveConnection of this._controller.wsServer.slaveConnections.values()) {
|
||||
// Do not broadcast back to the source
|
||||
if (slaveConnection === this) { continue; }
|
||||
if (slaveConnection.connector.closing) { continue; }
|
||||
@@ -57,7 +57,7 @@ class BaseConnection extends libLink.Link {
|
||||
}
|
||||
|
||||
async prepareDisconnectRequestHandler(message, request) {
|
||||
await libPlugin.invokeHook(this._master.plugins, "onPrepareSlaveDisconnect", this);
|
||||
await libPlugin.invokeHook(this._controller.plugins, "onPrepareSlaveDisconnect", this);
|
||||
this._disconnecting = true;
|
||||
this.connector.setClosing();
|
||||
return await super.prepareDisconnectRequestHandler(message, request);
|
||||
@@ -87,7 +87,7 @@ class BaseConnection extends libLink.Link {
|
||||
|
||||
async getModPackRequestHandler(message) {
|
||||
let { id } = message.data;
|
||||
let modPack = this._master.modPacks.get(id);
|
||||
let modPack = this._controller.modPacks.get(id);
|
||||
if (!modPack) {
|
||||
throw new libErrors.RequestError(`Mod pack with ID ${id} does not exist`);
|
||||
}
|
||||
@@ -95,11 +95,11 @@ class BaseConnection extends libLink.Link {
|
||||
}
|
||||
|
||||
async getDefaultModPackRequestHandler(message) {
|
||||
let id = this._master.config.get("master.default_mod_pack_id");
|
||||
let id = this._controller.config.get("controller.default_mod_pack_id");
|
||||
if (id === null) {
|
||||
throw new libErrors.RequestError("Default mod pack not set on master");
|
||||
throw new libErrors.RequestError("Default mod pack not set on controller");
|
||||
}
|
||||
let modPack = this._master.modPacks.get(id);
|
||||
let modPack = this._controller.modPacks.get(id);
|
||||
if (!modPack) {
|
||||
throw new libErrors.RequestError(`Default mod pack configured (${id}) does not exist`);
|
||||
}
|
||||
+115
-115
@@ -21,19 +21,19 @@ const routes = require("./routes");
|
||||
const strcmp = new Intl.Collator(undefined, { numerice: "true", sensitivity: "base" }).compare;
|
||||
|
||||
const queryLogTime = new libPrometheus.Summary(
|
||||
"clusterio_master_query_log_duration_seconds",
|
||||
"clusterio_controller_query_log_duration_seconds",
|
||||
"Time in seconds log queries took to execute."
|
||||
);
|
||||
|
||||
/**
|
||||
* Represents the connection to a control link
|
||||
*
|
||||
* @extends module:master/src/BaseConnection
|
||||
* @alias module:master/src/ControlConnection
|
||||
* @extends module:controller/src/BaseConnection
|
||||
* @alias module:controller/src/ControlConnection
|
||||
*/
|
||||
class ControlConnection extends BaseConnection {
|
||||
constructor(registerData, connector, master, user) {
|
||||
super("control", connector, master);
|
||||
constructor(registerData, connector, controller, user) {
|
||||
super("control", connector, controller);
|
||||
|
||||
this._agent = registerData.agent;
|
||||
this._version = registerData.version;
|
||||
@@ -77,7 +77,7 @@ class ControlConnection extends BaseConnection {
|
||||
this.logTransport = null;
|
||||
this.logSubscriptions = {
|
||||
all: false,
|
||||
master: false,
|
||||
controller: false,
|
||||
slave_ids: [],
|
||||
instance_ids: [],
|
||||
};
|
||||
@@ -92,42 +92,42 @@ class ControlConnection extends BaseConnection {
|
||||
logger.remove(this.logTransport);
|
||||
}
|
||||
if (this.ws_dumper) {
|
||||
this._master.debugEvents.off("message", this.ws_dumper);
|
||||
this._controller.debugEvents.off("message", this.ws_dumper);
|
||||
}
|
||||
});
|
||||
|
||||
for (let event of ["connect", "drop", "resume", "close"]) {
|
||||
this.connector.on(event, () => {
|
||||
for (let masterPlugin of this._master.plugins.values()) {
|
||||
masterPlugin.onControlConnectionEvent(this, event);
|
||||
for (let controllerPlugin of this._controller.plugins.values()) {
|
||||
controllerPlugin.onControlConnectionEvent(this, event);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async getMasterConfigRequestHandler() {
|
||||
return { serialized_config: this._master.config.serialize("control") };
|
||||
async getControllerConfigRequestHandler() {
|
||||
return { serialized_config: this._controller.config.serialize("control") };
|
||||
}
|
||||
|
||||
async setMasterConfigFieldRequestHandler(message) {
|
||||
this._master.config.set(message.data.field, message.data.value, "control");
|
||||
async setControllerConfigFieldRequestHandler(message) {
|
||||
this._controller.config.set(message.data.field, message.data.value, "control");
|
||||
}
|
||||
|
||||
async setMasterConfigPropRequestHandler(message) {
|
||||
async setControllerConfigPropRequestHandler(message) {
|
||||
let { field, prop, value } = message.data;
|
||||
this._master.config.setProp(field, prop, value, "control");
|
||||
this._controller.config.setProp(field, prop, value, "control");
|
||||
}
|
||||
|
||||
async listSlavesRequestHandler(message) {
|
||||
let list = [];
|
||||
for (let slave of this._master.slaves.values()) {
|
||||
for (let slave of this._controller.slaves.values()) {
|
||||
list.push({
|
||||
agent: slave.agent,
|
||||
version: slave.version,
|
||||
id: slave.id,
|
||||
name: slave.name,
|
||||
public_address: slave.public_address || null,
|
||||
connected: this._master.wsServer.slaveConnections.has(slave.id),
|
||||
connected: this._controller.wsServer.slaveConnections.has(slave.id),
|
||||
});
|
||||
}
|
||||
return { list };
|
||||
@@ -149,7 +149,7 @@ class ControlConnection extends BaseConnection {
|
||||
generateSlaveToken(slaveId) {
|
||||
return jwt.sign(
|
||||
{ aud: "slave", slave: slaveId },
|
||||
Buffer.from(this._master.config.get("master.auth_secret"), "base64")
|
||||
Buffer.from(this._controller.config.get("controller.auth_secret"), "base64")
|
||||
);
|
||||
}
|
||||
|
||||
@@ -165,7 +165,7 @@ class ControlConnection extends BaseConnection {
|
||||
let slaveConfig = new libConfig.SlaveConfig("control");
|
||||
await slaveConfig.init();
|
||||
|
||||
slaveConfig.set("slave.master_url", this._master.getMasterUrl());
|
||||
slaveConfig.set("slave.controller_url", this._controller.getControllerUrl());
|
||||
if (message.data.id !== null) {
|
||||
slaveConfig.set("slave.id", message.data.id);
|
||||
}
|
||||
@@ -174,14 +174,14 @@ class ControlConnection extends BaseConnection {
|
||||
}
|
||||
if (message.data.generate_token) {
|
||||
this.user.checkPermission("core.slave.generate_token");
|
||||
slaveConfig.set("slave.master_token", this.generateSlaveToken(slaveConfig.get("slave.id")));
|
||||
slaveConfig.set("slave.controller_token", this.generateSlaveToken(slaveConfig.get("slave.id")));
|
||||
}
|
||||
return { serialized_config: slaveConfig.serialize() };
|
||||
}
|
||||
|
||||
async getInstanceRequestHandler(message) {
|
||||
let id = message.data.id;
|
||||
let instance = this._master.getRequestInstance(id);
|
||||
let instance = this._controller.getRequestInstance(id);
|
||||
|
||||
return {
|
||||
id,
|
||||
@@ -194,7 +194,7 @@ class ControlConnection extends BaseConnection {
|
||||
|
||||
async listInstancesRequestHandler(message) {
|
||||
let list = [];
|
||||
for (let instance of this._master.instances.values()) {
|
||||
for (let instance of this._controller.instances.values()) {
|
||||
list.push({
|
||||
id: instance.config.get("instance.id"),
|
||||
name: instance.config.get("instance.name"),
|
||||
@@ -227,18 +227,18 @@ class ControlConnection extends BaseConnection {
|
||||
|
||||
// XXX should probably add a hook for slave reuqests?
|
||||
async createInstanceRequestHandler(message) {
|
||||
let instanceConfig = new libConfig.InstanceConfig("master");
|
||||
let instanceConfig = new libConfig.InstanceConfig("controller");
|
||||
await instanceConfig.load(message.data.serialized_config);
|
||||
|
||||
let instanceId = instanceConfig.get("instance.id");
|
||||
if (this._master.instances.has(instanceId)) {
|
||||
if (this._controller.instances.has(instanceId)) {
|
||||
throw new libErrors.RequestError(`Instance with ID ${instanceId} already exists`);
|
||||
}
|
||||
|
||||
// Add common settings for the Factorio server
|
||||
let settings = {
|
||||
"name": `${this._master.config.get("master.name")} - ${instanceConfig.get("instance.name")}`,
|
||||
"description": `Clusterio instance for ${this._master.config.get("master.name")}`,
|
||||
"name": `${this._controller.config.get("controller.name")} - ${instanceConfig.get("instance.name")}`,
|
||||
"description": `Clusterio instance for ${this._controller.config.get("controller.name")}`,
|
||||
"tags": ["clusterio"],
|
||||
"max_players": 0,
|
||||
"visibility": { "public": true, "lan": true },
|
||||
@@ -262,26 +262,26 @@ class ControlConnection extends BaseConnection {
|
||||
instanceConfig.set("factorio.settings", settings);
|
||||
|
||||
let instance = { config: instanceConfig, status: "unassigned" };
|
||||
this._master.instances.set(instanceId, instance);
|
||||
await libPlugin.invokeHook(this._master.plugins, "onInstanceStatusChanged", instance, null);
|
||||
this._master.addInstanceHooks(instance);
|
||||
this._controller.instances.set(instanceId, instance);
|
||||
await libPlugin.invokeHook(this._controller.plugins, "onInstanceStatusChanged", instance, null);
|
||||
this._controller.addInstanceHooks(instance);
|
||||
}
|
||||
|
||||
async deleteInstanceRequestHandler(message, request) {
|
||||
let instance = this._master.getRequestInstance(message.data.instance_id);
|
||||
let instance = this._controller.getRequestInstance(message.data.instance_id);
|
||||
if (instance.config.get("instance.assigned_slave") !== null) {
|
||||
await this.forwardRequestToInstance(message, request);
|
||||
}
|
||||
this._master.instances.delete(message.data.instance_id);
|
||||
this._controller.instances.delete(message.data.instance_id);
|
||||
|
||||
let prev = instance.status;
|
||||
instance.status = "deleted";
|
||||
this._master.instanceUpdated(instance);
|
||||
await libPlugin.invokeHook(this._master.plugins, "onInstanceStatusChanged", instance, prev);
|
||||
this._controller.instanceUpdated(instance);
|
||||
await libPlugin.invokeHook(this._controller.plugins, "onInstanceStatusChanged", instance, prev);
|
||||
}
|
||||
|
||||
async getInstanceConfigRequestHandler(message) {
|
||||
let instance = this._master.getRequestInstance(message.data.instance_id);
|
||||
let instance = this._controller.getRequestInstance(message.data.instance_id);
|
||||
return {
|
||||
serialized_config: instance.config.serialize("control"),
|
||||
};
|
||||
@@ -290,7 +290,7 @@ class ControlConnection extends BaseConnection {
|
||||
async updateInstanceConfig(instance) {
|
||||
let slaveId = instance.config.get("instance.assigned_slave");
|
||||
if (slaveId !== null) {
|
||||
let connection = this._master.wsServer.slaveConnections.get(slaveId);
|
||||
let connection = this._controller.wsServer.slaveConnections.get(slaveId);
|
||||
if (connection) {
|
||||
await libLink.messages.assignInstance.send(connection, {
|
||||
instance_id: instance.config.get("instance.id"),
|
||||
@@ -301,7 +301,7 @@ class ControlConnection extends BaseConnection {
|
||||
}
|
||||
|
||||
async setInstanceConfigFieldRequestHandler(message) {
|
||||
let instance = this._master.getRequestInstance(message.data.instance_id);
|
||||
let instance = this._controller.getRequestInstance(message.data.instance_id);
|
||||
if (message.data.field === "instance.assigned_slave") {
|
||||
throw new libErrors.RequestError("instance.assigned_slave must be set through the assign-slave interface");
|
||||
}
|
||||
@@ -316,7 +316,7 @@ class ControlConnection extends BaseConnection {
|
||||
}
|
||||
|
||||
async setInstanceConfigPropRequestHandler(message) {
|
||||
let instance = this._master.getRequestInstance(message.data.instance_id);
|
||||
let instance = this._controller.getRequestInstance(message.data.instance_id);
|
||||
let { field, prop, value } = message.data;
|
||||
instance.config.setProp(field, prop, value, "control");
|
||||
await this.updateInstanceConfig(instance);
|
||||
@@ -324,26 +324,26 @@ class ControlConnection extends BaseConnection {
|
||||
|
||||
async assignInstanceCommandRequestHandler(message, request) {
|
||||
let { slave_id, instance_id } = message.data;
|
||||
let instance = this._master.getRequestInstance(instance_id);
|
||||
let instance = this._controller.getRequestInstance(instance_id);
|
||||
|
||||
// Check if target slave is connected
|
||||
let newSlaveConnection;
|
||||
if (slave_id !== null) {
|
||||
newSlaveConnection = this._master.wsServer.slaveConnections.get(slave_id);
|
||||
newSlaveConnection = this._controller.wsServer.slaveConnections.get(slave_id);
|
||||
if (!newSlaveConnection) {
|
||||
// The case of the slave not getting the assign instance message
|
||||
// still have to be handled, so it's not a requirement that the
|
||||
// target slave be connected to the master while doing the
|
||||
// target slave be connected to the controller while doing the
|
||||
// assignment, but it is IMHO a better user experience if this
|
||||
// is the case.
|
||||
throw new libErrors.RequestError("Target slave is not connected to the master server");
|
||||
throw new libErrors.RequestError("Target slave is not connected to the controller");
|
||||
}
|
||||
}
|
||||
|
||||
// Unassign from currently assigned slave if it is connected.
|
||||
let currentAssignedSlave = instance.config.get("instance.assigned_slave");
|
||||
if (currentAssignedSlave !== null && slave_id !== currentAssignedSlave) {
|
||||
let oldSlaveConnection = this._master.wsServer.slaveConnections.get(currentAssignedSlave);
|
||||
let oldSlaveConnection = this._controller.wsServer.slaveConnections.get(currentAssignedSlave);
|
||||
if (oldSlaveConnection && !oldSlaveConnection.connector.closing) {
|
||||
await libLink.messages.unassignInstance.send(oldSlaveConnection, { instance_id });
|
||||
}
|
||||
@@ -358,7 +358,7 @@ class ControlConnection extends BaseConnection {
|
||||
});
|
||||
} else {
|
||||
instance.status = "unassigned";
|
||||
this._master.instanceUpdated(instance);
|
||||
this._controller.instanceUpdated(instance);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,7 +377,7 @@ class ControlConnection extends BaseConnection {
|
||||
|
||||
async downloadSaveRequestHandler(message) {
|
||||
let { instance_id, save } = message.data;
|
||||
let stream = await routes.createProxyStream(this._master.app);
|
||||
let stream = await routes.createProxyStream(this._controller.app);
|
||||
stream.filename = save;
|
||||
|
||||
let ready = new Promise((resolve, reject) => {
|
||||
@@ -388,7 +388,7 @@ class ControlConnection extends BaseConnection {
|
||||
});
|
||||
ready.catch(() => {});
|
||||
|
||||
await this._master.forwardRequestToInstance(libLink.messages.pushSave, {
|
||||
await this._controller.forwardRequestToInstance(libLink.messages.pushSave, {
|
||||
instance_id,
|
||||
stream_id: stream.id,
|
||||
save,
|
||||
@@ -409,8 +409,8 @@ class ControlConnection extends BaseConnection {
|
||||
if (instance_id === target_instance_id) {
|
||||
throw new libErrors.RequestError("Source and target instance may not be the same");
|
||||
}
|
||||
let sourceInstance = this._master.getRequestInstance(instance_id);
|
||||
let targetInstance = this._master.getRequestInstance(target_instance_id);
|
||||
let sourceInstance = this._controller.getRequestInstance(instance_id);
|
||||
let targetInstance = this._controller.getRequestInstance(target_instance_id);
|
||||
let sourceSlaveId = sourceInstance.config.get("instance.assigned_slave");
|
||||
let targetSlaveId = targetInstance.config.get("instance.assigned_slave");
|
||||
if (sourceSlaveId === null) {
|
||||
@@ -426,18 +426,18 @@ class ControlConnection extends BaseConnection {
|
||||
}
|
||||
|
||||
// Check connectivity
|
||||
let sourceSlaveConnection = this._master.wsServer.slaveConnections.get(sourceSlaveId);
|
||||
let sourceSlaveConnection = this._controller.wsServer.slaveConnections.get(sourceSlaveId);
|
||||
if (!sourceSlaveConnection || sourceSlaveConnection.closing) {
|
||||
throw new libErrors.RequestError("Source slave is not connected to the master server");
|
||||
throw new libErrors.RequestError("Source slave is not connected to the controller");
|
||||
}
|
||||
|
||||
let targetSlaveConnection = this._master.wsServer.slaveConnections.get(targetSlaveId);
|
||||
let targetSlaveConnection = this._controller.wsServer.slaveConnections.get(targetSlaveId);
|
||||
if (!targetSlaveConnection || targetSlaveConnection.closing) {
|
||||
throw new libErrors.RequestError("Target slave is not connected to the master server");
|
||||
throw new libErrors.RequestError("Target slave is not connected to the controller");
|
||||
}
|
||||
|
||||
// Create stream to proxy from target to source
|
||||
let stream = await routes.createProxyStream(this._master.app);
|
||||
let stream = await routes.createProxyStream(this._controller.app);
|
||||
stream.events.on("timeout", () => {
|
||||
if (stream.source) {
|
||||
stream.source.destroy();
|
||||
@@ -451,7 +451,7 @@ class ControlConnection extends BaseConnection {
|
||||
// Establish push from source slave to stream, this is done first to
|
||||
// ensure the file size is known prior to the target slave pull.
|
||||
await Promise.all([
|
||||
this._master.forwardRequestToInstance(libLink.messages.pushSave, {
|
||||
this._controller.forwardRequestToInstance(libLink.messages.pushSave, {
|
||||
instance_id,
|
||||
stream_id: stream.id,
|
||||
save: source_save,
|
||||
@@ -460,7 +460,7 @@ class ControlConnection extends BaseConnection {
|
||||
]);
|
||||
|
||||
// Establish pull from target slave to stream and wait for completion.
|
||||
let result = await this._master.forwardRequestToInstance(libLink.messages.pullSave, {
|
||||
let result = await this._controller.forwardRequestToInstance(libLink.messages.pullSave, {
|
||||
instance_id: target_instance_id,
|
||||
stream_id: stream.id,
|
||||
filename: target_save,
|
||||
@@ -468,7 +468,7 @@ class ControlConnection extends BaseConnection {
|
||||
|
||||
// Delete source save if this is not a copy
|
||||
if (!copy) {
|
||||
await this._master.forwardRequestToInstance(libLink.messages.deleteSave, {
|
||||
await this._controller.forwardRequestToInstance(libLink.messages.deleteSave, {
|
||||
instance_id,
|
||||
save: source_save,
|
||||
});
|
||||
@@ -478,7 +478,7 @@ class ControlConnection extends BaseConnection {
|
||||
}
|
||||
|
||||
async listModPacksRequestHandler(message) {
|
||||
return { list: [...this._master.modPacks.values()].map(pack => pack.toJSON()) };
|
||||
return { list: [...this._controller.modPacks.values()].map(pack => pack.toJSON()) };
|
||||
}
|
||||
|
||||
async setModPackSubscriptionsRequestHandler(message) {
|
||||
@@ -487,31 +487,31 @@ class ControlConnection extends BaseConnection {
|
||||
|
||||
async createModPackRequestHandler(message) {
|
||||
let modPack = new libData.ModPack(message.data.mod_pack);
|
||||
if (this._master.modPacks.has(modPack.id)) {
|
||||
if (this._controller.modPacks.has(modPack.id)) {
|
||||
throw new libErrors.RequestError(`Mod pack with ID ${modPack.id} already exist`);
|
||||
}
|
||||
this._master.modPacks.set(modPack.id, modPack);
|
||||
this._master.modPackUpdated(modPack);
|
||||
this._controller.modPacks.set(modPack.id, modPack);
|
||||
this._controller.modPackUpdated(modPack);
|
||||
}
|
||||
|
||||
async updateModPackRequestHandler(message) {
|
||||
let modPack = new libData.ModPack(message.data.mod_pack);
|
||||
if (!this._master.modPacks.has(modPack.id)) {
|
||||
if (!this._controller.modPacks.has(modPack.id)) {
|
||||
throw new libErrors.RequestError(`Mod pack with ID ${modPack.id} does not exist`);
|
||||
}
|
||||
this._master.modPacks.set(modPack.id, modPack);
|
||||
this._master.modPackUpdated(modPack);
|
||||
this._controller.modPacks.set(modPack.id, modPack);
|
||||
this._controller.modPackUpdated(modPack);
|
||||
}
|
||||
|
||||
async deleteModPackRequestHandler(message) {
|
||||
let { id } = message.data;
|
||||
let modPack = this._master.modPacks.get(id);
|
||||
let modPack = this._controller.modPacks.get(id);
|
||||
if (!modPack) {
|
||||
throw new libErrors.RequestError(`Mod pack with ID ${id} does not exist`);
|
||||
}
|
||||
modPack.isDeleted = true;
|
||||
this._master.modPacks.delete(id);
|
||||
this._master.modPackUpdated(modPack);
|
||||
this._controller.modPacks.delete(id);
|
||||
this._controller.modPackUpdated(modPack);
|
||||
}
|
||||
|
||||
modPackUpdated(modPack) {
|
||||
@@ -526,7 +526,7 @@ class ControlConnection extends BaseConnection {
|
||||
async getModRequestHandler(message) {
|
||||
let { name, version } = message.data;
|
||||
let filename = `${name}_${version}.zip`;
|
||||
let mod = this._master.mods.get(filename);
|
||||
let mod = this._controller.mods.get(filename);
|
||||
if (!mod) {
|
||||
throw new libErrors.RequestError(`Mod ${filename} does not exist`);
|
||||
}
|
||||
@@ -536,7 +536,7 @@ class ControlConnection extends BaseConnection {
|
||||
}
|
||||
|
||||
async listModsRequestHandler(message) {
|
||||
return { list: [...this._master.mods.values()].map(mod => mod.toJSON()) };
|
||||
return { list: [...this._controller.mods.values()].map(mod => mod.toJSON()) };
|
||||
}
|
||||
|
||||
static termsMatchesMod(terms, mod) {
|
||||
@@ -575,7 +575,7 @@ class ControlConnection extends BaseConnection {
|
||||
let factorioVersion = message.data.factorio_version;
|
||||
|
||||
let results = new Map();
|
||||
for (let mod of this._master.mods.values()) {
|
||||
for (let mod of this._controller.mods.values()) {
|
||||
if (
|
||||
mod.factorioVersion !== factorioVersion
|
||||
|| !ControlConnection.termsMatchesMod(query.terms, mod)
|
||||
@@ -634,13 +634,13 @@ class ControlConnection extends BaseConnection {
|
||||
async downloadModRequestHandler(message) {
|
||||
let { name, version } = message.data;
|
||||
let filename = `${name}_${version}.zip`;
|
||||
let mod = this._master.mods.get(filename);
|
||||
let mod = this._controller.mods.get(filename);
|
||||
if (!mod) {
|
||||
throw new libErrors.RequestError(`Mod ${filename} does not exist`);
|
||||
}
|
||||
let modPath = path.join(this._master.config.get("master.mods_directory"), mod.filename);
|
||||
let modPath = path.join(this._controller.config.get("controller.mods_directory"), mod.filename);
|
||||
|
||||
let stream = await routes.createProxyStream(this._master.app);
|
||||
let stream = await routes.createProxyStream(this._controller.app);
|
||||
stream.filename = mod.filename;
|
||||
stream.source = fs.createReadStream(modPath);
|
||||
stream.mime = "application/zip";
|
||||
@@ -650,7 +650,7 @@ class ControlConnection extends BaseConnection {
|
||||
}
|
||||
|
||||
async deleteModRequestHandler(message) {
|
||||
await this._master.deleteMod(message.data.name, message.data.version);
|
||||
await this._controller.deleteMod(message.data.name, message.data.version);
|
||||
}
|
||||
|
||||
async setLogSubscriptionsRequestHandler(message) {
|
||||
@@ -668,29 +668,29 @@ class ControlConnection extends BaseConnection {
|
||||
}
|
||||
|
||||
updateLogSubscriptions() {
|
||||
let { all, master, slave_ids, instance_ids } = this.logSubscriptions;
|
||||
if (all || master || slave_ids.length || instance_ids.length) {
|
||||
let { all, controller, slave_ids, instance_ids } = this.logSubscriptions;
|
||||
if (all || controller || slave_ids.length || instance_ids.length) {
|
||||
if (!this.logTransport) {
|
||||
this.logTransport = new libLoggingUtils.LinkTransport({ link: this });
|
||||
this._master.clusterLogger.add(this.logTransport);
|
||||
this._controller.clusterLogger.add(this.logTransport);
|
||||
}
|
||||
this.logTransport.filter = logFilter(this.logSubscriptions);
|
||||
|
||||
} else if (this.logTransport) {
|
||||
this._master.clusterLogger.remove(this.logTransport);
|
||||
this._controller.clusterLogger.remove(this.logTransport);
|
||||
this.logTransport = null;
|
||||
}
|
||||
}
|
||||
|
||||
async queryLogRequestHandler(message) {
|
||||
let observeDuration = queryLogTime.startTimer();
|
||||
let { all, master, slave_ids, instance_ids } = message.data;
|
||||
let { all, controller, slave_ids, instance_ids } = message.data;
|
||||
|
||||
let log;
|
||||
if (!all && master && !slave_ids.length && !instance_ids.length) {
|
||||
log = await this._master.queryMasterLog(message.data);
|
||||
if (!all && controller && !slave_ids.length && !instance_ids.length) {
|
||||
log = await this._controller.queryControllerLog(message.data);
|
||||
} else {
|
||||
log = await this._master.queryClusterLog(message.data);
|
||||
log = await this._controller.queryClusterLog(message.data);
|
||||
}
|
||||
|
||||
observeDuration();
|
||||
@@ -711,7 +711,7 @@ class ControlConnection extends BaseConnection {
|
||||
|
||||
async listRolesRequestHandler(message) {
|
||||
let list = [];
|
||||
for (let role of this._master.userManager.roles.values()) {
|
||||
for (let role of this._controller.userManager.roles.values()) {
|
||||
list.push({
|
||||
id: role.id,
|
||||
name: role.name,
|
||||
@@ -723,17 +723,17 @@ class ControlConnection extends BaseConnection {
|
||||
}
|
||||
|
||||
async createRoleRequestHandler(message) {
|
||||
let lastId = Math.max.apply(null, [...this._master.userManager.roles.keys()]);
|
||||
let lastId = Math.max.apply(null, [...this._controller.userManager.roles.keys()]);
|
||||
|
||||
// Start at 5 to leave space for future default roles
|
||||
let id = Math.max(5, lastId+1);
|
||||
this._master.userManager.roles.set(id, new libUsers.Role({ id, ...message.data }));
|
||||
this._controller.userManager.roles.set(id, new libUsers.Role({ id, ...message.data }));
|
||||
return { id };
|
||||
}
|
||||
|
||||
async updateRoleRequestHandler(message) {
|
||||
let { id, name, description, permissions } = message.data;
|
||||
let role = this._master.userManager.roles.get(id);
|
||||
let role = this._controller.userManager.roles.get(id);
|
||||
if (!role) {
|
||||
throw new libErrors.RequestError(`Role with ID ${id} does not exist`);
|
||||
}
|
||||
@@ -741,36 +741,36 @@ class ControlConnection extends BaseConnection {
|
||||
role.name = name;
|
||||
role.description = description;
|
||||
role.permissions = new Set(permissions);
|
||||
this._master.rolePermissionsUpdated(role);
|
||||
this._controller.rolePermissionsUpdated(role);
|
||||
}
|
||||
|
||||
async grantDefaultRolePermissionsRequestHandler(message) {
|
||||
let role = this._master.userManager.roles.get(message.data.id);
|
||||
let role = this._controller.userManager.roles.get(message.data.id);
|
||||
if (!role) {
|
||||
throw new libErrors.RequestError(`Role with ID ${message.data.id} does not exist`);
|
||||
}
|
||||
|
||||
role.grantDefaultPermissions();
|
||||
this._master.rolePermissionsUpdated(role);
|
||||
this._controller.rolePermissionsUpdated(role);
|
||||
}
|
||||
|
||||
async deleteRoleRequestHandler(message) {
|
||||
let id = message.data.id;
|
||||
let role = this._master.userManager.roles.get(id);
|
||||
let role = this._controller.userManager.roles.get(id);
|
||||
if (!role) {
|
||||
throw new libErrors.RequestError(`Role with ID ${id} does not exist`);
|
||||
}
|
||||
|
||||
this._master.userManager.roles.delete(id);
|
||||
for (let user of this._master.userManager.users.values()) {
|
||||
this._controller.userManager.roles.delete(id);
|
||||
for (let user of this._controller.userManager.users.values()) {
|
||||
user.roles.delete(role);
|
||||
this._master.userPermissionsUpdated(user);
|
||||
this._controller.userPermissionsUpdated(user);
|
||||
}
|
||||
}
|
||||
|
||||
async getUserRequestHandler(message) {
|
||||
let name = message.data.name;
|
||||
let user = this._master.userManager.users.get(name);
|
||||
let user = this._controller.userManager.users.get(name);
|
||||
if (!user) {
|
||||
throw new libErrors.RequestError(`User ${name} does not exist`);
|
||||
}
|
||||
@@ -790,7 +790,7 @@ class ControlConnection extends BaseConnection {
|
||||
|
||||
async listUsersRequestHandler(message) {
|
||||
let list = [];
|
||||
for (let user of this._master.userManager.users.values()) {
|
||||
for (let user of this._controller.userManager.users.values()) {
|
||||
list.push({
|
||||
name: user.name,
|
||||
roles: [...user.roles].map(role => role.id),
|
||||
@@ -829,12 +829,12 @@ class ControlConnection extends BaseConnection {
|
||||
}
|
||||
|
||||
async createUserRequestHandler(message) {
|
||||
let user = this._master.userManager.createUser(message.data.name);
|
||||
this._master.userUpdated(user);
|
||||
let user = this._controller.userManager.createUser(message.data.name);
|
||||
this._controller.userUpdated(user);
|
||||
}
|
||||
|
||||
async revokeUserTokenRequestHandler(message) {
|
||||
let user = this._master.userManager.users.get(message.data.name);
|
||||
let user = this._controller.userManager.users.get(message.data.name);
|
||||
if (!user) {
|
||||
throw new libErrors.RequestError(`User '${message.data.name}' does not exist`);
|
||||
}
|
||||
@@ -843,23 +843,23 @@ class ControlConnection extends BaseConnection {
|
||||
}
|
||||
|
||||
user.invalidateToken();
|
||||
for (let controlConnection of this._master.wsServer.controlConnections) {
|
||||
for (let controlConnection of this._controller.wsServer.controlConnections) {
|
||||
if (controlConnection.user.name === user.name) {
|
||||
controlConnection.connector.terminate();
|
||||
}
|
||||
}
|
||||
this._master.userUpdated(user);
|
||||
this._controller.userUpdated(user);
|
||||
}
|
||||
|
||||
async updateUserRolesRequestHandler(message) {
|
||||
let user = this._master.userManager.users.get(message.data.name);
|
||||
let user = this._controller.userManager.users.get(message.data.name);
|
||||
if (!user) {
|
||||
throw new libErrors.RequestError(`User '${message.data.name}' does not exist`);
|
||||
}
|
||||
|
||||
let resolvedRoles = new Set();
|
||||
for (let roleId of message.data.roles) {
|
||||
let role = this._master.userManager.roles.get(roleId);
|
||||
let role = this._controller.userManager.roles.get(roleId);
|
||||
if (!role) {
|
||||
throw new libErrors.RequestError(`Role with ID ${roleId} does not exist`);
|
||||
}
|
||||
@@ -868,34 +868,34 @@ class ControlConnection extends BaseConnection {
|
||||
}
|
||||
|
||||
user.roles = resolvedRoles;
|
||||
this._master.userPermissionsUpdated(user);
|
||||
this._master.userUpdated(user);
|
||||
this._controller.userPermissionsUpdated(user);
|
||||
this._controller.userUpdated(user);
|
||||
}
|
||||
|
||||
async setUserAdminRequestHandler(message) {
|
||||
let { name, create, admin } = message.data;
|
||||
let user = this._master.userManager.users.get(name);
|
||||
let user = this._controller.userManager.users.get(name);
|
||||
if (!user) {
|
||||
if (create) {
|
||||
this.user.checkPermission("core.user.create");
|
||||
user = this._master.userManager.createUser(name);
|
||||
user = this._controller.userManager.createUser(name);
|
||||
} else {
|
||||
throw new libErrors.RequestError(`User '${name}' does not exist`);
|
||||
}
|
||||
}
|
||||
|
||||
user.isAdmin = admin;
|
||||
this._master.userUpdated(user);
|
||||
this._controller.userUpdated(user);
|
||||
this.broadcastEventToSlaves({ data: { name, admin }}, libLink.messages.adminlistUpdate);
|
||||
}
|
||||
|
||||
async setUserBannedRequestHandler(message) {
|
||||
let { name, create, banned, reason } = message.data;
|
||||
let user = this._master.userManager.users.get(name);
|
||||
let user = this._controller.userManager.users.get(name);
|
||||
if (!user) {
|
||||
if (create) {
|
||||
this.user.checkPermission("core.user.create");
|
||||
user = this._master.userManager.createUser(name);
|
||||
user = this._controller.userManager.createUser(name);
|
||||
} else {
|
||||
throw new libErrors.RequestError(`User '${name}' does not exist`);
|
||||
}
|
||||
@@ -903,36 +903,36 @@ class ControlConnection extends BaseConnection {
|
||||
|
||||
user.isBanned = banned;
|
||||
user.banReason = reason;
|
||||
this._master.userUpdated(user);
|
||||
this._controller.userUpdated(user);
|
||||
this.broadcastEventToSlaves({ data: { name, banned, reason }}, libLink.messages.banlistUpdate);
|
||||
}
|
||||
|
||||
async setUserWhitelistedRequestHandler(message) {
|
||||
let { name, create, whitelisted } = message.data;
|
||||
let user = this._master.userManager.users.get(name);
|
||||
let user = this._controller.userManager.users.get(name);
|
||||
if (!user) {
|
||||
if (create) {
|
||||
this.user.checkPermission("core.user.create");
|
||||
user = this._master.userManager.createUser(name);
|
||||
user = this._controller.userManager.createUser(name);
|
||||
} else {
|
||||
throw new libErrors.RequestError(`User '${name}' does not exist`);
|
||||
}
|
||||
}
|
||||
|
||||
user.isWhitelisted = whitelisted;
|
||||
this._master.userUpdated(user);
|
||||
this._controller.userUpdated(user);
|
||||
this.broadcastEventToSlaves({ data: { name, whitelisted }}, libLink.messages.whitelistUpdate);
|
||||
}
|
||||
|
||||
async deleteUserRequestHandler(message) {
|
||||
let user = this._master.userManager.users.get(message.data.name);
|
||||
let user = this._controller.userManager.users.get(message.data.name);
|
||||
if (!user) {
|
||||
throw new libErrors.RequestError(`User '${message.data.name}' does not exist`);
|
||||
}
|
||||
|
||||
user.isDeleted = true;
|
||||
this._master.userManager.users.delete(message.data.name);
|
||||
this._master.userUpdated(user);
|
||||
this._controller.userManager.users.delete(message.data.name);
|
||||
this._controller.userUpdated(user);
|
||||
|
||||
if (user.is_admin) {
|
||||
this.broadcastEventToSlaves({ data: { name, admin: false }}, libLink.messages.adminlistUpdate);
|
||||
@@ -952,7 +952,7 @@ class ControlConnection extends BaseConnection {
|
||||
}
|
||||
};
|
||||
this.connector._socket.clusterio_ignore_dump = true;
|
||||
this._master.debugEvents.on("message", this.ws_dumper);
|
||||
this._controller.debugEvents.on("message", this.ws_dumper);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,21 +27,21 @@ const WsServer = require("./WsServer");
|
||||
|
||||
|
||||
const endpointDurationSummary = new libPrometheus.Summary(
|
||||
"clusterio_master_http_endpoint_duration_seconds",
|
||||
"clusterio_controller_http_endpoint_duration_seconds",
|
||||
"Time it took to respond to a an HTTP request",
|
||||
{ labels: ["route"] }
|
||||
);
|
||||
|
||||
const logSizeGauge = new libPrometheus.Gauge(
|
||||
"clusterio_master_log_bytes",
|
||||
"Size of all log files currently stored on the master."
|
||||
"clusterio_controller_log_bytes",
|
||||
"Size of all log files currently stored on the controller."
|
||||
);
|
||||
|
||||
/**
|
||||
* Manages all master related operations
|
||||
* @alias module:master/src/Master
|
||||
* Manages all controller related operations
|
||||
* @alias module:controller/src/Controller
|
||||
*/
|
||||
class Master {
|
||||
class Controller {
|
||||
constructor(clusterLogger, pluginInfos, configPath, config) {
|
||||
this.clusterLogger = clusterLogger;
|
||||
/**
|
||||
@@ -51,13 +51,13 @@ class Master {
|
||||
this.pluginInfos = pluginInfos;
|
||||
this.configPath = configPath;
|
||||
/**
|
||||
* Master config.
|
||||
* @type {module:lib/config.MasterConfig}
|
||||
* Controller config.
|
||||
* @type {module:lib/config.ControllerConfig}
|
||||
*/
|
||||
this.config = config;
|
||||
|
||||
this.app = express();
|
||||
this.app.locals.master = this;
|
||||
this.app.locals.controller = this;
|
||||
this.app.locals.streams = new Map();
|
||||
|
||||
/**
|
||||
@@ -86,7 +86,7 @@ class Master {
|
||||
|
||||
/**
|
||||
* User and roles manager for the cluster
|
||||
* @type {module:master/src/UserManager}
|
||||
* @type {module:controller/src/UserManager}
|
||||
*/
|
||||
this.userManager = null;
|
||||
this.httpServer = null;
|
||||
@@ -96,13 +96,13 @@ class Master {
|
||||
|
||||
/**
|
||||
* Mapping of plugin name to loaded plugin
|
||||
* @type {Map<string, module:lib/plugin.BaseMasterPlugin>}
|
||||
* @type {Map<string, module:lib/plugin.BaseControllerPlugin>}
|
||||
*/
|
||||
this.plugins = new Map();
|
||||
|
||||
/**
|
||||
* WebSocket server
|
||||
* @type {module:master/src/WsServer}
|
||||
* @type {module:controller/src/WsServer}
|
||||
*/
|
||||
this.wsServer = new WsServer(this);
|
||||
|
||||
@@ -144,7 +144,7 @@ class Master {
|
||||
async logSize() {
|
||||
return (await Promise.all([
|
||||
libFileOps.directorySize(path.join(this.logDirectory, "cluster")),
|
||||
libFileOps.directorySize(path.join(this.logDirectory, "master")),
|
||||
libFileOps.directorySize(path.join(this.logDirectory, "controller")),
|
||||
libFileOps.directorySize(path.join(this.logDirectory, "slave")),
|
||||
])).reduce((a, v) => a + v, 0);
|
||||
}
|
||||
@@ -189,38 +189,38 @@ class Master {
|
||||
this.app.use(this.devMiddleware);
|
||||
}
|
||||
|
||||
let databaseDirectory = this.config.get("master.database_directory");
|
||||
let databaseDirectory = this.config.get("controller.database_directory");
|
||||
await fs.ensureDir(databaseDirectory);
|
||||
|
||||
this.slaves = await Master.loadSlaves(path.join(databaseDirectory, "slaves.json"));
|
||||
this.instances = await Master.loadInstances(path.join(databaseDirectory, "instances.json"));
|
||||
this.modPacks = await Master.loadModPacks(path.join(databaseDirectory, "mod-packs.json"));
|
||||
this.slaves = await Controller.loadSlaves(path.join(databaseDirectory, "slaves.json"));
|
||||
this.instances = await Controller.loadInstances(path.join(databaseDirectory, "instances.json"));
|
||||
this.modPacks = await Controller.loadModPacks(path.join(databaseDirectory, "mod-packs.json"));
|
||||
this.userManager = new UserManager(this.config);
|
||||
await this.userManager.load(path.join(databaseDirectory, "users.json"));
|
||||
|
||||
let modsDirectory = this.config.get("master.mods_directory");
|
||||
let modsDirectory = this.config.get("controller.mods_directory");
|
||||
await fs.ensureDir(modsDirectory);
|
||||
this.mods = await Master.loadModInfos(modsDirectory);
|
||||
this.mods = await Controller.loadModInfos(modsDirectory);
|
||||
|
||||
this.config.on("fieldChanged", (group, field, prev) => {
|
||||
libPlugin.invokeHook(this.plugins, "onMasterConfigFieldChanged", group, field, prev);
|
||||
libPlugin.invokeHook(this.plugins, "onControllerConfigFieldChanged", group, field, prev);
|
||||
});
|
||||
for (let instance of this.instances.values()) {
|
||||
this.addInstanceHooks(instance);
|
||||
}
|
||||
|
||||
// Make sure we're actually going to listen on a port
|
||||
let httpPort = this.config.get("master.http_port");
|
||||
let httpsPort = this.config.get("master.https_port");
|
||||
let bindAddress = this.config.get("master.bind_address") || "";
|
||||
let httpPort = this.config.get("controller.http_port");
|
||||
let httpsPort = this.config.get("controller.https_port");
|
||||
let bindAddress = this.config.get("controller.bind_address") || "";
|
||||
if (!httpPort && !httpsPort) {
|
||||
logger.fatal("Error: at least one of http_port and https_port must be configured");
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
let tls_cert = this.config.get("master.tls_certificate");
|
||||
let tls_key = this.config.get("master.tls_private_key");
|
||||
let tls_cert = this.config.get("controller.tls_certificate");
|
||||
let tls_key = this.config.get("controller.tls_private_key");
|
||||
|
||||
if (httpsPort && (!tls_cert || !tls_key)) {
|
||||
throw new libErrors.StartupError(
|
||||
@@ -228,10 +228,10 @@ class Master {
|
||||
);
|
||||
}
|
||||
|
||||
Master.addAppRoutes(this.app, this.pluginInfos);
|
||||
Controller.addAppRoutes(this.app, this.pluginInfos);
|
||||
|
||||
if (!args.dev) {
|
||||
let manifest = await Master.loadJsonObject(path.join(__dirname, "..", "dist", "web", "manifest.json"));
|
||||
let manifest = await Controller.loadJsonObject(path.join(__dirname, "..", "dist", "web", "manifest.json"));
|
||||
if (!manifest["main.js"]) {
|
||||
logger.error("Missing main.js entry in dist/web/manifest.json");
|
||||
}
|
||||
@@ -272,12 +272,12 @@ class Master {
|
||||
logger.info(`Listening for HTTPS on port ${this.httpsServer.address().port}`);
|
||||
}
|
||||
|
||||
logger.info("Started master");
|
||||
logger.info("Started controller");
|
||||
this._state = "running";
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the master server.
|
||||
* Stops the controller.
|
||||
*
|
||||
* Save data and bring down the active connections. This is the reverse
|
||||
* of start and is valid at any point in time.
|
||||
@@ -299,7 +299,7 @@ class Master {
|
||||
async _stopInternal() {
|
||||
// This function should never throw.
|
||||
this._state = "stopping";
|
||||
logger.info("Stopping master");
|
||||
logger.info("Stopping controller");
|
||||
clearInterval(this.clusterLogBuildInterval);
|
||||
if (this.clusterLogIndex) {
|
||||
await this.clusterLogIndex.save();
|
||||
@@ -312,17 +312,17 @@ class Master {
|
||||
await new Promise((resolve, reject) => { this.devMiddleware.close(resolve); });
|
||||
}
|
||||
|
||||
let databaseDirectory = this.config.get("master.database_directory");
|
||||
let databaseDirectory = this.config.get("controller.database_directory");
|
||||
if (this.slaves) {
|
||||
await Master.saveSlaves(path.join(databaseDirectory, "slaves.json"), this.slaves);
|
||||
await Controller.saveSlaves(path.join(databaseDirectory, "slaves.json"), this.slaves);
|
||||
}
|
||||
|
||||
if (this.instances) {
|
||||
await Master.saveInstances(path.join(databaseDirectory, "instances.json"), this.instances);
|
||||
await Controller.saveInstances(path.join(databaseDirectory, "instances.json"), this.instances);
|
||||
}
|
||||
|
||||
if (this.modPacks) {
|
||||
await Master.saveModPacks(path.join(databaseDirectory, "mod-packs.json"), this.modPacks);
|
||||
await Controller.saveModPacks(path.join(databaseDirectory, "mod-packs.json"), this.modPacks);
|
||||
}
|
||||
|
||||
if (this.userManager) {
|
||||
@@ -376,7 +376,7 @@ class Master {
|
||||
try {
|
||||
let serialized = JSON.parse(await fs.readFile(filePath));
|
||||
for (let serializedConfig of serialized) {
|
||||
let instanceConfig = new libConfig.InstanceConfig("master");
|
||||
let instanceConfig = new libConfig.InstanceConfig("controller");
|
||||
await instanceConfig.load(serializedConfig);
|
||||
let status = instanceConfig.get("instance.assigned_slave") === null ? "unassigned" : "unknown";
|
||||
let instance = { config: instanceConfig, status };
|
||||
@@ -471,7 +471,7 @@ class Master {
|
||||
throw new Error(`Mod ${filename} does not exist`);
|
||||
}
|
||||
|
||||
let modsDirectory = this.config.get("master.mods_directory");
|
||||
let modsDirectory = this.config.get("controller.mods_directory");
|
||||
await fs.unlink(path.join(modsDirectory, filename));
|
||||
this.mods.delete(filename);
|
||||
mod.isDeleted = true;
|
||||
@@ -479,16 +479,16 @@ class Master {
|
||||
}
|
||||
|
||||
/**
|
||||
* Query master log
|
||||
* Query controller log
|
||||
*
|
||||
* @param {module:lib/logging_utils~QueryLogFilter} filter -
|
||||
* Filter to limit entries with. Note that only the master log can
|
||||
* Filter to limit entries with. Note that only the controller log can
|
||||
* be queried from this function.
|
||||
* @returns {Promise<Array<Object>>} log entries matching the filter
|
||||
*/
|
||||
async queryMasterLog(filter) {
|
||||
async queryControllerLog(filter) {
|
||||
return await libLoggingUtils.queryLog(
|
||||
path.join(this.logDirectory, "master"), filter,
|
||||
path.join(this.logDirectory, "controller"), filter,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -530,11 +530,11 @@ class Master {
|
||||
|
||||
// Add routes for the web interface
|
||||
for (let route of routes.webRoutes) {
|
||||
app.get(route, Master.serveWeb(route));
|
||||
app.get(route, Controller.serveWeb(route));
|
||||
}
|
||||
for (let pluginInfo of pluginInfos) {
|
||||
for (let route of pluginInfo.routes || []) {
|
||||
app.get(route, Master.serveWeb(route));
|
||||
app.get(route, Controller.serveWeb(route));
|
||||
}
|
||||
|
||||
let pluginPackagePath = require.resolve(path.posix.join(pluginInfo.requirePath, "package.json"));
|
||||
@@ -684,7 +684,7 @@ class Master {
|
||||
for (let pluginInfo of this.pluginInfos) {
|
||||
try {
|
||||
let manifestPath = path.posix.join(pluginInfo.requirePath, "dist", "web", "manifest.json");
|
||||
pluginInfo.manifest = await Master.loadJsonObject(require.resolve(manifestPath), true);
|
||||
pluginInfo.manifest = await Controller.loadJsonObject(require.resolve(manifestPath), true);
|
||||
} catch (err) {
|
||||
logger.warn(`Unable to load dist/web/manifest.json for plugin ${pluginInfo.name}`);
|
||||
}
|
||||
@@ -693,15 +693,15 @@ class Master {
|
||||
continue;
|
||||
}
|
||||
|
||||
let MasterPluginClass = libPlugin.BaseMasterPlugin;
|
||||
let ControllerPluginClass = libPlugin.BaseControllerPlugin;
|
||||
try {
|
||||
if (pluginInfo.masterEntrypoint) {
|
||||
MasterPluginClass = await libPluginLoader.loadMasterPluginClass(pluginInfo);
|
||||
if (pluginInfo.controllerEntrypoint) {
|
||||
ControllerPluginClass = await libPluginLoader.loadControllerPluginClass(pluginInfo);
|
||||
}
|
||||
|
||||
let masterPlugin = new MasterPluginClass(pluginInfo, this, metrics, logger);
|
||||
await masterPlugin.init();
|
||||
this.plugins.set(pluginInfo.name, masterPlugin);
|
||||
let controllerPlugin = new ControllerPluginClass(pluginInfo, this, metrics, logger);
|
||||
await controllerPlugin.init();
|
||||
this.plugins.set(pluginInfo.name, controllerPlugin);
|
||||
|
||||
} catch (err) {
|
||||
throw new libErrors.PluginError(pluginInfo.name, err);
|
||||
@@ -743,21 +743,21 @@ class Master {
|
||||
|
||||
|
||||
/**
|
||||
* Returns the URL needed to connect to the master server.
|
||||
* Returns the URL needed to connect to the controller.
|
||||
*
|
||||
* @returns {string} master URL.
|
||||
* @returns {string} controller URL.
|
||||
*/
|
||||
getMasterUrl() {
|
||||
return Master.calculateMasterUrl(this.config);
|
||||
getControllerUrl() {
|
||||
return Controller.calculateControllerUrl(this.config);
|
||||
}
|
||||
|
||||
static calculateMasterUrl(config) {
|
||||
let url = config.get("master.external_address");
|
||||
static calculateControllerUrl(config) {
|
||||
let url = config.get("controller.external_address");
|
||||
if (!url) {
|
||||
if (config.get("master.https_port")) {
|
||||
url = `https://localhost:${config.get("master.https_port")}/`;
|
||||
if (config.get("controller.https_port")) {
|
||||
url = `https://localhost:${config.get("controller.https_port")}/`;
|
||||
} else {
|
||||
url = `http://localhost:${config.get("master.http_port")}/`;
|
||||
url = `http://localhost:${config.get("controller.http_port")}/`;
|
||||
}
|
||||
}
|
||||
return url;
|
||||
@@ -789,7 +789,7 @@ class Master {
|
||||
fs.readFile(path.join(__dirname, "..", "web", "index.html"), "utf8").then((content) => {
|
||||
res.type("text/html");
|
||||
res.send(content
|
||||
.replace(/__CLUSTER_NAME__/g, res.app.locals.master.config.get("master.name"))
|
||||
.replace(/__CLUSTER_NAME__/g, res.app.locals.controller.config.get("controller.name"))
|
||||
.replace(/__WEB_ROOT__/g, webRoot)
|
||||
.replace(/__STATIC_ROOT__/g, staticRoot)
|
||||
.replace(/__MAIN_BUNDLE__/g, mainBundle)
|
||||
@@ -832,4 +832,4 @@ class Master {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Master;
|
||||
module.exports = Controller;
|
||||
@@ -17,7 +17,7 @@ const https = require("https");
|
||||
* // in the shutdown logic
|
||||
* await serverCloser.close();
|
||||
*
|
||||
* @alias module:master/src/HttpCloser
|
||||
* @alias module:controller/src/HttpCloser
|
||||
*/
|
||||
class HttpCloser {
|
||||
/**
|
||||
+40
-38
@@ -11,12 +11,12 @@ const BaseConnection = require("./BaseConnection");
|
||||
/**
|
||||
* Represents the connection to a slave
|
||||
*
|
||||
* @extends module:master/src/BaseConnection
|
||||
* @alias module:master/src/SlaveConnection
|
||||
* @extends module:controller/src/BaseConnection
|
||||
* @alias module:controller/src/SlaveConnection
|
||||
*/
|
||||
class SlaveConnection extends BaseConnection {
|
||||
constructor(registerData, connector, master) {
|
||||
super("slave", connector, master);
|
||||
constructor(registerData, connector, controller) {
|
||||
super("slave", connector, controller);
|
||||
|
||||
this._agent = registerData.agent;
|
||||
this._id = registerData.id;
|
||||
@@ -25,7 +25,7 @@ class SlaveConnection extends BaseConnection {
|
||||
this.plugins = new Map(Object.entries(registerData.plugins));
|
||||
this._checkPluginVersions();
|
||||
|
||||
this._master.slaves.set(this._id, {
|
||||
this._controller.slaves.set(this._id, {
|
||||
agent: this._agent,
|
||||
id: this._id,
|
||||
name: this._name,
|
||||
@@ -37,7 +37,7 @@ class SlaveConnection extends BaseConnection {
|
||||
for (let event of ["connect", "drop", "resume", "close"]) {
|
||||
// eslint-disable-next-line no-loop-func
|
||||
this.connector.on(event, () => {
|
||||
for (let plugin of this._master.plugins.values()) {
|
||||
for (let plugin of this._controller.plugins.values()) {
|
||||
plugin.onSlaveConnectionEvent(this, event);
|
||||
}
|
||||
});
|
||||
@@ -45,32 +45,32 @@ class SlaveConnection extends BaseConnection {
|
||||
|
||||
this.connector.on("close", () => {
|
||||
// Update status to unknown for instances on this slave.
|
||||
for (let instance of this._master.instances.values()) {
|
||||
for (let instance of this._controller.instances.values()) {
|
||||
if (instance.config.get("instance.assigned_slave") !== this._id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let prev = instance.status;
|
||||
instance.status = "unknown";
|
||||
this._master.instanceUpdated(instance);
|
||||
libPlugin.invokeHook(this._master.plugins, "onInstanceStatusChanged", instance, prev);
|
||||
this._controller.instanceUpdated(instance);
|
||||
libPlugin.invokeHook(this._controller.plugins, "onInstanceStatusChanged", instance, prev);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_checkPluginVersions() {
|
||||
let pluginInfos = new Map(this._master.pluginInfos.map(i => [i.name, i]));
|
||||
let pluginInfos = new Map(this._controller.pluginInfos.map(i => [i.name, i]));
|
||||
for (let [name, version] of this.plugins) {
|
||||
let info = pluginInfos.get(name);
|
||||
if (!info) {
|
||||
logger.warn(`Slave ${this._name} has plugin ${name} ${version} which the master does not have`);
|
||||
logger.warn(`Slave ${this._name} has plugin ${name} ${version} which the controller does not have`);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (info.version !== version) {
|
||||
logger.warn(
|
||||
`Slave ${this._name} has plugin ${name} ${version} which does not match the version of this ` +
|
||||
`plugin on the master (${info.version})`
|
||||
`plugin on the controller (${info.version})`
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -92,7 +92,7 @@ class SlaveConnection extends BaseConnection {
|
||||
}
|
||||
|
||||
async instanceStatusChangedEventHandler(message, event) {
|
||||
let instance = this._master.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
|
||||
// or is not assigned to the slave it originated from if it was
|
||||
@@ -116,13 +116,13 @@ class SlaveConnection extends BaseConnection {
|
||||
instance.status = message.data.status;
|
||||
instance.game_port = message.data.game_port;
|
||||
logger.verbose(`Instance ${instance.config.get("instance.name")} State: ${instance.status}`);
|
||||
this._master.instanceUpdated(instance);
|
||||
await libPlugin.invokeHook(this._master.plugins, "onInstanceStatusChanged", instance, prev);
|
||||
this._controller.instanceUpdated(instance);
|
||||
await libPlugin.invokeHook(this._controller.plugins, "onInstanceStatusChanged", instance, prev);
|
||||
}
|
||||
|
||||
async updateInstancesRequestHandler(message) {
|
||||
// Push updated instance configs
|
||||
for (let instance of this._master.instances.values()) {
|
||||
for (let instance of this._controller.instances.values()) {
|
||||
if (instance.config.get("instance.assigned_slave") === this._id) {
|
||||
await libLink.messages.assignInstance.send(this, {
|
||||
instance_id: instance.config.get("instance.id"),
|
||||
@@ -131,41 +131,43 @@ class SlaveConnection extends BaseConnection {
|
||||
}
|
||||
}
|
||||
|
||||
// Assign instances the slave has but master does not
|
||||
// Assign instances the slave has but controller does not
|
||||
for (let instanceData of message.data.instances) {
|
||||
let instanceConfig = new libConfig.InstanceConfig("master");
|
||||
let instanceConfig = new libConfig.InstanceConfig("controller");
|
||||
await instanceConfig.load(instanceData.serialized_config, "slave");
|
||||
|
||||
let masterInstance = this._master.instances.get(instanceConfig.get("instance.id"));
|
||||
if (masterInstance) {
|
||||
let controllerInstance = this._controller.instances.get(instanceConfig.get("instance.id"));
|
||||
if (controllerInstance) {
|
||||
// Check if this instance is assigned somewhere else.
|
||||
if (masterInstance.config.get("instance.assigned_slave") !== this._id) {
|
||||
if (controllerInstance.config.get("instance.assigned_slave") !== this._id) {
|
||||
await libLink.messages.unassignInstance.send(this, {
|
||||
instance_id: masterInstance.config.get("instance.id"),
|
||||
instance_id: controllerInstance.config.get("instance.id"),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// Already have this instance, update state instead
|
||||
if (masterInstance.status !== instanceData.status) {
|
||||
let prev = masterInstance.status;
|
||||
masterInstance.status = instanceData.status;
|
||||
if (controllerInstance.status !== instanceData.status) {
|
||||
let prev = controllerInstance.status;
|
||||
controllerInstance.status = instanceData.status;
|
||||
logger.verbose(`Instance ${instanceConfig.get("instance.name")} State: ${instanceData.status}`);
|
||||
this._master.instanceUpdated(instance);
|
||||
await libPlugin.invokeHook(this._master.plugins, "onInstanceStatusChanged", masterInstance, prev);
|
||||
this._controller.instanceUpdated(instance);
|
||||
await libPlugin.invokeHook(
|
||||
this._controller.plugins, "onInstanceStatusChanged", controllerInstance, prev
|
||||
);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
instanceConfig.set("instance.assigned_slave", this._id);
|
||||
let newInstance = { config: instanceConfig, status: instanceData.status };
|
||||
this._master.instances.set(instanceConfig.get("instance.id"), newInstance);
|
||||
this._master.addInstanceHooks(newInstance);
|
||||
this._controller.instances.set(instanceConfig.get("instance.id"), newInstance);
|
||||
this._controller.addInstanceHooks(newInstance);
|
||||
await libLink.messages.assignInstance.send(this, {
|
||||
instance_id: instanceConfig.get("instance.id"),
|
||||
serialized_config: instanceConfig.serialize("slave"),
|
||||
});
|
||||
await libPlugin.invokeHook(this._master.plugins, "onInstanceStatusChanged", newInstance, null);
|
||||
await libPlugin.invokeHook(this._controller.plugins, "onInstanceStatusChanged", newInstance, null);
|
||||
}
|
||||
|
||||
// Push lists to make sure they are in sync.
|
||||
@@ -173,7 +175,7 @@ class SlaveConnection extends BaseConnection {
|
||||
let banlist = [];
|
||||
let whitelist = [];
|
||||
|
||||
for (let user of this._master.userManager.users.values()) {
|
||||
for (let user of this._controller.userManager.users.values()) {
|
||||
if (user.isAdmin) {
|
||||
adminlist.push(user.name);
|
||||
}
|
||||
@@ -189,11 +191,11 @@ class SlaveConnection extends BaseConnection {
|
||||
}
|
||||
|
||||
async saveListUpdateEventHandler(message) {
|
||||
this._master.saveListUpdate(message.data);
|
||||
this._controller.saveListUpdate(message.data);
|
||||
}
|
||||
|
||||
async logMessageEventHandler(message) {
|
||||
this._master.clusterLogger.log({
|
||||
this._controller.clusterLogger.log({
|
||||
...message.data.info,
|
||||
slave_id: this._id,
|
||||
slave_name: this._name,
|
||||
@@ -202,9 +204,9 @@ class SlaveConnection extends BaseConnection {
|
||||
|
||||
async playerEventEventHandler(message) {
|
||||
let { instance_id, name, type, stats } = message.data;
|
||||
let user = this._master.userManager.users.get(name);
|
||||
let user = this._controller.userManager.users.get(name);
|
||||
if (!user) {
|
||||
user = this._master.userManager.createUser(name);
|
||||
user = this._controller.userManager.createUser(name);
|
||||
}
|
||||
|
||||
if (type === "join") {
|
||||
@@ -214,11 +216,11 @@ class SlaveConnection extends BaseConnection {
|
||||
}
|
||||
user.instanceStats.set(instance_id, new PlayerStats(stats));
|
||||
user.recalculatePlayerStats();
|
||||
this._master.userUpdated(user);
|
||||
this._controller.userUpdated(user);
|
||||
|
||||
delete message.data.stats;
|
||||
let instance = this._master.instances.get(instance_id);
|
||||
await libPlugin.invokeHook(this._master.plugins, "onPlayerEvent", instance, message.data);
|
||||
let instance = this._controller.instances.get(instance_id);
|
||||
await libPlugin.invokeHook(this._controller.plugins, "onPlayerEvent", instance, message.data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ const libUsers = require("@clusterio/lib/users");
|
||||
|
||||
/**
|
||||
* Manages users and roles
|
||||
* @alias module:master/src/UserManager
|
||||
* @alias module:controller/src/UserManager
|
||||
*/
|
||||
class UserManager {
|
||||
constructor(config) {
|
||||
@@ -79,7 +79,7 @@ class UserManager {
|
||||
throw new Error(`User '${name}' already exists`);
|
||||
}
|
||||
|
||||
let defaultRoleId = this._config.get("master.default_role_id");
|
||||
let defaultRoleId = this._config.get("controller.default_role_id");
|
||||
let user = new libUsers.User({ name, roles: [defaultRoleId] }, this.roles);
|
||||
this.users.set(name, user);
|
||||
return user;
|
||||
@@ -14,36 +14,36 @@ const WsServerConnector = require("./WsServerConnector");
|
||||
|
||||
|
||||
const wsMessageCounter = new libPrometheus.Counter(
|
||||
"clusterio_master_websocket_message_total",
|
||||
"How many messages have been received over WebSocket on the master server",
|
||||
"clusterio_controller_websocket_message_total",
|
||||
"How many messages have been received over WebSocket on the controller",
|
||||
{ labels: ["direction"] }
|
||||
);
|
||||
|
||||
const wsConnectionsCounter = new libPrometheus.Counter(
|
||||
"clusterio_master_websocket_connections_total",
|
||||
"How many WebSocket connections have been initiated on the master server"
|
||||
"clusterio_controller_websocket_connections_total",
|
||||
"How many WebSocket connections have been initiated on the controller"
|
||||
);
|
||||
|
||||
const wsRejectedConnectionsCounter = new libPrometheus.Counter(
|
||||
"clusterio_master_websocket_rejected_connections_total",
|
||||
"How many WebSocket connections have been rejected during the handshake on the master server"
|
||||
"clusterio_controller_websocket_rejected_connections_total",
|
||||
"How many WebSocket connections have been rejected during the handshake on the controller"
|
||||
);
|
||||
|
||||
/**
|
||||
* WebSocket server
|
||||
* @alias module:master/src/WsServer
|
||||
* @alias module:controller/src/WsServer
|
||||
*/
|
||||
class WsServer {
|
||||
constructor(master) {
|
||||
this.master = master;
|
||||
constructor(controller) {
|
||||
this.controller = controller;
|
||||
|
||||
this.stopAcceptingNewSessions = false;
|
||||
|
||||
/** @type {Array<module:master/src/ControlConnection>} */
|
||||
/** @type {Array<module:controller/src/ControlConnection>} */
|
||||
this.controlConnections = [];
|
||||
/** @type {Map<number, module:master/src/SlaveConnection>} */
|
||||
/** @type {Map<number, module:controller/src/SlaveConnection>} */
|
||||
this.slaveConnections = new Map();
|
||||
/** @type {Map<number, module:master/src/WsServerConnector>} */
|
||||
/** @type {Map<number, module:controller/src/WsServerConnector>} */
|
||||
this.activeConnectors = new Map();
|
||||
/** @type {Set<module:ws>} */
|
||||
this.pendingSockets = new Set();
|
||||
@@ -104,21 +104,21 @@ class WsServer {
|
||||
socket.on("message", (message) => {
|
||||
wsMessageCounter.labels("in").inc();
|
||||
if (!socket.clusterio_ignore_dump) {
|
||||
this.master.debugEvents.emit("message", { direction: "in", content: message });
|
||||
this.controller.debugEvents.emit("message", { direction: "in", content: message });
|
||||
}
|
||||
});
|
||||
let originalSend = socket.send;
|
||||
socket.send = (...args) => {
|
||||
wsMessageCounter.labels("out").inc();
|
||||
if (typeof args[0] === "string" && !socket.clusterio_ignore_dump) {
|
||||
this.master.debugEvents.emit("message", { direction: "out", content: args[0] });
|
||||
this.controller.debugEvents.emit("message", { direction: "out", content: args[0] });
|
||||
}
|
||||
return originalSend.call(socket, ...args);
|
||||
};
|
||||
|
||||
// Start connection handshake.
|
||||
let loadedPlugins = {};
|
||||
for (let [name, plugin] of this.master.plugins) {
|
||||
for (let [name, plugin] of this.controller.plugins) {
|
||||
loadedPlugins[name] = plugin.info.version;
|
||||
}
|
||||
|
||||
@@ -183,7 +183,7 @@ ${err.stack}`
|
||||
try {
|
||||
let payload = jwt.verify(
|
||||
data.session_token,
|
||||
Buffer.from(this.master.config.get("master.auth_secret"), "base64"),
|
||||
Buffer.from(this.controller.config.get("controller.auth_secret"), "base64"),
|
||||
{ audience: this.sessionAud }
|
||||
);
|
||||
|
||||
@@ -215,7 +215,7 @@ ${err.stack}`
|
||||
if (type === "register_slave") {
|
||||
let tokenPayload = jwt.verify(
|
||||
data.token,
|
||||
Buffer.from(this.master.config.get("master.auth_secret"), "base64"),
|
||||
Buffer.from(this.controller.config.get("controller.auth_secret"), "base64"),
|
||||
{ audience: "slave" }
|
||||
);
|
||||
|
||||
@@ -226,11 +226,11 @@ ${err.stack}`
|
||||
} else if (type === "register_control") {
|
||||
let tokenPayload = jwt.verify(
|
||||
data.token,
|
||||
Buffer.from(this.master.config.get("master.auth_secret"), "base64"),
|
||||
Buffer.from(this.controller.config.get("controller.auth_secret"), "base64"),
|
||||
{ audience: "user" }
|
||||
);
|
||||
|
||||
user = this.master.userManager.users.get(tokenPayload.user);
|
||||
user = this.controller.userManager.users.get(tokenPayload.user);
|
||||
if (!user) {
|
||||
throw new Error("invalid user");
|
||||
}
|
||||
@@ -251,10 +251,10 @@ ${err.stack}`
|
||||
this.nextSessionId += 1;
|
||||
let sessionToken = jwt.sign(
|
||||
{ aud: this.sessionAud, sid: sessionId },
|
||||
Buffer.from(this.master.config.get("master.auth_secret"), "base64"),
|
||||
Buffer.from(this.controller.config.get("controller.auth_secret"), "base64"),
|
||||
);
|
||||
let sessionTimeout = this.master.config.get("master.session_timeout");
|
||||
let heartbeatInterval = this.master.config.get("master.heartbeat_interval");
|
||||
let sessionTimeout = this.controller.config.get("controller.session_timeout");
|
||||
let heartbeatInterval = this.controller.config.get("controller.heartbeat_interval");
|
||||
let connector = new WsServerConnector(sessionId, sessionTimeout, heartbeatInterval);
|
||||
this.activeConnectors.set(sessionId, connector);
|
||||
connector.on("close", () => {
|
||||
@@ -274,27 +274,27 @@ ${err.stack}`
|
||||
);
|
||||
if (data.agent === "Clusterio Slave" && data.version !== packageVersion) {
|
||||
logger.warn(
|
||||
`Slave ${data.name} (${data.id}) connected using version ${data.version} which does not match ` +
|
||||
`the version of the master is currently running (${packageVersion}). It may not work as expected.`
|
||||
`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.`
|
||||
);
|
||||
}
|
||||
|
||||
connection = new SlaveConnection(data, connector, this.master);
|
||||
connection = new SlaveConnection(data, connector, this.controller);
|
||||
connector.on("close", () => {
|
||||
if (this.slaveConnections.get(data.id) === connection) {
|
||||
this.slaveConnections.delete(data.id);
|
||||
this.master.slaveUpdated(this.master.slaves.get(data.id));
|
||||
this.controller.slaveUpdated(this.controller.slaves.get(data.id));
|
||||
} else {
|
||||
logger.warn("Unlisted SlaveConnection closed");
|
||||
}
|
||||
});
|
||||
this.slaveConnections.set(data.id, connection);
|
||||
this.master.slaveUpdated(this.master.slaves.get(data.id));
|
||||
this.controller.slaveUpdated(this.controller.slaves.get(data.id));
|
||||
|
||||
|
||||
} else if (type === "register_control") {
|
||||
logger.verbose(`WsServer | registered control from ${req.socket.remoteAddress}`);
|
||||
let connection = new ControlConnection(data, connector, this.master, user);
|
||||
let connection = new ControlConnection(data, connector, this.controller, user);
|
||||
connector.on("close", () => {
|
||||
let index = this.controlConnections.indexOf(connection);
|
||||
if (index !== -1) {
|
||||
+2
-2
@@ -6,10 +6,10 @@ const { logger } = require("@clusterio/lib/logging");
|
||||
|
||||
|
||||
/**
|
||||
* Connector for master server connections
|
||||
* Connector for controller connections
|
||||
*
|
||||
* @extends module:lib/link.WebSocketBaseConnector
|
||||
* @alias module:master/src/WsServerConnector
|
||||
* @alias module:controller/src/WsServerConnector
|
||||
*/
|
||||
class WsServerConnector extends libLink.WebSocketBaseConnector {
|
||||
constructor(sessionId, sessionTimeout, heartbeatInterval) {
|
||||
@@ -4,7 +4,7 @@ const libPrometheus = require("@clusterio/lib/prometheus");
|
||||
const util = require("util");
|
||||
|
||||
exports.endpointHitCounter = new libPrometheus.Counter(
|
||||
"clusterio_master_http_endpoint_hits_total",
|
||||
"clusterio_controller_http_endpoint_hits_total",
|
||||
"How many requests a particular HTTP endpoint has gotten",
|
||||
{ labels: ["route"], register: false }
|
||||
);
|
||||
@@ -40,7 +40,7 @@ function mergeSamples(destinationResult, sourceResult) {
|
||||
// Prometheus polling endpoint
|
||||
async function getMetrics(req, res, next) {
|
||||
let results = [];
|
||||
let pluginResults = await libPlugin.invokeHook(req.app.locals.master.plugins, "onMetrics");
|
||||
let pluginResults = await libPlugin.invokeHook(req.app.locals.controller.plugins, "onMetrics");
|
||||
for (let metricIterator of pluginResults) {
|
||||
for await (let metric of metricIterator) {
|
||||
results.push(metric);
|
||||
@@ -48,8 +48,8 @@ async function getMetrics(req, res, next) {
|
||||
}
|
||||
|
||||
let requests = [];
|
||||
let timeout = req.app.locals.master.config.get("master.metrics_timeout") * 1000;
|
||||
for (let slaveConnection of req.app.locals.master.wsServer.slaveConnections.values()) {
|
||||
let timeout = req.app.locals.controller.config.get("controller.metrics_timeout") * 1000;
|
||||
for (let slaveConnection of req.app.locals.controller.wsServer.slaveConnections.values()) {
|
||||
if (!slaveConnection.connected) {
|
||||
continue;
|
||||
}
|
||||
@@ -98,10 +98,10 @@ async function getMetrics(req, res, next) {
|
||||
|
||||
function getPlugins(req, res) {
|
||||
let plugins = [];
|
||||
for (let pluginInfo of req.app.locals.master.pluginInfos) {
|
||||
for (let pluginInfo of req.app.locals.controller.pluginInfos) {
|
||||
let name = pluginInfo.name;
|
||||
let loaded = req.app.locals.master.plugins.has(name);
|
||||
let enabled = loaded && req.app.locals.master.config.group(pluginInfo.name).get("load_plugin");
|
||||
let loaded = req.app.locals.controller.plugins.has(name);
|
||||
let enabled = loaded && req.app.locals.controller.config.group(pluginInfo.name).get("load_plugin");
|
||||
let web = {};
|
||||
let devPlugins = req.app.locals.devPlugins;
|
||||
if (devPlugins && devPlugins.has(name)) {
|
||||
@@ -133,7 +133,7 @@ function validateSlaveToken(req, res, next) {
|
||||
try {
|
||||
jwt.verify(
|
||||
token,
|
||||
Buffer.from(req.app.locals.master.config.get("master.auth_secret"), "base64"),
|
||||
Buffer.from(req.app.locals.controller.config.get("controller.auth_secret"), "base64"),
|
||||
{ audience: "slave" }
|
||||
);
|
||||
|
||||
@@ -155,10 +155,10 @@ function validateUserToken(req, res, next) {
|
||||
try {
|
||||
let tokenPayload = jwt.verify(
|
||||
token,
|
||||
Buffer.from(req.app.locals.master.config.get("master.auth_secret"), "base64"),
|
||||
Buffer.from(req.app.locals.controller.config.get("controller.auth_secret"), "base64"),
|
||||
{ audience: "user" }
|
||||
);
|
||||
let user = req.app.locals.master.userManager.users.get(tokenPayload.user);
|
||||
let user = req.app.locals.controller.userManager.users.get(tokenPayload.user);
|
||||
if (!user) {
|
||||
throw new Error("invalid user");
|
||||
}
|
||||
@@ -191,7 +191,7 @@ async function uploadExport(req, res) {
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
let modPack = res.app.locals.master.modPacks.get(modPackId);
|
||||
let modPack = res.app.locals.controller.modPacks.get(modPackId);
|
||||
if (!modPack) {
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
@@ -234,7 +234,7 @@ async function uploadExport(req, res) {
|
||||
|
||||
modPack.exportManifest = new libData.ExportManifest({ assets });
|
||||
modPack.fillDefaultSettings(settingPrototypes, logger);
|
||||
res.app.locals.master.modPackUpdated(modPack);
|
||||
res.app.locals.controller.modPackUpdated(modPack);
|
||||
|
||||
res.sendStatus(200);
|
||||
}
|
||||
@@ -251,7 +251,7 @@ async function createProxyStream(app) {
|
||||
events: new events.EventEmitter(),
|
||||
timeout: setTimeout(() => {
|
||||
stream.events.emit("timeout");
|
||||
}, app.locals.master.config.get("master.proxy_stream_timeout") * 1000),
|
||||
}, app.locals.controller.config.get("controller.proxy_stream_timeout") * 1000),
|
||||
};
|
||||
stream.events.on("close", () => {
|
||||
clearTimeout(stream.timeout);
|
||||
@@ -362,7 +362,7 @@ async function uploadSave(req, res) {
|
||||
|
||||
try {
|
||||
let result = await Promise.race([
|
||||
req.app.locals.master.forwardRequestToInstance(libLink.messages.pullSave, {
|
||||
req.app.locals.controller.forwardRequestToInstance(libLink.messages.pullSave, {
|
||||
instance_id: instanceId,
|
||||
stream_id: proxyStream.id,
|
||||
filename,
|
||||
@@ -498,7 +498,7 @@ async function uploadMod(req, res) {
|
||||
return;
|
||||
}
|
||||
|
||||
const modsDirectory = req.app.locals.master.config.get("master.mods_directory");
|
||||
const modsDirectory = req.app.locals.controller.config.get("controller.mods_directory");
|
||||
let tempFilename = filename.replace(/(\.zip)?$/, ".tmp.zip");
|
||||
try {
|
||||
|
||||
@@ -521,8 +521,8 @@ async function uploadMod(req, res) {
|
||||
|
||||
const modInfo = await libData.ModInfo.fromModFile(path.join(modsDirectory, tempFilename));
|
||||
await fs.rename(path.join(modsDirectory, tempFilename), path.join(modsDirectory, modInfo.filename));
|
||||
req.app.locals.master.mods.set(modInfo.filename, modInfo);
|
||||
req.app.locals.master.modUpdated(modInfo);
|
||||
req.app.locals.controller.mods.set(modInfo.filename, modInfo);
|
||||
req.app.locals.controller.modUpdated(modInfo);
|
||||
mods.push(modInfo.toJSON());
|
||||
|
||||
} catch (err) {
|
||||
@@ -624,10 +624,10 @@ function addRouteHandlers(app) {
|
||||
);
|
||||
}
|
||||
|
||||
// Routes used in the web interface and served by the master server
|
||||
// Routes used in the web interface and served by the controller
|
||||
const webRoutes = [
|
||||
"/",
|
||||
"/master",
|
||||
"/controller",
|
||||
"/slaves",
|
||||
"/slaves/:id/view",
|
||||
"/instances",
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
@@ -16,7 +16,7 @@ module.exports = (env = {}) => merge(common(env), {
|
||||
},
|
||||
plugins: [
|
||||
new webpack.container.ModuleFederationPlugin({
|
||||
name: "master",
|
||||
name: "controller",
|
||||
shared: {
|
||||
"@clusterio/lib": { singleton: true },
|
||||
"@clusterio/web_ui": { singleton: true },
|
||||
+41
-41
@@ -110,11 +110,11 @@ async function execFile(cmd, args) {
|
||||
});
|
||||
}
|
||||
|
||||
async function execMaster(args) {
|
||||
async function execController(args) {
|
||||
if (dev) {
|
||||
return await execFile("node", [path.join("packages", "master"), ...args]);
|
||||
return await execFile("node", [path.join("packages", "controller"), ...args]);
|
||||
}
|
||||
return await execFile(path.join("node_modules", ".bin", `clusteriomaster${scriptExt}`), args);
|
||||
return await execFile(path.join("node_modules", ".bin", `clusteriocontroller${scriptExt}`), args);
|
||||
}
|
||||
|
||||
async function execSlave(args) {
|
||||
@@ -194,8 +194,8 @@ async function installClusterio(mode, plugins) {
|
||||
}
|
||||
|
||||
let components = [];
|
||||
if (["standalone", "master"].includes(mode)) {
|
||||
components.push("@clusterio/master");
|
||||
if (["standalone", "controller"].includes(mode)) {
|
||||
components.push("@clusterio/controller");
|
||||
}
|
||||
if (["standalone", "slave"].includes(mode)) {
|
||||
components.push("@clusterio/slave");
|
||||
@@ -250,22 +250,22 @@ async function groupIdToName(gid) {
|
||||
}
|
||||
|
||||
async function writeScripts(mode) {
|
||||
if (["standalone", "master"].includes(mode)) {
|
||||
if (["standalone", "controller"].includes(mode)) {
|
||||
if (process.platform === "win32") {
|
||||
await safeOutputFile(
|
||||
"run-master.cmd",
|
||||
"@echo off\n.\\node_modules\\.bin\\clusteriomaster.cmd run\n"
|
||||
"run-controller.cmd",
|
||||
"@echo off\n.\\node_modules\\.bin\\clusteriocontroller.cmd run\n"
|
||||
);
|
||||
} else {
|
||||
await safeOutputFile(
|
||||
"run-master.sh",
|
||||
"#!/bin/sh\nexec ./node_modules/.bin/clusteriomaster run\n",
|
||||
"run-controller.sh",
|
||||
"#!/bin/sh\nexec ./node_modules/.bin/clusteriocontroller run\n",
|
||||
{ mode: 0o755 },
|
||||
);
|
||||
await safeOutputFile(
|
||||
"systemd/clusteriomaster.service",
|
||||
"systemd/clusteriocontroller.service",
|
||||
`[Unit]
|
||||
Description=Clusterio Master
|
||||
Description=Clusterio Controller
|
||||
|
||||
[Service]
|
||||
User=${os.userInfo().username}
|
||||
@@ -273,7 +273,7 @@ Group=${await groupIdToName(os.userInfo().gid)}
|
||||
WorkingDirectory=${process.cwd()}
|
||||
KillMode=mixed
|
||||
KillSignal=SIGINT
|
||||
ExecStart=${process.cwd()}/node_modules/.bin/clusteriomaster run --log-level=warn
|
||||
ExecStart=${process.cwd()}/node_modules/.bin/clusteriocontroller run --log-level=warn
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -324,8 +324,8 @@ async function inquirerMissingArgs(args) {
|
||||
message: "Operating mode to install",
|
||||
default: "standalone",
|
||||
choices: [
|
||||
{ name: "Standalone (install both master and slave on this computer)", value: "standalone" },
|
||||
{ name: "Master only", value: "master" },
|
||||
{ name: "Standalone (install both controller and slave on this computer)", value: "standalone" },
|
||||
{ name: "Controller only", value: "controller" },
|
||||
{ name: "Slave only", value: "slave" },
|
||||
{ name: "Ctl only", value: "ctl" },
|
||||
{ name: "Plugins only", value: "plugins" },
|
||||
@@ -333,7 +333,7 @@ async function inquirerMissingArgs(args) {
|
||||
},
|
||||
], answers);
|
||||
|
||||
if (["standalone", "master"].includes(answers.mode)) {
|
||||
if (["standalone", "controller"].includes(answers.mode)) {
|
||||
if (args.admin) { answers.admin = args.admin; }
|
||||
answers = await inquirer.prompt([
|
||||
{
|
||||
@@ -362,27 +362,27 @@ async function inquirerMissingArgs(args) {
|
||||
}
|
||||
|
||||
if (["slave", "ctl"].includes(answers.mode)) {
|
||||
if (args.masterUrl) { answers.masterUrl = args.masterUrl; }
|
||||
if (args.controllerUrl) { answers.controllerUrl = args.controllerUrl; }
|
||||
answers = await inquirer.prompt([
|
||||
{
|
||||
type: "input",
|
||||
name: "masterUrl",
|
||||
message: "Master server URL",
|
||||
name: "controllerUrl",
|
||||
message: "Controller URL",
|
||||
},
|
||||
], answers);
|
||||
|
||||
if (args.masterToken) { answers.masterToken = args.masterToken; }
|
||||
if (args.controllerToken) { answers.controllerToken = args.controllerToken; }
|
||||
answers = await inquirer.prompt([
|
||||
{
|
||||
type: "input",
|
||||
name: "masterToken",
|
||||
message: "Master authentication Token",
|
||||
name: "controllerToken",
|
||||
message: "Controller authentication Token",
|
||||
},
|
||||
], answers);
|
||||
}
|
||||
|
||||
if (answers.mode === "slave") {
|
||||
validateSlaveToken(answers.masterToken);
|
||||
validateSlaveToken(answers.controllerToken);
|
||||
}
|
||||
|
||||
if (["standalone", "slave"].includes(answers.mode)) {
|
||||
@@ -532,19 +532,19 @@ async function main() {
|
||||
})
|
||||
.option("mode", {
|
||||
nargs: 1, describe: "Operating mode to install",
|
||||
choices: ["standalone", "master", "slave", "ctl", "plugins"],
|
||||
choices: ["standalone", "controller", "slave", "ctl", "plugins"],
|
||||
})
|
||||
.option("admin", {
|
||||
nargs: 1, describe: "Admin account name [standalone/master]", type: "string",
|
||||
nargs: 1, describe: "Admin account name [standalone/controller]", type: "string",
|
||||
})
|
||||
.option("slave-name", {
|
||||
nargs: 1, describe: "Slave name [slave]", type: "string",
|
||||
})
|
||||
.option("master-url", {
|
||||
nargs: 1, describe: "Master URL [slave/ctl]", type: "string",
|
||||
.option("controller-url", {
|
||||
nargs: 1, describe: "Controller URL [slave/ctl]", type: "string",
|
||||
})
|
||||
.option("master-token", {
|
||||
nargs: 1, describe: "Master authentication token [slave/ctl]", type: "string",
|
||||
.option("controller-token", {
|
||||
nargs: 1, describe: "Controller authentication token [slave/ctl]", type: "string",
|
||||
})
|
||||
.option("public-address", {
|
||||
nargs: 1, describe: "DNS/IP Address to connect to this server [standalone/slave]", type: "string",
|
||||
@@ -594,10 +594,10 @@ async function main() {
|
||||
}
|
||||
|
||||
let adminToken = null;
|
||||
if (["standalone", "master"].includes(answers.mode)) {
|
||||
logger.info("Setting up master");
|
||||
await execMaster(["bootstrap", "create-admin", answers.admin]);
|
||||
let result = await execMaster(["bootstrap", "generate-user-token", answers.admin]);
|
||||
if (["standalone", "controller"].includes(answers.mode)) {
|
||||
logger.info("Setting up controller");
|
||||
await execController(["bootstrap", "create-admin", answers.admin]);
|
||||
let result = await execController(["bootstrap", "generate-user-token", answers.admin]);
|
||||
adminToken = result.stdout.split("\n").slice(-2)[0];
|
||||
}
|
||||
|
||||
@@ -608,33 +608,33 @@ async function main() {
|
||||
let result = await execSlave(["config", "show", "slave.id"]);
|
||||
let slaveId = Number.parseInt(result.stdout.split("\n").slice(-2)[0], 10);
|
||||
|
||||
result = await execMaster(["bootstrap", "generate-slave-token", slaveId]);
|
||||
result = await execController(["bootstrap", "generate-slave-token", slaveId]);
|
||||
let slaveToken = result.stdout.split("\n").slice(-2)[0];
|
||||
|
||||
await execSlave(["config", "set", "slave.master_token", slaveToken]);
|
||||
await execSlave(["config", "set", "slave.controller_token", slaveToken]);
|
||||
await execSlave(["config", "set", "slave.public_address", answers.publicAddress]);
|
||||
await execSlave(["config", "set", "slave.factorio_directory", answers.factorioDir]);
|
||||
}
|
||||
|
||||
if (answers.mode === "slave") {
|
||||
logger.info("Setting up slave");
|
||||
let slaveId = JSON.parse(Buffer.from(answers.masterToken.split(".")[1], "base64")).slave;
|
||||
let slaveId = JSON.parse(Buffer.from(answers.controllerToken.split(".")[1], "base64")).slave;
|
||||
await execSlave(["config", "set", "slave.id", slaveId]);
|
||||
await execSlave(["config", "set", "slave.name", answers.slaveName]);
|
||||
await execSlave(["config", "set", "slave.master_url", answers.masterUrl]);
|
||||
await execSlave(["config", "set", "slave.master_token", answers.masterToken]);
|
||||
await execSlave(["config", "set", "slave.controller_url", answers.controllerUrl]);
|
||||
await execSlave(["config", "set", "slave.controller_token", answers.controllerToken]);
|
||||
await execSlave(["config", "set", "slave.public_address", answers.publicAddress]);
|
||||
await execSlave(["config", "set", "slave.factorio_directory", answers.factorioDir]);
|
||||
}
|
||||
|
||||
if (!dev && ["standalone", "master", "slave"].includes(answers.mode)) {
|
||||
if (!dev && ["standalone", "controller", "slave"].includes(answers.mode)) {
|
||||
logger.info("Writing run scripts");
|
||||
await writeScripts(answers.mode);
|
||||
}
|
||||
|
||||
if (answers.mode === "ctl") {
|
||||
await execCtl(["control-config", "set", "control.master_url", answers.masterUrl]);
|
||||
await execCtl(["control-config", "set", "control.master_token", answers.masterToken]);
|
||||
await execCtl(["control-config", "set", "control.controller_url", answers.controllerUrl]);
|
||||
await execCtl(["control-config", "set", "control.controller_token", answers.controllerToken]);
|
||||
}
|
||||
|
||||
/* eslint-disable no-console */
|
||||
|
||||
@@ -37,7 +37,7 @@ For example, installing the Subspace Storage plugin:
|
||||
npm install @clusterio/plugin-subspace_storage
|
||||
npx clusterioctl plugin add @clusterio/plugin-subspace_storage
|
||||
|
||||
Since the `plugin-list.json` is shared between master, slave and ctl you usually only need to do this once per machine.
|
||||
Since the `plugin-list.json` is shared between controller, slave and ctl you usually only need to do this once per machine.
|
||||
|
||||
|
||||
#### `plugin remove <name>`
|
||||
@@ -51,7 +51,7 @@ For example, uninstalling the Subspace Storage plugin:
|
||||
npx clusterioctl plugin remove subspace_storage
|
||||
npm uninstall @clusterio/plugin-subspace_storage
|
||||
|
||||
Since the `plugin-list.json` is shared between master, slave and ctl you usually only need to do this once per machine.
|
||||
Since the `plugin-list.json` is shared between controller, slave and ctl you usually only need to do this once per machine.
|
||||
|
||||
|
||||
#### `plugin list`
|
||||
@@ -62,7 +62,7 @@ Lists the plugins set up to be available by name followed by path.
|
||||
### `config-control`
|
||||
|
||||
Manage the ctl configuration.
|
||||
This allows setting the url and token to the master server to manage.
|
||||
This allows setting the url and token to the controller to manage.
|
||||
|
||||
|
||||
#### `config-control set <config-entry> [value]`
|
||||
|
||||
+11
-11
@@ -24,7 +24,7 @@ const commands = require("./src/commands");
|
||||
|
||||
|
||||
/**
|
||||
* Connector for control connection to master server
|
||||
* Connector for control connection to controller
|
||||
* @private
|
||||
*/
|
||||
class ControlConnector extends libLink.WebSocketClientConnector {
|
||||
@@ -46,22 +46,22 @@ class ControlConnector extends libLink.WebSocketClientConnector {
|
||||
/**
|
||||
* Handles running the control
|
||||
*
|
||||
* Connects to the master server over WebSocket and sends commands to it.
|
||||
* Connects to the controller over WebSocket and sends commands to it.
|
||||
* @static
|
||||
*/
|
||||
class Control extends libLink.Link {
|
||||
|
||||
constructor(connector, controlConfig, tlsCa, controlPlugins) {
|
||||
super("control", "master", connector);
|
||||
super("control", "controller", connector);
|
||||
libLink.attachAllMessages(this);
|
||||
|
||||
/**
|
||||
* Control config used for connecting to the master.
|
||||
* Control config used for connecting to the controller.
|
||||
* @type {module:lib/config.ControlConfig}
|
||||
*/
|
||||
this.config = controlConfig;
|
||||
/**
|
||||
* Certificate authority used to validate TLS connections to the master.
|
||||
* Certificate authority used to validate TLS connections to the controller.
|
||||
* @type {?string}
|
||||
*/
|
||||
this.tlsCa = tlsCa;
|
||||
@@ -97,13 +97,13 @@ class Control extends libLink.Link {
|
||||
|
||||
async setLogSubscriptions({
|
||||
all = false,
|
||||
master = false,
|
||||
controller = false,
|
||||
slave_ids = [],
|
||||
instance_ids = [],
|
||||
max_level = null,
|
||||
}) {
|
||||
await libLink.messages.setLogSubscriptions.send(this, {
|
||||
all, master, slave_ids, instance_ids, max_level,
|
||||
all, controller, slave_ids, instance_ids, max_level,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -251,8 +251,8 @@ async function startControl() {
|
||||
targetCommand = targetCommand.get(commandPath.shift());
|
||||
}
|
||||
|
||||
// The remaining commands require connecting to the master server.
|
||||
if (!controlConfig.get("control.master_url") || !controlConfig.get("control.master_token")) {
|
||||
// The remaining commands require connecting to the controller.
|
||||
if (!controlConfig.get("control.controller_url") || !controlConfig.get("control.controller_token")) {
|
||||
logger.error("Missing URL and/or token to connect with. See README.md for setting up access.");
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
@@ -265,10 +265,10 @@ async function startControl() {
|
||||
}
|
||||
|
||||
let controlConnector = new ControlConnector(
|
||||
controlConfig.get("control.master_url"),
|
||||
controlConfig.get("control.controller_url"),
|
||||
controlConfig.get("control.max_reconnect_delay"),
|
||||
tlsCa,
|
||||
controlConfig.get("control.master_token")
|
||||
controlConfig.get("control.controller_token")
|
||||
);
|
||||
let control = new Control(controlConnector, controlConfig, tlsCa, controlPlugins);
|
||||
try {
|
||||
|
||||
@@ -84,14 +84,14 @@ async function serializedConfigToString(serializedConfig, configGroup, disallowe
|
||||
return allConfigElements;
|
||||
}
|
||||
|
||||
const masterCommands = new libCommand.CommandTree({ name: "master", description: "Master management" });
|
||||
const masterConfigCommands = new libCommand.CommandTree({
|
||||
name: "config", alias: ["c"], description: "master config management",
|
||||
const controllerCommands = new libCommand.CommandTree({ name: "controller", description: "Controller management" });
|
||||
const controllerConfigCommands = new libCommand.CommandTree({
|
||||
name: "config", alias: ["c"], description: "controller config management",
|
||||
});
|
||||
masterConfigCommands.add(new libCommand.Command({
|
||||
definition: ["list", "List master configuration"],
|
||||
controllerConfigCommands.add(new libCommand.Command({
|
||||
definition: ["list", "List controller configuration"],
|
||||
handler: async function(args, control) {
|
||||
let response = await libLink.messages.getMasterConfig.send(control);
|
||||
let response = await libLink.messages.getControllerConfig.send(control);
|
||||
|
||||
for (let group of response.serialized_config.groups) {
|
||||
for (let [name, value] of Object.entries(group.fields)) {
|
||||
@@ -101,8 +101,8 @@ masterConfigCommands.add(new libCommand.Command({
|
||||
},
|
||||
}));
|
||||
|
||||
masterConfigCommands.add(new libCommand.Command({
|
||||
definition: ["set <field> [value]", "Set field in master config", (yargs) => {
|
||||
controllerConfigCommands.add(new libCommand.Command({
|
||||
definition: ["set <field> [value]", "Set field in controller config", (yargs) => {
|
||||
yargs.positional("field", { describe: "Field to set", type: "string" });
|
||||
yargs.positional("value", { describe: "Value to set", type: "string" });
|
||||
yargs.options({
|
||||
@@ -115,15 +115,15 @@ masterConfigCommands.add(new libCommand.Command({
|
||||
} else if (args.value === undefined) {
|
||||
args.value = "";
|
||||
}
|
||||
await libLink.messages.setMasterConfigField.send(control, {
|
||||
await libLink.messages.setControllerConfigField.send(control, {
|
||||
field: args.field,
|
||||
value: args.value,
|
||||
});
|
||||
},
|
||||
}));
|
||||
|
||||
masterConfigCommands.add(new libCommand.Command({
|
||||
definition: ["set-prop <field> <prop> [value]", "Set property of field in master config", (yargs) => {
|
||||
controllerConfigCommands.add(new libCommand.Command({
|
||||
definition: ["set-prop <field> <prop> [value]", "Set property of field in controller config", (yargs) => {
|
||||
yargs.positional("field", { describe: "Field to set", type: "string" });
|
||||
yargs.positional("prop", { describe: "Property to set", type: "string" });
|
||||
yargs.positional("value", { describe: "JSON parsed value to set", type: "string" });
|
||||
@@ -150,11 +150,11 @@ masterConfigCommands.add(new libCommand.Command({
|
||||
}
|
||||
request.value = args.value;
|
||||
}
|
||||
await libLink.messages.setMasterConfigProp.send(control, request);
|
||||
await libLink.messages.setControllerConfigProp.send(control, request);
|
||||
},
|
||||
}));
|
||||
masterConfigCommands.add(new libCommand.Command({
|
||||
definition: ["edit [editor]", "Edit master configuration", (yargs) => {
|
||||
controllerConfigCommands.add(new libCommand.Command({
|
||||
definition: ["edit [editor]", "Edit controller configuration", (yargs) => {
|
||||
yargs.positional("editor", {
|
||||
describe: "Editor to use",
|
||||
type: "string",
|
||||
@@ -162,14 +162,16 @@ masterConfigCommands.add(new libCommand.Command({
|
||||
});
|
||||
}],
|
||||
handler: async function(args, control) {
|
||||
let response = await libLink.messages.getMasterConfig.send(control);
|
||||
let response = await libLink.messages.getControllerConfig.send(control);
|
||||
let tmpFile = await libFileOps.getTempFile("ctl-", "-tmp", os.tmpdir());
|
||||
let editor = await getEditor(args.editor);
|
||||
if (editor === -1) {
|
||||
throw new libErrors.CommandError(`No editor avalible. Checked CLI input, EDITOR and VISUAL env vars
|
||||
Try "ctl master config edit <editor of choice>"`);
|
||||
Try "ctl controller config edit <editor of choice>"`);
|
||||
}
|
||||
let allConfigElements = await serializedConfigToString(response.serialized_config, libConfig.MasterConfig, {});
|
||||
let allConfigElements = await serializedConfigToString(
|
||||
response.serialized_config, libConfig.ControllerConfig, {}
|
||||
);
|
||||
await fs.writeFile(tmpFile, allConfigElements, (err) => {
|
||||
if (err) {
|
||||
throw err;
|
||||
@@ -189,7 +191,7 @@ masterConfigCommands.add(new libCommand.Command({
|
||||
for (let index in final) {
|
||||
if (index in final) {
|
||||
try {
|
||||
await libLink.messages.setMasterConfigField.send(control, {
|
||||
await libLink.messages.setControllerConfigField.send(control, {
|
||||
field: index,
|
||||
value: final[index],
|
||||
});
|
||||
@@ -213,16 +215,16 @@ masterConfigCommands.add(new libCommand.Command({
|
||||
},
|
||||
}));
|
||||
|
||||
masterCommands.add(masterConfigCommands);
|
||||
controllerCommands.add(controllerConfigCommands);
|
||||
|
||||
|
||||
const masterPluginCommands = new libCommand.CommandTree({
|
||||
name: "plugin", alias: ["p"], description: "master plugin inspection",
|
||||
const controllerPluginCommands = new libCommand.CommandTree({
|
||||
name: "plugin", alias: ["p"], description: "controller plugin inspection",
|
||||
});
|
||||
masterPluginCommands.add(new libCommand.Command({
|
||||
definition: ["list", "List plugins on master"],
|
||||
controllerPluginCommands.add(new libCommand.Command({
|
||||
definition: ["list", "List plugins on controller"],
|
||||
handler: async function(args, control) {
|
||||
let url = new URL(control.config.get("control.master_url"));
|
||||
let url = new URL(control.config.get("control.controller_url"));
|
||||
url.pathname += "api/plugins";
|
||||
let response = await phin({
|
||||
url,
|
||||
@@ -232,12 +234,12 @@ masterPluginCommands.add(new libCommand.Command({
|
||||
print(asTable(response.body));
|
||||
},
|
||||
}));
|
||||
masterCommands.add(masterPluginCommands);
|
||||
controllerCommands.add(controllerPluginCommands);
|
||||
|
||||
|
||||
const slaveCommands = new libCommand.CommandTree({ name: "slave", description: "Slave management" });
|
||||
slaveCommands.add(new libCommand.Command({
|
||||
definition: [["list", "l"], "List slaves connected to the master"],
|
||||
definition: [["list", "l"], "List slaves connected to the controller"],
|
||||
handler: async function(args, control) {
|
||||
let response = await libLink.messages.listSlaves.send(control);
|
||||
print(asTable(response.list));
|
||||
@@ -293,7 +295,7 @@ const instanceCommands = new libCommand.CommandTree({
|
||||
name: "instance", alias: ["i"], description: "Instance management",
|
||||
});
|
||||
instanceCommands.add(new libCommand.Command({
|
||||
definition: [["list", "l"], "List instances known to the master"],
|
||||
definition: [["list", "l"], "List instances known to the controller"],
|
||||
handler: async function(args, control) {
|
||||
let response = await libLink.messages.listInstances.send(control);
|
||||
print(asTable(response.list));
|
||||
@@ -315,7 +317,7 @@ instanceCommands.add(new libCommand.Command({
|
||||
instanceConfig.set("instance.id", args.id);
|
||||
}
|
||||
instanceConfig.set("instance.name", args.name);
|
||||
let serialized_config = instanceConfig.serialize("master");
|
||||
let serialized_config = instanceConfig.serialize("controller");
|
||||
await libLink.messages.createInstance.send(control, { serialized_config });
|
||||
},
|
||||
}));
|
||||
@@ -426,7 +428,7 @@ instanceConfigCommands.add(new libCommand.Command({
|
||||
let editor = await getEditor(args.editor);
|
||||
if (editor === -1) {
|
||||
throw new libErrors.CommandError(`No editor avalible. Checked CLI input, EDITOR and VISUAL env vars
|
||||
Try "ctl master 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 allConfigElements = await serializedConfigToString(
|
||||
@@ -614,7 +616,7 @@ instanceSaveCommands.add(new libCommand.Command({
|
||||
let content = await fs.readFile(args.filepath);
|
||||
|
||||
let instanceId = await libCommand.resolveInstance(control, args.instance);
|
||||
let url = new URL(control.config.get("control.master_url"));
|
||||
let url = new URL(control.config.get("control.controller_url"));
|
||||
url.pathname += "api/upload-save";
|
||||
url.searchParams.append("instance_id", instanceId);
|
||||
url.searchParams.append("filename", filename);
|
||||
@@ -622,7 +624,7 @@ instanceSaveCommands.add(new libCommand.Command({
|
||||
let result = await phin({
|
||||
url, method: "POST",
|
||||
headers: {
|
||||
"X-Access-Token": control.config.get("control.master_token"),
|
||||
"X-Access-Token": control.config.get("control.controller_token"),
|
||||
"Content-Type": "application/zip",
|
||||
},
|
||||
core: { ca: control.tlsCa },
|
||||
@@ -688,7 +690,7 @@ instanceSaveCommands.add(new libCommand.Command({
|
||||
save: args.save,
|
||||
});
|
||||
|
||||
let url = new URL(control.config.get("control.master_url"));
|
||||
let url = new URL(control.config.get("control.controller_url"));
|
||||
url.pathname += `api/stream/${result.stream_id}`;
|
||||
let response = await phin({
|
||||
url, method: "GET",
|
||||
@@ -1233,14 +1235,14 @@ modCommands.add(new libCommand.Command({
|
||||
// phin doesn't support streaming requests :(
|
||||
let content = await fs.readFile(args.file);
|
||||
|
||||
let url = new URL(control.config.get("control.master_url"));
|
||||
let url = new URL(control.config.get("control.controller_url"));
|
||||
url.pathname += "api/upload-mod";
|
||||
url.searchParams.append("filename", filename);
|
||||
|
||||
let result = await phin({
|
||||
url, method: "POST",
|
||||
headers: {
|
||||
"X-Access-Token": control.config.get("control.master_token"),
|
||||
"X-Access-Token": control.config.get("control.controller_token"),
|
||||
"Content-Type": "application/zip",
|
||||
},
|
||||
core: { ca: control.tlsCa },
|
||||
@@ -1278,7 +1280,7 @@ modCommands.add(new libCommand.Command({
|
||||
version: args.modVersion,
|
||||
});
|
||||
|
||||
let url = new URL(control.config.get("control.master_url"));
|
||||
let url = new URL(control.config.get("control.controller_url"));
|
||||
url.pathname += `api/stream/${result.stream_id}`;
|
||||
let response = await phin({
|
||||
url, method: "GET",
|
||||
@@ -1583,20 +1585,20 @@ logCommands.add(new libCommand.Command({
|
||||
definition: ["follow", "follow cluster log", (yargs) => {
|
||||
yargs.options({
|
||||
"all": { describe: "Follow the whole cluster log", nargs: 0, type: "boolean", default: false },
|
||||
"master": { describe: "Follow log of the master server", 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 },
|
||||
"instance": { describe: "Follow log of given instance", nargs: 1, type: "string", default: null },
|
||||
});
|
||||
}],
|
||||
handler: async function(args, control) {
|
||||
if (!args.all && !args.master && !args.slave && !args.instance) {
|
||||
logger.error("At least one of --all, --master, --slave and --instance must be passed");
|
||||
if (!args.all && !args.controller && !args.slave && !args.instance) {
|
||||
logger.error("At least one of --all, --controller, --slave and --instance must be passed");
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
let instance_ids = args.instance ? [await libCommand.resolveInstance(control, args.instance)] : [];
|
||||
let slave_ids = args.slave ? [await libCommand.resolveSlave(control, args.slave)] : [];
|
||||
await control.setLogSubscriptions({ all: args.all, master: args.master, slave_ids, instance_ids });
|
||||
await control.setLogSubscriptions({ all: args.all, controller: args.controller, slave_ids, instance_ids });
|
||||
control.keepOpen = true;
|
||||
},
|
||||
}));
|
||||
@@ -1605,7 +1607,7 @@ logCommands.add(new libCommand.Command({
|
||||
definition: ["query", "Query cluster log", (yargs) => {
|
||||
yargs.options({
|
||||
"all": { describe: "Query the whole cluster log", nargs: 0, type: "boolean", default: false },
|
||||
"master": { describe: "Query log of the master server", 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 },
|
||||
"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 },
|
||||
@@ -1614,8 +1616,8 @@ logCommands.add(new libCommand.Command({
|
||||
});
|
||||
}],
|
||||
handler: async function(args, control) {
|
||||
if (!args.all && !args.master && !args.slave && !args.instance) {
|
||||
logger.error("At least one of --all, --master, --slave and --instance must be passed");
|
||||
if (!args.all && !args.controller && !args.slave && !args.instance) {
|
||||
logger.error("At least one of --all, --controller, --slave and --instance must be passed");
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
@@ -1623,7 +1625,7 @@ logCommands.add(new libCommand.Command({
|
||||
let slave_ids = args.slave ? [await libCommand.resolveSlave(control, args.slave)] : [];
|
||||
let result = await libLink.messages.queryLog.send(control, {
|
||||
all: args.all,
|
||||
master: args.master,
|
||||
controller: args.controller,
|
||||
slave_ids,
|
||||
instance_ids,
|
||||
max_level: args.maxLevel,
|
||||
@@ -1651,7 +1653,7 @@ logCommands.add(new libCommand.Command({
|
||||
|
||||
const debugCommands = new libCommand.CommandTree({ name: "debug", description: "Debugging utilities" });
|
||||
debugCommands.add(new libCommand.Command({
|
||||
definition: ["dump-ws", "Dump WebSocket messages sent and received by master", (yargs) => { }],
|
||||
definition: ["dump-ws", "Dump WebSocket messages sent and received by controller", (yargs) => { }],
|
||||
handler: async function(args, control) {
|
||||
await libLink.messages.debugDumpWs.send(control);
|
||||
control.keepOpen = true;
|
||||
@@ -1660,7 +1662,7 @@ debugCommands.add(new libCommand.Command({
|
||||
|
||||
async function registerCommands(controlPlugins, yargs) {
|
||||
const rootCommands = new libCommand.CommandTree({ name: "clusterioctl", description: "Manage cluster" });
|
||||
rootCommands.add(masterCommands);
|
||||
rootCommands.add(controllerCommands);
|
||||
rootCommands.add(slaveCommands);
|
||||
rootCommands.add(instanceCommands);
|
||||
rootCommands.add(modPackCommands);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Common Library for Clusterio
|
||||
|
||||
This package contains functionality shared between master, slave, ctl and/or plugins of Clusterio.
|
||||
This package contains functionality shared between controller, slave, ctl and/or plugins of Clusterio.
|
||||
|
||||
See [the Clusterio repository](https://github.com/clusterio/clusterio) for more information on Clusterio.
|
||||
@@ -140,7 +140,7 @@ class CommandTree {
|
||||
* Resolves a string with either an slave name or an id into an integer with
|
||||
* the slave ID.
|
||||
*
|
||||
* @param {module:lib/link.Link} client - link to master server to query slave on.
|
||||
* @param {module:lib/link.Link} client - link to controller to query slave on.
|
||||
* @param {string} slaveName - string with name or id of slave.
|
||||
* @returns {Promise<number>} slave ID.
|
||||
* @static
|
||||
@@ -172,7 +172,7 @@ async function resolveSlave(client, slaveName) {
|
||||
* Resolves a string with either an instance name or an id into an integer
|
||||
* with the instance ID.
|
||||
*
|
||||
* @param {module:lib/link.Link} client - link to master server to query instance on.
|
||||
* @param {module:lib/link.Link} client - link to controller to query instance on.
|
||||
* @param {string} instanceName - string with name or id of instance.
|
||||
* @returns {Promise<number>} instance ID.
|
||||
* @static
|
||||
@@ -205,7 +205,7 @@ async function resolveInstance(client, instanceName) {
|
||||
* with the mod pack ID.
|
||||
*
|
||||
* @param {module:lib/link.Link} client -
|
||||
* link to master server to query mod pack on.
|
||||
* link to controller to query mod pack on.
|
||||
* @param {string} modPackName - string with name or id of mod pack.
|
||||
* @returns {Promise<number>} mod pack ID.
|
||||
* @static
|
||||
@@ -237,7 +237,7 @@ async function resolveModPack(client, modPackName) {
|
||||
* Resolves a string with either a role name or an id into an object
|
||||
* representing the role.
|
||||
*
|
||||
* @param {module:lib/link.Link} client - link to master server to query role on.
|
||||
* @param {module:lib/link.Link} client - link to controller to query role on.
|
||||
* @param {string} roleName - string with name or id of role.
|
||||
* @returns {Promise<object>} Role info.
|
||||
* @static
|
||||
|
||||
@@ -5,35 +5,35 @@ const classes = require("./classes");
|
||||
|
||||
|
||||
/**
|
||||
* Master config group for {@link module:lib/config.MasterConfig}
|
||||
* Controller config group for {@link module:lib/config.ControllerConfig}
|
||||
* @extends module:lib/config.ConfigGroup
|
||||
* @memberof module:lib/config
|
||||
*/
|
||||
class MasterGroup extends classes.ConfigGroup { }
|
||||
MasterGroup.defaultAccess = ["master", "slave", "control"];
|
||||
MasterGroup.groupName = "master";
|
||||
MasterGroup.define({
|
||||
class ControllerGroup extends classes.ConfigGroup { }
|
||||
ControllerGroup.defaultAccess = ["controller", "slave", "control"];
|
||||
ControllerGroup.groupName = "controller";
|
||||
ControllerGroup.define({
|
||||
name: "name",
|
||||
title: "Name",
|
||||
description: "Name of the cluster.",
|
||||
type: "string",
|
||||
initial_value: "Your Cluster",
|
||||
});
|
||||
MasterGroup.define({
|
||||
ControllerGroup.define({
|
||||
name: "mods_directory",
|
||||
title: "Mods Directory",
|
||||
description: "Path to directory where mods shared with the cluster are stored.",
|
||||
type: "string",
|
||||
initial_value: "mods",
|
||||
});
|
||||
MasterGroup.define({
|
||||
ControllerGroup.define({
|
||||
name: "database_directory",
|
||||
title: "Database directory",
|
||||
description: "Directory where item and configuration data is stored.",
|
||||
type: "string",
|
||||
initial_value: "database",
|
||||
});
|
||||
MasterGroup.define({
|
||||
ControllerGroup.define({
|
||||
name: "http_port",
|
||||
title: "HTTP Port",
|
||||
description: "Port to listen for HTTP connections on, set to null to not listen for HTTP connections.",
|
||||
@@ -42,7 +42,7 @@ MasterGroup.define({
|
||||
optional: true,
|
||||
initial_value: 8080,
|
||||
});
|
||||
MasterGroup.define({
|
||||
ControllerGroup.define({
|
||||
name: "https_port",
|
||||
title: "HTTPS Port",
|
||||
description: "Port to listen for HTTPS connection on, set to null to not listen for HTTPS connections.",
|
||||
@@ -50,7 +50,7 @@ MasterGroup.define({
|
||||
type: "number",
|
||||
optional: true,
|
||||
});
|
||||
MasterGroup.define({
|
||||
ControllerGroup.define({
|
||||
name: "bind_address",
|
||||
title: "Bind Address",
|
||||
description: "IP address to bind the HTTP and HTTPS ports on.",
|
||||
@@ -58,14 +58,14 @@ MasterGroup.define({
|
||||
type: "string",
|
||||
optional: true,
|
||||
});
|
||||
MasterGroup.define({
|
||||
ControllerGroup.define({
|
||||
name: "external_address",
|
||||
title: "External Address",
|
||||
description: "Public facing address the master server is hosted on.",
|
||||
description: "Public facing address the controller is hosted on.",
|
||||
type: "string",
|
||||
optional: true,
|
||||
});
|
||||
MasterGroup.define({
|
||||
ControllerGroup.define({
|
||||
name: "tls_certificate",
|
||||
title: "TLS Certificate",
|
||||
description: "Path to the certificate to use for HTTPS.",
|
||||
@@ -73,7 +73,7 @@ MasterGroup.define({
|
||||
type: "string",
|
||||
optional: true,
|
||||
});
|
||||
MasterGroup.define({
|
||||
ControllerGroup.define({
|
||||
name: "tls_private_key",
|
||||
title: "TLS Private Key",
|
||||
description: "Path to the private key to use for HTTPS.",
|
||||
@@ -81,10 +81,10 @@ MasterGroup.define({
|
||||
type: "string",
|
||||
optional: true,
|
||||
});
|
||||
MasterGroup.define({
|
||||
access: ["master"],
|
||||
ControllerGroup.define({
|
||||
access: ["controller"],
|
||||
name: "auth_secret",
|
||||
title: "Master Authentication Secret",
|
||||
title: "Controller Authentication Secret",
|
||||
description:
|
||||
"Secret used to generate and verify authentication tokens. " +
|
||||
"Should be a long string of random letters and numbers. " +
|
||||
@@ -93,42 +93,42 @@ MasterGroup.define({
|
||||
type: "string",
|
||||
optional: true,
|
||||
});
|
||||
MasterGroup.define({
|
||||
ControllerGroup.define({
|
||||
name: "heartbeat_interval",
|
||||
title: "Heartbeat Interval",
|
||||
description: "Interval heartbeats are sent out on WebSocket connections.",
|
||||
type: "number",
|
||||
initial_value: 15,
|
||||
});
|
||||
MasterGroup.define({
|
||||
ControllerGroup.define({
|
||||
name: "session_timeout",
|
||||
title: "Session Timeout",
|
||||
description: "Time in seconds before giving up resuming a dropped WebSocket session.",
|
||||
type: "number",
|
||||
initial_value: 60,
|
||||
});
|
||||
MasterGroup.define({
|
||||
ControllerGroup.define({
|
||||
name: "metrics_timeout",
|
||||
title: "Metrics Timeout",
|
||||
description: "Timeout in seconds for metrics gathering from slaves.",
|
||||
type: "number",
|
||||
initial_value: 8,
|
||||
});
|
||||
MasterGroup.define({
|
||||
ControllerGroup.define({
|
||||
name: "proxy_stream_timeout",
|
||||
title: "Proxy Stream Timeout",
|
||||
description: "Timeout in seconds for proxy streams to start flowing.",
|
||||
type: "number",
|
||||
initial_value: 15,
|
||||
});
|
||||
MasterGroup.define({
|
||||
ControllerGroup.define({
|
||||
name: "default_mod_pack_id",
|
||||
title: "Default Mod Pack",
|
||||
description: "Mod pack used by default for instances.",
|
||||
type: "number",
|
||||
optional: true,
|
||||
});
|
||||
MasterGroup.define({
|
||||
ControllerGroup.define({
|
||||
name: "default_role_id",
|
||||
title: "Default role",
|
||||
description: "ID of role assigned by default to new users.",
|
||||
@@ -136,15 +136,15 @@ MasterGroup.define({
|
||||
optional: true,
|
||||
initial_value: 1,
|
||||
});
|
||||
MasterGroup.finalize();
|
||||
ControllerGroup.finalize();
|
||||
|
||||
/**
|
||||
* Master Config class
|
||||
* Controller Config class
|
||||
* @extends module:lib/config.Config
|
||||
* @memberof module:lib/config
|
||||
*/
|
||||
class MasterConfig extends classes.Config { }
|
||||
MasterConfig.registerGroup(MasterGroup);
|
||||
class ControllerConfig extends classes.Config { }
|
||||
ControllerConfig.registerGroup(ControllerGroup);
|
||||
|
||||
|
||||
/**
|
||||
@@ -153,7 +153,7 @@ MasterConfig.registerGroup(MasterGroup);
|
||||
* @memberof module:lib/config
|
||||
*/
|
||||
class SlaveGroup extends classes.ConfigGroup {}
|
||||
SlaveGroup.defaultAccess = ["master", "slave", "control"];
|
||||
SlaveGroup.defaultAccess = ["controller", "slave", "control"];
|
||||
SlaveGroup.groupName = "slave";
|
||||
SlaveGroup.define({
|
||||
name: "name",
|
||||
@@ -183,7 +183,7 @@ SlaveGroup.define({
|
||||
SlaveGroup.define({
|
||||
name: "mods_directory_is_shared",
|
||||
title: "Mods Directory is Shared",
|
||||
description: "True if the mods directory is shared with the master server.",
|
||||
description: "True if the mods directory is shared with the controller.",
|
||||
type: "boolean",
|
||||
initial_value: true,
|
||||
});
|
||||
@@ -195,22 +195,22 @@ SlaveGroup.define({
|
||||
initial_value: "instances",
|
||||
});
|
||||
SlaveGroup.define({
|
||||
name: "master_url",
|
||||
description: "URL to connect to the master server at",
|
||||
name: "controller_url",
|
||||
description: "URL to connect to the controller at",
|
||||
restartRequired: true,
|
||||
type: "string",
|
||||
initial_value: "http://localhost:8080/",
|
||||
});
|
||||
SlaveGroup.define({
|
||||
name: "master_token",
|
||||
description: "Token to authenticate to master server with.",
|
||||
name: "controller_token",
|
||||
description: "Token to authenticate to controller with.",
|
||||
restartRequired: true,
|
||||
type: "string",
|
||||
initial_value: "enter token here",
|
||||
});
|
||||
SlaveGroup.define({
|
||||
name: "tls_ca",
|
||||
description: "Path to Certificate Authority to validate TLS connection to master against.",
|
||||
description: "Path to Certificate Authority to validate TLS connection to controller against.",
|
||||
restartRequired: true,
|
||||
type: "string",
|
||||
optional: true,
|
||||
@@ -245,7 +245,7 @@ SlaveConfig.registerGroup(SlaveGroup);
|
||||
* @memberof module:lib/config
|
||||
*/
|
||||
class InstanceGroup extends classes.ConfigGroup { }
|
||||
InstanceGroup.defaultAccess = ["master", "slave", "control"];
|
||||
InstanceGroup.defaultAccess = ["controller", "slave", "control"];
|
||||
InstanceGroup.groupName = "instance";
|
||||
InstanceGroup.define({
|
||||
name: "name",
|
||||
@@ -277,7 +277,7 @@ InstanceGroup.finalize();
|
||||
* @memberof module:lib/config
|
||||
*/
|
||||
class FactorioGroup extends classes.ConfigGroup { }
|
||||
FactorioGroup.defaultAccess = ["master", "slave", "control"];
|
||||
FactorioGroup.defaultAccess = ["controller", "slave", "control"];
|
||||
FactorioGroup.groupName = "factorio";
|
||||
FactorioGroup.define({
|
||||
name: "version",
|
||||
@@ -353,7 +353,7 @@ FactorioGroup.define({
|
||||
"require_user_verification", "tags", "visibility",
|
||||
],
|
||||
type: "object",
|
||||
initial_value: {}, // See create instance handler in master.
|
||||
initial_value: {}, // See create instance handler in controller.
|
||||
});
|
||||
FactorioGroup.define({
|
||||
name: "verbose_logging",
|
||||
@@ -371,19 +371,19 @@ FactorioGroup.define({
|
||||
});
|
||||
FactorioGroup.define({
|
||||
name: "sync_adminlist",
|
||||
description: "Synchronize adminlist with master server",
|
||||
description: "Synchronize adminlist with controller",
|
||||
type: "boolean",
|
||||
initial_value: true,
|
||||
});
|
||||
FactorioGroup.define({
|
||||
name: "sync_whitelist",
|
||||
description: "Synchronize whitelist with master server",
|
||||
description: "Synchronize whitelist with controller",
|
||||
type: "boolean",
|
||||
initial_value: true,
|
||||
});
|
||||
FactorioGroup.define({
|
||||
name: "sync_banlist",
|
||||
description: "Synchronize banlist with master server",
|
||||
description: "Synchronize banlist with controller",
|
||||
type: "boolean",
|
||||
initial_value: true,
|
||||
});
|
||||
@@ -415,20 +415,20 @@ class ControlGroup extends classes.ConfigGroup {}
|
||||
ControlGroup.defaultAccess = ["control"];
|
||||
ControlGroup.groupName = "control";
|
||||
ControlGroup.define({
|
||||
name: "master_url",
|
||||
description: "URL to connect to the master server at",
|
||||
name: "controller_url",
|
||||
description: "URL to connect to the controller at",
|
||||
type: "string",
|
||||
optional: true,
|
||||
});
|
||||
ControlGroup.define({
|
||||
name: "master_token",
|
||||
description: "Token to authenticate to master server with.",
|
||||
name: "controller_token",
|
||||
description: "Token to authenticate to controller with.",
|
||||
type: "string",
|
||||
optional: true,
|
||||
});
|
||||
ControlGroup.define({
|
||||
name: "tls_ca",
|
||||
description: "Path to Certificate Authority to validate TLS connection to master against.",
|
||||
description: "Path to Certificate Authority to validate TLS connection to controller against.",
|
||||
type: "string",
|
||||
optional: true,
|
||||
});
|
||||
@@ -472,16 +472,16 @@ function validateGroup(pluginInfo, groupName) {
|
||||
*/
|
||||
function registerPluginConfigGroups(pluginInfos) {
|
||||
for (let pluginInfo of pluginInfos) {
|
||||
if (pluginInfo.MasterConfigGroup) {
|
||||
validateGroup(pluginInfo, "MasterConfigGroup");
|
||||
MasterConfig.registerGroup(pluginInfo.MasterConfigGroup);
|
||||
if (pluginInfo.ControllerConfigGroup) {
|
||||
validateGroup(pluginInfo, "ControllerConfigGroup");
|
||||
ControllerConfig.registerGroup(pluginInfo.ControllerConfigGroup);
|
||||
|
||||
} else {
|
||||
class MasterConfigGroup extends classes.PluginConfigGroup { }
|
||||
MasterConfigGroup.defaultAccess = ["master", "slave", "control"];
|
||||
MasterConfigGroup.groupName = pluginInfo.name;
|
||||
MasterConfigGroup.finalize();
|
||||
MasterConfig.registerGroup(MasterConfigGroup);
|
||||
class ControllerConfigGroup extends classes.PluginConfigGroup { }
|
||||
ControllerConfigGroup.defaultAccess = ["controller", "slave", "control"];
|
||||
ControllerConfigGroup.groupName = pluginInfo.name;
|
||||
ControllerConfigGroup.finalize();
|
||||
ControllerConfig.registerGroup(ControllerConfigGroup);
|
||||
}
|
||||
|
||||
if (pluginInfo.instanceEntrypoint) {
|
||||
@@ -491,7 +491,7 @@ function registerPluginConfigGroups(pluginInfos) {
|
||||
|
||||
} else {
|
||||
class InstanceConfigGroup extends classes.PluginConfigGroup { }
|
||||
InstanceConfigGroup.defaultAccess = ["master", "slave", "control"];
|
||||
InstanceConfigGroup.defaultAccess = ["controller", "slave", "control"];
|
||||
InstanceConfigGroup.groupName = pluginInfo.name;
|
||||
InstanceConfigGroup.finalize();
|
||||
InstanceConfig.registerGroup(InstanceConfigGroup);
|
||||
@@ -506,20 +506,20 @@ function registerPluginConfigGroups(pluginInfos) {
|
||||
* @memberof module:lib/config
|
||||
*/
|
||||
function finalizeConfigs() {
|
||||
MasterConfig.finalize();
|
||||
ControllerConfig.finalize();
|
||||
SlaveConfig.finalize();
|
||||
InstanceConfig.finalize();
|
||||
ControlConfig.finalize();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
MasterGroup,
|
||||
ControllerGroup,
|
||||
SlaveGroup,
|
||||
InstanceGroup,
|
||||
FactorioGroup,
|
||||
ControlGroup,
|
||||
|
||||
MasterConfig,
|
||||
ControllerConfig,
|
||||
SlaveConfig,
|
||||
InstanceConfig,
|
||||
ControlConfig,
|
||||
|
||||
@@ -11,7 +11,7 @@ const { integerModVersion, integerFactorioVersion, modVersionRegExp } = require(
|
||||
|
||||
|
||||
/**
|
||||
* Info about a mod available on the master server.
|
||||
* Info about a mod available on the controller.
|
||||
*
|
||||
* See https://wiki.factorio.com/Tutorial:Mod_structure#info.json for
|
||||
* details of the invidiual fields sourced from info.json.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Implementation of master databases
|
||||
* Implementation of controller databases
|
||||
*
|
||||
* @module lib/database
|
||||
* @author Hornwitser
|
||||
@@ -159,7 +159,7 @@ function checkCount(count) {
|
||||
/**
|
||||
* Item database
|
||||
*
|
||||
* Stores counts of items for the master server. Items that have not been
|
||||
* Stores counts of items for the controller. Items that have not been
|
||||
* stored in the database are treated as having a stored count of zero. When
|
||||
* serialized the database discards entries with a zero counts, and when
|
||||
* deserialized the content is verified.
|
||||
|
||||
@@ -179,7 +179,7 @@ class WebSocketBaseConnector extends events.EventEmitter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Connector for master server clients
|
||||
* Connector for controller clients
|
||||
*
|
||||
* @extends module:lib/link.WebSocketBaseConnector
|
||||
* @memberof module:lib/link
|
||||
@@ -278,7 +278,7 @@ class WebSocketClientConnector extends WebSocketBaseConnector {
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to the master server
|
||||
* Connect to the controller
|
||||
*/
|
||||
async connect() {
|
||||
this._check("closed");
|
||||
@@ -294,7 +294,7 @@ class WebSocketClientConnector extends WebSocketBaseConnector {
|
||||
let url = new URL(this._url);
|
||||
url.pathname += "api/socket";
|
||||
|
||||
// Open WebSocket to master
|
||||
// Open WebSocket to controller
|
||||
logger.verbose(`Connector | connecting to ${url}`);
|
||||
|
||||
// eslint-disable-next-line node/no-process-env
|
||||
@@ -447,7 +447,7 @@ class WebSocketClientConnector extends WebSocketBaseConnector {
|
||||
|
||||
let { type, data } = message;
|
||||
if (type === "hello") {
|
||||
logger.verbose(`Connector | received hello from master version ${data.version}`);
|
||||
logger.verbose(`Connector | received hello from controller version ${data.version}`);
|
||||
this.emit("hello", data);
|
||||
if (this._sessionToken) {
|
||||
logger.verbose("Connector | Attempting resume");
|
||||
@@ -460,7 +460,7 @@ class WebSocketClientConnector extends WebSocketBaseConnector {
|
||||
}
|
||||
|
||||
} else if (type === "ready") {
|
||||
logger.verbose("Connector | received ready from master");
|
||||
logger.verbose("Connector | received ready from controller");
|
||||
this._state = "connected";
|
||||
this._sessionToken = data.session_token;
|
||||
this._sessionTimeout = data.session_timeout;
|
||||
@@ -485,7 +485,7 @@ class WebSocketClientConnector extends WebSocketBaseConnector {
|
||||
this.emit("resume");
|
||||
|
||||
} else if (type === "invalidate") {
|
||||
logger.warn("Connector | session invalidated by master");
|
||||
logger.warn("Connector | session invalidated by controller");
|
||||
this._state = "connecting";
|
||||
this._seq = 1;
|
||||
this._lastReceivedSeq = null;
|
||||
|
||||
@@ -6,10 +6,10 @@ const libErrors = require("../errors");
|
||||
const { logger } = require("../logging");
|
||||
|
||||
// Some definitions for the terminology used here:
|
||||
// link: Either side of a master - client connection
|
||||
// link: Either side of a controller - client connection
|
||||
// connector: Adapter between a link and a socket connection
|
||||
// connection: the master server side of a link
|
||||
// client: the side that is not the master server of a link
|
||||
// connection: the controller side of a link
|
||||
// client: the side that is not the controller of a link
|
||||
// message: the complete object sent using the 'message' event
|
||||
// data: the data property of a message, essentially the payload.
|
||||
// type: the type property of a message, identifies what to expect in data.
|
||||
|
||||
+124
-122
@@ -67,11 +67,11 @@ class Request extends Message {
|
||||
* containing "<source>-<target>" specifications.
|
||||
* @param {?string} permission -
|
||||
* Permission required to send this request. Only applies to
|
||||
* requests sent from control to master.
|
||||
* requests sent from control to controller.
|
||||
* @param {?string} forwardTo -
|
||||
* Optional target to forward this request to. 'instance' add
|
||||
* instance_id into the requestProperties and forward to the given
|
||||
* instance. 'master' forwards it to the master server.
|
||||
* instance. 'controller' forwards it to the controller.
|
||||
* @param {?Array<string>} requestRequired -
|
||||
* List of properties that are required to be present in the data
|
||||
* payload of this request. Defaults to all.
|
||||
@@ -101,11 +101,13 @@ class Request extends Message {
|
||||
this.requestType = `${type}_request`;
|
||||
this.responseType = `${type}_response`;
|
||||
|
||||
if (permission === undefined && links.includes("control-master")) {
|
||||
throw new Error(`permission is required for ${this.type} request over control-master links`);
|
||||
if (permission === undefined && links.includes("control-controller")) {
|
||||
throw new Error(`permission is required for ${this.type} request over control-controller links`);
|
||||
}
|
||||
if (permission && !links.includes("control-master")) {
|
||||
throw new Error(`permission is not allowed on ${this.type} request as it is not over control-master link`);
|
||||
if (permission && !links.includes("control-controller")) {
|
||||
throw new Error(
|
||||
`permission is not allowed on ${this.type} request as it is not over control-controller link`
|
||||
);
|
||||
}
|
||||
|
||||
if (!requestRequired) {
|
||||
@@ -119,7 +121,7 @@ class Request extends Message {
|
||||
...requestProperties,
|
||||
};
|
||||
|
||||
} else if (forwardTo !== "master" && forwardTo !== null) {
|
||||
} else if (forwardTo !== "controller" && forwardTo !== null) {
|
||||
throw new Error(`Invalid forwardTo value ${forwardTo}`);
|
||||
}
|
||||
|
||||
@@ -176,7 +178,7 @@ class Request extends Message {
|
||||
* If forwardTo was set to 'instance' the `forwardRequestToInstance`
|
||||
* method on the link is used as the default handler.
|
||||
*
|
||||
* If forwardTo was set to 'master' the `forwardRequesToMaster` method
|
||||
* If forwardTo was set to 'controller' the `forwardRequesToController` method
|
||||
* on the link is used as the default handler.
|
||||
*
|
||||
* Does nothing if the link is neither a source nor a target for this
|
||||
@@ -201,8 +203,8 @@ class Request extends Message {
|
||||
if (!handler) {
|
||||
if (this.forwardTo === "instance") {
|
||||
handler = link.forwardRequestToInstance;
|
||||
} else if (this.forwardTo === "master") {
|
||||
handler = link.forwardRequestToMaster;
|
||||
} else if (this.forwardTo === "controller") {
|
||||
handler = link.forwardRequestToController;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,8 +212,8 @@ class Request extends Message {
|
||||
throw new MissingLinkHandlerError(this.requestType, link.source, link.target);
|
||||
}
|
||||
|
||||
// Check permission if this is a handler for a control connection on the master
|
||||
if (this.permission !== null && `${link.source}-${link.target}` === "master-control") {
|
||||
// Check permission if this is a handler for a control connection on the controller
|
||||
if (this.permission !== null && `${link.source}-${link.target}` === "controller-control") {
|
||||
let origHandler = handler;
|
||||
handler = async function(message, request) {
|
||||
// Abuse this binding to get a hold of the ControlConnection instance
|
||||
@@ -276,15 +278,15 @@ class Request extends Message {
|
||||
let messages = {};
|
||||
|
||||
// Connection requests
|
||||
let wsLinks = ["master-control", "control-master", "master-slave", "slave-master"];
|
||||
let wsLinks = ["controller-control", "control-controller", "controller-slave", "slave-controller"];
|
||||
messages.prepareDisconnect = new Request({
|
||||
type: "prepare_disconnect",
|
||||
links: wsLinks,
|
||||
permission: null,
|
||||
});
|
||||
|
||||
messages.prepareMasterDisconnect = new Request({
|
||||
type: "prepare_master_disconnect",
|
||||
messages.prepareControllerDisconnect = new Request({
|
||||
type: "prepare_controller_disconnect",
|
||||
links: ["slave-instance"],
|
||||
});
|
||||
|
||||
@@ -296,35 +298,35 @@ messages.ping = new Request({
|
||||
|
||||
messages.debugDumpWs = new Request({
|
||||
type: "debug_dump_ws",
|
||||
links: ["control-master"],
|
||||
links: ["control-controller"],
|
||||
permission: "core.debug.dump_ws",
|
||||
});
|
||||
|
||||
|
||||
// Management requests
|
||||
messages.getMasterConfig = new Request({
|
||||
type: "get_master_config",
|
||||
links: ["control-master"],
|
||||
permission: "core.master.get_config",
|
||||
messages.getControllerConfig = new Request({
|
||||
type: "get_controller_config",
|
||||
links: ["control-controller"],
|
||||
permission: "core.controller.get_config",
|
||||
responseProperties: {
|
||||
"serialized_config": { type: "object" },
|
||||
},
|
||||
});
|
||||
|
||||
messages.setMasterConfigField = new Request({
|
||||
type: "set_master_config_field",
|
||||
links: ["control-master"],
|
||||
permission: "core.master.update_config",
|
||||
messages.setControllerConfigField = new Request({
|
||||
type: "set_controller_config_field",
|
||||
links: ["control-controller"],
|
||||
permission: "core.controller.update_config",
|
||||
requestProperties: {
|
||||
"field": { type: "string" },
|
||||
"value": { type: "string" },
|
||||
},
|
||||
});
|
||||
|
||||
messages.setMasterConfigProp = new Request({
|
||||
type: "set_master_config_prop",
|
||||
links: ["control-master"],
|
||||
permission: "core.master.update_config",
|
||||
messages.setControllerConfigProp = new Request({
|
||||
type: "set_controller_config_prop",
|
||||
links: ["control-controller"],
|
||||
permission: "core.controller.update_config",
|
||||
requestRequired: ["field", "prop"],
|
||||
requestProperties: {
|
||||
"field": { type: "string" },
|
||||
@@ -344,7 +346,7 @@ let slaveProperties = {
|
||||
|
||||
messages.listSlaves = new Request({
|
||||
type: "list_slaves",
|
||||
links: ["control-master"],
|
||||
links: ["control-controller"],
|
||||
permission: "core.slave.list",
|
||||
responseProperties: {
|
||||
"list": {
|
||||
@@ -360,7 +362,7 @@ messages.listSlaves = new Request({
|
||||
|
||||
messages.setSlaveSubscriptions = new Request({
|
||||
type: "set_slave_subscriptions",
|
||||
links: ["control-master"],
|
||||
links: ["control-controller"],
|
||||
permission: "core.slave.subscribe",
|
||||
requestProperties: {
|
||||
"all": { type: "boolean" },
|
||||
@@ -384,7 +386,7 @@ let instanceProperties = {
|
||||
|
||||
messages.getInstance = new Request({
|
||||
type: "get_instance",
|
||||
links: ["control-master"],
|
||||
links: ["control-controller"],
|
||||
permission: "core.instance.get",
|
||||
requestProperties: {
|
||||
"id": { type: "integer" },
|
||||
@@ -394,7 +396,7 @@ messages.getInstance = new Request({
|
||||
|
||||
messages.listInstances = new Request({
|
||||
type: "list_instances",
|
||||
links: ["control-master"],
|
||||
links: ["control-controller"],
|
||||
permission: "core.instance.list",
|
||||
responseProperties: {
|
||||
"list": {
|
||||
@@ -410,7 +412,7 @@ messages.listInstances = new Request({
|
||||
|
||||
messages.setInstanceSubscriptions = new Request({
|
||||
type: "set_instance_subscriptions",
|
||||
links: ["control-master"],
|
||||
links: ["control-controller"],
|
||||
permission: "core.instance.subscribe",
|
||||
requestProperties: {
|
||||
"all": { type: "boolean" },
|
||||
@@ -423,7 +425,7 @@ messages.setInstanceSubscriptions = new Request({
|
||||
|
||||
messages.generateSlaveToken = new Request({
|
||||
type: "generate_slave_token",
|
||||
links: ["control-master"],
|
||||
links: ["control-controller"],
|
||||
permission: "core.slave.generate_token",
|
||||
requestProperties: {
|
||||
"slave_id": { type: ["integer", "null"] },
|
||||
@@ -435,7 +437,7 @@ messages.generateSlaveToken = new Request({
|
||||
|
||||
messages.createSlaveConfig = new Request({
|
||||
type: "create_slave_config",
|
||||
links: ["control-master"],
|
||||
links: ["control-controller"],
|
||||
permission: "core.slave.create_config",
|
||||
requestProperties: {
|
||||
"id": { type: ["integer", "null"] },
|
||||
@@ -449,7 +451,7 @@ messages.createSlaveConfig = new Request({
|
||||
|
||||
messages.createInstance = new Request({
|
||||
type: "create_instance",
|
||||
links: ["control-master"],
|
||||
links: ["control-controller"],
|
||||
permission: "core.instance.create",
|
||||
requestProperties: {
|
||||
"serialized_config": { type: "object" },
|
||||
@@ -458,7 +460,7 @@ messages.createInstance = new Request({
|
||||
|
||||
messages.getInstanceConfig = new Request({
|
||||
type: "get_instance_config",
|
||||
links: ["control-master"],
|
||||
links: ["control-controller"],
|
||||
permission: "core.instance.get_config",
|
||||
requestProperties: {
|
||||
"instance_id": { type: "integer" },
|
||||
@@ -470,7 +472,7 @@ messages.getInstanceConfig = new Request({
|
||||
|
||||
messages.setInstanceConfigField = new Request({
|
||||
type: "set_instance_config_field",
|
||||
links: ["control-master"],
|
||||
links: ["control-controller"],
|
||||
permission: "core.instance.update_config",
|
||||
requestProperties: {
|
||||
"instance_id": { type: "integer" },
|
||||
@@ -481,7 +483,7 @@ messages.setInstanceConfigField = new Request({
|
||||
|
||||
messages.setInstanceConfigProp = new Request({
|
||||
type: "set_instance_config_prop",
|
||||
links: ["control-master"],
|
||||
links: ["control-controller"],
|
||||
permission: "core.instance.update_config",
|
||||
requestRequired: ["instance_id", "field", "prop"],
|
||||
requestProperties: {
|
||||
@@ -494,7 +496,7 @@ messages.setInstanceConfigProp = new Request({
|
||||
|
||||
messages.assignInstanceCommand = new Request({
|
||||
type: "assign_instance_command",
|
||||
links: ["control-master"],
|
||||
links: ["control-controller"],
|
||||
permission: "core.instance.assign",
|
||||
requestProperties: {
|
||||
"instance_id": { type: "integer" },
|
||||
@@ -504,7 +506,7 @@ messages.assignInstanceCommand = new Request({
|
||||
|
||||
messages.startInstance = new Request({
|
||||
type: "start_instance",
|
||||
links: ["control-master", "master-slave", "slave-instance"],
|
||||
links: ["control-controller", "controller-slave", "slave-instance"],
|
||||
permission: "core.instance.start",
|
||||
requestProperties: {
|
||||
"save": { type: ["string", "null"] },
|
||||
@@ -531,7 +533,7 @@ let saveList = {
|
||||
|
||||
messages.listSaves = new Request({
|
||||
type: "list_saves",
|
||||
links: ["control-master", "master-slave", "slave-instance"],
|
||||
links: ["control-controller", "controller-slave", "slave-instance"],
|
||||
permission: "core.instance.save.list",
|
||||
forwardTo: "instance",
|
||||
responseProperties: {
|
||||
@@ -541,7 +543,7 @@ messages.listSaves = new Request({
|
||||
|
||||
messages.createSave = new Request({
|
||||
type: "create_save",
|
||||
links: ["control-master", "master-slave", "slave-instance"],
|
||||
links: ["control-controller", "controller-slave", "slave-instance"],
|
||||
permission: "core.instance.save.create",
|
||||
forwardTo: "instance",
|
||||
requestProperties: {
|
||||
@@ -554,7 +556,7 @@ messages.createSave = new Request({
|
||||
|
||||
messages.renameSave = new Request({
|
||||
type: "rename_save",
|
||||
links: ["control-master", "master-slave"],
|
||||
links: ["control-controller", "controller-slave"],
|
||||
permission: "core.instance.save.rename",
|
||||
forwardTo: "instance",
|
||||
requestProperties: {
|
||||
@@ -565,7 +567,7 @@ messages.renameSave = new Request({
|
||||
|
||||
messages.copySave = new Request({
|
||||
type: "copy_save",
|
||||
links: ["control-master", "master-slave"],
|
||||
links: ["control-controller", "controller-slave"],
|
||||
permission: "core.instance.save.copy",
|
||||
forwardTo: "instance",
|
||||
requestProperties: {
|
||||
@@ -576,7 +578,7 @@ messages.copySave = new Request({
|
||||
|
||||
messages.deleteSave = new Request({
|
||||
type: "delete_save",
|
||||
links: ["control-master", "master-slave"],
|
||||
links: ["control-controller", "controller-slave"],
|
||||
permission: "core.instance.save.delete",
|
||||
forwardTo: "instance",
|
||||
requestProperties: {
|
||||
@@ -586,7 +588,7 @@ messages.deleteSave = new Request({
|
||||
|
||||
messages.downloadSave = new Request({
|
||||
type: "download_save",
|
||||
links: ["control-master"],
|
||||
links: ["control-controller"],
|
||||
permission: "core.instance.save.download",
|
||||
requestProperties: {
|
||||
"instance_id": { type: "integer" },
|
||||
@@ -599,7 +601,7 @@ messages.downloadSave = new Request({
|
||||
|
||||
messages.transferSave = new Request({
|
||||
type: "transfer_save",
|
||||
links: ["control-master", "master-slave"],
|
||||
links: ["control-controller", "controller-slave"],
|
||||
permission: "core.instance.save.transfer",
|
||||
requestProperties: {
|
||||
"instance_id": { type: "integer" },
|
||||
@@ -615,7 +617,7 @@ messages.transferSave = new Request({
|
||||
|
||||
messages.pullSave = new Request({
|
||||
type: "pull_save",
|
||||
links: ["master-slave"],
|
||||
links: ["controller-slave"],
|
||||
requestProperties: {
|
||||
"instance_id": { type: "integer" },
|
||||
"stream_id": { type: "string" },
|
||||
@@ -628,7 +630,7 @@ messages.pullSave = new Request({
|
||||
|
||||
messages.pushSave = new Request({
|
||||
type: "push_save",
|
||||
links: ["master-slave"],
|
||||
links: ["controller-slave"],
|
||||
requestProperties: {
|
||||
"instance_id": { type: "integer" },
|
||||
"stream_id": { type: "string" },
|
||||
@@ -638,7 +640,7 @@ messages.pushSave = new Request({
|
||||
|
||||
messages.setSaveListSubscriptions = new Request({
|
||||
type: "set_save_list_subscriptions",
|
||||
links: ["control-master"],
|
||||
links: ["control-controller"],
|
||||
permission: "core.instance.save.list_subscribe",
|
||||
requestProperties: {
|
||||
"all": { type: "boolean" },
|
||||
@@ -651,7 +653,7 @@ messages.setSaveListSubscriptions = new Request({
|
||||
|
||||
messages.loadScenario = new Request({
|
||||
type: "load_scenario",
|
||||
links: ["control-master", "master-slave", "slave-instance"],
|
||||
links: ["control-controller", "controller-slave", "slave-instance"],
|
||||
permission: "core.instance.load_scenario",
|
||||
forwardTo: "instance",
|
||||
requestProperties: {
|
||||
@@ -664,42 +666,42 @@ messages.loadScenario = new Request({
|
||||
|
||||
messages.exportData = new Request({
|
||||
type: "export_data",
|
||||
links: ["control-master", "master-slave", "slave-instance"],
|
||||
links: ["control-controller", "controller-slave", "slave-instance"],
|
||||
permission: "core.instance.export_data",
|
||||
forwardTo: "instance",
|
||||
});
|
||||
|
||||
messages.extractPlayers = new Request({
|
||||
type: "extract_players",
|
||||
links: ["control-master", "master-slave", "slave-instance"],
|
||||
links: ["control-controller", "controller-slave", "slave-instance"],
|
||||
permission: "core.instance.extract_players",
|
||||
forwardTo: "instance",
|
||||
});
|
||||
|
||||
messages.stopInstance = new Request({
|
||||
type: "stop_instance",
|
||||
links: ["control-master", "master-slave", "slave-instance"],
|
||||
links: ["control-controller", "controller-slave", "slave-instance"],
|
||||
permission: "core.instance.stop",
|
||||
forwardTo: "instance",
|
||||
});
|
||||
|
||||
messages.killInstance = new Request({
|
||||
type: "kill_instance",
|
||||
links: ["control-master", "master-slave", "slave-instance"],
|
||||
links: ["control-controller", "controller-slave", "slave-instance"],
|
||||
permission: "core.instance.kill",
|
||||
forwardTo: "instance",
|
||||
});
|
||||
|
||||
messages.deleteInstance = new Request({
|
||||
type: "delete_instance",
|
||||
links: ["control-master", "master-slave"],
|
||||
links: ["control-controller", "controller-slave"],
|
||||
permission: "core.instance.delete",
|
||||
forwardTo: "instance",
|
||||
});
|
||||
|
||||
messages.sendRcon = new Request({
|
||||
type: "send_rcon",
|
||||
links: ["control-master", "master-slave", "slave-instance"],
|
||||
links: ["control-controller", "controller-slave", "slave-instance"],
|
||||
permission: "core.instance.send_rcon",
|
||||
forwardTo: "instance",
|
||||
requestProperties: {
|
||||
@@ -712,9 +714,9 @@ messages.sendRcon = new Request({
|
||||
|
||||
messages.getModPack = new Request({
|
||||
type: "get_mod_pack",
|
||||
links: ["control-master", "instance-slave", "slave-master"],
|
||||
links: ["control-controller", "instance-slave", "slave-controller"],
|
||||
permission: "core.mod_pack.get",
|
||||
forwardTo: "master",
|
||||
forwardTo: "controller",
|
||||
requestProperties: {
|
||||
"id": { type: "integer" },
|
||||
},
|
||||
@@ -725,9 +727,9 @@ messages.getModPack = new Request({
|
||||
|
||||
messages.getDefaultModPack = new Request({
|
||||
type: "get_default_mod_pack",
|
||||
links: ["control-master", "instance-slave", "slave-master"],
|
||||
links: ["control-controller", "instance-slave", "slave-controller"],
|
||||
permission: "core.mod_pack.get",
|
||||
forwardTo: "master",
|
||||
forwardTo: "controller",
|
||||
responseProperties: {
|
||||
"mod_pack": libData.ModPack.jsonSchema,
|
||||
},
|
||||
@@ -735,7 +737,7 @@ messages.getDefaultModPack = new Request({
|
||||
|
||||
messages.listModPacks = new Request({
|
||||
type: "list_mod_packs",
|
||||
links: ["control-master"],
|
||||
links: ["control-controller"],
|
||||
permission: "core.mod_pack.list",
|
||||
responseProperties: {
|
||||
"list": {
|
||||
@@ -747,7 +749,7 @@ messages.listModPacks = new Request({
|
||||
|
||||
messages.createModPack = new Request({
|
||||
type: "create_mod_pack",
|
||||
links: ["control-master"],
|
||||
links: ["control-controller"],
|
||||
permission: "core.mod_pack.create",
|
||||
requestProperties: {
|
||||
"mod_pack": libData.ModPack.jsonSchema,
|
||||
@@ -756,7 +758,7 @@ messages.createModPack = new Request({
|
||||
|
||||
messages.updateModPack = new Request({
|
||||
type: "update_mod_pack",
|
||||
links: ["control-master"],
|
||||
links: ["control-controller"],
|
||||
permission: "core.mod_pack.update",
|
||||
requestProperties: {
|
||||
"mod_pack": libData.ModPack.jsonSchema,
|
||||
@@ -765,7 +767,7 @@ messages.updateModPack = new Request({
|
||||
|
||||
messages.deleteModPack = new Request({
|
||||
type: "delete_mod_pack",
|
||||
links: ["control-master"],
|
||||
links: ["control-controller"],
|
||||
permission: "core.mod_pack.delete",
|
||||
requestProperties: {
|
||||
"id": { type: "integer" },
|
||||
@@ -774,7 +776,7 @@ messages.deleteModPack = new Request({
|
||||
|
||||
messages.setModPackSubscriptions = new Request({
|
||||
type: "set_mod_pack_subscriptions",
|
||||
links: ["control-master"],
|
||||
links: ["control-controller"],
|
||||
permission: "core.mod_pack.subscribe",
|
||||
requestProperties: {
|
||||
"all": { type: "boolean" },
|
||||
@@ -787,7 +789,7 @@ messages.setModPackSubscriptions = new Request({
|
||||
|
||||
messages.getMod = new Request({
|
||||
type: "get_mod",
|
||||
links: ["control-master"],
|
||||
links: ["control-controller"],
|
||||
permission: "core.mod.get",
|
||||
requestProperties: {
|
||||
"name": { type: "string" },
|
||||
@@ -800,7 +802,7 @@ messages.getMod = new Request({
|
||||
|
||||
messages.listMods = new Request({
|
||||
type: "list_mods",
|
||||
links: ["control-master"],
|
||||
links: ["control-controller"],
|
||||
permission: "core.mod.list",
|
||||
responseProperties: {
|
||||
"list": {
|
||||
@@ -812,7 +814,7 @@ messages.listMods = new Request({
|
||||
|
||||
messages.searchMods = new Request({
|
||||
type: "search_mods",
|
||||
links: ["control-master"],
|
||||
links: ["control-controller"],
|
||||
permission: "core.mod.search",
|
||||
requestRequired: ["query", "factorio_version", "page"],
|
||||
requestProperties: {
|
||||
@@ -848,7 +850,7 @@ messages.searchMods = new Request({
|
||||
|
||||
messages.setModSubscriptions = new Request({
|
||||
type: "set_mod_subscriptions",
|
||||
links: ["control-master"],
|
||||
links: ["control-controller"],
|
||||
permission: "core.mod.subscribe",
|
||||
requestProperties: {
|
||||
"all": { type: "boolean" },
|
||||
@@ -861,7 +863,7 @@ messages.setModSubscriptions = new Request({
|
||||
|
||||
messages.downloadMod = new Request({
|
||||
type: "download_mod",
|
||||
links: ["control-master"],
|
||||
links: ["control-controller"],
|
||||
permission: "core.mod.download",
|
||||
requestProperties: {
|
||||
"name": { type: "string" },
|
||||
@@ -874,7 +876,7 @@ messages.downloadMod = new Request({
|
||||
|
||||
messages.deleteMod = new Request({
|
||||
type: "delete_mod",
|
||||
links: ["control-master"],
|
||||
links: ["control-controller"],
|
||||
permission: "core.mod.delete",
|
||||
requestProperties: {
|
||||
"name": { type: "string" },
|
||||
@@ -884,7 +886,7 @@ messages.deleteMod = new Request({
|
||||
|
||||
messages.listPermissions = new Request({
|
||||
type: "list_permissions",
|
||||
links: ["control-master"],
|
||||
links: ["control-controller"],
|
||||
permission: "core.permission.list",
|
||||
responseProperties: {
|
||||
"list": {
|
||||
@@ -904,7 +906,7 @@ messages.listPermissions = new Request({
|
||||
|
||||
messages.listRoles = new Request({
|
||||
type: "list_roles",
|
||||
links: ["control-master"],
|
||||
links: ["control-controller"],
|
||||
permission: "core.role.list",
|
||||
responseProperties: {
|
||||
"list": {
|
||||
@@ -925,7 +927,7 @@ messages.listRoles = new Request({
|
||||
|
||||
messages.createRole = new Request({
|
||||
type: "create_role",
|
||||
links: ["control-master"],
|
||||
links: ["control-controller"],
|
||||
permission: "core.role.create",
|
||||
requestProperties: {
|
||||
"name": { type: "string" },
|
||||
@@ -939,7 +941,7 @@ messages.createRole = new Request({
|
||||
|
||||
messages.updateRole = new Request({
|
||||
type: "update_role",
|
||||
links: ["control-master"],
|
||||
links: ["control-controller"],
|
||||
permission: "core.role.update",
|
||||
requestProperties: {
|
||||
"id": { type: "integer" },
|
||||
@@ -951,7 +953,7 @@ messages.updateRole = new Request({
|
||||
|
||||
messages.grantDefaultRolePermissions = new Request({
|
||||
type: "grant_default_role_permissions",
|
||||
links: ["control-master"],
|
||||
links: ["control-controller"],
|
||||
permission: "core.role.update",
|
||||
requestProperties: {
|
||||
"id": { type: "integer" },
|
||||
@@ -960,7 +962,7 @@ messages.grantDefaultRolePermissions = new Request({
|
||||
|
||||
messages.deleteRole = new Request({
|
||||
type: "delete_role",
|
||||
links: ["control-master"],
|
||||
links: ["control-controller"],
|
||||
permission: "core.role.delete",
|
||||
requestProperties: {
|
||||
"id": { type: "integer" },
|
||||
@@ -989,7 +991,7 @@ const userProperties = {
|
||||
|
||||
messages.getUser = new Request({
|
||||
type: "get_user",
|
||||
links: ["control-master"],
|
||||
links: ["control-controller"],
|
||||
permission: "core.user.get",
|
||||
requestProperties: {
|
||||
"name": { type: "string" },
|
||||
@@ -1000,7 +1002,7 @@ messages.getUser = new Request({
|
||||
|
||||
messages.listUsers = new Request({
|
||||
type: "list_users",
|
||||
links: ["control-master"],
|
||||
links: ["control-controller"],
|
||||
permission: "core.user.list",
|
||||
responseProperties: {
|
||||
"list": {
|
||||
@@ -1016,7 +1018,7 @@ messages.listUsers = new Request({
|
||||
|
||||
messages.setUserSubscriptions = new Request({
|
||||
type: "set_user_subscriptions",
|
||||
links: ["control-master"],
|
||||
links: ["control-controller"],
|
||||
permission: "core.user.subscribe",
|
||||
requestProperties: {
|
||||
"all": { type: "boolean" },
|
||||
@@ -1029,7 +1031,7 @@ messages.setUserSubscriptions = new Request({
|
||||
|
||||
messages.createUser = new Request({
|
||||
type: "create_user",
|
||||
links: ["control-master"],
|
||||
links: ["control-controller"],
|
||||
permission: "core.user.create",
|
||||
requestProperties: {
|
||||
"name": { type: "string" },
|
||||
@@ -1038,7 +1040,7 @@ messages.createUser = new Request({
|
||||
|
||||
messages.revokeUserToken = new Request({
|
||||
type: "revoke_user_token",
|
||||
links: ["control-master"],
|
||||
links: ["control-controller"],
|
||||
permission: "core.user.revoke_token",
|
||||
requestProperties: {
|
||||
"name": { type: "string" },
|
||||
@@ -1047,7 +1049,7 @@ messages.revokeUserToken = new Request({
|
||||
|
||||
messages.updateUserRoles = new Request({
|
||||
type: "update_user_roles",
|
||||
links: ["control-master"],
|
||||
links: ["control-controller"],
|
||||
permission: "core.user.update_roles",
|
||||
requestProperties: {
|
||||
"name": { type: "string" },
|
||||
@@ -1057,7 +1059,7 @@ messages.updateUserRoles = new Request({
|
||||
|
||||
messages.setUserAdmin = new Request({
|
||||
type: "set_user_admin",
|
||||
links: ["control-master"],
|
||||
links: ["control-controller"],
|
||||
permission: "core.user.set_admin",
|
||||
requestProperties: {
|
||||
"name": { type: "string" },
|
||||
@@ -1068,7 +1070,7 @@ messages.setUserAdmin = new Request({
|
||||
|
||||
messages.setUserWhitelisted = new Request({
|
||||
type: "set_user_whitelisted",
|
||||
links: ["control-master"],
|
||||
links: ["control-controller"],
|
||||
permission: "core.user.set_whitelisted",
|
||||
requestProperties: {
|
||||
"name": { type: "string" },
|
||||
@@ -1079,7 +1081,7 @@ messages.setUserWhitelisted = new Request({
|
||||
|
||||
messages.setUserBanned = new Request({
|
||||
type: "set_user_banned",
|
||||
links: ["control-master"],
|
||||
links: ["control-controller"],
|
||||
permission: "core.user.set_banned",
|
||||
requestProperties: {
|
||||
"name": { type: "string" },
|
||||
@@ -1091,7 +1093,7 @@ messages.setUserBanned = new Request({
|
||||
|
||||
messages.deleteUser = new Request({
|
||||
type: "delete_user",
|
||||
links: ["control-master"],
|
||||
links: ["control-controller"],
|
||||
permission: "core.user.delete",
|
||||
requestProperties: {
|
||||
"name": { type: "string" },
|
||||
@@ -1100,11 +1102,11 @@ messages.deleteUser = new Request({
|
||||
|
||||
messages.setLogSubscriptions = new Request({
|
||||
type: "set_log_subscriptions",
|
||||
links: ["control-master"],
|
||||
links: ["control-controller"],
|
||||
permission: "core.log.follow",
|
||||
requestProperties: {
|
||||
"all": { type: "boolean" },
|
||||
"master": { type: "boolean" },
|
||||
"controller": { type: "boolean" },
|
||||
"slave_ids": {
|
||||
type: "array",
|
||||
items: { type: "integer" },
|
||||
@@ -1119,11 +1121,11 @@ messages.setLogSubscriptions = new Request({
|
||||
|
||||
messages.queryLog = new Request({
|
||||
type: "query_log",
|
||||
links: ["control-master"],
|
||||
links: ["control-controller"],
|
||||
permission: "core.log.query",
|
||||
requestProperties: {
|
||||
"all": { type: "boolean" },
|
||||
"master": { type: "boolean" },
|
||||
"controller": { type: "boolean" },
|
||||
"slave_ids": {
|
||||
type: "array",
|
||||
items: { type: "integer" },
|
||||
@@ -1148,7 +1150,7 @@ messages.queryLog = new Request({
|
||||
// Internal requests
|
||||
messages.updateInstances = new Request({
|
||||
type: "update_instances",
|
||||
links: ["slave-master"],
|
||||
links: ["slave-controller"],
|
||||
requestProperties: {
|
||||
"instances": {
|
||||
type: "array",
|
||||
@@ -1169,7 +1171,7 @@ messages.updateInstances = new Request({
|
||||
|
||||
messages.assignInstance = new Request({
|
||||
type: "assign_instance",
|
||||
links: ["master-slave"],
|
||||
links: ["controller-slave"],
|
||||
requestProperties: {
|
||||
"serialized_config": { type: "object" },
|
||||
},
|
||||
@@ -1178,13 +1180,13 @@ messages.assignInstance = new Request({
|
||||
|
||||
messages.unassignInstance = new Request({
|
||||
type: "unassigne_instance",
|
||||
links: ["master-slave"],
|
||||
links: ["controller-slave"],
|
||||
forwardTo: "instance",
|
||||
});
|
||||
|
||||
messages.getMetrics = new Request({
|
||||
type: "get_metrics",
|
||||
links: ["master-slave", "slave-instance"],
|
||||
links: ["controller-slave", "slave-instance"],
|
||||
responseProperties: {
|
||||
"results": { type: "array" },
|
||||
},
|
||||
@@ -1213,7 +1215,7 @@ class Event extends Message {
|
||||
* containing "<source>-<target>" specifications.
|
||||
* @param {?string} forwardTo -
|
||||
* Optional target to forward this request to. Supported values are
|
||||
* 'master' and 'instance'. If set to 'instance' an instance_id
|
||||
* 'controller' and 'instance'. If set to 'instance' an instance_id
|
||||
* property is implicity added to eventProperties.
|
||||
* @param {?string} broadcastTo -
|
||||
* Optional target to broadcast this event to. Only 'instance' is
|
||||
@@ -1247,7 +1249,7 @@ class Event extends Message {
|
||||
...eventProperties,
|
||||
};
|
||||
|
||||
} else if (forwardTo !== "master" && forwardTo !== null) {
|
||||
} else if (forwardTo !== "controller" && forwardTo !== null) {
|
||||
throw new Error(`Invalid forwardTo value ${forwardTo}`);
|
||||
}
|
||||
|
||||
@@ -1275,7 +1277,7 @@ class Event extends Message {
|
||||
* Set a the handler for the event on the given link if it's a target
|
||||
* for the event.
|
||||
*
|
||||
* If forwardTo was set to 'master' the `forwardEventToMaster` method on
|
||||
* If forwardTo was set to 'controller' the `forwardEventToController` method on
|
||||
* the link is used as the default handler. If it was set to 'instance'
|
||||
* the `forwardEventToInstance` method on the link is used by default.
|
||||
*
|
||||
@@ -1297,15 +1299,15 @@ class Event extends Message {
|
||||
if (!handler) {
|
||||
if (this.forwardTo === "instance") {
|
||||
handler = link.forwardEventToInstance;
|
||||
} else if (this.forwardTo === "master") {
|
||||
handler = link.forwardEventToMaster;
|
||||
} else if (this.forwardTo === "controller") {
|
||||
handler = link.forwardEventToController;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
this.broadcastTo === "instance"
|
||||
&& [
|
||||
"instance-slave", "slave-master", "control-master", "master-slave",
|
||||
"instance-slave", "slave-controller", "control-controller", "controller-slave",
|
||||
].includes(`${link.target}-${link.source}`)
|
||||
) {
|
||||
if (!handler) {
|
||||
@@ -1352,7 +1354,7 @@ class Event extends Message {
|
||||
|
||||
messages.debugWsMessage = new Event({
|
||||
type: "debug_ws_message",
|
||||
links: ["master-control"],
|
||||
links: ["controller-control"],
|
||||
eventProperties: {
|
||||
"direction": { type: "string" },
|
||||
"content": { type: "string" },
|
||||
@@ -1361,7 +1363,7 @@ messages.debugWsMessage = new Event({
|
||||
|
||||
messages.accountUpdate = new Event({
|
||||
type: "account_update",
|
||||
links: ["master-control"],
|
||||
links: ["controller-control"],
|
||||
eventRequired: [],
|
||||
eventProperties: {
|
||||
"roles": {
|
||||
@@ -1385,7 +1387,7 @@ messages.accountUpdate = new Event({
|
||||
|
||||
messages.logMessage = new Event({
|
||||
type: "log_message",
|
||||
links: ["slave-master", "master-control"],
|
||||
links: ["slave-controller", "controller-control"],
|
||||
eventProperties: {
|
||||
"info": {
|
||||
type: "object",
|
||||
@@ -1400,7 +1402,7 @@ messages.logMessage = new Event({
|
||||
|
||||
messages.slaveUpdate = new Event({
|
||||
type: "slave_update",
|
||||
links: ["master-control"],
|
||||
links: ["controller-control"],
|
||||
eventProperties: slaveProperties,
|
||||
});
|
||||
|
||||
@@ -1418,7 +1420,7 @@ messages.instanceInitialized = new Event({
|
||||
});
|
||||
messages.instanceStatusChanged = new Event({
|
||||
type: "instance_status_changed",
|
||||
links: ["instance-slave", "slave-master"],
|
||||
links: ["instance-slave", "slave-controller"],
|
||||
eventRequired: ["instance_id", "status"],
|
||||
eventProperties: {
|
||||
"instance_id": { type: "integer" },
|
||||
@@ -1431,14 +1433,14 @@ messages.instanceStatusChanged = new Event({
|
||||
|
||||
messages.instanceUpdate = new Event({
|
||||
type: "instance_update",
|
||||
links: ["master-control"],
|
||||
links: ["controller-control"],
|
||||
eventProperties: instanceProperties,
|
||||
});
|
||||
|
||||
messages.saveListUpdate = new Event({
|
||||
type: "save_list_update",
|
||||
links: ["instance-slave", "slave-master", "master-control"],
|
||||
forwardTo: "master",
|
||||
links: ["instance-slave", "slave-controller", "controller-control"],
|
||||
forwardTo: "controller",
|
||||
eventProperties: {
|
||||
"instance_id": { type: "integer" },
|
||||
"list": saveList,
|
||||
@@ -1447,7 +1449,7 @@ messages.saveListUpdate = new Event({
|
||||
|
||||
messages.modPackUpdate = new Event({
|
||||
type: "mod_pack_update",
|
||||
links: ["master-control"],
|
||||
links: ["controller-control"],
|
||||
eventProperties: {
|
||||
"mod_pack": libData.ModPack.jsonSchema,
|
||||
},
|
||||
@@ -1455,7 +1457,7 @@ messages.modPackUpdate = new Event({
|
||||
|
||||
messages.modUpdate = new Event({
|
||||
type: "mod_update",
|
||||
links: ["master-control"],
|
||||
links: ["controller-control"],
|
||||
eventProperties: {
|
||||
"mod": libData.ModInfo.jsonSchema,
|
||||
},
|
||||
@@ -1463,13 +1465,13 @@ messages.modUpdate = new Event({
|
||||
|
||||
messages.userUpdate = new Event({
|
||||
type: "user_update",
|
||||
links: ["master-control"],
|
||||
links: ["controller-control"],
|
||||
eventRequired: userRequired,
|
||||
eventProperties: userProperties,
|
||||
});
|
||||
|
||||
messages.masterConnectionEvent = new Event({
|
||||
type: "master_connection_event",
|
||||
messages.controllerConnectionEvent = new Event({
|
||||
type: "controller_connection_event",
|
||||
links: ["slave-instance"],
|
||||
eventProperties: {
|
||||
"event": { type: "string" },
|
||||
@@ -1478,7 +1480,7 @@ messages.masterConnectionEvent = new Event({
|
||||
|
||||
messages.syncUserLists = new Event({
|
||||
type: "sync_user_lists",
|
||||
links: ["master-slave"],
|
||||
links: ["controller-slave"],
|
||||
eventProperties: {
|
||||
"adminlist": {
|
||||
type: "array",
|
||||
@@ -1501,7 +1503,7 @@ messages.syncUserLists = new Event({
|
||||
|
||||
messages.banlistUpdate = new Event({
|
||||
type: "banlist_update",
|
||||
links: ["master-slave", "slave-instance"],
|
||||
links: ["controller-slave", "slave-instance"],
|
||||
broadcastTo: "instance",
|
||||
eventProperties: {
|
||||
"name": { type: "string" },
|
||||
@@ -1512,7 +1514,7 @@ messages.banlistUpdate = new Event({
|
||||
|
||||
messages.adminlistUpdate = new Event({
|
||||
type: "adminlist_update",
|
||||
links: ["master-slave", "slave-instance"],
|
||||
links: ["controller-slave", "slave-instance"],
|
||||
eventProperties: {
|
||||
"name": { type: "string" },
|
||||
"admin": { type: "boolean" },
|
||||
@@ -1522,7 +1524,7 @@ messages.adminlistUpdate = new Event({
|
||||
|
||||
messages.whitelistUpdate = new Event({
|
||||
type: "whitelist_update",
|
||||
links: ["master-slave", "slave-instance"],
|
||||
links: ["controller-slave", "slave-instance"],
|
||||
eventProperties: {
|
||||
"name": { type: "string" },
|
||||
"whitelisted": { type: "boolean" },
|
||||
@@ -1532,8 +1534,8 @@ messages.whitelistUpdate = new Event({
|
||||
|
||||
messages.playerEvent = new Event({
|
||||
type: "player_event",
|
||||
links: ["instance-slave", "slave-master"],
|
||||
forwardTo: "master",
|
||||
links: ["instance-slave", "slave-controller"],
|
||||
forwardTo: "controller",
|
||||
eventRequired: ["instance_id", "type", "name"],
|
||||
eventProperties: {
|
||||
"instance_id": { type: "integer" },
|
||||
|
||||
@@ -88,9 +88,9 @@ class WebConsoleFormat {
|
||||
* @property {string} [max_level] -
|
||||
* Maximum log level to include. Higher levels are more verbose.
|
||||
* @property {boolean} [all] -
|
||||
* Include log entries from master, all slaves and all instances.
|
||||
* @property {boolean} [master] -
|
||||
* Include log entries from the master server.
|
||||
* Include log entries from controller, all slaves and all instances.
|
||||
* @property {boolean} [controller] -
|
||||
* Include log entries from the controller.
|
||||
* @property {Array<number>} [slave_ids] -
|
||||
* Include log entries for the given slaves and instances of those
|
||||
* slaves by id.
|
||||
@@ -107,7 +107,7 @@ class WebConsoleFormat {
|
||||
* filter returning true for log entries that match it.
|
||||
* @static
|
||||
*/
|
||||
function logFilter({ all, master, slave_ids, instance_ids, max_level }) {
|
||||
function logFilter({ all, controller, slave_ids, instance_ids, max_level }) {
|
||||
return info => {
|
||||
// Note: reversed to filter out undefined levels
|
||||
if (max_level && !(levels[info.level] <= levels[max_level])) {
|
||||
@@ -117,7 +117,7 @@ function logFilter({ all, master, slave_ids, instance_ids, max_level }) {
|
||||
if (all) {
|
||||
return true;
|
||||
}
|
||||
if (master && info.slave_id === undefined) {
|
||||
if (controller && info.slave_id === undefined) {
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
|
||||
@@ -157,7 +157,7 @@ class LogIndex {
|
||||
for (let [file, serializedEntry] of Object.entries(serialized.files)) {
|
||||
this.index.set(file, {
|
||||
levels: new Set(serializedEntry.levels),
|
||||
master: serializedEntry.master,
|
||||
controller: serializedEntry.controller,
|
||||
slave_ids: new Set(serializedEntry.slave_ids),
|
||||
instance_ids: new Set(serializedEntry.instance_ids),
|
||||
});
|
||||
@@ -173,7 +173,7 @@ class LogIndex {
|
||||
for (let [file, entry] of this.index) {
|
||||
serialized.files[file] = {
|
||||
levels: [...entry.levels],
|
||||
master: entry.master,
|
||||
controller: entry.controller,
|
||||
slave_ids: [...entry.slave_ids],
|
||||
instance_ids: [...entry.instance_ids],
|
||||
};
|
||||
@@ -224,7 +224,7 @@ class LogIndex {
|
||||
fileStream.pipe(lineStream);
|
||||
let entry = {
|
||||
levels: new Set(),
|
||||
master: false,
|
||||
controller: false,
|
||||
slave_ids: new Set(),
|
||||
instance_ids: new Set(),
|
||||
};
|
||||
@@ -234,14 +234,14 @@ class LogIndex {
|
||||
info = JSON.parse(line);
|
||||
} catch (err) {
|
||||
entry.levels.add("info");
|
||||
entry.master = true;
|
||||
entry.controller = true;
|
||||
continue;
|
||||
}
|
||||
if (info.level) {
|
||||
entry.levels.add(info.level);
|
||||
}
|
||||
if (info.slave_id === undefined) {
|
||||
entry.master = true;
|
||||
entry.controller = true;
|
||||
} else if (info.instance_id === undefined) {
|
||||
entry.slave_ids.add(info.slave_id);
|
||||
}
|
||||
@@ -279,7 +279,7 @@ class LogIndex {
|
||||
* @returns {boolean}
|
||||
* true if the file may contain entries included by the filter.
|
||||
*/
|
||||
filterIncludesFile(file, { max_level, all, master, instance_ids, slave_ids }) {
|
||||
filterIncludesFile(file, { max_level, all, controller, instance_ids, slave_ids }) {
|
||||
let entry = this.index.get(file);
|
||||
if (!entry) {
|
||||
return true;
|
||||
@@ -298,7 +298,7 @@ class LogIndex {
|
||||
if (all) {
|
||||
return true;
|
||||
}
|
||||
if (master && entry.master) {
|
||||
if (controller && entry.controller) {
|
||||
return true;
|
||||
}
|
||||
if (slave_ids && slave_ids.some(id => entry.slave_ids.has(id))) {
|
||||
@@ -321,9 +321,9 @@ class LogIndex {
|
||||
* @property {string} [max_level] -
|
||||
* Maximum log level to include. Higher levels are more verbose.
|
||||
* @property {boolean} [all] -
|
||||
* Include log entries from master, all slaves and all instances.
|
||||
* @property {boolean} [master] -
|
||||
* Include log entries from the master server.
|
||||
* Include log entries from controller, all slaves and all instances.
|
||||
* @property {boolean} [controller] -
|
||||
* Include log entries from the controller.
|
||||
* @property {Array<number>} [slave_ids] -
|
||||
* Include log entries for the given slaves and instances of those
|
||||
* slaves by id.
|
||||
|
||||
+62
-62
@@ -7,9 +7,9 @@
|
||||
const helpers = require("./helpers");
|
||||
|
||||
/**
|
||||
* Conceptual base for master and instance plugins.
|
||||
* Conceptual base for controller and instance plugins.
|
||||
* @typedef {
|
||||
* module:lib/plugin.BaseMasterPlugin
|
||||
* module:lib/plugin.BaseControllerPlugin
|
||||
* |module:lib/plugin.BaseInstancePlugin
|
||||
* |module:lib/plugin.BaseControlPlugin
|
||||
* } module:lib/plugin.BasePlugin
|
||||
@@ -150,22 +150,22 @@ class BaseInstancePlugin {
|
||||
async onOutput(parsed, line) { }
|
||||
|
||||
/**
|
||||
* Called when an event on the master connection happens
|
||||
* Called when an event on the controller connection happens
|
||||
*
|
||||
* The event param may be one of connect, drop, resume and close and has
|
||||
* the following meaning:
|
||||
*
|
||||
* ##### connect
|
||||
*
|
||||
* Invoked when a new connection to the master has been established.
|
||||
* Invoked when a new connection to the controller has been established.
|
||||
*
|
||||
* ##### drop
|
||||
*
|
||||
* Invoked when a connection loss is detected between the slave and the
|
||||
* master. Plugins should respond to this event by throtteling messages
|
||||
* it is sending to the master to an absolute minimum.
|
||||
* controller. Plugins should respond to this event by throtteling messages
|
||||
* it is sending to the controller to an absolute minimum.
|
||||
*
|
||||
* Messages sent over a dropped master 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
|
||||
* re-established again.
|
||||
*
|
||||
@@ -176,29 +176,29 @@ class BaseInstancePlugin {
|
||||
*
|
||||
* ##### close
|
||||
*
|
||||
* Invoked when the connection to the master has been closed. This
|
||||
* typically means the master server has shut down. Plugins should not
|
||||
* send any messages that goes to or via the master server after the
|
||||
* Invoked when the connection to the controller has been closed. This
|
||||
* typically means the controller has shut down. Plugins should not
|
||||
* send any messages that goes to or via the controller after the
|
||||
* connection has been closed and before a new one is established.
|
||||
*
|
||||
* @param {string} event - one of connect, drop, resume and close
|
||||
*/
|
||||
onMasterConnectionEvent(event) { }
|
||||
onControllerConnectionEvent(event) { }
|
||||
|
||||
/**
|
||||
* Called when the master is preparing to disconnect from the slave
|
||||
* Called when the controller is preparing to disconnect from the slave
|
||||
*
|
||||
* Invoked when the master server 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
|
||||
* shutting down.
|
||||
*
|
||||
* Plugins must stop sending messages to the master, or are forwarded
|
||||
* via the master server after the prepare disconnect has been handled.
|
||||
* Plugins must stop sending messages to the controller, or are forwarded
|
||||
* via the controller after the prepare disconnect has been handled.
|
||||
*
|
||||
* @param {module:master/src/SlaveConnection} connection -
|
||||
* @param {module:controller/src/SlaveConnection} connection -
|
||||
* The connection to the slave preparing to disconnect.
|
||||
*/
|
||||
async onPrepareMasterDisconnect(connection) { }
|
||||
async onPrepareControllerDisconnect(connection) { }
|
||||
|
||||
/**
|
||||
* Called when a player joins or leaves the game
|
||||
@@ -284,25 +284,25 @@ class BaseInstancePlugin {
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for master plugins
|
||||
* Base class for controller plugins
|
||||
*
|
||||
* Master plugins are subclasses of this class which get instantiated by
|
||||
* the master server on startup when the plugin is enabled in the config.
|
||||
* To be discovered the class must be exported under the name `MasterPlugin`
|
||||
* in the module specified by the `masterEntrypoint` in the plugin's info.js
|
||||
* Controller plugins are subclasses of this class which get instantiated by
|
||||
* the controller on startup when the plugin is enabled in the config.
|
||||
* To be discovered the class must be exported under the name `ControllerPlugin`
|
||||
* in the module specified by the `controllerEntrypoint` in the plugin's info.js
|
||||
* file.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
class BaseMasterPlugin {
|
||||
constructor(info, master, metrics, logger) {
|
||||
class BaseControllerPlugin {
|
||||
constructor(info, controller, metrics, logger) {
|
||||
this.info = info;
|
||||
|
||||
/**
|
||||
* Master server
|
||||
* @type {module:master/src/Master}
|
||||
* Controller
|
||||
* @type {module:controller/src/Controller}
|
||||
*/
|
||||
this.master = master;
|
||||
this.controller = controller;
|
||||
this.metrics = metrics;
|
||||
|
||||
/**
|
||||
@@ -323,7 +323,7 @@ class BaseMasterPlugin {
|
||||
/**
|
||||
* Called when the status of an instance changes
|
||||
*
|
||||
* Invoked when the master server 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
|
||||
* changed. The possible statuses that can be notified about are:
|
||||
* - `unassigned:`: Instance is no longer asssigned to a slave.
|
||||
@@ -338,21 +338,21 @@ class BaseMasterPlugin {
|
||||
* icons and locale data.
|
||||
* - `deleted`: Instance was deleted.
|
||||
*
|
||||
* On master 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
|
||||
* master 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
|
||||
* checking if prev equals `unknown`. If the slave disconnects the
|
||||
* instances will again get the `unknown` status.
|
||||
*
|
||||
* When instances are created on the master 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
|
||||
* in most cases `unassigned` it's possible for the created instance to
|
||||
* start with any state in some slave connection corner cases.
|
||||
*
|
||||
* 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
|
||||
* session between the master server and the slave.
|
||||
* session between the controller and the slave.
|
||||
*
|
||||
* @param {Object} instance - the instance that changed.
|
||||
* @param {?string} prev - the previous status of the instance.
|
||||
@@ -360,17 +360,17 @@ class BaseMasterPlugin {
|
||||
async onInstanceStatusChanged(instance, prev) { }
|
||||
|
||||
/**
|
||||
* Called when the value of a master config field changed.
|
||||
* Called when the value of a controller config field changed.
|
||||
*
|
||||
* Invoked after the value of the config field given by `field` has
|
||||
* changed on the master.
|
||||
* changed on the controller.
|
||||
*
|
||||
* @param {module:lib/config.ConfigGroup} group -
|
||||
* The group who's field got changed.
|
||||
* @param {string} field - Name of the field that changed.
|
||||
* @param {*} prev - The previous value of the field.
|
||||
*/
|
||||
async onMasterConfigFieldChanged(group, field, prev) { }
|
||||
async onControllerConfigFieldChanged(group, field, prev) { }
|
||||
|
||||
/**
|
||||
* Called when the value of an instance config field changed.
|
||||
@@ -404,7 +404,7 @@ class BaseMasterPlugin {
|
||||
async onMetrics() { }
|
||||
|
||||
/**
|
||||
* Called when the master server is shutting down
|
||||
* Called when the controller is shutting down
|
||||
*/
|
||||
async onShutdown() { }
|
||||
|
||||
@@ -420,12 +420,12 @@ class BaseMasterPlugin {
|
||||
*
|
||||
* ##### drop
|
||||
*
|
||||
* Invoked when a connection loss is detected between the master and a
|
||||
* Invoked when a connection loss is detected between the controller and a
|
||||
* slave. Plugins should respond to this event by throtteling messages
|
||||
* it is sending to the given slave connection to an absolute minimum.
|
||||
*
|
||||
* Messages sent over a dropped slave connection will get queued up in
|
||||
* memory on the master 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.
|
||||
*
|
||||
* ##### resume
|
||||
@@ -437,7 +437,7 @@ class BaseMasterPlugin {
|
||||
*
|
||||
* Invoked when the connection to the slave has been closed.
|
||||
*
|
||||
* @param {module:master/src/SlaveConnection} connection -
|
||||
* @param {module:controller/src/SlaveConnection} connection -
|
||||
* The connection the event occured on.
|
||||
* @param {string} event - one of connect, drop, resume and close
|
||||
*/
|
||||
@@ -455,13 +455,13 @@ class BaseMasterPlugin {
|
||||
*
|
||||
* ##### drop
|
||||
*
|
||||
* Invoked when a connection loss is detected between the master and a
|
||||
* Invoked when a connection loss is detected between the controller and a
|
||||
* control. Plugins should respond to this event by throtteling
|
||||
* messages it is sending to the given control connection to an absolute
|
||||
* minimum.
|
||||
*
|
||||
* Messages sent over a dropped control connection will get queued up in
|
||||
* memory on the master 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.
|
||||
*
|
||||
* ##### resume
|
||||
@@ -473,22 +473,22 @@ class BaseMasterPlugin {
|
||||
*
|
||||
* Invoked when the connection to the control has been closed.
|
||||
*
|
||||
* @param {module:master/src/ControlConnection} connection -
|
||||
* @param {module:controller/src/ControlConnection} connection -
|
||||
* The connection the event occured on.
|
||||
* @param {string} event - one of connect, drop, resume, and close.
|
||||
*/
|
||||
onControlConnectionEvent(connection, event) { }
|
||||
|
||||
/**
|
||||
* Called when a slave is preparing to disconnect from the master
|
||||
* Called when a slave is preparing to disconnect from the controller
|
||||
*
|
||||
* Invoked when a slave has requested the disconnection from the master.
|
||||
* Invoked when a slave has requested the disconnection from the controller.
|
||||
* This typically happens when it is in the process of shutting down.
|
||||
*
|
||||
* Plugins must stop sending messages to the slave in question after the
|
||||
* prepare disconnect has been handled.
|
||||
*
|
||||
* @param {module:master/src/SlaveConnection} connection -
|
||||
* @param {module:controller/src/SlaveConnection} connection -
|
||||
* The connection to the slave preparing to disconnect.
|
||||
*/
|
||||
async onPrepareSlaveDisconnect(connection) { }
|
||||
@@ -507,7 +507,7 @@ class BaseMasterPlugin {
|
||||
async onModPackUpdated(modPack) { }
|
||||
|
||||
/**
|
||||
* Called when a mod stored on the master server is updated
|
||||
* Called when a mod stored on the controller is updated
|
||||
*
|
||||
* Invoked when a mod has been added, updated or deleted from the pool
|
||||
* of shared mods stored on the cluster.
|
||||
@@ -538,7 +538,7 @@ class BaseMasterPlugin {
|
||||
/**
|
||||
* Broadcast event to all connected slaves
|
||||
*
|
||||
* Sends the given event to all slaves connected to the master server.
|
||||
* Sends the given event to all slaves connected to the controller.
|
||||
* This does not include slaves that are in the process of closing the
|
||||
* connection, which typically happens when they are shutting down.
|
||||
*
|
||||
@@ -546,7 +546,7 @@ class BaseMasterPlugin {
|
||||
* @param {Object} data - Data ta pass with the event.
|
||||
*/
|
||||
broadcastEventToSlaves(event, data={}) {
|
||||
for (let slaveConnection of this.master.wsServer.slaveConnections.values()) {
|
||||
for (let slaveConnection of this.controller.wsServer.slaveConnections.values()) {
|
||||
if (
|
||||
!slaveConnection.connector.closing
|
||||
&& (!event.plugin || slaveConnection.plugins.has(event.plugin))
|
||||
@@ -660,8 +660,8 @@ class BaseWebPlugin {
|
||||
this.info = info;
|
||||
|
||||
/**
|
||||
* Control link to the master server, not available until the
|
||||
* connect event in onMasterConnectionEvent is signaled.
|
||||
* Control link to the controller, not available until the
|
||||
* connect event in onControllerConnectionEvent is signaled.
|
||||
* @type {?module:lib/link.Link}
|
||||
*/
|
||||
this.control = null;
|
||||
@@ -700,8 +700,8 @@ class BaseWebPlugin {
|
||||
* component extra.
|
||||
*
|
||||
* @type {object}
|
||||
* @property {React.ComponentType} MasterPage -
|
||||
* Placed at the end of the master page.
|
||||
* @property {React.ComponentType} ControllerPage -
|
||||
* Placed at the end of the controller page.
|
||||
* @property {React.ComponentType} SlavesPage -
|
||||
* Placed at the end of the slaves list page.
|
||||
* @property {React.ComponentType} SlaveViewPage -
|
||||
@@ -732,22 +732,22 @@ class BaseWebPlugin {
|
||||
async init() { }
|
||||
|
||||
/**
|
||||
* Called when an event on the master connection happens
|
||||
* Called when an event on the controller connection happens
|
||||
*
|
||||
* The event param may be one of connect, drop, resume and close and has
|
||||
* the following meaning:
|
||||
*
|
||||
* ##### connect
|
||||
*
|
||||
* Invoked when a new connection to the master has been established.
|
||||
* Invoked when a new connection to the controller has been established.
|
||||
*
|
||||
* ##### drop
|
||||
*
|
||||
* Invoked when a connection loss is detected between the control link
|
||||
* and the master. Plugins should respond to this event by throtteling
|
||||
* messages it is sending to the master to an absolute minimum.
|
||||
* and the controller. Plugins should respond to this event by throtteling
|
||||
* messages it is sending to the controller to an absolute minimum.
|
||||
*
|
||||
* Messages sent over a dropped master connection will get queued up in
|
||||
* Messages sent over a dropped controller connection will get queued up in
|
||||
* memory in the browser and sent all in one go when the connection is
|
||||
* re-established again.
|
||||
*
|
||||
@@ -758,14 +758,14 @@ class BaseWebPlugin {
|
||||
*
|
||||
* ##### close
|
||||
*
|
||||
* Invoked when the connection to the master has been closed. This
|
||||
* typically means the master server has shut down. Plugins should not
|
||||
* send any messages that goes to or via the master server after the
|
||||
* Invoked when the connection to the controller has been closed. This
|
||||
* typically means the controller has shut down. Plugins should not
|
||||
* send any messages that goes to or via the controller after the
|
||||
* connection has been closed and before a new one is established.
|
||||
*
|
||||
* @param {string} event - one of connect, drop, resume and close
|
||||
*/
|
||||
onMasterConnectionEvent(event) { }
|
||||
onControllerConnectionEvent(event) { }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -840,7 +840,7 @@ async function invokeHook(plugins, hook, ...args) {
|
||||
|
||||
module.exports = {
|
||||
BaseInstancePlugin,
|
||||
BaseMasterPlugin,
|
||||
BaseControllerPlugin,
|
||||
BaseControlPlugin,
|
||||
BaseWebPlugin,
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ function loadPluginClass(entrypointName, className, pluginClass, pluginInfo) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Load master plugin class of a plugin
|
||||
* Load controller plugin class of a plugin
|
||||
*
|
||||
* @param {Object} pluginInfo -
|
||||
* Plugin info object returned from {@link
|
||||
@@ -74,8 +74,8 @@ function loadPluginClass(entrypointName, className, pluginClass, pluginInfo) {
|
||||
* @returns {Promise<function>} plugin class
|
||||
* @static
|
||||
*/
|
||||
async function loadMasterPluginClass(pluginInfo) {
|
||||
return loadPluginClass("masterEntrypoint", "MasterPlugin", libPlugin.BaseMasterPlugin, pluginInfo);
|
||||
async function loadControllerPluginClass(pluginInfo) {
|
||||
return loadPluginClass("controllerEntrypoint", "ControllerPlugin", libPlugin.BaseControllerPlugin, pluginInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -106,7 +106,7 @@ async function loadControlPluginClass(pluginInfo) {
|
||||
|
||||
module.exports = {
|
||||
loadPluginInfos,
|
||||
loadMasterPluginClass,
|
||||
loadControllerPluginClass,
|
||||
loadInstancePluginClass,
|
||||
loadControlPluginClass,
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Implementation of commands shared between master/slave/ctl.
|
||||
* Implementation of commands shared between controller/slave/ctl.
|
||||
* @module lib/shared_commands
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
+16
-16
@@ -350,19 +350,19 @@ definePermission({
|
||||
definePermission({
|
||||
name: "core.control.connect",
|
||||
title: "Connect to WebSocket",
|
||||
description: "Connect to the master's WebSocket control interface.",
|
||||
description: "Connect to the controller's WebSocket control interface.",
|
||||
grantByDefault: true,
|
||||
});
|
||||
|
||||
definePermission({
|
||||
name: "core.master.get_config",
|
||||
title: "Get master config",
|
||||
description: "Get the config of master.",
|
||||
name: "core.controller.get_config",
|
||||
title: "Get controller config",
|
||||
description: "Get the config of controller.",
|
||||
});
|
||||
definePermission({
|
||||
name: "core.master.update_config",
|
||||
title: "Modify master config",
|
||||
description: "Modify the master config or entries of the master config.",
|
||||
name: "core.controller.update_config",
|
||||
title: "Modify controller config",
|
||||
description: "Modify the controller config or entries of the controller config.",
|
||||
});
|
||||
definePermission({
|
||||
name: "core.slave.list",
|
||||
@@ -384,7 +384,7 @@ definePermission({
|
||||
definePermission({
|
||||
name: "core.slave.create_config",
|
||||
title: "Create slave config",
|
||||
description: "Create slave configs via the master server.",
|
||||
description: "Create slave configs via the controller.",
|
||||
});
|
||||
|
||||
definePermission({
|
||||
@@ -408,7 +408,7 @@ definePermission({
|
||||
definePermission({
|
||||
name: "core.instance.create",
|
||||
title: "Create instance",
|
||||
description: "Create new instances on the master server.",
|
||||
description: "Create new instances on the controller.",
|
||||
});
|
||||
definePermission({
|
||||
name: "core.instance.get_config",
|
||||
@@ -473,7 +473,7 @@ definePermission({
|
||||
definePermission({
|
||||
name: "core.instance.export_data",
|
||||
title: "Export locale and icons",
|
||||
description: "Export the the locale and icons from an instance and upload it to the master.",
|
||||
description: "Export the the locale and icons from an instance and upload it to the controller.",
|
||||
});
|
||||
definePermission({
|
||||
name: "core.instance.extract_players",
|
||||
@@ -553,13 +553,13 @@ definePermission({
|
||||
definePermission({
|
||||
name: "core.mod.list",
|
||||
title: "List mods",
|
||||
description: "Get the full list of mods stored on the master.",
|
||||
description: "Get the full list of mods stored on the controller.",
|
||||
grantByDefault: true,
|
||||
});
|
||||
definePermission({
|
||||
name: "core.mod.search",
|
||||
title: "Search mods",
|
||||
description: "Search through the list of mods stored on the master.",
|
||||
description: "Search through the list of mods stored on the controller.",
|
||||
grantByDefault: true,
|
||||
});
|
||||
definePermission({
|
||||
@@ -571,18 +571,18 @@ definePermission({
|
||||
definePermission({
|
||||
name: "core.mod.upload",
|
||||
title: "Upload mod",
|
||||
description: "Upload mods to the master.",
|
||||
description: "Upload mods to the controller.",
|
||||
});
|
||||
definePermission({
|
||||
name: "core.mod.download",
|
||||
title: "Download mod",
|
||||
description: "Download mods stored on the master.",
|
||||
description: "Download mods stored on the controller.",
|
||||
grantByDefault: true,
|
||||
});
|
||||
definePermission({
|
||||
name: "core.mod.delete",
|
||||
title: "Delete mod",
|
||||
description: "Delete mods stored on the master.",
|
||||
description: "Delete mods stored on the controller.",
|
||||
});
|
||||
|
||||
definePermission({
|
||||
@@ -687,7 +687,7 @@ definePermission({
|
||||
definePermission({
|
||||
name: "core.debug.dump_ws",
|
||||
title: "Dump WebSocket",
|
||||
description: "Dump all WebSocket communicatation from the master.",
|
||||
description: "Dump all WebSocket communicatation from the controller.",
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Clusterio Slave
|
||||
|
||||
Node hosting Factorio servers in a Clusterio cluster.
|
||||
Clusterio slaves connect to the master server and waits for commands from the master server to start up and stop instances.
|
||||
Clusterio slaves 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.
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ For example, installing the Subspace Storage plugin:
|
||||
npm install @clusterio/plugin-subspace_storage
|
||||
npx clusterioslave plugin add @clusterio/plugin-subspace_storage
|
||||
|
||||
Since the `plugin-list.json` is shared between master, slave and ctl you usually only need to do this once per machine.
|
||||
Since the `plugin-list.json` is shared between controller, slave and ctl you usually only need to do this once per machine.
|
||||
|
||||
|
||||
#### `plugin remove <name>`
|
||||
@@ -50,7 +50,7 @@ For example, uninstalling the Subspace Storage plugin:
|
||||
npx clusterioslave plugin remove subspace_storage
|
||||
npm uninstall @clusterio/plugin-subspace_storage
|
||||
|
||||
Since the `plugin-list.json` is shared between master, slave and ctl you usually only need to do this once per machine.
|
||||
Since the `plugin-list.json` is shared between controller, slave and ctl you usually only need to do this once per machine.
|
||||
|
||||
|
||||
#### `plugin list`
|
||||
|
||||
+10
-10
@@ -3,9 +3,9 @@
|
||||
/**
|
||||
* Clusterio slave
|
||||
*
|
||||
* Connects to the master server 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
|
||||
* module:master/master}.
|
||||
* module:controller/controller}.
|
||||
*
|
||||
* @module slave/slave
|
||||
* @author Danielv123, Hornwitser
|
||||
@@ -36,7 +36,7 @@ const Slave = require("./src/Slave");
|
||||
class SlaveConnector extends libLink.WebSocketClientConnector {
|
||||
constructor(slaveConfig, tlsCa, pluginInfos) {
|
||||
super(
|
||||
slaveConfig.get("slave.master_url"),
|
||||
slaveConfig.get("slave.controller_url"),
|
||||
slaveConfig.get("slave.max_reconnect_delay"),
|
||||
tlsCa
|
||||
);
|
||||
@@ -52,7 +52,7 @@ class SlaveConnector extends libLink.WebSocketClientConnector {
|
||||
}
|
||||
|
||||
this.sendHandshake("register_slave", {
|
||||
token: this.slaveConfig.get("slave.master_token"),
|
||||
token: this.slaveConfig.get("slave.controller_token"),
|
||||
agent: "Clusterio Slave",
|
||||
version,
|
||||
id: this.slaveConfig.get("slave.id"),
|
||||
@@ -187,13 +187,13 @@ async function startSlave() {
|
||||
// and as the process name in ps/top on linux.
|
||||
process.title = "clusterioSlave";
|
||||
|
||||
// make sure we have the master access token
|
||||
if (slaveConfig.get("slave.master_token") === "enter token here") {
|
||||
// make sure we have the controller access token
|
||||
if (slaveConfig.get("slave.controller_token") === "enter token here") {
|
||||
logger.fatal("ERROR invalid config!");
|
||||
logger.fatal(
|
||||
"Master server 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"+
|
||||
"clusterioslave config set slave.master_token <token>. You can generate an\n"+
|
||||
"clusterioslave config set slave.controller_token <token>. You can generate an\n"+
|
||||
"auth token using npx clusterioctl generate-slave-token."
|
||||
);
|
||||
process.exitCode = 1;
|
||||
@@ -201,9 +201,9 @@ async function startSlave() {
|
||||
}
|
||||
|
||||
// make sure url ends with /
|
||||
if (!slaveConfig.get("slave.master_url").endsWith("/")) {
|
||||
if (!slaveConfig.get("slave.controller_url").endsWith("/")) {
|
||||
logger.fatal("ERROR invalid config!");
|
||||
logger.fatal("slave.master_url must end with '/'");
|
||||
logger.fatal("slave.controller_url must end with '/'");
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -962,12 +962,12 @@ rcon.print(game.table_to_json(players))`.replace(/\r?\n/g, " ");
|
||||
await this.server.kill(true);
|
||||
}
|
||||
|
||||
async masterConnectionEventEventHandler(message) {
|
||||
await libPlugin.invokeHook(this.plugins, "onMasterConnectionEvent", message.data.event);
|
||||
async controllerConnectionEventEventHandler(message) {
|
||||
await libPlugin.invokeHook(this.plugins, "onControllerConnectionEvent", message.data.event);
|
||||
}
|
||||
|
||||
async prepareMasterDisconnectRequestHandler() {
|
||||
await libPlugin.invokeHook(this.plugins, "onPrepareMasterDisconnect");
|
||||
async prepareControllerDisconnectRequestHandler() {
|
||||
await libPlugin.invokeHook(this.plugins, "onPrepareControllerDisconnect");
|
||||
}
|
||||
|
||||
async getMetricsRequestHandler() {
|
||||
@@ -1072,7 +1072,7 @@ rcon.print(game.table_to_json(players))`.replace(/\r?\n/g, " ");
|
||||
let zip = await libFactorio.exportData(this.server);
|
||||
|
||||
let content = await zip.generateAsync({ type: "nodebuffer" });
|
||||
let url = new URL(this._slave.config.get("slave.master_url"));
|
||||
let url = new URL(this._slave.config.get("slave.controller_url"));
|
||||
url.pathname += "api/upload-export";
|
||||
url.searchParams.set("mod_pack_id", this.activeModPack.id);
|
||||
let response = await phin({
|
||||
@@ -1081,7 +1081,7 @@ rcon.print(game.table_to_json(players))`.replace(/\r?\n/g, " ");
|
||||
core: { ca: this._slave.tlsCa },
|
||||
headers: {
|
||||
"Content-Type": "application/zip",
|
||||
"x-access-token": this._slave.config.get("slave.master_token"),
|
||||
"x-access-token": this._slave.config.get("slave.controller_token"),
|
||||
},
|
||||
});
|
||||
if (response.statusCode !== 200) {
|
||||
|
||||
@@ -18,7 +18,7 @@ class InstanceConnection extends libLink.Link {
|
||||
}
|
||||
}
|
||||
|
||||
async forwardRequestToMaster(message, request) {
|
||||
async forwardRequestToController(message, request) {
|
||||
return await request.send(this.slave, message.data);
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ class InstanceConnection extends libLink.Link {
|
||||
let instanceConnection = this.slave.instanceConnections.get(instanceId);
|
||||
if (!instanceConnection) {
|
||||
// Instance is probably on another slave
|
||||
return await this.forwardRequestToMaster(message, request);
|
||||
return await this.forwardRequestToController(message, request);
|
||||
}
|
||||
|
||||
if (request.plugin && !instanceConnection.plugins.has(request.plugin)) {
|
||||
@@ -43,7 +43,7 @@ class InstanceConnection extends libLink.Link {
|
||||
let instanceConnection = this.slave.instanceConnections.get(instanceId);
|
||||
if (!instanceConnection) {
|
||||
// Instance is probably on another slave
|
||||
await this.forwardEventToMaster(message, event);
|
||||
await this.forwardEventToController(message, event);
|
||||
return;
|
||||
}
|
||||
if (event.plugin && !instanceConnection.plugins.has(event.plugin)) { return; }
|
||||
@@ -51,7 +51,7 @@ class InstanceConnection extends libLink.Link {
|
||||
event.send(instanceConnection, message.data);
|
||||
}
|
||||
|
||||
async forwardEventToMaster(message, event) {
|
||||
async forwardEventToController(message, event) {
|
||||
if (!this.slave.connector.hasSession) {
|
||||
return;
|
||||
}
|
||||
@@ -78,7 +78,7 @@ class InstanceConnection extends libLink.Link {
|
||||
if (this.status === "stopped") {
|
||||
this.slave.instanceConnections.delete(this.instanceId);
|
||||
}
|
||||
this.forwardEventToMaster(message, event);
|
||||
this.forwardEventToController(message, event);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+20
-18
@@ -83,14 +83,14 @@ async function discoverInstances(instancesDir) {
|
||||
/**
|
||||
* Handles running the slave
|
||||
*
|
||||
* Connects to the master server over the WebSocket and manages intsances.
|
||||
* Connects to the controller over the WebSocket and manages intsances.
|
||||
* @alias module:slave/src/Slave
|
||||
*/
|
||||
class Slave extends libLink.Link {
|
||||
// I don't like God classes, but the alternative of putting all this state
|
||||
// into global variables is not much better.
|
||||
constructor(connector, slaveConfig, tlsCa, pluginInfos) {
|
||||
super("slave", "master", connector);
|
||||
super("slave", "controller", connector);
|
||||
libLink.attachAllMessages(this);
|
||||
|
||||
this.pluginInfos = pluginInfos;
|
||||
@@ -101,7 +101,7 @@ class Slave extends libLink.Link {
|
||||
this.config = slaveConfig;
|
||||
|
||||
/**
|
||||
* Certificate authority used to validate TLS connections to the master.
|
||||
* Certificate authority used to validate TLS connections to the controller.
|
||||
* @type {?string}
|
||||
*/
|
||||
this.tlsCa = tlsCa;
|
||||
@@ -146,12 +146,12 @@ class Slave extends libLink.Link {
|
||||
if (this._disconnecting) {
|
||||
this._disconnecting = false;
|
||||
this.connector.connect().catch((err) => {
|
||||
logger.fatal(`Unexpected error reconnecting to master:\n${err.stack}`);
|
||||
logger.fatal(`Unexpected error reconnecting to controller:\n${err.stack}`);
|
||||
return this.shutdown();
|
||||
});
|
||||
|
||||
} else {
|
||||
logger.fatal("Master connection was unexpectedly closed");
|
||||
logger.fatal("Controller connection was unexpectedly closed");
|
||||
this.shutdown();
|
||||
}
|
||||
});
|
||||
@@ -159,7 +159,7 @@ class Slave extends libLink.Link {
|
||||
for (let event of ["connect", "drop", "resume", "close"]) {
|
||||
this.connector.on(event, () => {
|
||||
for (let instanceConnection of this.instanceConnections.values()) {
|
||||
libLink.messages.masterConnectionEvent.send(instanceConnection, { event });
|
||||
libLink.messages.controllerConnectionEvent.send(instanceConnection, { event });
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -306,17 +306,17 @@ class Slave extends libLink.Link {
|
||||
let { instance_id, serialized_config } = message.data;
|
||||
let instanceInfo = this.instanceInfos.get(instance_id);
|
||||
if (instanceInfo) {
|
||||
instanceInfo.config.update(serialized_config, true, "master");
|
||||
instanceInfo.config.update(serialized_config, true, "controller");
|
||||
logger.verbose(`Updated config for ${instanceInfo.path}`, this.instanceLogMeta(instance_id, instanceInfo));
|
||||
|
||||
} else {
|
||||
instanceInfo = this.discoveredInstanceInfos.get(instance_id);
|
||||
if (instanceInfo) {
|
||||
instanceInfo.config.update(serialized_config, true, "master");
|
||||
instanceInfo.config.update(serialized_config, true, "controller");
|
||||
|
||||
} else {
|
||||
let instanceConfig = new libConfig.InstanceConfig("slave");
|
||||
await instanceConfig.load(serialized_config, "master");
|
||||
await instanceConfig.load(serialized_config, "controller");
|
||||
|
||||
let instanceDir = await this._createNewInstanceDir(instanceConfig.get("instance.name"));
|
||||
|
||||
@@ -335,7 +335,7 @@ class Slave extends libLink.Link {
|
||||
}
|
||||
|
||||
// Somewhat hacky, but in the event of a lost session the status is
|
||||
// resent on assigment since the master server sends an assigment
|
||||
// resent on assigment since the controller sends an assigment
|
||||
// request for all the instances it knows should be on this slave.
|
||||
let instanceConnection = this.instanceConnections.get(instance_id);
|
||||
libLink.messages.instanceStatusChanged.send(this, {
|
||||
@@ -345,7 +345,7 @@ class Slave extends libLink.Link {
|
||||
|
||||
// save a copy of the instance config
|
||||
let warnedOutput = {
|
||||
_warning: "Changes to this file will be overwritten by the master server's copy.",
|
||||
_warning: "Changes to this file will be overwritten by the controller's copy.",
|
||||
...instanceInfo.config.serialize(),
|
||||
};
|
||||
await libFileOps.safeOutputFile(
|
||||
@@ -601,7 +601,7 @@ class Slave extends libLink.Link {
|
||||
checkRequestSaveName(filename);
|
||||
let instanceInfo = this.getRequestInstanceInfo(instance_id);
|
||||
|
||||
let url = new URL(this.config.get("slave.master_url"));
|
||||
let url = new URL(this.config.get("slave.controller_url"));
|
||||
url.pathname += `api/stream/${stream_id}`;
|
||||
let response = await phin({
|
||||
url, method: "GET",
|
||||
@@ -656,13 +656,15 @@ class Slave extends libLink.Link {
|
||||
throw err;
|
||||
}
|
||||
|
||||
let url = new URL(this.config.get("slave.master_url"));
|
||||
let url = new URL(this.config.get("slave.controller_url"));
|
||||
url.pathname += `api/stream/${stream_id}`;
|
||||
phin({
|
||||
url, method: "PUT",
|
||||
core: { ca: this.tlsCa },
|
||||
data: content,
|
||||
}).catch(err => logger.error(`Error pushing save to master:\n${err.stack}`, this.instanceLogMeta(instance_id)));
|
||||
}).catch(err => {
|
||||
logger.error(`Error pushing save to controller:\n${err.stack}`, this.instanceLogMeta(instance_id));
|
||||
});
|
||||
}
|
||||
|
||||
async exportDataRequestHandler(message, request) {
|
||||
@@ -704,7 +706,7 @@ class Slave extends libLink.Link {
|
||||
* Discover available instances
|
||||
*
|
||||
* Looks through the instances directory for instances and updates
|
||||
* the slave and master server with the new list of instances.
|
||||
* the slave and controller with the new list of instances.
|
||||
*/
|
||||
async updateInstances() {
|
||||
this.discoveredInstanceInfos = await discoverInstances(this.config.get("slave.instances_directory"));
|
||||
@@ -712,7 +714,7 @@ class Slave extends libLink.Link {
|
||||
for (let [instanceId, instanceInfo] of this.discoveredInstanceInfos) {
|
||||
let instanceConnection = this.instanceConnections.get(instanceId);
|
||||
list.push({
|
||||
serialized_config: instanceInfo.config.serialize("master"),
|
||||
serialized_config: instanceInfo.config.serialize("controller"),
|
||||
status: instanceConnection ? instanceConnection.status : "stopped",
|
||||
});
|
||||
}
|
||||
@@ -743,7 +745,7 @@ class Slave extends libLink.Link {
|
||||
|
||||
async prepareDisconnectRequestHandler(message, request) {
|
||||
for (let instanceConnection of this.instanceConnections.values()) {
|
||||
await libLink.messages.prepareMasterDisconnect.send(instanceConnection);
|
||||
await libLink.messages.prepareControllerDisconnect.send(instanceConnection);
|
||||
}
|
||||
this._disconnecting = true;
|
||||
this.connector.setClosing();
|
||||
@@ -790,7 +792,7 @@ ${err.stack}`
|
||||
}
|
||||
|
||||
/**
|
||||
* True if the connection to the master is connected, not in the dropped
|
||||
* True if the connection to the controller is connected, not in the dropped
|
||||
* state,and not in the process of disconnecting.
|
||||
* @type {boolean}
|
||||
*/
|
||||
|
||||
@@ -25,18 +25,18 @@ function ErrorCard(props) {
|
||||
|
||||
export default function App(props) {
|
||||
let [connected, setConnected] = useState(false);
|
||||
let [token, setToken] = useState(localStorage.getItem("master_token") || null);
|
||||
let [token, setToken] = useState(localStorage.getItem("controller_token") || null);
|
||||
let connector = props.control.connector;
|
||||
|
||||
function clearToken() {
|
||||
setToken(null);
|
||||
connector.token = null;
|
||||
localStorage.removeItem("master_token");
|
||||
localStorage.removeItem("controller_token");
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
function onConnect() {
|
||||
localStorage.setItem("master_token", token);
|
||||
localStorage.setItem("controller_token", token);
|
||||
setConnected(true);
|
||||
}
|
||||
|
||||
|
||||
+5
-5
@@ -6,16 +6,16 @@ import BaseConfigTree from "./BaseConfigTree";
|
||||
import ControlContext from "./ControlContext";
|
||||
|
||||
|
||||
export default function MasterConfigTree(props) {
|
||||
export default function ControllerConfigTree(props) {
|
||||
let control = useContext(ControlContext);
|
||||
|
||||
async function retrieveConfig() {
|
||||
let result = await libLink.messages.getMasterConfig.send(control);
|
||||
let result = await libLink.messages.getControllerConfig.send(control);
|
||||
return result.serialized_config;
|
||||
}
|
||||
|
||||
async function setField(field, value) {
|
||||
await libLink.messages.setMasterConfigField.send(control, {
|
||||
await libLink.messages.setControllerConfigField.send(control, {
|
||||
field,
|
||||
value: String(value),
|
||||
});
|
||||
@@ -30,11 +30,11 @@ export default function MasterConfigTree(props) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
await libLink.messages.setMasterConfigProp.send(control, data);
|
||||
await libLink.messages.setControllerConfigProp.send(control, data);
|
||||
}
|
||||
|
||||
return <BaseConfigTree
|
||||
ConfigClass={libConfig.MasterConfig}
|
||||
ConfigClass={libConfig.ControllerConfig}
|
||||
retrieveConfig={retrieveConfig}
|
||||
setField={setField}
|
||||
setProp={setProp}
|
||||
+7
-7
@@ -4,22 +4,22 @@ import { Typography } from "antd";
|
||||
import PluginExtra from "./PluginExtra";
|
||||
import LogConsole from "./LogConsole";
|
||||
import { useAccount } from "../model/account";
|
||||
import MasterConfigTree from "./MasterConfigTree";
|
||||
import ControllerConfigTree from "./ControllerConfigTree";
|
||||
import PageLayout from "./PageLayout";
|
||||
|
||||
const { Title } = Typography;
|
||||
|
||||
|
||||
export default function MasterPage() {
|
||||
export default function ControllerPage() {
|
||||
let account = useAccount();
|
||||
|
||||
return <PageLayout nav={[{ name: "Master" }]}>
|
||||
<h2>Master</h2>
|
||||
return <PageLayout nav={[{ name: "Controller" }]}>
|
||||
<h2>Controller</h2>
|
||||
{account.hasPermission("core.log.follow") && <>
|
||||
<Title level={5} style={{ marginTop: 16 }}>Console</Title>
|
||||
<LogConsole master={true} />
|
||||
<LogConsole controller={true} />
|
||||
</>}
|
||||
{account.hasPermission("core.master.get_config") && <MasterConfigTree />}
|
||||
<PluginExtra component="MasterPage" />
|
||||
{account.hasPermission("core.controller.get_config") && <ControllerConfigTree />}
|
||||
<PluginExtra component="ControllerPage" />
|
||||
</PageLayout>;
|
||||
};
|
||||
@@ -78,7 +78,7 @@ export default function InstanceViewPage(props) {
|
||||
<Alert
|
||||
message={instance["status"] === "deleted" ? "Instance has been deleted" : "Instance not found" }
|
||||
showIcon
|
||||
description={<>Instance with id {instanceId} was not found on the master server.</>}
|
||||
description={<>Instance with id {instanceId} was not found on the controller.</>}
|
||||
type="warning"
|
||||
action={
|
||||
<Button
|
||||
|
||||
@@ -28,7 +28,7 @@ function CreateInstanceButton(props) {
|
||||
let instanceConfig = new libConfig.InstanceConfig("control");
|
||||
await instanceConfig.init();
|
||||
instanceConfig.set("instance.name", values.instanceName);
|
||||
let serialized_config = instanceConfig.serialize("master");
|
||||
let serialized_config = instanceConfig.serialize("controller");
|
||||
await libLink.messages.createInstance.send(control, { serialized_config });
|
||||
setVisible(false);
|
||||
history.push(`/instances/${instanceConfig.get("instance.id")}/view`);
|
||||
|
||||
@@ -59,7 +59,7 @@ export default function LogConsole(props) {
|
||||
useEffect(() => {
|
||||
let logFilter = {
|
||||
all: props.all || false,
|
||||
master: props.master || false,
|
||||
controller: props.controller || false,
|
||||
slave_ids: props.slaves || [],
|
||||
instance_ids: props.instances || [],
|
||||
};
|
||||
@@ -94,7 +94,7 @@ export default function LogConsole(props) {
|
||||
return () => {
|
||||
control.offLog(logFilter, logHandler);
|
||||
};
|
||||
}, [props.all, props.master, (props.slaves || []).join(), (props.instances || []).join()]);
|
||||
}, [props.all, props.controller, (props.slaves || []).join(), (props.instances || []).join()]);
|
||||
|
||||
return <>
|
||||
<Paragraph code className="instance-console">
|
||||
|
||||
@@ -10,7 +10,7 @@ const { Paragraph } = Typography;
|
||||
|
||||
function TokenAuth(props) {
|
||||
return <>
|
||||
<Paragraph>If you have generated an authentication token from the master server, use that here.</Paragraph>
|
||||
<Paragraph>If you have generated an authentication token from the controller, use that here.</Paragraph>
|
||||
<Form
|
||||
name="login"
|
||||
onFinish={(values) => { props.setToken(values.token.trim()); }}
|
||||
|
||||
@@ -13,9 +13,10 @@ export default function ModDetails(props) {
|
||||
|
||||
return <Descriptions className="borderless" bordered size="small" column={{ xs: 1, sm: 1, md: 1, lg: 2 }}>
|
||||
{mod.error && <Descriptions.Item label={<><ExclamationCircleOutlined/> Error</>} span={2}>
|
||||
{mod.error === "missing" && "The given version of this mod is missing from the master storage."}
|
||||
{mod.error === "missing" && "The given version of this mod is missing from the controller storage."}
|
||||
{mod.error === "bad_checksum"
|
||||
&& "Checksum of the mod in the master storage does not match the checksum provided by this mod pack."
|
||||
&& "Checksum of the mod in the controller storage does not match the checksum provided " +
|
||||
"by this mod pack."
|
||||
}
|
||||
</Descriptions.Item>}
|
||||
{props.actions && !screens.lg
|
||||
|
||||
@@ -670,7 +670,7 @@ export default function ModPackViewPage() {
|
||||
className="site-page-header"
|
||||
title="Mod Pack Not Found"
|
||||
/>
|
||||
<p>Mod pack with id {modPackId} was not found on the master server.</p>
|
||||
<p>Mod pack with id {modPackId} was not found on the controller.</p>
|
||||
</PageLayout>;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ export default function PluginViewPage() {
|
||||
if (!pluginMeta) {
|
||||
return <PageLayout nav={nav}>
|
||||
<h2>Plugin not found</h2>
|
||||
<p>Plugin with name {pluginName} was not found on the master server.</p>
|
||||
<p>Plugin with name {pluginName} was not found on the controller.</p>
|
||||
</PageLayout>;
|
||||
}
|
||||
|
||||
@@ -84,9 +84,9 @@ export default function PluginViewPage() {
|
||||
message="Version missmatch detected"
|
||||
description={
|
||||
`The version of the web interface module for this plugin (${plugin.package.version}) `+
|
||||
`does not match the version running on the master server (${pluginMeta.version}), `+
|
||||
`does not match the version running on the controller (${pluginMeta.version}), `+
|
||||
"spurious errors may occur. This usually happens when the plugin is updated but the "+
|
||||
"master server has not been restarted yet, but may also be due to an outdated build."
|
||||
"controller has not been restarted yet, but may also be due to an outdated build."
|
||||
}
|
||||
type="warning"
|
||||
showIcon
|
||||
|
||||
@@ -67,7 +67,7 @@ export default function RoleViewPage() {
|
||||
className="site-page-header"
|
||||
title="Role not found"
|
||||
/>
|
||||
<p>Role with id {roleId} was not found on the master server.</p>
|
||||
<p>Role with id {roleId} was not found on the controller.</p>
|
||||
</PageLayout>;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ export default function SlaveViewPage(props) {
|
||||
className="site-page-header"
|
||||
title={slaveId}
|
||||
/>
|
||||
<p>Slave with id {slaveId} was not found on the master server.</p>
|
||||
<p>Slave with id {slaveId} was not found on the controller.</p>
|
||||
</PageLayout>;
|
||||
}
|
||||
|
||||
|
||||
@@ -99,10 +99,10 @@ function GenerateSlaveTokenButton(props) {
|
||||
<div className="codeblock">
|
||||
<CopyButton
|
||||
message={"Copied configuration commands to clipboard"}
|
||||
text={`npx clusterioslave config set slave.master_token ${token}
|
||||
text={`npx clusterioslave config set slave.controller_token ${token}
|
||||
npx clusterioslave config set slave.id ${slaveId}`}
|
||||
/>
|
||||
<p>npx clusterioslave config set slave.master_token <token></p>
|
||||
<p>npx clusterioslave config set slave.controller_token <token></p>
|
||||
<p>npx clusterioslave config set slave.id <slaveId></p>
|
||||
</div>
|
||||
<Typography.Paragraph>
|
||||
@@ -116,16 +116,16 @@ function GenerateSlaveTokenButton(props) {
|
||||
cd clusterio
|
||||
`+
|
||||
// eslint-disable-next-line max-len
|
||||
`npm init "@clusterio" -- --master-token ${token} --mode "slave" --download-headless --master-url ${document.location.origin}/ --slave-name "Slave ${slaveId || "?"}" --public-address localhost ${pluginString.length ? "--plugins" : ""} ${pluginString}`
|
||||
`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}`
|
||||
}/>
|
||||
<p>> mkdir clusterio</p>
|
||||
<p>> cd clusterio</p>
|
||||
<p>
|
||||
> npm init "@clusterio" --
|
||||
--master-token <span className="highlight">{token} </span>
|
||||
--controller-token <span className="highlight">{token} </span>
|
||||
--mode "slave"
|
||||
--download-headless
|
||||
--master-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>
|
||||
--public-address <span className="highlight">localhost </span>
|
||||
{pluginString.length ? "--plugins" : ""} <span className="highlight">{pluginString}</span>
|
||||
|
||||
@@ -76,7 +76,7 @@ export default function UserViewPage() {
|
||||
className="site-page-header"
|
||||
title="User not found"
|
||||
/>
|
||||
<p>User with name {userName} was not found on the master server.</p>
|
||||
<p>User with name {userName} was not found on the controller.</p>
|
||||
</PageLayout>;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ export { default as ControlContext } from "./components/ControlContext";
|
||||
|
||||
export { default as BaseConfigTree } from "./components/BaseConfigTree";
|
||||
export { default as InstanceConfigTree } from "./components/InstanceConfigTree";
|
||||
export { default as MasterConfigTree } from "./components/MasterConfigTree";
|
||||
export { default as ControllerConfigTree } from "./components/ControllerConfigTree";
|
||||
|
||||
export { default as LogConsole } from "./components/LogConsole";
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
|
||||
import MasterPage from "./components/MasterPage";
|
||||
import ControllerPage from "./components/ControllerPage";
|
||||
import SlavesPage from "./components/SlavesPage";
|
||||
import SlaveViewPage from "./components/SlaveViewPage";
|
||||
import InstancesPage from "./components/InstancesPage";
|
||||
@@ -17,9 +17,9 @@ import PluginViewPage from "./components/PluginViewPage";
|
||||
|
||||
export const pages = [
|
||||
{
|
||||
path: "/master",
|
||||
sidebarName: "Master",
|
||||
content: <MasterPage />,
|
||||
path: "/controller",
|
||||
sidebarName: "Controller",
|
||||
content: <ControllerPage />,
|
||||
},
|
||||
{
|
||||
path: "/slaves",
|
||||
|
||||
@@ -4,7 +4,7 @@ import packageJson from "../../package.json";
|
||||
|
||||
|
||||
/**
|
||||
* Connector for control connection to master server
|
||||
* Connector for control connection to controller
|
||||
* @private
|
||||
*/
|
||||
export class ControlConnector extends libLink.WebSocketClientConnector {
|
||||
@@ -30,12 +30,12 @@ export class ControlConnector extends libLink.WebSocketClientConnector {
|
||||
/**
|
||||
* Handles running the control
|
||||
*
|
||||
* Connects to the master server over WebSocket and sends commands to it.
|
||||
* Connects to the controller over WebSocket and sends commands to it.
|
||||
* @static
|
||||
*/
|
||||
export class Control extends libLink.Link {
|
||||
constructor(connector, plugins) {
|
||||
super("control", "master", connector);
|
||||
super("control", "controller", connector);
|
||||
libLink.attachAllMessages(this);
|
||||
|
||||
/**
|
||||
@@ -111,7 +111,7 @@ export class Control extends libLink.Link {
|
||||
for (let event of ["connect", "drop", "resume", "close"]) {
|
||||
this.connector.on(event, () => {
|
||||
for (let plugin of this.plugins.values()) {
|
||||
plugin.onMasterConnectionEvent(event);
|
||||
plugin.onControllerConnectionEvent(event);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -535,14 +535,14 @@ export class Control extends libLink.Link {
|
||||
|
||||
let combinedFilter = {
|
||||
all: false,
|
||||
master: false,
|
||||
controller: false,
|
||||
slave_ids: [],
|
||||
instance_ids: [],
|
||||
};
|
||||
|
||||
for (let filter of this.logHandlers.keys()) {
|
||||
if (filter.all) { combinedFilter.all = true; }
|
||||
if (filter.master) { combinedFilter.master = true; }
|
||||
if (filter.controller) { combinedFilter.controller = true; }
|
||||
for (let slaveId of filter.slave_ids || []) {
|
||||
if (!combinedFilter.slave_ids.includes(slaveId)) {
|
||||
combinedFilter.slave_ids.push(slaveId);
|
||||
|
||||
@@ -8,6 +8,6 @@ Plugin sharing the in-game chat between instances.
|
||||
Run the following commands in the folder Clusterio is installed to:
|
||||
|
||||
npm install @clusterio/plugin-global_chat
|
||||
npx clusteriomaster plugin add @clusterio/plugin-global_chat
|
||||
npx clusteriocontroller plugin add @clusterio/plugin-global_chat
|
||||
|
||||
Substitute clusteriomaster with clusterioslave or clusterioctl if this a dedicate slave or ctl installation respectively.
|
||||
Substitute clusteriocontroller with clusterioslave or clusterioctl if this a dedicate slave or ctl installation respectively.
|
||||
@@ -11,8 +11,8 @@ module.exports = {
|
||||
messages: {
|
||||
chat: new libLink.Event({
|
||||
type: "global_chat:chat",
|
||||
links: ["instance-slave", "slave-master", "master-slave", "slave-instance", "control-master"],
|
||||
forwardTo: "master",
|
||||
links: ["instance-slave", "slave-controller", "controller-slave", "slave-instance", "control-controller"],
|
||||
forwardTo: "controller",
|
||||
broadcastTo: "instance",
|
||||
eventProperties: {
|
||||
"instance_name": { type: "string" },
|
||||
|
||||
@@ -21,7 +21,7 @@ class InstancePlugin extends libPlugin.BaseInstancePlugin {
|
||||
this.messageQueue = [];
|
||||
}
|
||||
|
||||
onMasterConnectionEvent(event) {
|
||||
onControllerConnectionEvent(event) {
|
||||
if (event === "connect") {
|
||||
for (let message of this.messageQueue) {
|
||||
this.sendChat(message);
|
||||
|
||||
@@ -13,9 +13,9 @@ When a server crashes while you are online there won't be time to immediately up
|
||||
Run the following commands in the folder Clusterio is installed to:
|
||||
|
||||
npm install @clusterio/plugin-inventory_sync
|
||||
npx clusteriomaster plugin add @clusterio/plugin-inventory_sync
|
||||
npx clusteriocontroller plugin add @clusterio/plugin-inventory_sync
|
||||
|
||||
Substitute clusteriomaster with clusterioslave or clusterioctl if this a dedicated slave or ctl installation respectively.
|
||||
Substitute clusteriocontroller with clusterioslave or clusterioctl if this a dedicated slave or ctl installation respectively.
|
||||
|
||||
## Method of operation
|
||||
|
||||
@@ -23,17 +23,17 @@ This plugin does event based synchronization of inventories.
|
||||
The greatly simplified data flow is as follows:
|
||||
|
||||
1. Player joins server.
|
||||
2. Scenario script asks master for exclusive access to the player.
|
||||
3. Master grants exclusive access for the player to the instance.
|
||||
2. Scenario script asks controller for exclusive access to the player.
|
||||
3. Controller grants exclusive access for the player to the instance.
|
||||
4. Scenario script checks the access response and then acts according to the situation:
|
||||
|
||||
1. If the player has no inventory on the master then the current inventory becomes the synced inventory and the process is done.
|
||||
2. If the player inventory was previously uploaded and is the same as the one stored on the master then the current inventory becomes the synced inventory and the process is done.
|
||||
1. If the player has no inventory on the controller then the current inventory becomes the synced inventory and the process is done.
|
||||
2. If the player inventory was previously uploaded and is the same as the one stored on the controller then the current inventory becomes the synced inventory and the process is done.
|
||||
3. If the player inventory was previously uploaded but is not the same then the player inventory is deleted and the player is turned into a spectator.
|
||||
4. Otherwise the player inventory is kept.
|
||||
|
||||
5. Scenario script asks for the player inventory from the master.
|
||||
6. Master sends it to the scenario in a stream of chunks.
|
||||
5. Scenario script asks for the player inventory from the controller.
|
||||
6. Controller sends it to the scenario in a stream of chunks.
|
||||
7. Scenario displays and updates a progress bar as each chunk is received.
|
||||
8. Once all chunks have loaded the player's synced inventory is recreated from the data and the player can start playing.
|
||||
|
||||
@@ -41,5 +41,5 @@ When the player leaves the inventory is uploaded if it's a synced inventory and
|
||||
Should an error occur during this process the player is given the option to use a temporary inventory instead, which will be merged back into the synced inventory the next time the sync succeeds on that instance.
|
||||
|
||||
Communication between the server and instance goes over stdout or rcon, depending on the size of the data.
|
||||
Communication between the instance and the master server goes over websockets.
|
||||
Communication between the instance and the controller goes over websockets.
|
||||
Overall, we are able to achieve a latency between 3 and infinite ticks from server join, depending on the size of the inventory. The major limiter is rcon transfer speeds with larger inventories, especially if they contain blueprints.
|
||||
@@ -6,7 +6,7 @@ const libFileOps = require("@clusterio/lib/file_ops");
|
||||
const libPlugin = require("@clusterio/lib/plugin");
|
||||
|
||||
async function loadDatabase(config, logger) {
|
||||
let itemsPath = path.resolve(config.get("master.database_directory"), "inventories.json");
|
||||
let itemsPath = path.resolve(config.get("controller.database_directory"), "inventories.json");
|
||||
logger.verbose(`Loading ${itemsPath}`);
|
||||
try {
|
||||
let content = await fs.readFile(itemsPath);
|
||||
@@ -21,24 +21,24 @@ async function loadDatabase(config, logger) {
|
||||
}
|
||||
}
|
||||
|
||||
async function saveDatabase(masterConfig, playerDatastore, logger) {
|
||||
async function saveDatabase(controllerConfig, playerDatastore, logger) {
|
||||
if (playerDatastore) {
|
||||
let file = path.resolve(masterConfig.get("master.database_directory"), "inventories.json");
|
||||
let file = path.resolve(controllerConfig.get("controller.database_directory"), "inventories.json");
|
||||
logger.verbose(`writing ${file}`);
|
||||
let content = JSON.stringify(Array.from(playerDatastore));
|
||||
await libFileOps.safeOutputFile(file, content);
|
||||
}
|
||||
}
|
||||
|
||||
class MasterPlugin extends libPlugin.BaseMasterPlugin {
|
||||
class ControllerPlugin extends libPlugin.BaseControllerPlugin {
|
||||
async init() {
|
||||
this.acquiredPlayers = new Map();
|
||||
this.playerDatastore = await loadDatabase(this.master.config, this.logger);
|
||||
this.playerDatastore = await loadDatabase(this.controller.config, this.logger);
|
||||
this.autosaveId = setInterval(() => {
|
||||
saveDatabase(this.master.config, this.playerDatastore, this.logger).catch(err => {
|
||||
saveDatabase(this.controller.config, this.playerDatastore, this.logger).catch(err => {
|
||||
this.logger.error(`Unexpected error autosaving player data:\n${err.stack}`);
|
||||
});
|
||||
}, this.master.config.get("inventory_sync.autosave_interval") * 1000);
|
||||
}, this.controller.config.get("inventory_sync.autosave_interval") * 1000);
|
||||
}
|
||||
|
||||
async onInstanceStatusChanged(instance) {
|
||||
@@ -52,7 +52,7 @@ class MasterPlugin extends libPlugin.BaseMasterPlugin {
|
||||
}
|
||||
|
||||
if (["unknown", "stopped"].includes(instance.status)) {
|
||||
let timeout = this.master.config.get("inventory_sync.player_lock_timeout") * 1000;
|
||||
let timeout = this.controller.config.get("inventory_sync.player_lock_timeout") * 1000;
|
||||
for (let acquisitonRecord of this.acquiredPlayers.values()) {
|
||||
if (acquisitonRecord.instanceId === instanceId && !acquisitonRecord.expires) {
|
||||
acquisitonRecord.expires = Date.now() + timeout;
|
||||
@@ -74,7 +74,7 @@ class MasterPlugin extends libPlugin.BaseMasterPlugin {
|
||||
if (
|
||||
!acquisitionRecord
|
||||
|| acquisitionRecord.instanceId === instanceId
|
||||
|| !this.master.instances.has(acquisitionRecord.instanceId)
|
||||
|| !this.controller.instances.has(acquisitionRecord.instanceId)
|
||||
|| acquisitionRecord.expires && acquisitionRecord.expires < Date.now()
|
||||
) {
|
||||
this.acquiredPlayers.set(playerName, { instanceId });
|
||||
@@ -88,7 +88,7 @@ class MasterPlugin extends libPlugin.BaseMasterPlugin {
|
||||
let { instance_id, player_name } = message.data;
|
||||
if (!this.acquire(instance_id, player_name)) {
|
||||
let acquisitionRecord = this.acquiredPlayers.get(player_name);
|
||||
let instance = this.master.instances.get(acquisitionRecord.instanceId);
|
||||
let instance = this.controller.instances.get(acquisitionRecord.instanceId);
|
||||
return {
|
||||
status: "busy",
|
||||
message: instance.config.get("instance.name"),
|
||||
@@ -117,7 +117,7 @@ class MasterPlugin extends libPlugin.BaseMasterPlugin {
|
||||
|
||||
async uploadRequestHandler(message) {
|
||||
let { instance_id, player_name, player_data } = message.data;
|
||||
let instanceName = this.master.instances.get(instance_id).config.get("instance.name");
|
||||
let instanceName = this.controller.instances.get(instance_id).config.get("instance.name");
|
||||
let store = true;
|
||||
let acquisitionRecord = this.acquiredPlayers.get(player_name);
|
||||
if (!acquisitionRecord) {
|
||||
@@ -151,7 +151,7 @@ class MasterPlugin extends libPlugin.BaseMasterPlugin {
|
||||
|
||||
async downloadRequestHandler(message) {
|
||||
let { instance_id, player_name } = message.data;
|
||||
let instanceName = this.master.instances.get(instance_id).config.get("instance.name");
|
||||
let instanceName = this.controller.instances.get(instance_id).config.get("instance.name");
|
||||
|
||||
let acquisitionRecord = this.acquiredPlayers.get(player_name);
|
||||
if (!acquisitionRecord) {
|
||||
@@ -168,7 +168,7 @@ class MasterPlugin extends libPlugin.BaseMasterPlugin {
|
||||
|
||||
async onShutdown() {
|
||||
clearInterval(this.autosaveId);
|
||||
await saveDatabase(this.master.config, this.playerDatastore, this.logger);
|
||||
await saveDatabase(this.controller.config, this.playerDatastore, this.logger);
|
||||
}
|
||||
|
||||
async databaseStatsRequestHandler(message) {
|
||||
@@ -190,5 +190,5 @@ class MasterPlugin extends libPlugin.BaseMasterPlugin {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
MasterPlugin,
|
||||
ControllerPlugin,
|
||||
};
|
||||
@@ -1,17 +1,17 @@
|
||||
"use strict";
|
||||
const { libConfig, libLink, libUsers } = require("@clusterio/lib");
|
||||
|
||||
class MasterConfigGroup extends libConfig.PluginConfigGroup { }
|
||||
MasterConfigGroup.defaultAccess = ["master", "slave", "control"];
|
||||
MasterConfigGroup.groupName = "inventory_sync";
|
||||
MasterConfigGroup.define({
|
||||
class ControllerConfigGroup extends libConfig.PluginConfigGroup { }
|
||||
ControllerConfigGroup.defaultAccess = ["controller", "slave", "control"];
|
||||
ControllerConfigGroup.groupName = "inventory_sync";
|
||||
ControllerConfigGroup.define({
|
||||
name: "autosave_interval",
|
||||
title: "Autosave Interval",
|
||||
description: "Interval the player data is autosaved at in seconds.",
|
||||
type: "number",
|
||||
initial_value: 600, // 10 minutes
|
||||
});
|
||||
MasterConfigGroup.define({
|
||||
ControllerConfigGroup.define({
|
||||
name: "player_lock_timeout",
|
||||
title: "Player Lock Timeout",
|
||||
description:
|
||||
@@ -19,10 +19,10 @@ MasterConfigGroup.define({
|
||||
type: "number",
|
||||
initial_value: 60,
|
||||
});
|
||||
MasterConfigGroup.finalize();
|
||||
ControllerConfigGroup.finalize();
|
||||
|
||||
class InstanceConfigGroup extends libConfig.PluginConfigGroup { }
|
||||
InstanceConfigGroup.defaultAccess = ["master", "slave", "control"];
|
||||
InstanceConfigGroup.defaultAccess = ["controller", "slave", "control"];
|
||||
InstanceConfigGroup.groupName = "inventory_sync";
|
||||
InstanceConfigGroup.define({
|
||||
name: "rcon_chunk_size",
|
||||
@@ -48,8 +48,8 @@ module.exports = {
|
||||
instanceEntrypoint: "instance",
|
||||
InstanceConfigGroup,
|
||||
|
||||
masterEntrypoint: "master",
|
||||
MasterConfigGroup,
|
||||
controllerEntrypoint: "controller",
|
||||
ControllerConfigGroup,
|
||||
|
||||
webEntrypoint: "./web",
|
||||
routes: ["/inventory"],
|
||||
@@ -57,8 +57,8 @@ module.exports = {
|
||||
messages: {
|
||||
acquire: new libLink.Request({
|
||||
type: "inventory_sync:acquire",
|
||||
links: ["instance-slave", "slave-master"],
|
||||
forwardTo: "master",
|
||||
links: ["instance-slave", "slave-controller"],
|
||||
forwardTo: "controller",
|
||||
requestProperties: {
|
||||
"instance_id": { type: "integer" },
|
||||
"player_name": { type: "string" },
|
||||
@@ -73,8 +73,8 @@ module.exports = {
|
||||
}),
|
||||
release: new libLink.Request({
|
||||
type: "inventory_sync:release",
|
||||
links: ["instance-slave", "slave-master"],
|
||||
forwardTo: "master",
|
||||
links: ["instance-slave", "slave-controller"],
|
||||
forwardTo: "controller",
|
||||
requestProperties: {
|
||||
"instance_id": { type: "integer" },
|
||||
"player_name": { type: "string" },
|
||||
@@ -82,8 +82,8 @@ module.exports = {
|
||||
}),
|
||||
upload: new libLink.Request({
|
||||
type: "inventory_sync:upload",
|
||||
links: ["instance-slave", "slave-master"],
|
||||
forwardTo: "master",
|
||||
links: ["instance-slave", "slave-controller"],
|
||||
forwardTo: "controller",
|
||||
requestProperties: {
|
||||
"instance_id": { type: "integer" },
|
||||
"player_name": { type: "string" },
|
||||
@@ -92,8 +92,8 @@ module.exports = {
|
||||
}),
|
||||
download: new libLink.Request({
|
||||
type: "inventory_sync:download",
|
||||
links: ["instance-slave", "slave-master"],
|
||||
forwardTo: "master",
|
||||
links: ["instance-slave", "slave-controller"],
|
||||
forwardTo: "controller",
|
||||
requestProperties: {
|
||||
"instance_id": { type: "integer" },
|
||||
"player_name": { type: "string" },
|
||||
@@ -104,7 +104,7 @@ module.exports = {
|
||||
}),
|
||||
databaseStats: new libLink.Request({
|
||||
type: "inventory_sync:databaseStats",
|
||||
links: ["control-master"],
|
||||
links: ["control-controller"],
|
||||
permission: "inventory_sync.inventory.view",
|
||||
responseProperties: {
|
||||
"database_size": { type: "integer" },
|
||||
|
||||
@@ -32,11 +32,11 @@ class InstancePlugin extends libPlugin.BaseInstancePlugin {
|
||||
.catch(err => this.logger.error(`Error handling ipc-inventory_sync_download:\n${err.stack}`)));
|
||||
}
|
||||
|
||||
onPrepareMasterDisconnect() {
|
||||
onPrepareControllerDisconnect() {
|
||||
this.disconnecting = true;
|
||||
}
|
||||
|
||||
onMasterConnectionEvent(event) {
|
||||
onControllerConnectionEvent(event) {
|
||||
if (event === "connect") {
|
||||
this.disconnecting = false;
|
||||
(async () => {
|
||||
@@ -60,7 +60,7 @@ class InstancePlugin extends libPlugin.BaseInstancePlugin {
|
||||
let response = {
|
||||
player_name: request.player_name,
|
||||
status: "error",
|
||||
message: "Master is temporarily unavailable",
|
||||
message: "Controller is temporarily unavailable",
|
||||
};
|
||||
|
||||
if (this.slave.connector.connected && !this.disconnecting) {
|
||||
|
||||
@@ -25,8 +25,8 @@ local function download_inventory(player_name, data, number, total)
|
||||
|
||||
if total == 0 then
|
||||
-- Download was requested but the player had no data stored.
|
||||
log("ERROR: Inventory sync failed, got empty player data for " .. player.name .. " from the master")
|
||||
player.print("ERROR: Inventory sync failed, got empty player data from the master")
|
||||
log("ERROR: Inventory sync failed, got empty player data for " .. player.name .. " from the controller")
|
||||
player.print("ERROR: Inventory sync failed, got empty player data from the controller")
|
||||
|
||||
progress_dialog.remove(player)
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ function dialog_failed_download.create(player, acquire_response)
|
||||
|
||||
local reason
|
||||
if acquire_response.status == "timeout" then
|
||||
reason = "Timed out fetching inventory from the master server."
|
||||
reason = "Timed out fetching inventory from the controller."
|
||||
|
||||
elseif acquire_response.status == "busy" then
|
||||
reason =
|
||||
@@ -58,10 +58,10 @@ function dialog_failed_download.create(player, acquire_response)
|
||||
"If you just logged out from there it might take some time before the inventory is available."
|
||||
|
||||
elseif acquire_response.status == "error" then
|
||||
reason = "Error fetching inventory from the master server: " .. acquire_response.message .. "."
|
||||
reason = "Error fetching inventory from the controller: " .. acquire_response.message .. "."
|
||||
|
||||
else
|
||||
reason = "Unknown status " .. acquire_response.status .. " fetching inventory from the master server."
|
||||
reason = "Unknown status " .. acquire_response.status .. " fetching inventory from the controller."
|
||||
end
|
||||
|
||||
local p1 = content.add {
|
||||
@@ -129,7 +129,7 @@ dialog_failed_download.events = {
|
||||
global.inventory_sync.players[player.name].sync = false
|
||||
|
||||
elseif event.element.name == "inventory_sync_failed_download_retry" then
|
||||
-- Retry acquiring the player from the master
|
||||
-- Retry acquiring the player from the controller
|
||||
local player = game.get_player(event.player_index)
|
||||
inventory_sync.acquire(player)
|
||||
player.gui.screen.dialog_failed_download.destroy()
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
Inventory Sync
|
||||
|
||||
When a player leaves the game, serialize the player state and upload it to the master.
|
||||
When a player joins the game, request the player data from master and deserialize the player state from it.
|
||||
When a player leaves the game, serialize the player state and upload it to the controller.
|
||||
When a player joins the game, request the player data from controller and deserialize the player state from it.
|
||||
|
||||
]]
|
||||
|
||||
@@ -23,7 +23,7 @@ end
|
||||
local function create_player(player, dirty)
|
||||
global.inventory_sync.players[player.name] = {
|
||||
dirty = dirty, -- Player inventory has changes that should be persisted
|
||||
sync = false, -- Player inventory is synced from the master
|
||||
sync = false, -- Player inventory is synced from the controller
|
||||
generation = 0,
|
||||
}
|
||||
end
|
||||
@@ -40,7 +40,7 @@ end
|
||||
|
||||
|
||||
inventory_sync = {}
|
||||
-- This function is called by instance.js as /sc inventory_sync.download_inventory("danielv", Escaped JSON string, package_number, total_packages_count) with data from the master
|
||||
-- This function is called by instance.js as /sc inventory_sync.download_inventory("danielv", Escaped JSON string, package_number, total_packages_count) with data from the controller
|
||||
inventory_sync.download_inventory = download_inventory
|
||||
|
||||
-- This function is called internally when a player leaves the game to
|
||||
@@ -280,7 +280,7 @@ function inventory_sync.sync_player(acquire_response)
|
||||
end
|
||||
|
||||
if acquire_response.status == "acquired" then
|
||||
-- The stored data on the master server has changed since the
|
||||
-- The stored data on the controller has changed since the
|
||||
-- download was started. Restart it.
|
||||
game.print("Restarting download")
|
||||
download_record.restart = true
|
||||
@@ -342,7 +342,7 @@ function inventory_sync.finish_download(player, finished_record)
|
||||
player_record.generation = finished_record.generation
|
||||
end
|
||||
|
||||
-- Download inventory from master
|
||||
-- Download inventory from controller
|
||||
inventory_sync.events[defines.events.on_player_joined_game] = function(event)
|
||||
-- Send acquire request even if an active download is currently in plogress
|
||||
local player = game.get_player(event.player_index)
|
||||
@@ -406,7 +406,7 @@ inventory_sync.events[defines.events.on_pre_player_left_game] = function(event)
|
||||
clusterio_api.send_json("inventory_sync_upload", result)
|
||||
end
|
||||
|
||||
-- Invoked by the instance code to confirm the master has received the uploaded data
|
||||
-- Invoked by the instance code to confirm the controller has received the uploaded data
|
||||
function inventory_sync.confirm_upload(player_name, generation)
|
||||
local player = game.get_player(player_name)
|
||||
if not player or player.connected then
|
||||
|
||||
@@ -40,7 +40,7 @@ export class WebPlugin extends libPlugin.BaseWebPlugin {
|
||||
];
|
||||
}
|
||||
|
||||
onMasterConnectionEvent(event) {
|
||||
onControllerConnectionEvent(event) {
|
||||
if (event === "connect") {
|
||||
|
||||
}
|
||||
|
||||
@@ -10,12 +10,12 @@ Once the player enters the second code into the dialog in-game the web interface
|
||||
Run the following commands in the folder Clusterio is installed to:
|
||||
|
||||
npm install @clusterio/plugin-player_auth
|
||||
npx clusteriomaster plugin add @clusterio/plugin-player_auth
|
||||
npx clusteriocontroller plugin add @clusterio/plugin-player_auth
|
||||
|
||||
Substitute clusteriomaster with clusterioslave or clusterioctl if this a dedicate slave or ctl installation respectively.
|
||||
Substitute clusteriocontroller with clusterioslave or clusterioctl if this a dedicate slave or ctl installation respectively.
|
||||
|
||||
|
||||
## Master Configuration
|
||||
## Controller Configuration
|
||||
|
||||
#### player_auth.code_length
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ async function generateCode(length) {
|
||||
}
|
||||
|
||||
|
||||
class MasterPlugin extends libPlugin.BaseMasterPlugin {
|
||||
class ControllerPlugin extends libPlugin.BaseControllerPlugin {
|
||||
async init() {
|
||||
// Store of validation attempts by players
|
||||
this.players = new Map();
|
||||
@@ -43,9 +43,9 @@ class MasterPlugin extends libPlugin.BaseMasterPlugin {
|
||||
}
|
||||
}, 60e3).unref();
|
||||
|
||||
this.master.app.get("/api/player_auth/servers", (req, res) => {
|
||||
this.controller.app.get("/api/player_auth/servers", (req, res) => {
|
||||
let servers = [];
|
||||
for (let instance of this.master.instances.values()) {
|
||||
for (let instance of this.controller.instances.values()) {
|
||||
if (instance.status === "running" && instance.config.get("player_auth.load_plugin")) {
|
||||
servers.push(instance.config.get("factorio.settings")["name"] || "unnamed server");
|
||||
}
|
||||
@@ -53,11 +53,11 @@ class MasterPlugin extends libPlugin.BaseMasterPlugin {
|
||||
res.send(servers);
|
||||
});
|
||||
|
||||
this.master.app.post("/api/player_auth/player_code", express.json(), (req, res, next) => {
|
||||
this.controller.app.post("/api/player_auth/player_code", express.json(), (req, res, next) => {
|
||||
this.handlePlayerCode(req, res).catch(next);
|
||||
});
|
||||
|
||||
this.master.app.post("/api/player_auth/verify", express.json(), (req, res, next) => {
|
||||
this.controller.app.post("/api/player_auth/verify", express.json(), (req, res, next) => {
|
||||
this.handleVerify(req, res).catch(next);
|
||||
});
|
||||
}
|
||||
@@ -76,8 +76,8 @@ class MasterPlugin extends libPlugin.BaseMasterPlugin {
|
||||
|
||||
for (let entry of this.players.values()) {
|
||||
if (entry.playerCode === playerCode && entry.expires > Date.now()) {
|
||||
let verifyCode = await generateCode(this.master.config.get("player_auth.code_length"));
|
||||
let secret = Buffer.from(this.master.config.get("master.auth_secret"), "base64");
|
||||
let verifyCode = await generateCode(this.controller.config.get("player_auth.code_length"));
|
||||
let secret = Buffer.from(this.controller.config.get("controller.auth_secret"), "base64");
|
||||
let verifyToken = jwt.sign(
|
||||
{
|
||||
aud: "player_auth.verify_code",
|
||||
@@ -120,7 +120,7 @@ class MasterPlugin extends libPlugin.BaseMasterPlugin {
|
||||
return;
|
||||
}
|
||||
|
||||
let secret = Buffer.from(this.master.config.get("master.auth_secret"), "base64");
|
||||
let secret = Buffer.from(this.controller.config.get("controller.auth_secret"), "base64");
|
||||
try {
|
||||
let payload = jwt.verify(verifyToken, secret, { audience: "player_auth.verify_code" });
|
||||
if (payload.verify_code !== verifyCode) {
|
||||
@@ -139,7 +139,7 @@ class MasterPlugin extends libPlugin.BaseMasterPlugin {
|
||||
for (let [player, entry] of this.players) {
|
||||
if (entry.playerCode === playerCode && entry.expires > Date.now()) {
|
||||
if (entry.verifyCode === verifyCode) {
|
||||
let user = this.master.userManager.users.get(player);
|
||||
let user = this.controller.userManager.users.get(player);
|
||||
if (!user) {
|
||||
res.send({ error: true, message: "invalid user" });
|
||||
return;
|
||||
@@ -160,10 +160,10 @@ class MasterPlugin extends libPlugin.BaseMasterPlugin {
|
||||
}
|
||||
|
||||
async fetchPlayerCodeRequestHandler(message) {
|
||||
let playerCode = await generateCode(this.master.config.get("player_auth.code_length"));
|
||||
let expires = Date.now() + this.master.config.get("player_auth.code_timeout") * 1000;
|
||||
let playerCode = await generateCode(this.controller.config.get("player_auth.code_length"));
|
||||
let expires = Date.now() + this.controller.config.get("player_auth.code_timeout") * 1000;
|
||||
this.players.set(message.data.player, { playerCode, verifyCode: null, expires });
|
||||
return { player_code: playerCode, master_url: this.master.getMasterUrl() };
|
||||
return { player_code: playerCode, controller_url: this.controller.getControllerUrl() };
|
||||
}
|
||||
|
||||
async setVerifyCodeRequestHandler(message) {
|
||||
@@ -182,7 +182,7 @@ class MasterPlugin extends libPlugin.BaseMasterPlugin {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
MasterPlugin,
|
||||
ControllerPlugin,
|
||||
|
||||
// For testing only
|
||||
_generateCode: generateCode,
|
||||
+13
-13
@@ -2,51 +2,51 @@
|
||||
let { libConfig, libLink } = require("@clusterio/lib");
|
||||
|
||||
|
||||
class MasterConfigGroup extends libConfig.PluginConfigGroup {}
|
||||
MasterConfigGroup.defaultAccess = ["master", "slave", "control"];
|
||||
MasterConfigGroup.groupName = "player_auth";
|
||||
MasterConfigGroup.define({
|
||||
class ControllerConfigGroup extends libConfig.PluginConfigGroup {}
|
||||
ControllerConfigGroup.defaultAccess = ["controller", "slave", "control"];
|
||||
ControllerConfigGroup.groupName = "player_auth";
|
||||
ControllerConfigGroup.define({
|
||||
name: "code_length",
|
||||
title: "Code Length",
|
||||
description: "Length in characters of the generated codes.",
|
||||
type: "number",
|
||||
initial_value: 6,
|
||||
});
|
||||
MasterConfigGroup.define({
|
||||
ControllerConfigGroup.define({
|
||||
name: "code_timeout",
|
||||
title: "Code Timeout",
|
||||
description: "Time in seconds for the generated codes to stay valid.",
|
||||
type: "number",
|
||||
initial_value: 120,
|
||||
});
|
||||
MasterConfigGroup.finalize();
|
||||
ControllerConfigGroup.finalize();
|
||||
|
||||
module.exports = {
|
||||
name: "player_auth",
|
||||
title: "Player Auth",
|
||||
description: "Provides authentication to the cluster via logging into a Factorio server.",
|
||||
masterEntrypoint: "master",
|
||||
controllerEntrypoint: "controller",
|
||||
instanceEntrypoint: "instance",
|
||||
webEntrypoint: "./web",
|
||||
MasterConfigGroup,
|
||||
ControllerConfigGroup,
|
||||
|
||||
messages: {
|
||||
fetchPlayerCode: new libLink.Request({
|
||||
type: "player_auth:fetch_player_code",
|
||||
links: ["instance-slave", "slave-master"],
|
||||
forwardTo: "master",
|
||||
links: ["instance-slave", "slave-controller"],
|
||||
forwardTo: "controller",
|
||||
requestProperties: {
|
||||
"player": { type: "string" },
|
||||
},
|
||||
responseProperties: {
|
||||
"player_code": { type: "string" },
|
||||
"master_url": { type: "string" },
|
||||
"controller_url": { type: "string" },
|
||||
},
|
||||
}),
|
||||
setVerifyCode: new libLink.Request({
|
||||
type: "player_auth:set_verify_code",
|
||||
links: ["instance-slave", "slave-master"],
|
||||
forwardTo: "master",
|
||||
links: ["instance-slave", "slave-controller"],
|
||||
forwardTo: "controller",
|
||||
requestProperties: {
|
||||
"player": { type: "string" },
|
||||
"verify_code": { type: "string" },
|
||||
|
||||
@@ -30,7 +30,7 @@ class InstancePlugin extends libPlugin.BaseInstancePlugin {
|
||||
await this.sendRcon(`/web-login error ${event.player} ${err.message}`);
|
||||
return;
|
||||
}
|
||||
await this.sendRcon(`/web-login open ${event.player} ${response.master_url} ${response.player_code}`);
|
||||
await this.sendRcon(`/web-login open ${event.player} ${response.controller_url} ${response.player_code}`);
|
||||
|
||||
} else if (event.type === "set_verify_code") {
|
||||
try {
|
||||
|
||||
@@ -5,24 +5,24 @@ const jwt = require("jsonwebtoken");
|
||||
|
||||
const mock = require("../../../test/mock");
|
||||
|
||||
const master = require("../master");
|
||||
const controller = require("../controller");
|
||||
const instance = require("../instance");
|
||||
const info = require("../info");
|
||||
const libErrors = require("@clusterio/lib/errors");
|
||||
|
||||
|
||||
describe("player_auth", function() {
|
||||
describe("master.js", function() {
|
||||
describe("controller.js", function() {
|
||||
describe("generateCode()", function() {
|
||||
it("should generate a code of the given length", async function() {
|
||||
let code = await master._generateCode(20);
|
||||
let code = await controller._generateCode(20);
|
||||
assert.equal(typeof code, "string");
|
||||
assert.equal(code.length, 20, "incorrect length");
|
||||
});
|
||||
it("should only use the easy to recognize letters in the alphabet", async function() {
|
||||
let chars = new Set();
|
||||
for (let i = 0; i < 20; i++) {
|
||||
let code = await master._generateCode(15);
|
||||
let code = await controller._generateCode(15);
|
||||
for (let char of code) {
|
||||
chars.add(char);
|
||||
}
|
||||
@@ -35,25 +35,25 @@ describe("player_auth", function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe("MasterPlugin", function() {
|
||||
let masterPlugin;
|
||||
let masterUrl;
|
||||
describe("ControllerPlugin", function() {
|
||||
let controllerPlugin;
|
||||
let controllerUrl;
|
||||
before(async function() {
|
||||
masterPlugin = await mock.createMasterPlugin(master.MasterPlugin, info);
|
||||
masterPlugin.master.mockConfigEntries.set("player_auth.code_length", 10);
|
||||
masterPlugin.master.mockConfigEntries.set("player_auth.code_timeout", 1);
|
||||
masterUrl = await masterPlugin.master.startServer();
|
||||
controllerPlugin = await mock.createControllerPlugin(controller.ControllerPlugin, info);
|
||||
controllerPlugin.controller.mockConfigEntries.set("player_auth.code_length", 10);
|
||||
controllerPlugin.controller.mockConfigEntries.set("player_auth.code_timeout", 1);
|
||||
controllerUrl = await controllerPlugin.controller.startServer();
|
||||
});
|
||||
after(async function() {
|
||||
if (masterPlugin) {
|
||||
await masterPlugin.master.stopServer();
|
||||
if (controllerPlugin) {
|
||||
await controllerPlugin.controller.stopServer();
|
||||
}
|
||||
});
|
||||
|
||||
describe("/api/player_auth/servers", function() {
|
||||
it("should return a list of running servers with player_auth loaded", async function() {
|
||||
function addInstance(id, status, load, name) {
|
||||
masterPlugin.master.instances.set(id, {
|
||||
controllerPlugin.controller.instances.set(id, {
|
||||
config: {
|
||||
get(field) {
|
||||
if (field === "player_auth.load_plugin") {
|
||||
@@ -73,12 +73,12 @@ describe("player_auth", function() {
|
||||
addInstance(4, "stopped", false, "stopped unloaded");
|
||||
addInstance(5, "running", true, undefined);
|
||||
let result = await phin({
|
||||
url: `${masterUrl}/api/player_auth/servers`,
|
||||
url: `${controllerUrl}/api/player_auth/servers`,
|
||||
parse: "json",
|
||||
});
|
||||
assert.deepEqual(result.body, ["running loaded", "unnamed server"]);
|
||||
for (let id of [1, 2, 3, 4, 5]) {
|
||||
masterPlugin.master.instances.delete(id);
|
||||
controllerPlugin.controller.instances.delete(id);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -86,7 +86,7 @@ describe("player_auth", function() {
|
||||
describe("/api/player_auth/player_code", function() {
|
||||
it("should return 400 on invalid json", async function() {
|
||||
let result = await phin({
|
||||
url: `${masterUrl}/api/player_auth/player_code`,
|
||||
url: `${controllerUrl}/api/player_auth/player_code`,
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
@@ -98,7 +98,7 @@ describe("player_auth", function() {
|
||||
it("should return 400 on valid json which is not an object", async function() {
|
||||
for (let obj of [true, false, null, "string", 123, ["an", "array"]]) {
|
||||
let result = await phin({
|
||||
url: `${masterUrl}/api/player_auth/player_code`,
|
||||
url: `${controllerUrl}/api/player_auth/player_code`,
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
@@ -111,7 +111,7 @@ describe("player_auth", function() {
|
||||
it("should return 400 if player_code is not a string", async function() {
|
||||
for (let obj of [true, false, null, 123, [], {}]) {
|
||||
let result = await phin({
|
||||
url: `${masterUrl}/api/player_auth/player_code`,
|
||||
url: `${controllerUrl}/api/player_auth/player_code`,
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
@@ -123,9 +123,9 @@ describe("player_auth", function() {
|
||||
});
|
||||
it("should return invalid player_code if the code is expired", async function() {
|
||||
let expires = Date.now() - 1000;
|
||||
masterPlugin.players.set("expired", { playerCode: "expried", verifyCode: null, expires });
|
||||
controllerPlugin.players.set("expired", { playerCode: "expried", verifyCode: null, expires });
|
||||
let result = await phin({
|
||||
url: `${masterUrl}/api/player_auth/player_code`,
|
||||
url: `${controllerUrl}/api/player_auth/player_code`,
|
||||
method: "POST",
|
||||
data: { player_code: "expired" },
|
||||
parse: "json",
|
||||
@@ -134,9 +134,9 @@ describe("player_auth", function() {
|
||||
});
|
||||
it("should return a verify code and token if code is valid", async function() {
|
||||
let expires = Date.now() + 1000;
|
||||
masterPlugin.players.set("valid", { playerCode: "valid", verifyCode: null, expires });
|
||||
controllerPlugin.players.set("valid", { playerCode: "valid", verifyCode: null, expires });
|
||||
let result = await phin({
|
||||
url: `${masterUrl}/api/player_auth/player_code`,
|
||||
url: `${controllerUrl}/api/player_auth/player_code`,
|
||||
method: "POST",
|
||||
data: { player_code: "valid" },
|
||||
parse: "json",
|
||||
@@ -149,7 +149,7 @@ describe("player_auth", function() {
|
||||
describe("/api/player_auth/verify", function() {
|
||||
it("should return 400 on invalid json", async function() {
|
||||
let result = await phin({
|
||||
url: `${masterUrl}/api/player_auth/verify`,
|
||||
url: `${controllerUrl}/api/player_auth/verify`,
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
@@ -161,7 +161,7 @@ describe("player_auth", function() {
|
||||
it("should return 400 on valid json which is not an object", async function() {
|
||||
for (let obj of [true, false, null, "string", 123, ["an", "array"]]) {
|
||||
let result = await phin({
|
||||
url: `${masterUrl}/api/player_auth/verify`,
|
||||
url: `${controllerUrl}/api/player_auth/verify`,
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
@@ -175,7 +175,7 @@ describe("player_auth", function() {
|
||||
for (let obj of [true, false, null, 123, [], {}]) {
|
||||
for (let field of ["player_code", "verify_code", "verify_token"]) {
|
||||
let result = await phin({
|
||||
url: `${masterUrl}/api/player_auth/verify`,
|
||||
url: `${controllerUrl}/api/player_auth/verify`,
|
||||
method: "POST",
|
||||
data: {
|
||||
player_code: "str",
|
||||
@@ -191,7 +191,7 @@ describe("player_auth", function() {
|
||||
it("should return error if verify_token is not valid", async function() {
|
||||
async function verify(tokenParams, error) {
|
||||
let result = await phin({
|
||||
url: `${masterUrl}/api/player_auth/verify`,
|
||||
url: `${controllerUrl}/api/player_auth/verify`,
|
||||
method: "POST",
|
||||
data: {
|
||||
player_code: "player",
|
||||
@@ -214,9 +214,9 @@ describe("player_auth", function() {
|
||||
});
|
||||
it("should return invalid player_code if the code is expired", async function() {
|
||||
let expires = Date.now() - 1000;
|
||||
masterPlugin.players.set("expired", { playerCode: "expried", verifyCode: "verify", expires });
|
||||
controllerPlugin.players.set("expired", { playerCode: "expried", verifyCode: "verify", expires });
|
||||
let result = await phin({
|
||||
url: `${masterUrl}/api/player_auth/verify`,
|
||||
url: `${controllerUrl}/api/player_auth/verify`,
|
||||
method: "POST",
|
||||
data: {
|
||||
player_code: "expired",
|
||||
@@ -233,9 +233,9 @@ describe("player_auth", function() {
|
||||
});
|
||||
it("should return verified false if verify code has not yet been set", async function() {
|
||||
let expires = Date.now() + 1000;
|
||||
masterPlugin.players.set("unverified", { playerCode: "unverified", verifyCode: null, expires });
|
||||
controllerPlugin.players.set("unverified", { playerCode: "unverified", verifyCode: null, expires });
|
||||
let result = await phin({
|
||||
url: `${masterUrl}/api/player_auth/verify`,
|
||||
url: `${controllerUrl}/api/player_auth/verify`,
|
||||
method: "POST",
|
||||
data: {
|
||||
player_code: "unverified",
|
||||
@@ -252,9 +252,9 @@ describe("player_auth", function() {
|
||||
});
|
||||
it("should return error if user is missing", async function() {
|
||||
let expires = Date.now() + 1000;
|
||||
masterPlugin.players.set("missing", { playerCode: "missing", verifyCode: "verify", expires });
|
||||
controllerPlugin.players.set("missing", { playerCode: "missing", verifyCode: "verify", expires });
|
||||
let result = await phin({
|
||||
url: `${masterUrl}/api/player_auth/verify`,
|
||||
url: `${controllerUrl}/api/player_auth/verify`,
|
||||
method: "POST",
|
||||
data: {
|
||||
player_code: "missing",
|
||||
@@ -271,9 +271,9 @@ describe("player_auth", function() {
|
||||
});
|
||||
it("should return verified true with token if valid verification", async function() {
|
||||
let expires = Date.now() + 1000;
|
||||
masterPlugin.players.set("player", { playerCode: "player", verifyCode: "verify", expires });
|
||||
controllerPlugin.players.set("player", { playerCode: "player", verifyCode: "verify", expires });
|
||||
let result = await phin({
|
||||
url: `${masterUrl}/api/player_auth/verify`,
|
||||
url: `${controllerUrl}/api/player_auth/verify`,
|
||||
method: "POST",
|
||||
data: {
|
||||
player_code: "player",
|
||||
@@ -293,10 +293,10 @@ describe("player_auth", function() {
|
||||
|
||||
describe(".fetchPlayerCodeRequestHandler()", function() {
|
||||
it("should return a code", async function() {
|
||||
let result = await masterPlugin.fetchPlayerCodeRequestHandler({ data: { player: "test" }});
|
||||
let result = await controllerPlugin.fetchPlayerCodeRequestHandler({ data: { player: "test" }});
|
||||
assert(typeof result.player_code === "string", "no code returned");
|
||||
assert(result.player_code.length === 10, "incorrect code length returned");
|
||||
let expires = masterPlugin.players.get("test").expires;
|
||||
let expires = controllerPlugin.players.get("test").expires;
|
||||
let msFromExpected = Math.abs(expires - Date.now() - 1000);
|
||||
assert(msFromExpected < 100, `expiry time expected outside window (${msFromExpected}ms)`);
|
||||
});
|
||||
@@ -305,7 +305,7 @@ describe("player_auth", function() {
|
||||
describe(".setVerifyCodeRequestHandler()", function() {
|
||||
it("should throw if player does not exist", async function() {
|
||||
await assert.rejects(
|
||||
masterPlugin.setVerifyCodeRequestHandler(
|
||||
controllerPlugin.setVerifyCodeRequestHandler(
|
||||
{ data: { player: "invalid", verify_code: "invalid" }}
|
||||
),
|
||||
new libErrors.RequestError("invalid player")
|
||||
@@ -313,9 +313,9 @@ describe("player_auth", function() {
|
||||
});
|
||||
it("should throw if player code has expired", async function() {
|
||||
let expires = Date.now() - 1000;
|
||||
masterPlugin.players.set("expired", { playerCode: "expried", verifyCode: null, expires });
|
||||
controllerPlugin.players.set("expired", { playerCode: "expried", verifyCode: null, expires });
|
||||
await assert.rejects(
|
||||
masterPlugin.setVerifyCodeRequestHandler(
|
||||
controllerPlugin.setVerifyCodeRequestHandler(
|
||||
{ data: { player: "expired", verify_code: "expired" }}
|
||||
),
|
||||
new libErrors.RequestError("invalid player")
|
||||
@@ -325,21 +325,23 @@ describe("player_auth", function() {
|
||||
|
||||
describe("integration", function() {
|
||||
it("should verify a full login flow", async function() {
|
||||
let app = masterPlugin.master.app;
|
||||
let { player_code } = await masterPlugin.fetchPlayerCodeRequestHandler({ data: { player: "test" }});
|
||||
let app = controllerPlugin.controller.app;
|
||||
let { player_code } = await controllerPlugin.fetchPlayerCodeRequestHandler(
|
||||
{ data: { player: "test" }}
|
||||
);
|
||||
|
||||
let playerCodeResult = await phin({
|
||||
url: `${masterUrl}/api/player_auth/player_code`,
|
||||
url: `${controllerUrl}/api/player_auth/player_code`,
|
||||
method: "POST",
|
||||
data: { player_code },
|
||||
parse: "json",
|
||||
});
|
||||
|
||||
let { verify_code, verify_token } = playerCodeResult.body;
|
||||
await masterPlugin.setVerifyCodeRequestHandler({ data: { player: "test", verify_code }});
|
||||
await controllerPlugin.setVerifyCodeRequestHandler({ data: { player: "test", verify_code }});
|
||||
|
||||
let verifyResult = await phin({
|
||||
url: `${masterUrl}/api/player_auth/verify`,
|
||||
url: `${controllerUrl}/api/player_auth/verify`,
|
||||
method: "POST",
|
||||
data: { player_code, verify_code, verify_token },
|
||||
parse: "json",
|
||||
@@ -359,7 +361,7 @@ describe("player_auth", function() {
|
||||
|
||||
describe(".handleEvent()", async function() {
|
||||
describe("open_dialog", async function() {
|
||||
it("should call /web-login error if not connected to master", async function() {
|
||||
it("should call /web-login error if not connected to controller", async function() {
|
||||
instancePlugin.instance.server.reset();
|
||||
instancePlugin.slave.connector.connected = false;
|
||||
await instancePlugin.handleEvent({ type: "open_dialog", player: "test" });
|
||||
@@ -367,7 +369,7 @@ describe("player_auth", function() {
|
||||
instancePlugin.slave.connector.connected = true;
|
||||
assert.equal(command, "/web-login error test login is temporarily unavailable");
|
||||
});
|
||||
it("should call /web-login error after error from the master", async function() {
|
||||
it("should call /web-login error after error from the controller", async function() {
|
||||
instancePlugin.instance.server.reset();
|
||||
instancePlugin.instance.connector.once("send", message => {
|
||||
instancePlugin.instance.connector.emit("message", {
|
||||
@@ -375,15 +377,15 @@ describe("player_auth", function() {
|
||||
type: "player_auth:fetch_player_code_response",
|
||||
data: {
|
||||
seq: message.seq,
|
||||
error: "master error",
|
||||
error: "controller error",
|
||||
},
|
||||
});
|
||||
});
|
||||
await instancePlugin.handleEvent({ type: "open_dialog", player: "test" });
|
||||
let command = instancePlugin.instance.server.rconCommands[0];
|
||||
assert.equal(command, "/web-login error test master error");
|
||||
assert.equal(command, "/web-login error test controller error");
|
||||
});
|
||||
it("should call /web-login open after a valid response from the master", async function() {
|
||||
it("should call /web-login open after a valid response from the controller", async function() {
|
||||
instancePlugin.instance.server.reset();
|
||||
instancePlugin.instance.connector.once("send", message => {
|
||||
instancePlugin.instance.connector.emit("message", {
|
||||
@@ -392,17 +394,17 @@ describe("player_auth", function() {
|
||||
data: {
|
||||
seq: message.seq,
|
||||
player_code: "code",
|
||||
master_url: "master-url",
|
||||
controller_url: "controller-url",
|
||||
},
|
||||
});
|
||||
});
|
||||
await instancePlugin.handleEvent({ type: "open_dialog", player: "test" });
|
||||
let command = instancePlugin.instance.server.rconCommands[0];
|
||||
assert.equal(command, "/web-login open test master-url code");
|
||||
assert.equal(command, "/web-login open test controller-url code");
|
||||
});
|
||||
});
|
||||
describe("open_dialog", async function() {
|
||||
it("should call /web-login code_set after a valid response from the master", async function() {
|
||||
it("should call /web-login code_set after a valid response from the controller", async function() {
|
||||
instancePlugin.instance.server.reset();
|
||||
instancePlugin.instance.connector.once("send", message => {
|
||||
instancePlugin.instance.connector.emit("message", {
|
||||
@@ -417,20 +419,20 @@ describe("player_auth", function() {
|
||||
let command = instancePlugin.instance.server.rconCommands[0];
|
||||
assert.equal(command, "/web-login code_set test");
|
||||
});
|
||||
it("should call /web-login error after error from the master", async function() {
|
||||
it("should call /web-login error after error from the controller", async function() {
|
||||
instancePlugin.instance.server.reset();
|
||||
instancePlugin.instance.connector.once("send", message => {
|
||||
instancePlugin.instance.connector.emit("message", {
|
||||
seq: 1,
|
||||
type: "player_auth:set_verify_code_response",
|
||||
data: { seq: message.seq, error: "master error" },
|
||||
data: { seq: message.seq, error: "controller error" },
|
||||
});
|
||||
});
|
||||
await instancePlugin.handleEvent(
|
||||
{ type: "set_verify_code", player: "test", verify_code: "verify" }
|
||||
);
|
||||
let command = instancePlugin.instance.server.rconCommands[0];
|
||||
assert.equal(command, "/web-login error test master error");
|
||||
assert.equal(command, "/web-login error test controller error");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,6 +8,6 @@ Plugin synchronizing research progress between instances and the cluster.
|
||||
Run the following commands in the folder Clusterio is installed to:
|
||||
|
||||
npm install @clusterio/plugin-research_sync
|
||||
npx clusteriomaster plugin add @clusterio/plugin-research_sync
|
||||
npx clusteriocontroller plugin add @clusterio/plugin-research_sync
|
||||
|
||||
Substitute clusteriomaster with clusterioslave or clusterioctl if this a dedicate slave or ctl installation respectively.
|
||||
Substitute clusteriocontroller with clusterioslave or clusterioctl if this a dedicate slave or ctl installation respectively.
|
||||
@@ -7,8 +7,8 @@ const libPlugin = require("@clusterio/lib/plugin");
|
||||
const RateLimiter = require("@clusterio/lib/RateLimiter");
|
||||
|
||||
|
||||
async function loadTechnologies(masterConfig, logger) {
|
||||
let filePath = path.join(masterConfig.get("master.database_directory"), "technologies.json");
|
||||
async function loadTechnologies(controllerConfig, logger) {
|
||||
let filePath = path.join(controllerConfig.get("controller.database_directory"), "technologies.json");
|
||||
logger.verbose(`Loading ${filePath}`);
|
||||
try {
|
||||
return new Map(JSON.parse(await fs.readFile(filePath)));
|
||||
@@ -23,15 +23,15 @@ async function loadTechnologies(masterConfig, logger) {
|
||||
}
|
||||
}
|
||||
|
||||
async function saveTechnologies(masterConfig, technologies, logger) {
|
||||
let filePath = path.join(masterConfig.get("master.database_directory"), "technologies.json");
|
||||
async function saveTechnologies(controllerConfig, technologies, logger) {
|
||||
let filePath = path.join(controllerConfig.get("controller.database_directory"), "technologies.json");
|
||||
logger.verbose(`writing ${filePath}`);
|
||||
await libFileOps.safeOutputFile(filePath, JSON.stringify([...technologies.entries()], null, 4));
|
||||
}
|
||||
|
||||
class MasterPlugin extends libPlugin.BaseMasterPlugin {
|
||||
class ControllerPlugin extends libPlugin.BaseControllerPlugin {
|
||||
async init() {
|
||||
this.technologies = await loadTechnologies(this.master.config, this.logger);
|
||||
this.technologies = await loadTechnologies(this.controller.config, this.logger);
|
||||
this.progressRateLimiter = new RateLimiter({
|
||||
maxRate: 1,
|
||||
action: () => this.broadcastProgress(),
|
||||
@@ -44,7 +44,7 @@ class MasterPlugin extends libPlugin.BaseMasterPlugin {
|
||||
|
||||
async onShutdown() {
|
||||
this.progressRateLimiter.cancel();
|
||||
await saveTechnologies(this.master.config, this.technologies, this.logger);
|
||||
await saveTechnologies(this.controller.config, this.technologies, this.logger);
|
||||
}
|
||||
|
||||
broadcastProgress() {
|
||||
@@ -169,5 +169,5 @@ class MasterPlugin extends libPlugin.BaseMasterPlugin {
|
||||
|
||||
|
||||
module.exports = {
|
||||
MasterPlugin,
|
||||
ControllerPlugin,
|
||||
};
|
||||
@@ -2,7 +2,7 @@
|
||||
const { libLink } = require("@clusterio/lib");
|
||||
|
||||
|
||||
// schema used for syncing technologies to and from the master
|
||||
// schema used for syncing technologies to and from the controller
|
||||
const technologies = {
|
||||
type: "array",
|
||||
items: {
|
||||
@@ -23,13 +23,13 @@ module.exports = {
|
||||
title: "Research Sync",
|
||||
description: "Synchronises technology research progress between instances.",
|
||||
instanceEntrypoint: "instance",
|
||||
masterEntrypoint: "master",
|
||||
controllerEntrypoint: "controller",
|
||||
|
||||
messages: {
|
||||
contribution: new libLink.Event({
|
||||
type: "research_sync:contribution",
|
||||
links: ["instance-slave", "slave-master"],
|
||||
forwardTo: "master",
|
||||
links: ["instance-slave", "slave-controller"],
|
||||
forwardTo: "controller",
|
||||
eventProperties: {
|
||||
"name": { type: "string" },
|
||||
"level": { type: "integer" },
|
||||
@@ -38,7 +38,7 @@ module.exports = {
|
||||
}),
|
||||
progress: new libLink.Event({
|
||||
type: "research_sync:progress",
|
||||
links: ["master-slave", "slave-instance"],
|
||||
links: ["controller-slave", "slave-instance"],
|
||||
broadcastTo: "instance",
|
||||
eventProperties: {
|
||||
"technologies": {
|
||||
@@ -57,8 +57,8 @@ module.exports = {
|
||||
}),
|
||||
finished: new libLink.Event({
|
||||
type: "research_sync:finished",
|
||||
links: ["instance-slave", "slave-master", "master-slave", "slave-instance"],
|
||||
forwardTo: "master",
|
||||
links: ["instance-slave", "slave-controller", "controller-slave", "slave-instance"],
|
||||
forwardTo: "controller",
|
||||
broadcastTo: "instance",
|
||||
eventProperties: {
|
||||
"name": { type: "string" },
|
||||
@@ -67,8 +67,8 @@ module.exports = {
|
||||
}),
|
||||
syncTechnologies: new libLink.Request({
|
||||
type: "research_sync:sync_technologies",
|
||||
links: ["instance-slave", "slave-master"],
|
||||
forwardTo: "master",
|
||||
links: ["instance-slave", "slave-controller"],
|
||||
forwardTo: "controller",
|
||||
requestProperties: {
|
||||
"technologies": technologies,
|
||||
},
|
||||
|
||||
@@ -66,8 +66,8 @@ class InstancePlugin extends libPlugin.BaseInstancePlugin {
|
||||
let response = await this.info.messages.syncTechnologies.send(this.instance, { technologies: techsToSend });
|
||||
this.syncStarted = true;
|
||||
let techsToSync = [];
|
||||
for (let masterTech of response.technologies) {
|
||||
let [name, level, progress, researched] = masterTech;
|
||||
for (let controllerTech of response.technologies) {
|
||||
let [name, level, progress, researched] = controllerTech;
|
||||
let instanceTech = instanceTechs.get(name);
|
||||
if (
|
||||
!instanceTech
|
||||
@@ -75,7 +75,7 @@ class InstancePlugin extends libPlugin.BaseInstancePlugin {
|
||||
|| (instanceTech.progress || null) !== progress
|
||||
|| instanceTech.researched !== researched
|
||||
) {
|
||||
techsToSync.push(masterTech);
|
||||
techsToSync.push(controllerTech);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ sync.events[clusterio_api.events.on_server_startup] = function(event)
|
||||
}
|
||||
end
|
||||
|
||||
-- Used when syncing completed technologies from the master
|
||||
-- Used when syncing completed technologies from the controller
|
||||
global.research_sync.ignore_research_finished = false
|
||||
|
||||
local force = game.forces["player"]
|
||||
@@ -165,14 +165,14 @@ function research_sync.update_progress(data)
|
||||
local techs = game.json_to_table(data)
|
||||
local force = game.forces["player"]
|
||||
|
||||
for _, masterTech in ipairs(techs) do
|
||||
local tech = force.technologies[masterTech.name]
|
||||
if tech and tech.level == masterTech.level then
|
||||
for _, controllerTech in ipairs(techs) do
|
||||
local tech = force.technologies[controllerTech.name]
|
||||
if tech and tech.level == controllerTech.level then
|
||||
send_contribution(tech)
|
||||
set_technology_progress(tech, masterTech.progress)
|
||||
set_technology_progress(tech, controllerTech.progress)
|
||||
global.research_sync.technologies[tech.name] = {
|
||||
level = tech.level,
|
||||
progress = masterTech.progress
|
||||
progress = controllerTech.progress
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
|
||||
"use strict";
|
||||
require("../instance");
|
||||
require("../master");
|
||||
require("../controller");
|
||||
@@ -8,9 +8,9 @@ Plugin exporting item production, fluid production, kill count, entity build and
|
||||
Run the following commands in the folder Clusterio is installed to:
|
||||
|
||||
npm install @clusterio/plugin-statistics_exporter
|
||||
npx clusteriomaster plugin add @clusterio/plugin-statistics_exporter
|
||||
npx clusteriocontroller plugin add @clusterio/plugin-statistics_exporter
|
||||
|
||||
Substitute clusteriomaster with clusterioslave or clusterioctl if this a dedicate slave or ctl installation respectively.
|
||||
Substitute clusteriocontroller with clusterioslave or clusterioctl if this a dedicate slave or ctl installation respectively.
|
||||
|
||||
|
||||
## Instance Configuration
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
const { libConfig } = require("@clusterio/lib");
|
||||
|
||||
class InstanceConfigGroup extends libConfig.PluginConfigGroup {}
|
||||
InstanceConfigGroup.defaultAccess = ["master", "slave", "control"];
|
||||
InstanceConfigGroup.defaultAccess = ["controller", "slave", "control"];
|
||||
InstanceConfigGroup.groupName = "statistics_exporter";
|
||||
InstanceConfigGroup.define({
|
||||
name: "command_timeout",
|
||||
|
||||
@@ -84,7 +84,7 @@ class InstancePlugin extends libPlugin.BaseInstancePlugin {
|
||||
// Wait configured timeout for the metrics to be collected. It may
|
||||
// take a long time for the command to go through if the command
|
||||
// stream is overloaded. Should the timeout be exceeded the
|
||||
// previous values for the metrics will end up being sent to master.
|
||||
// previous values for the metrics will end up being sent to controller.
|
||||
let timeout = this.instance.config.get("statistics_exporter.command_timeout") * 1000;
|
||||
await libHelpers.timeout(this.gatherMetrics(), timeout);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,6 @@ Plugin for the Subpsace Storage mod.
|
||||
Run the following commands in the folder Clusterio is installed to:
|
||||
|
||||
npm install @clusterio/plugin-subspace_storage
|
||||
npx clusteriomaster plugin add @clusterio/plugin-subspace_storage
|
||||
npx clusteriocontroller plugin add @clusterio/plugin-subspace_storage
|
||||
|
||||
Substitute clusteriomaster with clusterioslave or clusterioctl if this a dedicate slave or ctl installation respectively.
|
||||
Substitute clusteriocontroller with clusterioslave or clusterioctl if this a dedicate slave or ctl installation respectively.
|
||||
Loaded 100 of 119 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user