Read the statement by Michael Teeuw here.
MMM-Remote-Control
-
@KristjanESPERANTO Dear Kristijan,
after a long time I finally managed to see your “new” modul.
First of all: Thanks a lot for your effort, again - nice piece!In the past few days I’ve completely rebuilt my mirror and migrated it to trixie as well as to the actual mirror and node versions.
I ran into a reproducible issue with the restart functionality when MagicMirror is managed by pm2 (which is the recommended setup per the official MagicMirror documentation).
The Problem
When triggering a restart via the Remote-Control UI (or the /api/restart endpoint), MagicMirror enters an endless crash-loop with EADDRINUSE: address already in use :::8080.
Root Cause
The handleRestart function in node_helper.js (lines 717-741) uses the Electron-native restart mechanism:const {app} = require("electron"); app.relaunch(); app.quit();This works fine in standalone mode, but creates a race condition with pm2:
- app.relaunch() spawns a new Electron instance (child of the current process)
- app.quit() terminates the current Electron instance
- pm2 detects the termination as a crash and spawns another Electron instance
- Now two Electron instances compete for port 8080 → EADDRINUSE → both crash → pm2 keeps restarting → infinite loop
The only way to recover is to manually identify and kill the orphaned Electron process, then trigger a clean restart via touch config.js (which pm2’s file-watch picks up).
Interestingly, the catch-block fallback for server mode (lines 726-739) already does it correctly — it calls process.exit(0) and lets the process manager handle the restart. The Electron code path just doesn’t account for the pm2 scenario.
Possible Fix
When running under pm2, the Electron-native app.relaunch() should be skipped entirely.
pm2 can be detected via environment variables like PM2_HOME, pm_id, or PM2_USAGE:
handleRestart (query, res) { try { const {app} = require("electron"); if (!app) { throw "Could not get Electron app instance."; } // When running under pm2, don't use app.relaunch() — pm2 handles restart if (process.env.PM2_HOME || process.env.pm_id !== undefined) { Log.log("Running under pm2, exiting cleanly for process manager restart..."); this.sendResponse(res, undefined, {action: "RESTART", info: "Exiting for pm2 restart..."}); setTimeout(() => app.quit(), 1000); return; } this.sendResponse(res, undefined, {action: "RESTART", info: "Restarting Electron..."}); app.relaunch(); app.quit(); } catch (error) { // ... existing server mode fallback ... } }Environment
- MagicMirror 2.34.0
- MMM-Remote-Control 4.2.2 (commit b854832)
- Node.js v22, Electron 39.5.2
- Raspberry Pi 5, Debian 13 (Trixie)
- Process manager: pm2 with file-watch on config.js
Workaround
For now, I’ve documented this for my setup: never use the restart button in Remote-Control. Monitor ON/OFF works fine, only the restart is
affected.Thanks for considering this — happy to test any fix if needed!
Warmest regards,
Ralf -
Hi @vtek, glad you figured it out! Just for reference: when
secureEndpoints: trueis set, the API key can be passed in two ways:- URL parameter:
?apiKey=YOUR_KEY - HTTP header:
Authorization: apiKey YOUR_KEY
Also, starting with v4.2.4 the error message now directly hints at the solution, so the next person running into this should find it faster 🙂
- URL parameter:
-
Hi @rkorell, thank you for the detailed bug report — the root cause analysis was spot on and made it straightforward to fix!
The fix is included in v4.2.4: when MMM-Remote-Control detects it’s running under pm2 (via
PM2_HOMEorpm_idenvironment variables), it now skipsapp.relaunch()and only callsapp.quit(), letting pm2 handle the restart cleanly.Would be great if you could confirm it works on your setup!
-
@KristjanESPERANTO Dear Kristijan,
just tested v4.2.4 — works perfectly! Restart via the Remote-Control UI now completes cleanly
under pm2, no orphaned Electron process, no port conflict. Exactly the behavior you’d expect.I also noticed you added a dedicated handleRestart.test.js with 210 lines of unit tests covering
both the pm2 and standalone code paths — that’s really impressive and goes well beyond just a
quick fix. Having proper test coverage for this kind of dual-mode behavior is exactly the right
thing to do. Much appreciated!Thanks for the incredibly fast turnaround and the quality of the fix. Consider this confirmed
working on:- MagicMirror 2.34.0
- MMM-Remote-Control 4.2.4
- Node.js v22, Electron 39.5.2
- Raspberry Pi 5, Debian 13 (Trixie)
- pm2 with file-watch
Warmest regards,
Ralf -
@rkorell Thank you very much for this feedback and appreciation 🙂 It’s interesting that you took a deeper look at the changes.
