UNPKG

@clusterio/lib

Version:
711 lines (690 loc) 22.8 kB
// Core definitions for the configuration system import * as classes from "./classes"; import type { PluginNodeEnvInfo, PluginWebpackEnvInfo } from "../plugin"; import { Static } from "@sinclair/typebox"; type configFromJSON<T> = (...args: Parameters<typeof classes.Config.fromJSON>) => T; type configFromFile<T> = (...args: Parameters<typeof classes.Config.fromFile>) => Promise<T>; export interface ControllerConfigFields { "controller.version": string; "controller.name": string; "controller.mods_directory": string; "controller.database_directory": string; "controller.http_port": number | null; "controller.https_port": number | null; "controller.bind_address": string | null; "controller.trusted_proxies": string | null; "controller.public_url": string | null; "controller.tls_certificate": string | null; "controller.tls_private_key": string | null; "controller.auth_secret": string; "controller.heartbeat_interval": number; "controller.session_timeout": number; "controller.metrics_timeout": number; "controller.system_metrics_interval": number; "controller.proxy_stream_timeout": number; "controller.factorio_username": string | null; "controller.factorio_token": string | null; "controller.share_factorio_credentials_with_hosts": boolean; "controller.default_mod_pack_id": number | null; "controller.default_role_id": number | null; "controller.autosave_interval": number; "controller.mod_portal_cache_duration_minutes": number; "controller.mod_portal_page_size": number; "controller.allow_remote_updates": boolean; "controller.allow_plugin_updates": boolean; "controller.allow_plugin_install": boolean; [key: `${string}.load_plugin`]: boolean; } /** * Controller Config class * @extends classes.Config */ export class ControllerConfig extends classes.Config<ControllerConfigFields> { declare static fromJSON: configFromJSON<ControllerConfig>; declare static fromFile: configFromFile<ControllerConfig>; static migrations(config: Static<typeof this.jsonSchema>) { if (config.hasOwnProperty("controller.external_address")) { config["controller.public_url"] = config["controller.external_address"]; delete config["controller.external_address"]; } return config; } static fieldDefinitions: classes.ConfigDefs<ControllerConfigFields> = { "controller.version": { type: "string", initialValue: "", // Set on start readonly: ["controller"], hidden: true, }, "controller.name": { title: "Name", description: "Name of the cluster.", type: "string", initialValue: "Your Cluster", }, "controller.mods_directory": { title: "Mods Directory", description: "Path to directory where mods shared with the cluster are stored.", restartRequired: true, type: "string", initialValue: "mods", }, "controller.database_directory": { title: "Database directory", description: "Directory where item and configuration data is stored.", type: "string", initialValue: "database", }, "controller.autosave_interval": { title: "Autosave Interval", description: "Interval in seconds to autosave data in memory to disk.", type: "number", initialValue: 60, }, "controller.http_port": { title: "HTTP Port", description: "Port to listen for HTTP connections on, set to null to not listen for HTTP connections.", restartRequired: true, type: "number", optional: true, initialValue: 8080, }, "controller.https_port": { title: "HTTPS Port", description: "Port to listen for HTTPS connection on, set to null to not listen for HTTPS connections.", restartRequired: true, type: "number", optional: true, }, "controller.bind_address": { title: "Bind Address", description: "IP address to bind the HTTP and HTTPS ports on.", restartRequired: true, type: "string", optional: true, }, "controller.trusted_proxies": { title: "Trusted Proxies", description: "Comma separated list of IP addresses and/or CIDR blocks to trust the X-Forwarded-For header on", type: "string", optional: true, }, "controller.public_url": { title: "Public URL", description: "Public facing URL the controller is hosted on, including the protocol.", type: "string", optional: true, }, "controller.tls_certificate": { title: "TLS Certificate", description: "Path to the certificate to use for HTTPS.", restartRequired: true, type: "string", optional: true, }, "controller.tls_private_key": { title: "TLS Private Key", description: "Path to the private key to use for HTTPS.", restartRequired: true, type: "string", optional: true, }, "controller.auth_secret": { access: ["controller"], title: "Controller Authentication Secret", description: "Secret used to generate and verify authentication tokens. " + "Should be a long string of random letters and numbers. " + "Do not share this.", restartRequired: true, type: "string", optional: true, }, "controller.heartbeat_interval": { title: "Heartbeat Interval", description: "Interval heartbeats are sent out on WebSocket connections.", type: "number", initialValue: 15, }, "controller.session_timeout": { title: "Session Timeout", description: "Time in seconds before giving up resuming a dropped WebSocket session.", type: "number", initialValue: 60, }, "controller.metrics_timeout": { title: "Metrics Timeout", description: "Timeout in seconds for metrics gathering from hosts.", type: "number", initialValue: 8, }, "controller.system_metrics_interval": { title: "System Metrics Interval", description: "Interval in seconds to collect and update system metrics for the Web UI", type: "number", initialValue: 10, }, "controller.proxy_stream_timeout": { title: "Proxy Stream Timeout", description: "Timeout in seconds for proxy streams to start flowing.", type: "number", initialValue: 15, }, "controller.factorio_username": { title: "Factorio Username", description: "Username to authenticate with Factorio API with.", autoComplete: "section-factorio username", type: "string", optional: true, }, "controller.factorio_token": { title: "Factorio Token", description: "Token to authenticate with Factorio API with.", autoComplete: "section-factorio new-password", type: "string", credential: ["controller"], optional: true, }, "controller.share_factorio_credentials_with_hosts": { title: "Share Factorio credentials with Hosts", description: "If enabled, the Factorio Username and Token will be shared with hosts and " + "used in the server settings of instances in this cluster.", type: "boolean", initialValue: true, }, "controller.default_mod_pack_id": { title: "Default Mod Pack", description: "Mod pack used by default for instances.", inputComponent: "mod_pack", type: "number", optional: true, }, "controller.default_role_id": { title: "Default role", description: "ID of role assigned by default to new users.", inputComponent: "role", type: "number", optional: true, initialValue: 1, }, "controller.mod_portal_cache_duration_minutes": { title: "Mod Portal Cache Duration", description: "Duration in minutes to cache mod portal API responses.", type: "number", initialValue: 30, }, "controller.mod_portal_page_size": { title: "Mod Portal Page Size", description: "Maximum number of results per page when querying the Factorio mod portal API.", type: "number", initialValue: 1000, }, "controller.allow_remote_updates": { description: "When true, allows a remote event to trigger a clusterio update via npm", type: "boolean", initialValue: true, readonly: ["controller"], hidden: true, }, "controller.allow_plugin_updates": { description: "When true, allows a remote event to trigger a plugin update via npm", type: "boolean", initialValue: true, readonly: ["controller"], hidden: true, }, "controller.allow_plugin_install": { description: "When true, allows a remote event to trigger a plugin install via npm", type: "boolean", initialValue: false, readonly: ["controller"], hidden: true, }, }; } export interface HostConfigFields { "host.version": string; "host.name": string; "host.id": number; "host.factorio_directory": string; "host.mods_directory": string; "host.instances_directory": string; "host.controller_url": string; "host.controller_token": string; "host.tls_ca": string | null; "host.public_address": string; "host.factorio_port_range": string; "host.factorio_username": string | null, "host.factorio_token": string | null, "host.max_reconnect_delay": number; "host.allow_remote_updates": boolean; "host.allow_plugin_updates": boolean; "host.allow_plugin_install": boolean; [key: `${string}.load_plugin`]: boolean; } /** * Host Config class * @extends classes.Config */ export class HostConfig extends classes.Config<HostConfigFields> { declare static fromJSON: configFromJSON<HostConfig>; declare static fromFile: configFromFile<HostConfig>; static fieldDefinitions: classes.ConfigDefs<HostConfigFields> = { "host.version": { type: "string", initialValue: "", // Set on start readonly: ["host"], hidden: true, }, "host.name": { description: "Name of the host", type: "string", initialValue: "New Host", }, "host.id": { description: "ID of the host", type: "number", initialValue: () => Math.random() * 2**31 | 0, hidden: true, }, "host.factorio_directory": { description: "Path to directory to look for factorio installs", type: "string", initialValue: "factorio", }, "host.mods_directory": { title: "Mods Directory", description: "Path to directory where mods for instances are cached.", restartRequired: true, type: "string", initialValue: "mods", }, "host.instances_directory": { description: "Path to directory to store instances in.", restartRequired: true, type: "string", initialValue: "instances", }, "host.controller_url": { description: "URL to connect to the controller at", restartRequired: true, type: "string", initialValue: "http://localhost:8080/", }, "host.controller_token": { access: ["host"], description: "Token to authenticate to controller with.", restartRequired: true, type: "string", initialValue: "enter token here", }, "host.tls_ca": { description: "Path to Certificate Authority to validate TLS connection to controller against.", restartRequired: true, type: "string", optional: true, }, "host.public_address": { description: "Public facing address players should connect to in order to join instances on this host", type: "string", initialValue: "localhost", }, "host.factorio_port_range": { title: "Factorio port range", description: "Range of UDP ports to use for game connections. Supports both comma separated values and " + "ranges separated with a dash.", type: "string", initialValue: "34100-34199", }, "host.factorio_username": { title: "Factorio Username", description: "Username to authenticate with Factorio API with. If set this will be used in the server settings " + "of all instances on this host.", autoComplete: "section-factorio username", type: "string", optional: true, }, "host.factorio_token": { title: "Factorio Token", description: "Token to authenticate with Factorio API with. If set this will be used in the server settings " + "of all instances on this host.", autoComplete: "section-factorio new-password", type: "string", credential: ["host"], optional: true, }, "host.max_reconnect_delay": { title: "Max Reconnect Delay", description: "Maximum delay to wait before attempting to reconnect WebSocket", type: "number", initialValue: 60, }, "host.allow_remote_updates": { description: "When true, allows a remote event to trigger a clusterio update via npm", type: "boolean", initialValue: true, readonly: ["host"], hidden: true, }, "host.allow_plugin_updates": { description: "When true, allows a remote event to trigger a plugin update via npm", type: "boolean", initialValue: true, readonly: ["host"], hidden: true, }, "host.allow_plugin_install": { description: "When true, allows a remote event to trigger a plugin install via npm", type: "boolean", initialValue: false, readonly: ["host"], hidden: true, }, }; } export interface InstanceConfigFields { "instance.name": string; "instance.id": number; "instance.assigned_host": number | null; "instance.auto_start": boolean; "instance.exclude_from_start_all": boolean; [key: `${string}.load_plugin`]: boolean; "factorio.version": string; "factorio.executable_path": string | null; "factorio.shutdown_timeout": number; "factorio.game_port": number | null; "factorio.host_assigned_game_port": number | null; "factorio.rcon_port": number | null; "factorio.rcon_password": string | null; "factorio.player_online_autosave_slots": number; "factorio.mod_pack_id": number | null; "factorio.enable_save_patching": boolean; "factorio.enable_script_commands": boolean; "factorio.enable_whitelist": boolean; "factorio.enable_authserver_bans": boolean; "factorio.settings": Record<string, unknown>; "factorio.verbose_logging": boolean; "factorio.console_logging": boolean; "factorio.strip_paths": boolean; "factorio.sync_adminlist": "enabled" | "disabled" | "bidirectional"; "factorio.sync_whitelist": "enabled" | "disabled" | "bidirectional"; "factorio.sync_banlist": "enabled" | "disabled" | "bidirectional"; "factorio.max_concurrent_commands": number; } /** * Instance config class * @extends classes.Config */ export class InstanceConfig extends classes.Config<InstanceConfigFields> { declare static fromJSON: configFromJSON<InstanceConfig>; declare static fromFile: configFromFile<InstanceConfig>; static migrations(config: Static<typeof this.jsonSchema>) { function boolToEnableDisable(name: string) { if (config.hasOwnProperty(name) && typeof config[name] === "boolean") { config[name] = config[name] ? "enabled" : "disabled"; } } boolToEnableDisable("factorio.sync_adminlist"); boolToEnableDisable("factorio.sync_whitelist"); boolToEnableDisable("factorio.sync_banlist"); return config; } static fieldDefinitions: classes.ConfigDefs<InstanceConfigFields> = { "instance.name": { type: "string", initialValue: "New Instance", }, "instance.id": { description: "ID of the instance", type: "number", initialValue: () => Math.random() * 2**31 | 0, hidden: true, }, "instance.assigned_host": { type: "number", optional: true, hidden: true, }, "instance.auto_start": { description: "Automatically start this instance when the host hosting it is started up", type: "boolean", initialValue: false, }, "instance.exclude_from_start_all": { description: "Exclude this instance from the 'Start all' button operation", type: "boolean", initialValue: false, }, "factorio.version": { description: "Version of the game to run, use latest to run the latest installed version", restartRequired: true, type: "string", initialValue: "latest", }, "factorio.executable_path": { description: "Relative path from the Factorio installation directory to the executable to run. " + "Defaults to auto detect the path, only needed in special setups.", restartRequired: true, type: "string", optional: true, }, "factorio.shutdown_timeout": { description: "Timeout in seconds to wait after requesting the server to stop before killing " + "the process. Set to 0 to disable.", type: "number", initialValue: 300, optional: true, }, "factorio.game_port": { description: "UDP port to run game on, uses a port in host.factorio_port_range if null", restartRequired: true, type: "number", optional: true, }, "factorio.host_assigned_game_port": { access: ["host"], type: "number", optional: true, }, "factorio.rcon_port": { description: "TCP port to run RCON on, uses a random port if null", restartRequired: true, type: "number", optional: true, }, "factorio.rcon_password": { credential: ["host", "controller"], description: "Password for RCON, randomly generated if null.", restartRequired: true, type: "string", optional: true, }, "factorio.player_online_autosave_slots": { description: "Rename autosaves where players have been online since the previous autosave into a separate " + "autosave pool with this many slots. Requires autosaves to be enabled to work. Set to 0 to disable.", type: "number", initialValue: 5, }, "factorio.mod_pack_id": { title: "Mod Pack", description: "Mod pack to use on this server, if not set the default configured on the controller will be used", inputComponent: "mod_pack", restartRequired: true, type: "number", optional: true, }, "factorio.enable_save_patching": { description: "Patch saves with Lua code. Required for Clusterio integrations, lua modules, and most plugins.", restartRequired: true, type: "boolean", initialValue: true, }, "factorio.enable_script_commands": { description: "Allows achievement breaking commands to be executed over rcon. " + "Required for Clusterio integrations and most plugins. " + "This does not prevent players using script commands.", type: "boolean", initialValue: true, }, "factorio.enable_whitelist": { description: "Turn on whitelist for joining the server.", restartRequired: true, // Because of the cli option "--use-server-whitelist" type: "boolean", initialValue: false, }, "factorio.enable_authserver_bans": { description: "Turn on Factorio.com based multiplayer bans.", restartRequired: true, // Because of the cli option "--use-authserver-bans" type: "boolean", initialValue: false, }, "factorio.settings": { description: "Settings overridden in server-settings.json", restartRequired: true, restartRequiredProps: [ "afk_autokick_interval", "allow_commands", "autosave_interval", "autosave_only_on_server", "description", "ignore_player_limit_for_returning_players", "max_players", "max_upload_slots", "max_upload_in_kilobytes_per_second", "name", "only_admins_can_pause_the_game", "game_password", "require_user_verification", "tags", "visibility", ], type: "object", initialValue: {}, // See create instance handler in controller. }, "factorio.verbose_logging": { description: "Enable verbose logging on the Factorio server", restartRequired: true, // Because of the cli option "--verbose" type: "boolean", initialValue: false, }, "factorio.console_logging": { description: "Enable console logging to a separate file, useful for 3rd party integrations", restartRequired: true, // Because of the cli option "--console-log" type: "boolean", initialValue: false, }, "factorio.strip_paths": { description: "Strip down instance paths in the log", restartRequired: true, type: "boolean", initialValue: true, }, "factorio.sync_adminlist": { description: "Synchronize adminlist with the controller", type: "string", enum: ["disabled", "enabled", "bidirectional"], initialValue: "bidirectional", }, "factorio.sync_whitelist": { description: "Synchronize whitelist with the controller (whitelist must be enabled)", type: "string", enum: ["disabled", "enabled", "bidirectional"], initialValue: "bidirectional", }, "factorio.sync_banlist": { description: "Synchronize banlist with the controller", type: "string", enum: ["disabled", "enabled", "bidirectional"], initialValue: "bidirectional", }, "factorio.max_concurrent_commands": { description: "Maximum number of RCON commands trasmitted in parallel", restartRequired: true, type: "number", initialValue: 5, }, }; } export interface ControlConfigFields { "control.controller_url": string | null; "control.controller_token": string | null; "control.tls_ca": string | null; "control.max_reconnect_delay": number; } /** * Control config class * @extends classes.Config */ export class ControlConfig extends classes.Config<ControlConfigFields> { declare static fromJSON: configFromJSON<ControlConfig>; declare static fromFile: configFromFile<ControlConfig>; static fieldDefinitions: classes.ConfigDefs<ControlConfigFields> = { "control.controller_url": { description: "URL to connect to the controller at", type: "string", optional: true, }, "control.controller_token": { access: ["control"], description: "Token to authenticate to controller with.", type: "string", optional: true, }, "control.tls_ca": { description: "Path to Certificate Authority to validate TLS connection to controller against.", type: "string", optional: true, }, "control.max_reconnect_delay": { title: "Max Reconnect Delay", description: "Maximum delay to wait before attempting to reconnect WebSocket", type: "number", initialValue: 60, }, }; } function validateFields( pluginName: string, fields: Record<string, classes.FieldDefinition>, ) { for (const [name, field] of Object.entries(fields)) { if (!name.startsWith(`${pluginName}.`)) { throw new Error( `Expected name of config field '${name}' for ${pluginName} to start with '${pluginName}.'` ); } } } /** * Add config fields defined by the provided plugin infos * * @param {Array<Object>} pluginInfos - Array of plugin info objects. */ export function addPluginConfigFields(pluginInfos: PluginNodeEnvInfo[] | PluginWebpackEnvInfo[]) { function pluginConfig( pluginInfo: PluginNodeEnvInfo | PluginWebpackEnvInfo, kind: "controllerConfigFields" | "hostConfigFields" | "instanceConfigFields", Config: typeof ControllerConfig | typeof HostConfig | typeof InstanceConfig, ) { (Config.fieldDefinitions as any)[`${pluginInfo.name}.load_plugin`] = { title: "Load Plugin", restartRequired: true, type: "boolean", initialValue: true, }; const fields = pluginInfo[kind]; if (fields) { validateFields(pluginInfo.name, fields); Object.assign(Config.fieldDefinitions, fields); } } for (let pluginInfo of pluginInfos) { pluginConfig(pluginInfo, "controllerConfigFields", ControllerConfig); if (pluginInfo.hostEntrypoint || pluginInfo.instanceEntrypoint) { pluginConfig(pluginInfo, "hostConfigFields", HostConfig); } if (pluginInfo.instanceEntrypoint) { pluginConfig(pluginInfo, "instanceConfigFields", InstanceConfig); } } }