Commit Graph
3245 Commits
Author SHA1 Message Date
bbassieandClaude Opus 5.5 dab4dd4a4e Add test helpers for plugin entrypoint functions
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 05:11:02 +00:00
Cooldude2606 c3337bdedb Merge pull request #1049 from ColumbusLabs/fix/796-config-editor-field-names
Fix CLI config editor field names
2026-10-07 20:01:18 +01:00
Cooldude2606 c11079260d Fix copy paste error in error message 2026-10-07 19:52:16 +01:00
Zachary S 7ee09ac790 Fix CLI config editor field names 2026-10-07 00:31:52 +00:00
Cooldude2606 b07c25aa7e Merge pull request #1014 from bbassie/feature/patched-scenario
Add create-scenario command to clusteriohost
2026-10-06 16:37:16 +01:00
Cooldude2606 6eb7e1a81f Merge pull request #1048 from ILLISIS/fix/load-json-object-throw-on-missing
Throw on a missing file when loadJsonObject is asked to (follow-up to #1046)
2026-10-06 16:35:56 +01:00
ILLISISandClaude Opus 5.5 6626b1ce7d Move the changelog entry to the PR description
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BGUULoiHNbAegwMK4PSjHJ
2026-10-07 01:44:54 +11:00
ILLISISandClaude Opus 5.5 b2a426bb30 Throw on a missing file when loadJsonObject is asked to
Controller.loadJsonObject(path, true) returned {} for a missing file, so
loadPlugins' catch never ran and a plugin whose web build was expected but
missing was not reported (the second half of #1046; #1047 fixed the path).
With throwOnMissing set, every error now throws; without it only a missing
file is ignored, so a malformed JSON file throws again as before #1047.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BGUULoiHNbAegwMK4PSjHJ
2026-10-07 01:21:11 +11:00
Cooldude2606 d89a6e33f3 Merge pull request #1044 from bbassie/feature/role-permission-sections
Add a permission group list to the role page
2026-10-05 01:30:20 +01:00
bbassieandClaude Opus 5.5 0684f4719e Hide the permission group list on narrow screens and let it scroll
Below the lg breakpoint the list is not rendered and the permission
groups take the full width. At md the site sider already takes 250px,
so a 200px list next to it left too little room for the permissions.

The list keeps its sticky position but is capped at the viewport height
minus its top offset and scrolls on its own when there are more groups
than fit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 23:55:24 +00:00
bbassieandClaude Opus 5.5 2b272d230c Add a permission group list to the role page
A sticky list of permission groups sits left of the permissions. Clicking
an entry scrolls to that group, and its dot is blue when any permission
in the group is granted, grey otherwise. Same layout as the ExpCluster
scenario features page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 23:48:06 +00:00
Cooldude2606 7b2f7064b9 Fix lint error 2026-10-05 00:46:06 +01:00
Cooldude2606 4c0c89c5ff Fix broken manifest url resolution 2026-10-05 00:30:47 +01:00
Cooldude2606 d1b1f58f00 Fix broken manifest path resolution (#1047) 2026-10-04 20:42:57 +01:00
Bastiaan 793f23a390 Merge pull request #1045 from bbassie/feature/role-page-extension-slot
Add a role page extension slot above the permissions
2026-10-02 21:29:15 +01:00
Cooldude2606andClaude Fable 5.1 2efb53aab1 Add a request to mark hosts and the controller as needing a restart (#1043)
* Add a request to mark hosts and the controller as needing a restart

Not every plugin arrives through PluginInstallRequest. Plugins checked
out into external_plugins or installed by hand on the machine never
flag a restart, so the web UI shows nothing and tooling that acts on
the flag never fires.

HostMarkRestartRequiredRequest and ControllerMarkRestartRequiredRequest
set config.restartRequired, guarded by the existing restart
permissions. They are exposed as "Mark Restart Required" in the control
dropdown of the host and controller pages, hidden once the flag is set,
and as mark-restart-required ctl commands. The mark shows up on the
next system metrics poll.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Cover the mark-restart-required ctl commands

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-26 21:14:23 +01:00
Bastiaan 80d6bc8da6 Merge pull request #1042 from bbassie/fix/plugin-install-restart-required
Mark restart required after installing a new plugin
2026-09-26 21:10:15 +01:00
bbassieandClaude Fable 5.1 1299ea80b8 Cover the mark-restart-required ctl commands
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-17 22:31:20 +00:00
bbassieandClaude Fable 5.1 bca681b5ea Add a request to mark hosts and the controller as needing a restart
Not every plugin arrives through PluginInstallRequest. Plugins checked
out into external_plugins or installed by hand on the machine never
flag a restart, so the web UI shows nothing and tooling that acts on
the flag never fires.

HostMarkRestartRequiredRequest and ControllerMarkRestartRequiredRequest
set config.restartRequired, guarded by the existing restart
permissions. They are exposed as "Mark Restart Required" in the control
dropdown of the host and controller pages, hidden once the flag is set,
and as mark-restart-required ctl commands. The mark shows up on the
next system metrics poll.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-17 19:52:03 +00:00
Hornwitser e2817eba39 Merge pull request #1041 from Hornwitser/test-fixes
Test improvements
2026-09-15 22:41:10 +02:00
Hornwitser 9fbff1e8e5 Ignore errors in concurrent safeOutput test
On Windows this test throws "EPERM: operation not permitted, rename
<path>" presumably because Windows prevents concurrent renames.  This
does not affect the test outcome, so ignore the error(s).
2026-09-15 22:18:16 +02:00
Hornwitser 0c4fac37b0 Move test/host.js into the test/host folder
This is clearly part of the host tests. It should be inside of the host
test folder.
2026-09-15 22:18:16 +02:00
Hornwitser 92d2662078 Move slowTest and externalTest to test/common.js
These are used outside of the integration tests, but including the
integration index file causes the very slow setup of the contorller
and host.  Avoid the integration setup costs when running commands
such as `pnpm mocha test/lib` by moving these utility functions to
the common test functions.
2026-09-15 22:18:16 +02:00
Hornwitser ea1cb67502 Setup logging transport in tests
Without a transport Winston will print annoying "Attempt to write logs
with no transports" warnings all over the place.  Setup a transport in
tests that cause logging to suppress this.
2026-09-15 22:18:16 +02:00
Hornwitser 2c52c265cf Require config files to be explicitly created (#1040)
The behaviour of creating the config file if it's missing while
convenient causes confusion and problems when the tools are run
in the wrong folder.  They end up creating a shadow installation
and misdirect the user with nonsensical error messages, instead
pointing to the real problem which is that the tool can't find
the configuration needed to actually run.

Require a config file to be present at startup and refuse to run if this
file is missing.  To support the odd use-case of creating the config
when missing a --create-config flag is added. To conveniently generate
a config file to edit out from the "config create" command is added.
2026-09-15 22:16:54 +02:00
Hornwitser 5df5af7ca8 Rename Ctl's usage of "control" to "ctl" (#1039)
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.
2026-09-14 20:18:41 +02:00
Cooldude2606 1d1d9bb1c4 Merge pull request #1031 from bbassie/fix/stop-before-rcon-ready
Resolve instance stop when Factorio exits before RCON is ready
2026-09-13 18:35:14 +01:00
Cooldude2606 a6f2c2829e Merge pull request #1036 from bbassie/fix/host-save-details-ownership
Reject save detail updates for instances not on the sending host
2026-09-13 18:29:56 +01:00
bbassieandClaude Opus 5 2a967e84c5 Reject the RCON wait instead of resolving with an error
_waitForRconReadyOrExit resolved with an Error to signal the process
exited before RCON came up, which is an odd shape for a promise.  It now
resolves when RCON is ready and rejects otherwise, with the error emitted
with the exit, or "RCON connection lost" when there was none.

Observable behaviour is unchanged.  sendRcon rejects with the same
messages as before and stop ignores the rejection, as stopping is done
once the process is gone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-13 17:29:41 +00:00
Cooldude2606 0065d07fe3 Merge pull request #1028 from bbassie/fix/instance-config-change-crash
Log errors from instance config change handlers instead of exiting the host
2026-09-13 18:16:59 +01:00
Cooldude2606 16f28ec5c7 Merge pull request #1037 from bbassie/feature/changelog-stats
Add contributors title and statistics to the changelog script
2026-09-13 18:14:21 +01:00
bbassie 0273558edc Merge remote-tracking branch 'upstream/master' into fix/stop-before-rcon-ready 2026-09-13 17:10:02 +00:00
bbassie 97bab72eb9 Merge remote-tracking branch 'upstream/master' into fix/host-save-details-ownership 2026-09-13 17:10:00 +00:00
bbassieandClaude Opus 5 70c823ae9f Correct misaligned instance names on forwarded log messages
The instance name on a log line is whatever the host sent, so a host can
attribute a line for one of its own instances to the wrong name. Compare
it with the name the controller has on record and replace it when they
differ.

Host connections can live for days, so the warning suppression expires
after five minutes instead of latching for the life of the connection,
and the bogus instance and misaligned name warnings share it so the two
together cannot spam the log.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-13 17:09:53 +00:00
bbassieandClaude Opus 5 229bbf8eb8 Report why the server exited from sendRcon
Waiting for exit alongside rcon-ready made sendRcon reject with the flat
"RCON connection lost" for every case, including the unexpected exits
that used to reject with the EnvironmentError saying why the server went
away, as events.once rejects on error events.

Have the paired wait capture the error emitted with the exit and hand it
back, so sendRcon throws that when there is one and falls through to the
old message when there is not, which is the kill path where no error is
emitted. stop() ignores the error as its job is done once the process is
gone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-13 17:09:51 +00:00
bbassie 767f9e7fe3 Merge remote-tracking branch 'upstream/master' into fix/instance-config-change-crash 2026-09-13 17:09:02 +00:00
bbassieandClaude Opus 5 a76b742294 Invoke the config changed hook for every field
The fieldChanged handler repeated the hook invoke in each branch, and
factorio.shutdown_timeout had no invoke at all, so plugins were never
told about a change to it. Do the field specific side effect, then
invoke the hook once for every field.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-13 17:08:53 +00:00
Cooldude2606 9d04681a42 Change format output 2026-09-13 18:01:23 +01:00
Cooldude2606 775dd8126f Merge pull request #1029 from bbassie/fix/instance-assign-failure
Keep instances assignable after a failed assign or create
2026-09-13 17:47:48 +01:00
bbassieandClaude Opus 5 0f4671afd0 Add contributors title and statistics to the changelog script
Puts the contributors under a "Contributors" title on a single comma
separated line, and prints commits, merged pull requests, files changed
and line additions and deletions since the last release after them.

The compare API lists at most 300 files, so the file and line counts
come from the diff media type of the same comparison instead. To fetch
it, githubFetch now lets the caller override the Accept header and
checks the Content-Type against it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-13 16:29:38 +00:00
bbassie daa39fd7ec Merge remote-tracking branch 'upstream/master' into fix/instance-assign-failure 2026-09-13 14:43:43 +00:00
Cooldude2606andClaude Opus 5 a28e2bf9f6 Terminate control connections of deleted users (#1030)
* Terminate control connections of deleted users

ControlConnection keeps the User object it authenticated with and checks
permissions against it. Deleting the user removed the record but left
its sessions open, so a deleted user kept all its permissions until the
WebSocket closed.

Repro: create "victim" with role 0 and connect as it. Another admin
sends UserDeleteRequest("victim"). Victim can still send
UserCreateRequest("mallory") and UserUpdateRolesRequest("mallory", [0]).

Terminate the deleted user's connections like revoke-token does. When
a user deletes itself, defer terminating the requesting connection so
the response is sent first.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Terminate deleted user sessions immediately

Drop the special case that deferred terminating the connection issuing
the delete, so deleting yourself behaves the same as revoke-token and
every session of the deleted user is closed before the handler returns.

Set tokenValidAfter to the current time in createUser instead of 0.
Deleting a user and recreating it under the same name left tokens issued
before the deletion valid, as the check is iat < tokenValidAfter. Doing
it in createUser covers every path that makes a user. A token signed in
the same second as creation is still accepted, so create-admin and the
other production paths sign after the user exists and are unaffected.

The integration harness signed the control token at import time, before
bootstrap create-admin created the user. Sign it in the root before hook
instead and reuse it in link.js.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Handle SessionLost when deleting your own account

Deleting your own account now terminates the session immediately, so the
web UI received a SessionLost rejection and reported "Error deleting
user" for a delete that had succeeded. Use the same branch revoke does.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com>
2026-09-13 15:31:46 +01:00
Cooldude2606 c355fc6888 Merge branch 'master' into fix/terminate-deleted-user-sessions 2026-09-13 15:18:25 +01:00
d32d857eb3 Re-apply debug dump ignore flag on control connection resume (#1035)
handleDebugDumpWsRequest sets clusterio_ignore_dump on the current
socket so the WsServer send wrapper does not echo the dumper's own
frames back into debugEvents. The flag was only set on the connector
"connect" event, but WsServerConnector.continue() installs a fresh
socket and emits "resume", not "connect", so a resumed session lost
the flag.

After a resume the next outgoing frame was echoed to debugEvents,
ws_dumper sent a DebugWsMessageEvent, the wrapper echoed that too,
and so on: infinite synchronous recursion ending in a RangeError:
Invalid string length from JSON.stringify in link.ts sendEvent. That
uncaught error made handleUnhandledErrors exit the controller.

Repro: admin sends DebugDumpWsRequest, the control connection drops
and resumes (e.g. a network blip), controller crashes on resume.

Set the flag on "resume" as well as "connect".

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com>
2026-09-13 15:13:24 +01:00
bbassieandClaude Opus 5 cf4e85c6ee Guard forwarded log messages by instance assignment
Log messages share the root cause with the save details updates fixed in
the previous commit: the controller trusted host supplied instance
fields. LogMessageEvent only declares level and message in its schema,
but the Ajv instance is built without removeAdditional and the schema
does not set additionalProperties, so instance_id and instance_name sent
by a host survive validation and pass through fromJSON untouched. A host
could tag a log line with an instance assigned to another host and have
the controller store and serve it as that instance's output.

Lift the assignment check out of handleInstanceSaveDetailsUpdatesEvent
into a private method and use it from both handlers.

Strip the instance fields instead of dropping the line. A host racing a
reassignment does emit instance log lines for an instance the controller
has already moved, and dropping those loses real output exactly when a
migration is going wrong. The line is still genuine output from the host,
so it is recorded as a host level message, which grants the host nothing
it could not already log directly. Warn once per connection so a
misbehaving host cannot flood the log.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-13 14:11:23 +00:00
BastiaanandClaude Opus 4.8 fc73433ffb Stop host crash on malformed action log lines (#1034)
_watchServerLogActions dereferenced two regex results with ! and
threw on null. The name regex /^([^ ]+)/ returns null when a line
classified as an action starts with a space, and the BAN reason
regex /\. Reason: (.+)\.$/ returns null for a [BAN] line with no
". Reason: X." tail. The output listener runs synchronously off the
stdout LineSplitter, so the throw becomes an uncaughtException and
takes the whole host down with every instance on it.

Repro: instance with enable_save_patching off and enable_script_commands
on; a control client sends /sc print("2000-01-01 00:00:00 [BAN] evil").
The raw print reaches stdout, parses as a dated BAN action, and the
reason deref throws.

Skip the line when the name regex misses and default the ban reason
to empty when the tail is absent. Apply the same name guard to
_watchPlayerPromote. The autosave ![1] in server.ts is safe because
its filter regex already guarantees the capture.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-13 14:53:49 +01:00
BastiaanandClaude Opus 4.8 8239a96509 Deny instead of crashing when a control permission check throws (#1033)
A permission function runs during ingress permission checking, before
the request body is validated. UserBulkImportRequest.permission read the
raw, unvalidated message.data and threw a bare Error for an unknown
importType. That error is not a PermissionError, so _processMessage
rethrew it to the connector "message" listener, which emitted "error" on
the connector. Controller-side connectors have no "error" listener, so
Node rethrew and handleUnhandledErrors exited the process.

Repro: a control client with client-side validateSent disabled sends
UserBulkImportRequest with importType "foo"; the client gets a normal
RequestError response and the controller then logs a fatal uncaught
exception and exits.

Fix both layers: _processMessage now logs and drops an unexpected error
from the permission check (control connections only, where a response
was already sent) instead of rethrowing, and UserBulkImportRequest
throws a PermissionError for an unknown importType so it is denied
cleanly. A genuine PermissionError still denies silently as before.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-13 14:51:16 +01:00
bbassieandClaude Opus 5 49e8d6ecc5 Fix sendRcon hanging if Factorio exits before RCON connects
sendRcon waits for the rcon-ready event when RCON has not connected yet,
which is the same root cause as the stop() hang. events.once rejects on
error events, but _watchExit skips emitting error while the state is
stopping, and kill() sets that state before sending SIGKILL. exportData
does exactly that, so an RCON call racing the export never settles and
the request stays pending until the session closes.

Wait for exit as well, which leaves the existing RCON client check to
reject the call with "RCON connection lost" instead. Both call sites now
share a helper as the paired wait is identical.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-13 13:49:58 +00:00
BastiaanandClaude Opus 5 558999faa8 Parse the Map Settings field in the load scenario modal (#1032)
The Map Settings block parsed values.mapGenSettings, so filling in only
Map Settings failed with "undefined" is not valid JSON and the modal
would not submit, while filling in both sent the map gen settings JSON
to Factorio as --map-settings.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-13 14:45:50 +01:00
bbassieandClaude Opus 5 c9e8aeee5c Handle SessionLost when deleting your own account
Deleting your own account now terminates the session immediately, so the
web UI received a SessionLost rejection and reported "Error deleting
user" for a delete that had succeeded. Use the same branch revoke does.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-13 13:34:58 +00:00