PyRunner
1.3.0Run your Python Discord bot directly alongside your Minecraft server! Features auto-setup, live web dashboard, and in-game controls
1.3.0latestPyRunner 1.3.0May 14, 2026 What's new in 1.3.0
Java 25 & Build Modernization
- Upgraded to Java 25 — compiled with Java 25 bytecode for the latest JVM performance and language features. - Gradle 9.5.1 — build system upgraded from Gradle 8.x to 9.5.1 for native Java 25 support. - Virtual thread executor — the dashboard HTTP server now usesnewVirtualThreadPerTaskExecutor()for non-blocking I/O scaling on Java 25. - `plugin.yml` api-version updated to1.21to match the target platform.Compatibility
- Minecraft 1.21.x – 26.2.x — compiled against Paper 1.21.1 API, which is forward-compatible with all Paper versions through 26.2.x. Existing configs remain fully compatible — no config changes required.1.2.0PyRunner 1.2.0Apr 27, 2026 What's new in 1.2.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 touchingconfig.ymlor 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 - Python 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 fromconfig.yml- All changes persist to disk immediately Download logs — a new button on the sidebar downloads the currentbot.logas 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. `switchBot()` race fixed — historical logs are fully loaded before the SSE stream connects, preventing out-of-order or duplicate lines when switching bots.Bug Fixes
- `failedAttempts` map unbounded growth — swept hourly alongside the session map. - JSON injection in status/bots API — bot names and entry file paths serialised with Gson instead of string concatenation. - `setsid` path now covers Alpine Linux / Pterodactyl — checks/usr/bin/setsid,/bin/setsid, and/usr/local/bin/setsid. Previously process group killing silently did nothing on Alpine-based containers. - `FileReader` platform charset —/proc/<pid>/statusread with explicit UTF-8. - `FileUtils.deleteDir()` returns `boolean` — failures logged per-file instead of silently swallowed. - GET endpoints reject non-GET requests — 405 returned for wrong-method calls. - Log timestamps include the date —MM-dd HH:mm:ssformat so logs spanning midnight are unambiguous. - Python downloader fetches 20 releases — up from 5, so rare platform/version combos are found correctly. - Content-Security-Policy header added to all dashboard responses. - Gradle performance flags —parallel,caching, andG1GCJVM args added togradle.properties. - Fixed state race condition —BotStateis now managed viaAtomicReferenceinstead of a plainvolatilefield, eliminating a class of bugs where background threads could write the state without memory visibility guarantees. - Fixed `forceReinstall()` not actually reinstalling — The command now passes--force-reinstallto pip, guaranteeing packages are freshly installed. Previously it only deleted the flag file and ran a normalpip install, which would skip already-installed packages. - Fixed `restart()` busy-wait — The polling loop has been replaced with aCountDownLatch, so restarts are more reliable and use zero CPU while waiting for the process to fully stop. - Fixed GitHub API rate limiting causing confusing errors —findReleaseAsset()now checks the HTTP status code before attempting to parse JSON. A 403 or 429 from GitHub now produces a clear "rate limit reached" message instead of a crypticJsonParseException. - 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 `instance` not `volatile` — 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'stoJson()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 thebots: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.main.pyinstead ofbot/main.py), 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 could fill the pool and cause all other API requests to hang indefinitely. - Fixed `DashboardServer.jsonString()` unsafe escaping — Dashboard log API responses now useGSON.toJson()instead of a manualreplace()chain, correctly handling Unicode control characters (\u0000–\u001F) that could previously produce malformed JSON. - Fixed memory cache non-atomic reads — Three separatevolatilefields replaced with a singlevolatile MemCacherecord swapped atomically, preventing threads from observing a new timestamp with stale memory values. - Fixed SSE handler busy-polling —while (!heartbeatFuture.isDone()) Thread.sleep(1000)replaced withheartbeatFuture.get(), eliminating unnecessary 1-second wakeups while waiting for client disconnection. - Fixed session map growing without bound — A scheduled hourly sweep now removes expired sessions. Previously, repeated logins accumulated stale entries indefinitely. - Fixed dashboard password using string equality — Login now usesMessageDigest.isEqual()for constant-time comparison, preventing timing attacks. - Fixed duplicate `deleteDir()` logic — A new sharedutil/FileUtils.deleteDir()(NIO-based) replaces the identical recursive-delete duplicated acrossPyBotCommandandDashboardServer. - Fixed `plugin.yml` `api-version` outdated — Updated from'1.13'to'1.21'to match the actual target platform and suppress Paper deprecation warnings on load. - Fixed Gson used as implicit transitive dependency — Gson is now declared explicitly ascompileOnlyinbuild.gradle.kts. - Fixed legacy `§` color codes in commands — All in-game messages inPyBotCommandnow use the Paper Adventure API (MiniMessage), eliminating deprecation warnings and ensuring correct rendering in modern clients.Config changes
No new config keys. Existing configs remain fully compatible.1.1.0PyRunner 1.1.0Apr 11, 2026 What's new in 1.1.0
Per-Bot Environment Variables
Each bot in thebots:list can now have its ownenv:block that overrides the global environment variables — useful for running multiple bots with different tokens.Memory Monitoring
The web dashboard now shows live RSS memory usage of the bot process (Linux only, reads from/proc/<pid>/status).Multi-Bot Dashboard Selector
When running multiple bots, the dashboard now shows a bot selector dropdown to switch between bots without leaving the page.Dashboard Security Hardening
- CSRF protection — All mutating API endpoints now require anX-PR-Requestheader - Brute-force login protection — Lockout after 5 failed attempts (5 minute cooldown) - Session pruning — Expired sessions are now cleaned up automatically ---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 - PythonDownloader is now a shared singleton — multiple bots no longer trigger duplicate downloads - Webhook shutdown is now handled cleanly on plugin disableBug Fixes
- Fixed bot not stopping on Linux — now kills the entire process group viasetsid+kill -TERM -<pgid>- Fixed TunnelMC integration not detecting active tunnels correctly1.0.0PyRunner 1.0.0Mar 31, 2026
Similar projects
Config →VeinMiner
Mine the whole vein on mining a single ore/block. Make the tedious mining experience to something satisfying and fun! FTB Ultimine alternative
99.3MSueta
Simple Voice Chat
A working voice chat in Minecraft!
76.8MSueta
Chunky
Pre-generates chunks, quickly and efficiently
18.4MSueta
VeinMiner Enchantment
Veinminer Addon - Adds vineminer enchantment to enchanting tables. Only tools with the enchantment can veinmine
18MSueta
JourneyMap
Real-time map used for mapping in-game or your browser as you explore.
14.7MSueta
WorldEdit
A Minecraft Map Editor... that runs in-game! With selections, schematics, copy and paste, brushes, and scripting. Use it in creative, or use it temporarily in survival.
11.6MSueta