Links
Tags
Creators
Details
1.3.0
Compatibility
Changes
🆕 What's new in 1.3.0
Dashboard — Settings & Bot Management
The web dashboard has been completely overhauled. It is no longer just a log viewer — it now gives you full control over the plugin without ever touching config.yml or SSHing into the server.
New Settings tab — every config value is editable live from the browser:
- Process behaviour: auto-restart toggle, restart delay, max restarts, crash window, startup delay
- Node.js runtime: version pin and custom executable path
- Discord webhook: URL, enable/disable, and individual toggles for each event type (start, stop, crash, log lines)
- Dashboard: log history line count and password (changing the password immediately invalidates all active sessions)
New Bots tab — full bot instance management with TunnelMC-style modal dialogs:
- Add a new bot (name, entry file, env vars) — starts automatically
- Edit an existing bot — stops it, applies the new config, and restarts
- Delete a bot — stops it and removes it from
config.yml - All changes persist to disk immediately
Download logs — a new button on the sidebar downloads the current bot.log as a file attachment.
SSE push-based streaming — log lines are now pushed directly to the browser the moment they are written, rather than polled. This eliminates latency and CPU overhead.
Log counter fix — the line counter now reflects what is actually visible in the DOM (capped at 3000) rather than a stale incrementing integer that could exceed the cap.
switchBot() race fixed — historical logs are now fully loaded before the SSE stream connects, preventing out-of-order or duplicate lines when switching bots.
Bug Fixes
-
failedAttemptsmap unbounded growth — the brute-force attempt tracker is now swept hourly alongside the session map, preventing unbounded accumulation of stale IP entries. -
JSON injection in status/bots API — bot names and entry file paths are now serialised with Gson instead of string concatenation, preventing malformed JSON if a name contains quotes or backslashes.
-
setsidpath now covers Alpine Linux / Pterodactyl — checks/usr/bin/setsid,/bin/setsid, and/usr/local/bin/setsidbefore falling back gracefully. Previously only/usr/bin/setsidwas checked, so process group killing silently did nothing on Alpine-based containers. -
FileReaderplatform charset —/proc/<pid>/statusis now read with explicit UTF-8 instead of the JVM default charset. -
FileUtils.deleteDir()now returnsboolean— failures are logged per-file instead of silently swallowed. -
GET endpoints reject non-GET requests —
/api/status,/api/bots,/api/logs/recent,/api/logs/streamnow return 405 for wrong-method requests. -
Log timestamps include the date — entries now use
MM-dd HH:mm:ssformat so logs spanning midnight are unambiguous. -
Content-Security-Policy header added to all dashboard responses.
-
Gradle performance flags —
parallel,caching, andG1GCJVM args added togradle.propertiesfor faster local builds. -
Fixed state race condition —
BotStateis now managed viaAtomicReferenceinstead of a plainvolatilefield, eliminating a class of bugs where background threads could set the state back toRUNNINGafter a stop was already requested. -
Fixed child process orphaning on stop — NodeRunner now launches the bot with
setsidon Linux/macOS and sendskill -TERM -<pgid>on stop, matching PyRunner's behaviour. Previously, any subprocesses spawned by the bot (workers, shell scripts, etc.) were left running after the parent was killed. -
Fixed
extractTardouble-reading the archive — The extracted directory is now located by diffing the folder listing before and after extraction. The old approach rantar tfon the archive file a second time, which could fail if the archive had already been deleted. -
Fixed
restart()busy-wait — The polling loop (Thread.sleep(200)in awhileloop) has been replaced with aCountDownLatch, so restarts are both more reliable and use zero CPU while waiting. -
Fixed
HttpClientcreated per request — A single sharedHttpClientinstance is now reused across all Node.js version and download requests, rather than constructing a new one (with its own thread pools and connection pool) for each call. -
Fixed config values not validated — All numeric config getters (
restart-delay-seconds,max-restarts,crash-window-seconds,startup-delay-seconds) now clamp to valid ranges, preventingThread.sleep(-N)exceptions from negative values. -
Fixed log-history cap not enforced —
getDashboardLogHistory()is now capped at 500 (matchingLogManager's ring buffer size), so requesting more lines than the buffer holds no longer silently returns a truncated result without warning. -
Fixed static
instancenotvolatile— The singleton is now declaredvolatilefor safe cross-thread visibility, and is nulled out inonDisable()to prevent classloader leaks on plugin reload. -
Fixed Discord embed JSON escaping — Webhook embeds now use Gson's
toJson()for string serialisation instead of a manualreplace()chain, correctly escaping all Unicode control characters (\u0000–\u001F) that could cause Discord to reject payloads. -
Fixed malformed bot entries crashing plugin load — Each entry in the
bots:YAML list is now parsed inside a try-catch; a bad entry logs a warning and is skipped rather than throwing aClassCastExceptionthat aborts the whole enable sequence. -
Added default password warning — A prominent console warning is now printed at startup if the dashboard is enabled and the password is still set to
"changeme". -
Improved
getBotDir()fallback — Whenentry-filehas no directory component (e.g.index.jsinstead ofbot/index.js), the plugin now derives the directory from the actual file path and logs a descriptive warning, rather than silently assuming"bot". -
Fixed dashboard SSE exhausting the HTTP thread pool — The server now uses Java 21 virtual threads (
newVirtualThreadPerTaskExecutor) instead of a fixed 8-thread pool. Previously, 8+ simultaneous SSE log-stream connections (e.g. multiple open tabs or reconnect storms) would fill the pool and cause all other API requests — status polls, button clicks — to hang indefinitely. -
Fixed
DashboardServer.jsonString()unsafe escaping — Dashboard log API responses now useGSON.toJson()instead of a manualreplace()chain. Log lines containing Unicode control characters (\u0000–\u001F) could previously produce malformed JSON that failed to parse in the browser. -
Fixed memory cache non-atomic reads — The three separate
volatilefields (cachedMemKb,cachedMemPid,cachedMemTime) have been replaced with a singlevolatile MemCacherecord, swapped atomically. A thread could previously observe a new timestamp alongside stale memory values from the previous cycle. -
Fixed SSE handler busy-polling — The
while (!heartbeatFuture.isDone()) Thread.sleep(1000)loop is replaced withheartbeatFuture.get(), eliminating the 1-second wake-up overhead while waiting for clients to disconnect. -
Fixed session map growing without bound — A
ScheduledExecutorServicenow sweeps expired sessions every hour. Previously, repeated logins without a server restart would accumulate stale entries in the map indefinitely. -
Fixed dashboard password using string equality — The login check now uses
MessageDigest.isEqual()for constant-time comparison, preventing theoretical timing attacks on the password. -
Fixed duplicate
deleteDir()logic — A new sharedutil/FileUtils.deleteDir()(NIO-based) replaces the identical recursive-delete method that was duplicated acrossNodeBotCommandandDashboardServer. -
Fixed
plugin.ymlapi-versionoutdated — Updated from'1.13'to'1.21'to match the actual target platform and suppress Paper's deprecation warnings on load. -
Fixed Gson used as implicit transitive dependency — Gson is now declared explicitly as
compileOnlyinbuild.gradle.ktsso the build doesn't silently rely on Paper's bundled copy, which Paper reserves the right to relocate or remove. -
Fixed legacy
§color codes in commands — All in-game messages inNodeBotCommandnow use the Paper Adventure API (MiniMessage) instead of deprecated§-prefixed color codes, eliminating deprecation warnings and ensuring correct rendering in modern clients.
Changes
- Dashboard now uses a bounded thread pool (8 threads) instead of an unbounded cached pool
- SSE heartbeats use a shared scheduled executor instead of per-connection threads
- NodeDownloader is now a shared singleton — multiple bots no longer trigger duplicate downloads
- Webhook shutdown is now handled cleanly on plugin disable
- Restart now waits for the process to fully stop before starting again (prevents race conditions)
Bug Fixes
- Fixed stop command not waiting for the process to actually terminate before reporting "stopped"
- Fixed auto-restart thread not being interrupted when manually stopping a bot
Config changes
New option added to config.yml:
# Per-bot env vars (in bots: list)
bots:
- name: "modbot"
entry-file: "bot/modbot/index.js"
env:
BOT_TOKEN: "modbot-token-here"
Existing configs remain fully compatible — all new keys use their defaults if not present.
Projects on Modrinth are automatically available through a Maven repository for use with JVM build tools such as Gradle. To learn more about the Modrinth Maven API, click here.
Note: When available, you should use the creator's maven repo instead as it will have transitive dependency information that the Modrinth Maven API does not. You may also end up with duplicate dependencies if you use a mix of Modrinth and non-Modrinth Maven repositories for your dependencies, because the group identifier will be different when served through the Modrinth Maven API.
Maven coordinates:
Version ID:
build.gradle:
repositories {
exclusiveContent {
forRepository {
maven {
name = "Modrinth"
url = "https://api.modrinth.com/maven"
}
}
// forRepositories(fg.repository) // Uncomment when using ForgeGradle
filter {
includeGroup "maven.modrinth"
}
}
}
// Standard Gradle dependency
dependencies {
implementation "maven.modrinth:YL9bPIyR:lXfK1hC4"
}
// Legacy Loom dependency
dependencies {
modImplementation "maven.modrinth:YL9bPIyR:lXfK1hC4"
}

