The templates table shows name, inheritor count and a create instance
action instead of the instance status and start/stop columns.
factorio.rcon_password joins the ports as never inherited.
Part of #200
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review changes for #1142:
- base becomes template everywhere (is_template, template_id,
--template, --is-template, --templates, InstanceDetails.isTemplate).
- Object fields inherit per property. inherited_fields lists the
overridden properties for object fields and the template object is
merged with them.
- Linking a template also inherits fields that hold the default value.
The server name stays per instance.
- Ports are never inherited.
- InstanceDetails carries templateId.
Web UI: inherited values are locked until unlocked, overridden values
can be inherited again, templates are listed separately, the create
modal can create templates and instances from templates, and the
instance page has "Create template" which can link the source.
Part of #200
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Instances can be marked as a base with instance.is_base and other
instances inherit config values from it through instance.base_id.
The controller copies base values into inheriting configs and keeps
track of inherited fields in the hidden instance.inherited_fields, so
hosts receive resolved configs and need no changes. Setting a field
stops inheriting it, `instance config inherit` undoes that.
Bases cannot be assigned to a host, are hidden from `instance list`
unless --bases is given, and cannot be deleted or unmarked while
other instances inherit from them. Chains of bases are allowed and
cycles are rejected.
Part of #200
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Host not connected, host shutting down and instance not running are
now UnavailableError (503) instead of ConflictError. The request error
migration notes move to a new 2.1 guide, since this ships in 2.1.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Error responses now carry the registered name of the thrown error's
class, and the requester rejects with an instance of that class instead
of a generic RequestError. Error classes are registered like message
classes, and plugins list their own in `errors`.
RequestError is replaced by a hierarchy following the HTTP status codes:
ClientError (ValidationError, ForbiddenError, NotFoundError,
TimeoutError, ConflictError) and ServerError (InternalServerError).
PermissionError is PermissionDeniedError and the config errors
InvalidValue, InvalidField and InvalidAccess gain an Error suffix.
Link logs only errors whose class is not registered. These arrive on the
other side as InternalServerError with the original message and stack,
so an error forwarded through the controller is logged once, where it
was thrown.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add opt-in UDP messaging between the host and the Factorio server using
the --enable-lua-udp option of Factorio 2.0.59+. When
factorio.enable_lua_udp is set the instance binds a localhost UDP socket,
starts Factorio with --enable-lua-udp on factorio.lua_udp_port (random
if null, like the RCON port) and tells the clusterio module the host port
via clusterio_private.update_instance.
Data from Factorio is sent with clusterio_api.send_udp(channel, data) in
the same IPC format as send_json, so plugins receive it through the
existing ipc-<channel> events. Packets not sent from the Lua UDP port
are ignored, as that is the only unauthenticated way into the IPC
handler. Data to Factorio is sent with the new sendUdp method on
instances and instance plugins, and is dispatched in-game as
on_udp_packet_received events by polling helpers.recv_udp every tick
from the clusterio module.
Plugins requiring UDP declare the new LuaUdp feature flag, which like
SavePatching and ScriptCommands refuses to load the plugin on instances
without the option enabled.
Verified against a headless 2.1.17 server: send_udp and recv_udp are
silent no-ops when the option is off, so a stale port in a save that is
moved to an instance without UDP does no harm. The integration test
matrix now starts the instance with UDP enabled on Factorio 2.0.59+ and
round trips a player event over it.
Closes#969
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The staging copy made by Config.set() went through the constructor, so
plugin migrations ran a second time on the live fields. A version-gated
migration was applied twice when setPluginVersions() changed the version.
Staging now copies the fields with update() instead.
A config saved without <plugin>.version got the running version as its
default, so setPluginVersions() saw no change and nothing saved the stamp.
The constructor now writes the registered plugin versions after the
migrations run and marks the config dirty when that changes a loaded value.
Controller.bootstrap() saves the instances that came out dirty, and
setPluginVersions() is gone.
Restructure the SyncHook and plugin config tests by method, and suggest the
controller config version for role migrations in the docs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Plugins had no way to update the config fields and permissions core stores
for them, so renaming inventory_sync needed migrations in core packages.
A plugin can now declare a migrationEntrypoint. It's loaded on the
controller and hosts before configs and roles are read, for every plugin in
the plugin list, and attaches handlers to sync hooks for the controller,
host and instance configs and roles. lib gets helpers to rename config
fields and permissions, and each config gets a hidden <plugin>.version field
set after load so migrations can tell which version saved the data.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ctl ended up calling its main class for "Control" for historical
reasons, along with its config file and plugin entrypoint.
The plugin entrypoint was renamed to ctl in 808eda0513. This
finishes that renaming by also renaming the main class, default config
file, config-control command, and main entrypoint.
The config-control command is renamed to config to be consistent with
the host and controller's config commands.
The term "control" is still kept in the config and message locations as
a common specifier for both ctl and web_ui.
Config files hold the auth secret and tokens, so the default 0o644 lets
every user on the system read them. Config.save, the ctl config written
by the controller bootstrap, ctl host create-config and the config
migration in create now pass mode 0o600.
safeOutputFile also applies the mode to the temporary file it writes,
since writeFile only honours mode on creation and a leftover temporary
from a failed write would otherwise keep its old mode.
Closes#1005
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Migrate the usage of CommonJS specific code and imports to ESM. Roughly
summarised this:
- Changes all require() calls to import statement or expressions.
- Changes usage of module.exports to export statements.
- Replaces require.resolve with import.meta.resolve.
- Replaces conditional imports with conditional subpath imports.
- Replaces module === require.main with import.meta.main.
- Adds the full filename with extention to all relative imports.
- Changes all usage of __dirname and __filename to their import.meta
equivalents.
- Sets "type": "module" in package.json files.
- Removes usage of "use strict".
- Adds .ts an an alias of .js for the webpack build.
Due to differences in how require and import resolves files the plugin
entrypoints will have to include the filename and extension after this
migration in order to work.
Add option in the controller config to set where static assets are
loaded from in order to support deployments where the static assets are
server via a CDN while the API is directly accessed.
This is useful because some CDNs put low limits on how large requests
bodies can be, which causes issues with uploading large mods and saves.
An instance's factorio.version can now target a release channel such as
"stable" or "experimental" (any key from factorio.com/api/latest-releases)
in addition to "latest" and specific X.Y / X.Y.Z versions.
The controller proxies the latest-releases API through a ValueCache and
persists the last successful response to latest-releases.json, serving that
copy when the API is unreachable. On instance start the host resolves a
channel to the concrete version it points to and feeds it into the existing
checkForUpdates/findVersion path, so a channel auto-installs and runs like a
pinned version. The version selector in the web UI lists the available
channels alongside "latest".
Also clarifies the host log/error when a newer version cannot be downloaded
automatically because the host uses a direct (single-version) Factorio
install rather than a versioned directory.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
These packages are obsoleted by the promises API to the built-in Node fs
module. The fs-extra package is also causing problems with broken APIs
that don't behave the same as Node's fs module, and throwing errors
without code properties on them.
Implement a credential option to config entries which is a list of
locations the config entry can be read from. When specified and the
option is a string field this gets rendered as a password field in the
config UI. If the credential is not readable from control the value of
the fields is cleared when the UI is refreshed.
Refactor away the config group concept and simplyfy the way config
fields are defined. Fields are defined directly as properties of the
static field fieldDefinitions on the class itself with a separate
interface to declare the types of fields.
This makes it possible to easily make accessing the config fields type
checked in a way that lets plugins extend the interfaces with their own
config options by adding additional fields to the interface via
declation merging.
Refactor the Config and ConfigGroup classes to match the JSON data class
pattern with toJSON, toRemote and fromJSON methods replacing the load,
init, and serialize methods. Since fromJSON requires extra information
the messages structures passes around the plain JSON representation of
the configs instead of serialising instances of the config classes
automatically.
Move all functions and classes currently available through lib/name and
libName to be under a single flat namespace in lib. This make
reorganising the lib namespace much simpler as the location of symbols
can move without breaking the consumers of lib.
The multi-entrypoint design was already problematic for the web plugin
system which couldn't effectively use it due to how webpack's shared
module system is awkward to set up with multiple entrypoints per package.
Add a per field access parameter to the config class which specifies
which of master, slave and control can access the given field. A
location is added to the config class to specify which party owns it and
fields which do not have an access parameter are not present in the
config and will throw if attempted to be read from or writen to.
The serialize, load, update, get, set and setProp are ammended with a
location parameter which specifiy where the data is from or destined to
and the access checks are performed to ensure remote access to the
config is still limited by the access parameter of field.
With this system in place the master config can be shared with control
and slave connections without compromising secrets that should stay
private. This is primarily the master authentication secret, but other
plugins may define secrets it does not want to have shared with slave
and/or control connections.
Split up codebase into the packages @clsterio/lib, @clusterio/master,
@clusterio/slave, and @clusterio/ctl using lerna to manage them. This
will allow deployment to use only the parts needed reducing install
size, and better separate the code into shared library, master, slave
and control.