* Unpatch saves started with save patching disabled
Turning factorio.enable_save_patching off did nothing to a save that
was patched before. Its control.lua still loaded every module, so the
clusterio_api interface, player_sync's join handling and the other
module event handlers kept running with no plugin on the host side.
When patching is disabled the instance now restores the original
control.lua that patching kept as scenario.lua and removes the module
files, locale files included, as listed in clusterio.json. The patch
info stays with an empty module list so the patch number keeps
counting. Otherwise patching the save again would reuse the number
stored in the game and on_server_startup would not be raised. Data
the modules put in storage is left alone.
PatchInfo.fromSave dropped the version read from the save, so the
check against newer patch versions never fired. It now keeps it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Remove scenario.lua when unpatching a save
Unpatching moves scenario.lua back to control.lua instead of copying it, so the save only keeps clusterio.json for the patch number. Patching such a save again copies control.lua back to scenario.lua.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The mod pack page only looked up mod-setting-name.<name>, so settings
with a localised_name showed the internal name, and rich text tags in
the locale showed up as raw text.
Resolve localised_name and localised_description from the exported
settings prototypes against the exported locale, falling back to the
mod-setting-name/mod-setting-description keys and then the internal
name. The resolver handles parameters, the "" and "?" keys and
__ITEM__/__ENTITY__/__TILE__/__FLUID__ references. Rich text
formatting tags are removed and prototype icons are replaced with
their name.
Fixes#768
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Unpatching moves scenario.lua back to control.lua instead of copying it, so the save only keeps clusterio.json for the patch number. Patching such a save again copies control.lua back to scenario.lua.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Matches verifyUserToken returning the user. HostConnection._saveInfo is
renamed saveRecord to match User.saveRecord.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
controller.hosts is now a HostManager wrapping the hosts datastore, the
same way controller.users wraps the users datastore. It owns creating
hosts (moved from HostPairingManager), revoking them (moved from
ControlConnection) and signing and verifying host tokens (moved from
Controller, generateHostToken is renamed signHostToken to match
signUserToken).
UserManager gains verifyUserToken, replacing the copies of the user
token check in WsServer and routes. The WsServer copy accepted a token
without iat when the user had no tokenValidAfter; the shared check
always requires iat, which jsonwebtoken adds by default.
Follows Cooldude2606's comment on #1161.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Settings stored in a mod pack that no setting prototype in the export
data defines are now listed in a collapsed "Unknown settings" panel
under each scope instead of under an "Unknown Fields" heading. Each one
has a remove link, and a revert link once removed, matching what
`clusterioctl mod-pack edit --remove-setting` already allows. They are
never removed automatically.
Without export data nothing is called unknown and stored settings are
listed as before, without a mod heading.
Fixes#773
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Turning factorio.enable_save_patching off did nothing to a save that
was patched before. Its control.lua still loaded every module, so the
clusterio_api interface, player_sync's join handling and the other
module event handlers kept running with no plugin on the host side.
When patching is disabled the instance now restores the original
control.lua that patching kept as scenario.lua and removes the module
files, locale files included, as listed in clusterio.json. The patch
info stays with an empty module list so the patch number keeps
counting. Otherwise patching the save again would reuse the number
stored in the game and on_server_startup would not be raised. Data
the modules put in storage is left alone.
PatchInfo.fromSave dropped the version read from the save, so the
check against newer patch versions never fired. It now keeps it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
InstanceDetails now carries factorio.mod_pack_id as modPackId, so the
instance list and pages can show it without fetching each config. The
web UI resolves an unset value to the controller default and tags it
"Default". The name links to the mod pack page.
Closes#852
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
- Unlinking a field enables Apply on its own and sends the current value.
- The link button no longer toggles the checkbox of its label.
- Link and disconnect icons for all four states, pending ones highlighted.
- instance.is_template is hidden.
- ctl create takes --as-template.
- Clearer names for the InstanceManager inheritance helpers.
Part of #200
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
"safe" suggested the data being written survives a crash, it only
guarantees the old file is either kept or fully replaced. The new name
says what it does. No alias, plugin API breaks are allowed in minor
versions and the 2.1 migration guide lists the rename.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com>
findVersion takes headless as a plain optional argument, the helper is
checkInstallType, the warning is checkMissingBuiltinIcons and the full
install lookup is inlined into exportData.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Per review, drop host.factorio_graphics_directory. findVersion takes a
headless option that filters installs by build, telling them apart by
data/base/graphics/icons. When the instance runs the headless build the
export looks for a full install of the same major.minor version in the
host's factorio directory. Server startup is unchanged.
If none is found the export still runs, and the reason is included in
the missing icons warning.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
If host.factorio_graphics_directory had no install matching the major and
minor version of the instance, or did not exist, findVersion threw and the
whole export failed. Without the setting the same instance exports everything
except the base game icons, so it now logs a warning that names the setting
and exports without it.
Every view page handled a resource that can't be shown its own way, and
users without the subscribe permission were left on a spinner forever.
Adds useResourceState and a ResourceState component, used by the host,
instance, mod pack, icon reference, user, role and plugin pages and
exported for plugin pages. The single item hooks now also return the
subscription error.
Resolves#1165
An icon that could not be read or decoded, such as a truncated PNG, a
JPEG or a layered icon with no layers, threw out of exportIcons and
failed the whole data export. Log a warning naming the prototype and
export it without an icon instead. Also warn when a mod's zip is
missing while exporting the locale, which was skipped silently.
Revoking a host now unassigns its instances instead of being refused
while it has any, and removes its system metrics.
Approving and revoking no longer force a save. Hosts are saved by
autosave and on exit like everything else, which also removes the
approving state from pairing requests.
The approval code and link are logged as warnings so they show up in
the systemd service, which only prints warnings and above. The pairing
limits are now the controller.host_pairing_timeout and
controller.host_pairing_max_pending config values, 0 removes the limit.
Subscribers take an optional permitted function, used for the pairing
requests instead of overrides in a subclass.
A standalone install no longer adds its host to the controller database
by signing a token and writing hosts.json itself. The host asks to join
when it first starts and is approved once with the link it prints.
The list of waiting hosts opens the approval dialog instead of having its
own name input and buttons, so hosts are approved in one place. Removes
the limit on requests per minute, the limits on pending requests already
cap what an address can do.
Also updates text that still said the installer makes the requests.
- Saves of a datastore run one at a time, and changes made while saving
are saved by the next save. Overlapping saves from approving and
revoking hosts could write an older snapshot after a newer one.
- Map subscribers fetch everything again when a new session starts, as
the controller only sends values that exist. A host revoked while a
browser was disconnected stayed in its list.
- The host token check accepts the same pre-rename tokens as the
WebSocket, so upgrading a host with such a token does not make it ask
to join again.
* Answer the disconnect prepare in the close old connections test
The test's first connector had no Link, so the controller's disconnect
prepare went unanswered and it waited the full 10 s timeout.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Start the controller and host CLIs faster
Like ctl, the controller and host now compile schema validators on first
use instead of at import, which cost about 120 ms per invocation. The
Controller and Host classes are only imported by the commands that
start them, so config, bootstrap and the other commands no longer load
express, ws and the rest of the servers. clusteriocontroller config set
goes from 413 ms to 231 ms and clusteriohost config set from 412 ms to
225 ms.
The integration tests run the bins, like npx does, instead of the
package directories.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Take FieldData from antd instead of rc-field-form
web_ui depended on rc-field-form 1.x for a single type import, while
antd 5 uses rc-field-form 2.x. Derive the type from antd's FormProps
and drop the dependency.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Move typescript to web_ui devDependencies
web_ui only needs typescript to build its own types. The runtime
dependency is a leftover from webpack's ts-loader, Vite doesn't use it,
and it pulls in a 30 MB native compiler for anyone installing web_ui.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Use native FormData in controller route tests
Node's FormData and Blob do what the form-data package did here, and
fetch sets the multipart Content-Type header itself.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Remove leftover typedoc.json files
TypeDoc was dropped in #1147, but these configs still ship in the
published packages.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The installer only writes the controller URL and host name. Running the
host requests to join the cluster if it has not been approved yet, or if
the controller no longer accepts its token, and waits until an admin
approves it. Once approved it saves its id and token and starts as
normal. A rejected host exits with code 8.
clusteriohost run --user-token approves the host right away with the
token of a user allowed to approve hosts. The token is not saved.
This moves the pairing client into the host package, where it uses the
same certificate setup as the host's connection to the controller, and
removes --no-pair and the rerun handling from the installer.
Node's FormData and Blob do what the form-data package did here, and
fetch sets the multipart Content-Type header itself.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Like ctl, the controller and host now compile schema validators on first
use instead of at import, which cost about 120 ms per invocation. The
Controller and Host classes are only imported by the commands that
start them, so config, bootstrap and the other commands no longer load
express, ws and the rest of the servers. clusteriocontroller config set
goes from 413 ms to 231 ms and clusteriohost config set from 412 ms to
225 ms.
The integration tests run the bins, like npx does, instead of the
package directories.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The test's first connector had no Link, so the controller's disconnect
prepare went unanswered and it waited the full 10 s timeout.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Drops tests that restate message declarations, check that removed
commands are gone, test internal helpers or repeat the same behaviour
at another layer.
Hosts join the cluster by running the installer, which shows a code and
waits until the request is approved under Hosts > Add host in the web
interface or with clusterioctl host approve. An admin can pass their
user token to the installer to approve the host right away. The token
is only used for that and is not saved.
The controller only accepts hosts that have a record, and only
approving a host creates one. Revoking a host deletes its record, so
its token can no longer be used to reconnect. Hosts that have connected
before keep working.
Removes host generate-token, revoke-token and create-config from
clusterioctl, bootstrap generate-host-token, the host token messages and
the core.host.generate_token, revoke_token and create_config
permissions, which are also stripped from stored roles. Adds
clusterioctl host pending, approve, reject and revoke, and the
core.host.approve and core.host.revoke permissions.
Standalone installs add the host to the controller database directly,
since the controller is not running during the install.
loaded, enabled, pendingInstall, removed, noEntrypoint and nameTaken
allowed far more combinations than can happen, and every consumer had
to know which took priority. PluginDetails now has one state, with
loaded, loadsOnRestart and removed as getters over it.
2.0.3 web UIs and ctl send PluginListRequest straight to hosts and
require loaded and enabled, so those are still sent next to the
optional state. Plugins listed by 2.0.3 hosts get their state from
loaded and enabled.
The web UI planner gets the same treatment: MachinePlugin has one
state in place of disabled, noEntrypoint, dependencyTaken and
nameTaken. ctl host plugin list shows the state.
Closes#1134
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The headless build has no graphics, so export-data found none of the
base game's icon files. Plain icons were skipped silently and layered
icons were drawn blank and counted as exported.
Skip a layered icon when any of its layers is missing, and log one
warning with the number of base game icon files that were not found.
Add host.factorio_graphics_directory to read builtin mod graphics from
a full install when the instance runs the headless build.
Fixes#1068
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The user page set the role selector once and never updated it. The role
page reset its whole form on every role update, wiping unsaved edits.
Both pages now follow the live data and keep local edits on top. The role
page stores only the edited fields and drops them once they match the role
or when the viewer loses core.role.update. The user page syncs the
selector while it is not dirty and drops deleted roles from a dirty
selection. A role deleted while open now says so instead of "not found".
Fixes#775, fixes#776
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A stop request sent while the instance was starting ran alongside the
start. During save preparation it did nothing and the instance came up
running anyway. Once Factorio was launched it put the server into the
stopping state, the start then failed with "Expected state running but
state is stopping" and its cleanup killed the server mid shutdown.
Stop and restart requests now wait for any start, stop or save in
progress to finish first, then stop the instance if it is running.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Entries now explain why end users are not or only minimally affected,
and the docs say which version to key by and point to the breaking
changes decision.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The version-compat job fails every pull request once master breaks
compatibility with the latest release on purpose. List those breaks in
scripts/version-compat-breaks.json keyed by the published version they
break against, each with a pr number and reason. Listed messages warn
instead of failing, connect skips the cluster test, entries for other
versions are ignored, and a listed message that did not change fails.
Add the two src changes from #1129 against 2.0.3 so master passes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every RCON command makes an auto paused Factorio server run one tick, so
subspace_storage's 1 s inventory update and 5 s ping, plus a
statistics_exporter gather on every Prometheus scrape, kept a server with
nobody online running at about 1.3 ticks per second.
The instance now records the auto_pause server setting it was started
with and exposes autoPaused, true while that setting is on and no players
are online, along with an autoPauseChanged hook. subspace_storage skips
its timers while paused and sends a ping and inventory as soon as a player
joins. statistics_exporter gathers once after the game pauses and keeps
serving those values until it resumes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>