
NodeRunner
Run your Node.js Discord bot directly alongside your Minecraft server! Features auto-setup, live web dashboard, and in-game controls
Список изменений
🆕 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.
