Commit Graph
133 Commits
Author SHA1 Message Date
bbassieandClaude Opus 5.5 e455d17bb7 Write plugin versions while loading configs
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>
2026-10-14 16:37:03 +00:00
bbassieandClaude Opus 5.5 aec0fc7a1e Let plugins migrate the data core stores for them
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>
2026-10-14 15:33:27 +00:00
BastiaanandClaude Opus 5.5 24586a4b7c Redact credentials from logged request urls (#1074)
The controller logs the url and query of every HTTP request. Plugins
doing OAuth get the authorization code as a query parameter on their
callback route, so it ended up in the log. redactUrl now accepts a
path, code joins the redacted parameter names, and redactQuery covers
the parsed query in the log metadata.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 15:58:00 +01:00
Cooldude2606 208ecb826e Merge pull request #1056 from bbassie/fix/subscription-permission-revoke
Drop subscriptions when a user loses permission for them
2026-10-08 17:23:43 +01:00
Cooldude2606 ec1ce79be9 Merge pull request #1055 from bbassie/fix/subscription-limits
Limit the number of subscription filters per subscriber
2026-10-08 17:02:33 +01:00
bbassieandClaude Opus 5.5 d10da0f5fc Rename to checkLinkPermissions and stop log following on revoke
The old name read as an unconditional unsubscribe. Log following has
its own subscription outside SubscriptionController, so check
core.log.follow on permission changes too. Move the per-connection
work into one helper and split the tests into userPermissionsUpdated
and rolesUpdated, checking the account update event is sent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 15:21:02 +00:00
bbassieandClaude Opus 5.5 d2163cf6a0 Make the filter limit a restart setting with 0 for no limit
Lowering the limit at runtime would leave connections holding more
filters than allowed, so read it once when the controller starts.
Move the limit tests into the subscribe and replace groups.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 15:13:36 +00:00
BastiaanandClaude Opus 5.5 fe1391903a Redact credentials from logged HTTP request headers (#1054)
The controller HTTP access log and the express error log wrote the full
request headers at the http level, which both default log transports
keep. Clients send their access token in X-Access-Token or
Authorization: Bearer, so user and host tokens ended up in plain text
in controller and cluster logs, and the cluster log can be read back
over the API with core.log.query.

Mask the authorization, x-access-token, cookie and proxy-authorization
headers before logging.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 15:45:43 +01:00
bbassieandClaude Opus 5.5 beec2f523a Drop subscriptions when a user loses permission for them
The permission for a subscription was only checked when subscribing,
so a control connection kept receiving events after the user's roles
or role permissions changed to no longer allow it.

Add SubscriptionController.unsubscribeUnpermitted which rechecks every
subscription of a link and removes the ones the user is no longer
permitted, and call it for the affected control connections from
userPermissionsUpdated and rolesUpdated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 11:11:28 +00:00
bbassieandClaude Opus 5.5 690b79d2b7 Limit the number of subscription filters per subscriber
A subscriber could keep sending subscribe requests with new filters and
the controller would add every one of them to the subscriber's filter
set without bound, growing memory until it ran out.

Add controller.max_subscription_filters (default 10000) and reject
subscribe and replace requests that would leave a subscriber holding
more filters than that for one event.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 11:10:03 +00: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 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
bbassie db84455c34 Merge remote-tracking branch 'upstream/master' into refactor/plugin-hooks-v2
# Conflicts:
#	packages/lib/src/data/messages_plugin.ts
#	packages/web_ui/src/components/PluginViewPage.tsx
#	test/lib/plugin.js
2026-09-12 18:07:12 +00:00
BastiaanandClaude Fable 5.1 1a10b38743 Only report a missing plugin web build when one is expected (#1013)
Plugins with only host, instance or ctl code have no reason to ship a
webpack build, yet the plugin list showed "Missing dist/web/manifest.json"
as an error for them and the controller logged a warning on every start.

Decide whether a build is expected from the plugin declaration, using the
same rule the create tool uses to decide when to generate one: a web or
controller entrypoint, or any config fields. Plugins outside that rule get
no bundle path and no error, the web UI skips loading them, and the plugin
pages show them as having no web module instead of an error or a prompt to
reload.

Fixes #1004

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 01:59:16 +01:00
7122cbccf8 Add typed hook system for plugins
Replace invokeHook and the per plugin on* methods with AsyncHook
collections on the controller, host, instance, clusterioctl and web
control objects. Plugin entrypoints export a default function that
receives a load context and attaches handlers to the hooks it needs.
Class exports still load through fromContext with a deprecation
warning. The create templates and docs use the entrypoint function.

This is clusterio/clusterio#921 recreated on top of master after the
ESM migration and the PluginDeclaration permissions change.

Closes #730

Co-Authored-By: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 22:49:09 +00:00
Hornwitser 5dcaca4d0b Migrate to ECMAScript Modules
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.
2026-09-11 22:43:57 +02:00
Hornwitser a025d01294 Fix mainBundle being passed as array in dev mode
Values in the assetsByChunkName object are arrays not strings.  This
caused the dev mode to fail after 2f4e0c4418 added an additional step
filtering out the static/ prefix from what it expected to be a string.
2026-09-11 19:59:41 +02:00
Hornwitser 30083b96c1 Add packagePath to plugin info
Provide the absolute path to the package.json file in the resolved
plugin info to simplify the logic around loading files from it.
2026-09-11 19:51:15 +02:00
Hornwitser 7c0d7faa24 Add config to set URL for static assets
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.
2026-08-31 19:17:31 +02:00
bbassieandClaude Fable 5 2f4e0c4418 Make staticRoot include the static/ prefix and use it for plugin bundles
staticRoot was set to the same value as webRoot, so the static/ prefix
ended up scattered across consumers: index.html relied on the manifest
path containing it, the export stores hardcoded `${staticRoot}static/`,
and bootstrap loaded plugin bundles from webRoot instead. Now staticRoot
is `${webRoot}static/` and the controller strips the static/ prefix from
bundle paths before handing them to the page and to /api/plugins.

Plugins that fetch assets with `${staticRoot}static/...` need to drop the
static/ part.

Fixes #980

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-28 09:50:08 +00:00
Loi NguyenandLoi Nguyen 08651b5917 fix: prevent restart downgrades (#951)
Co-authored-by: Loi Nguyen <1948922+lntutor@users.noreply.github.com>
2026-07-26 12:20:22 +01:00
Cooldude2606 573274875c Rename to SourceVersion 2026-07-02 00:27:05 +01:00
Cooldude2606 2e27c4f499 Maintain strict tagging of GameVersion 2026-07-02 00:08:09 +01:00
bbassieandClaude Opus 4.8 8029ad3b2d Resolve GameVersion type as a plain-string supertype
Cooldude's GameVersion brand was disjoint from the clean version types in
both directions, forcing safeVersion/safeFactorioVersion companions and
breaking widening from FullVersion at file-name call sites.

Make GameVersion a plain string alias: the raw, un-normalised version as the
game reads it. The stricter template-literal types (FullVersion,
MajorMinorVersion, PartialVersion) now widen into it for free, while a
GameVersion still can't be used where a normalised version is required
without going through normaliseGameVersion/integerGameVersion. Runtime
validation stays in GameVersionSchema.

- Drop safeVersion/safeFactorioVersion; ModInfo.version stays raw (verbatim so
  it matches the mod's file name), integerVersion normalises via the new
  integerGameVersion. factorioVersion reverts to a normalised MajorMinorVersion
  since it is never part of a file name.
- ModRecord.version becomes a GameVersion so mod packs round-trip the exact
  file-name version.
- ModVersionEquality.testVersion / ModVersionRange.testVersion accept a raw
  GameVersion and normalise it; the equality bound stays strictly validated.
- Migration only pads clean partial versions; raw versions are kept verbatim.
- Cover the leading-zeros case: version kept verbatim in the file name while
  comparing equal to its normalised form.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 13:54:06 +00:00
bbassieandClaude Opus 4.8 9da48d5387 Add release channels as instance version targets
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>
2026-06-26 14:19:34 +00:00
Cooldude2606 21b050b220 Call subscriptionUpdate for all update cases 2026-05-28 00:38:15 +01:00
Hornwitser 2e48b728dd Remove fs-extra and klaw (#882)
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.
2026-04-09 22:53:13 +02:00
Cooldude2606 4868ed2d63 Merge branch 'master' into feature/config-validation 2026-03-16 23:22:54 +00:00
Hornwitser 2f70db4b7e Ignore errors fetching Factorio versions
If an error occurs trying to fetch the list of Factorio versions log a
warning and then return an empty list instead of preventing the system
from operating.
2026-03-09 15:16:46 +01:00
Hornwitser 276c36b6e9 Throw error if lib is imported twice (#863)
The lib library was written with the assumption that there's only one
copy of it in the whole application and everything imports that one
copy.  Unfortunately there are quite a few circumstances were package
managers end up installing multiple copies of lib and then different
parts use different versions of the library.

This causes Clusterio to break with strange and hard to track down
errors.  Most notably permissions and link messages for a plugin may get
registered to one copy of lib while the controller uses a different copy
and thus does not see or register that what the plugin added exists.

Detect this condition when importing @clusterio/lib and throw an error
when it has been imported twice in order to prevent the broken
installation from causing future hard to debug issues.
2026-03-06 18:50:18 +01:00
Cooldude2606 014b18caf8 Fix type of controller.auth_secret 2026-03-04 21:02:39 +00:00
Cooldude2606andHornwitser 2235294c59 Refactor user and instance manager (#837)
* Refactor UserManager to use Datastore

* Rename lib.User to UserDetails also adds to/fromUserDetails

* Fix existing tests

* Add user class tests

* Add tests for finaliseUsers

* Move HostDetails to own file with tests

* Move InstanceDetails to its own file with tests

* Rename HostInfo and add tests

* Rename InstanceInfo and add tests

- Also changes the names of the host properties related to instances
- Also removes the need to specify the location of InstanceInfo

* Make json round trip test validate schema

* Add InstanceManager

* Fix lint errors

* Extra tests

* Add final test coverage

* Add one last test to InstanceManager

* Response to comments

* Use generator rather than return anonumous generator

Co-authored-by: Hornwitser <github@hornwitser.no>

* Fix lint issue with generator

---------

Co-authored-by: Hornwitser <github@hornwitser.no>
2026-03-01 23:04:37 +00:00
Cooldude2606 1228baa9a3 Move ensure default roles onto Role class 2026-02-07 01:27:59 +00:00
Cooldude2606 ccd5232e70 Fix system info migration logic 2026-02-01 22:35:32 +00:00
Cooldude2606 9729743a29 Add startedAtMs to all processes 2026-02-01 21:30:00 +00:00
Cooldude2606 6f68cf74a7 Add network events and switch to tree select 2026-01-24 16:27:28 +00:00
Cooldude2606 43def44136 Fix package path for restart required 2025-11-07 21:47:48 +00:00
Cooldude2606 d52931a326 Better handling of mod dependencies (#801)
* Add ModDependency and version types

* Add warnings for mod dependencies

* Add warning for wrong factorio version

* Add fix issues button

* Add automatic dependency resolution and download

* Add abilty to check for updates

* Add extra test coverage

* One extra test

* Fix copy paste error
2025-11-07 19:22:17 +00:00
Cooldude2606 73cc019d2f Sets restart required when a plugin is updated (#794)
* Template type for load plugin config

* Add plugin update checking

* Add restart icon to instance list

* Add tests

* Log package name when failing to read package json
2025-11-01 16:11:11 +00:00
Cooldude2606 681e78967f Webpack Build Improvements (#791)
* Bump @swc/core to 1.13.3

* Switch to using --mode production rather than --env

Closes #792

* Add cache
2025-08-17 15:54:37 +01:00
Danielv123 7e2f10e842 Fix linting issues with new rules 2025-08-01 22:26:25 +00:00
Cooldude2606 df1edec623 Add role subscriptions and data model 2025-06-14 00:28:03 +01:00
Cooldude2606 a5bf8d92d9 Separate roles to their own file 2025-06-13 22:24:56 +01:00
Cooldude2606 331fc7d947 Add types to event emitters and clean up connections (#772)
* Event types for controller

* Clean up ctl event emitters

* Event types for server

* Event types for lib

* Remove TypedEventEmitter

* Event types for webui

* Fix compile issues

* Small cleanup of connectors
2025-06-13 22:20:50 +01:00
Cooldude2606 63e30dd0ee Add missing migrations for system info and tests (#766)
* Add missing migration for SystemInfo

* Add missing migration tests
2025-05-21 17:07:24 +01:00
Cooldude2606 726a3538c2 Move save and load into config class 2025-05-12 21:32:44 +01:00
Cooldude2606 17db53403a Move filepath into config class 2025-05-12 21:30:48 +01:00
Daniel 0adb45bba4 Merge pull request #758 from Cooldude2606/feature/pending-restart
Add indicators for host version mismatch and required restart
2025-05-11 14:14:39 +02:00