mirror of
https://github.com/MichMich/MagicMirror.git
synced 2025-09-14 16:13:57 +00:00
Having repeatedly seen that users are unaware of the meaning of the EADDRINUSE error message (see, for example, this [forum thread](https://forum.magicmirror.builders/topic/19871/update-package-list/5)), I thought we should intercept this message and provide clearer output. This may help users identify the cause of the problem more quickly themselves. ## before ``` [2025-09-13 09:54:32.903] [LOG] Starting MagicMirror: v2.33.0-develop ... [2025-09-13 09:54:33.533] [LOG] Starting server on port 8080 ... [2025-09-13 09:54:33.537] [WARN] You're using a full whitelist configuration to allow for all IPs [2025-09-13 09:54:33.568] [ERROR] Whoops! There was an uncaught exception... [2025-09-13 09:54:33.574] [ERROR] Error: listen EADDRINUSE: address already in use 0.0.0.0:8080 at Server.setupListenHandle [as _listen2] (node:net:1940:16) at listenInCluster (node:net:1997:12) at node:net:2206:7 at process.processTicksAndRejections (node:internal/process/task_queues:90:21) { code: 'EADDRINUSE', errno: -98, syscall: 'listen', address: '0.0.0.0', port: 8080 } [2025-09-13 09:54:33.574] [ERROR] MagicMirror² will not quit, but it might be a good idea to check why this happened. Maybe no internet connection? [2025-09-13 09:54:33.574] [ERROR] If you think this really is an issue, please open an issue on GitHub: https://github.com/MagicMirrorOrg/MagicMirror/issues [2025-09-13 09:54:35.235] [INFO] #### System Information #### ... ``` ## after ``` [2025-09-13 09:53:20.151] [LOG] Starting MagicMirror: v2.33.0-develop ... [2025-09-13 09:53:20.928] [LOG] Starting server on port 8080 ... [2025-09-13 09:53:20.931] [WARN] You're using a full whitelist configuration to allow for all IPs [2025-09-13 09:53:20.970] [ERROR] ──────────────────────────────────────────────────────────────── PORT IN USE: 0.0.0.0:8080 Another process (most likely another MagicMirror instance) is already using this port. Stop the other process (free the port) or use a different port. ──────────────────────────────────────────────────────────────── [2025-09-13 09:53:22.471] [INFO] #### System Information #### ... ```
147 lines
4.7 KiB
JavaScript
147 lines
4.7 KiB
JavaScript
const fs = require("node:fs");
|
|
const http = require("node:http");
|
|
const https = require("node:https");
|
|
const path = require("node:path");
|
|
const express = require("express");
|
|
const ipfilter = require("express-ipfilter").IpFilter;
|
|
const helmet = require("helmet");
|
|
const socketio = require("socket.io");
|
|
const Log = require("logger");
|
|
const { cors, getConfig, getHtml, getVersion, getStartup, getEnvVars } = require("#server_functions");
|
|
|
|
const vendor = require(`${__dirname}/vendor`);
|
|
|
|
/**
|
|
* Server
|
|
* @param {object} config The MM config
|
|
* @class
|
|
*/
|
|
function Server (config) {
|
|
const app = express();
|
|
const port = process.env.MM_PORT || config.port;
|
|
const serverSockets = new Set();
|
|
let server = null;
|
|
|
|
/**
|
|
* Opens the server for incoming connections
|
|
* @returns {Promise} A promise that is resolved when the server listens to connections
|
|
*/
|
|
this.open = function () {
|
|
return new Promise((resolve) => {
|
|
if (config.useHttps) {
|
|
const options = {
|
|
key: fs.readFileSync(config.httpsPrivateKey),
|
|
cert: fs.readFileSync(config.httpsCertificate)
|
|
};
|
|
server = https.Server(options, app);
|
|
} else {
|
|
server = http.Server(app);
|
|
}
|
|
const io = socketio(server, {
|
|
cors: {
|
|
origin: /.*$/,
|
|
credentials: true
|
|
},
|
|
allowEIO3: true,
|
|
pingInterval: 120000, // server → client ping every 2 mins
|
|
pingTimeout: 120000 // wait up to 2 mins for client pong
|
|
});
|
|
|
|
server.on("connection", (socket) => {
|
|
serverSockets.add(socket);
|
|
socket.on("close", () => {
|
|
serverSockets.delete(socket);
|
|
});
|
|
});
|
|
|
|
Log.log(`Starting server on port ${port} ... `);
|
|
|
|
// Add explicit error handling BEFORE calling listen so we can give user-friendly feedback
|
|
server.once("error", (err) => {
|
|
if (err && err.code === "EADDRINUSE") {
|
|
const bindAddr = config.address || "localhost";
|
|
const portInUseMessage = [
|
|
"",
|
|
"────────────────────────────────────────────────────────────────",
|
|
` PORT IN USE: ${bindAddr}:${port}`,
|
|
"",
|
|
" Another process (most likely another MagicMirror instance)",
|
|
" is already using this port.",
|
|
"",
|
|
" Stop the other process (free the port) or use a different port.",
|
|
"────────────────────────────────────────────────────────────────"
|
|
].join("\n");
|
|
Log.error(portInUseMessage);
|
|
return;
|
|
}
|
|
|
|
Log.error("Failed to start server:", err);
|
|
});
|
|
|
|
server.listen(port, config.address || "localhost");
|
|
|
|
if (config.ipWhitelist instanceof Array && config.ipWhitelist.length === 0) {
|
|
Log.warn("You're using a full whitelist configuration to allow for all IPs");
|
|
}
|
|
|
|
app.use(function (req, res, next) {
|
|
ipfilter(config.ipWhitelist, { mode: config.ipWhitelist.length === 0 ? "deny" : "allow", log: false })(req, res, function (err) {
|
|
if (err === undefined) {
|
|
res.header("Access-Control-Allow-Origin", "*");
|
|
return next();
|
|
}
|
|
Log.log(err.message);
|
|
res.status(403).send("This device is not allowed to access your mirror. <br> Please check your config.js or config.js.sample to change this.");
|
|
});
|
|
});
|
|
|
|
app.use(helmet(config.httpHeaders));
|
|
app.use("/js", express.static(__dirname));
|
|
|
|
let directories = ["/config", "/css", "/modules", "/node_modules/animate.css", "/node_modules/@fontsource", "/node_modules/@fortawesome", "/translations", "/tests/configs", "/tests/mocks"];
|
|
for (const [key, value] of Object.entries(vendor)) {
|
|
const dirArr = value.split("/");
|
|
if (dirArr[0] === "node_modules") directories.push(`/${dirArr[0]}/${dirArr[1]}`);
|
|
}
|
|
const uniqDirs = [...new Set(directories)];
|
|
for (const directory of uniqDirs) {
|
|
app.use(directory, express.static(path.resolve(global.root_path + directory)));
|
|
}
|
|
|
|
app.get("/cors", async (req, res) => await cors(req, res));
|
|
|
|
app.get("/version", (req, res) => getVersion(req, res));
|
|
|
|
app.get("/config", (req, res) => getConfig(req, res));
|
|
|
|
app.get("/startup", (req, res) => getStartup(req, res));
|
|
|
|
app.get("/env", (req, res) => getEnvVars(req, res));
|
|
|
|
app.get("/", (req, res) => getHtml(req, res));
|
|
|
|
server.on("listening", () => {
|
|
resolve({
|
|
app,
|
|
io
|
|
});
|
|
});
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Closes the server and destroys all lingering connections to it.
|
|
* @returns {Promise} A promise that resolves when server has successfully shut down
|
|
*/
|
|
this.close = function () {
|
|
return new Promise((resolve) => {
|
|
for (const socket of serverSockets.values()) {
|
|
socket.destroy();
|
|
}
|
|
server.close(resolve);
|
|
});
|
|
};
|
|
}
|
|
|
|
module.exports = Server;
|