mirror of
https://github.com/MichMich/MagicMirror.git
synced 2026-07-03 21:27:17 -07:00
4b4a59534f
## Release Notes Thanks to: @angeldeejay, @egeekial, @khassel, @KristjanESPERANTO, @MikeBishop, @rejas > ⚠️ This release needs nodejs version >=22.21.1 <23 || >=24 (no change to previous release) [Compare to previous Release v2.36.0](https://github.com/MagicMirrorOrg/MagicMirror/compare/v2.36.0...develop) ### [core] - Prepare Release 2.37.0 (#4193) - fix(electron): map IPv6 :: wildcard to localhost (#4188) - refactor(main): modernize DOM update flow with async/await (#4186) - refactor(main): simplify _updateDom with async/await (#4185) - fix(security): prevent unauthorized secret expansion in socket payloads (#4184) - refactor(main): simplify updateDomWithContent async flow (#4182) - fix: modules losing data after HTTP 304 responses (#4180) - chore: add missing core defaults (#4181) - fix(server): enforce ipWhitelist for Socket.IO too (#4169) - feat(systeminfo): include Git hash and branch in system information log (#4167) - feat(electron): support object-based electronSwitches (#4161) - systeminformation thread not ending: move error handling from utils to app (#4160) - fix systeminformation thread not ending (#4155) - refactor: use ES module imports in browser core (#4158) - refactor(core): remove old Object.assign polyfill (#4157) - refactor: rewrite Module as an ES6 class (#4151) - refactor: rewrite NodeHelper as an ES6 class (#4147) - update eletron to v42 (#4144) - refactor(utils): drop ajv dependency (#4142) - fix(systeminformation): output right 'used node' version (from parent process) (#4141) - fix: skip postinstall git clean when not in a git repository (#4139) - Remove unnecessary conditionals and fix falsy property check in imperial conversion (#4135) - update version in package.json ### [dependencies] - update dependencies (#4191) - Bump actions/checkout from 6 to 7 (#4190) - chore: update dependencies and adjust import path for SunCalc (#4189) - update dependencies incl. electron and revert yauzl-electron-install-fix (#4183) - update dependencies, add electron fix in package.json (#4175) - chore: update dependencies (#4162) - Bump actions/dependency-review-action from 4 to 5 (#4152) - Unify linting: replace Stylelint and markdownlint with ESLint (#4148) - update dependencies and workflows to node v26 (#4140) ### [modules/alert] - CodeQL cleanup for alerts #18, #19, #20 (#4153) - fix: resolve CodeQL alerts #24 and #26 (#4145) - fix(electron): resolve CodeQL alerts #22 and #25 in electron.js (#4136) ### [modules/calendar] - perf(calendar): pre-filter ICS data before parsing (#4168) - perf(calendar): use async ICS parsing to avoid blocking event loop (#4143) ### [modules/newsfeed] - [newsfeed] add allowBasicHtmlTags option for basic emphasis (#4176) ### [modules/updatenotification] - fix(updatenotification): don't spawn a child process when running under PM2 (#4166) - fix(updatenotification): use process.argv[0] as restart binary (#4163) - fix(updatenotification): preserve start mode on restart (#4156) - fix(updatenotification): fix ref diff parsing for fetch --dry-run (#4138) - refactor(updatenotification): replace pm2 usage with node logic (#4134) ### [modules/weather] - feat(weather): add Buienradar provider (#4164) ### [testing] - remove warning in unit tests (for nodejs >= v25) (#4149) - polish HTTP 304 docs/test/handling (#4129) --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Kristjan ESPERANTO <35647502+KristjanESPERANTO@users.noreply.github.com> Co-authored-by: BugHaver <43462320+bughaver@users.noreply.github.com> Co-authored-by: BugHaver <43462320+lsaadeh@users.noreply.github.com> Co-authored-by: Karsten Hassel <hassel@gmx.de> Co-authored-by: Magnus <34011212+MagMar94@users.noreply.github.com> Co-authored-by: Koen Konst <koenspero@gmail.com> Co-authored-by: Koen Konst <c.h.konst@avisi.nl> Co-authored-by: Bugsounet - Cédric <github@bugsounet.fr> Co-authored-by: dathbe <github@beffa.us> Co-authored-by: veeck <gitkraken@veeck.de> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Marcel <m-idler@users.noreply.github.com> Co-authored-by: sam detweiler <sdetweil@gmail.com> Co-authored-by: Kevin G. <crazylegstoo@gmail.com> Co-authored-by: Jboucly <33218155+jboucly@users.noreply.github.com> Co-authored-by: Jboucly <contact@jboucly.fr> Co-authored-by: Jarno <54169345+jarnoml@users.noreply.github.com> Co-authored-by: Jordan Welch <JordanHWelch@gmail.com> Co-authored-by: Blackspirits <blackspirits@gmail.com> Co-authored-by: Samed Ozdemir <samed@xsor.io> Co-authored-by: in-voker <58696565+in-voker@users.noreply.github.com> Co-authored-by: Andrés Vanegas Jiménez <142350+angeldeejay@users.noreply.github.com> Co-authored-by: cgillinger <christian.gillinger@gmail.com> Co-authored-by: Sonny B <43247590+sonnyb9@users.noreply.github.com> Co-authored-by: sonnyb9 <sonnyb9@users.noreply.github.com> Co-authored-by: Morgan McBee <egeekial@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Mike Bishop <mbishop@evequefou.be>
188 lines
5.3 KiB
JavaScript
188 lines
5.3 KiB
JavaScript
const Exec = require("node:child_process").exec;
|
|
const Spawn = require("node:child_process").spawn;
|
|
|
|
const Log = require("logger");
|
|
|
|
/*
|
|
* class Updater
|
|
* Allow to self updating 3rd party modules from command defined in config
|
|
*
|
|
* [constructor] read value in config:
|
|
* updates: [ // array of modules update commands
|
|
* {
|
|
* <module name>: <update command>
|
|
* },
|
|
* {
|
|
* ...
|
|
* }
|
|
* ],
|
|
* updateTimeout: 2 * 60 * 1000, // max update duration
|
|
* updateAutorestart: false // autoRestart MM when update done ?
|
|
*
|
|
* [main command]: parse(<Array of modules>):
|
|
* parse if module update is needed
|
|
* --> Apply ONLY one update (first of the module list)
|
|
* --> auto-restart MagicMirror or wait manual restart by user
|
|
* return array with modules update state information for `updatenotification` module displayer information
|
|
* [
|
|
* {
|
|
* name = <module-name>, // name of the module
|
|
* updateCommand = <update command>, // update command (if found)
|
|
* inProgress = <boolean>, // an update if in progress for this module
|
|
* error = <boolean>, // an error if detected when updating
|
|
* updated = <boolean>, // updated successfully
|
|
* needRestart = <boolean> // manual restart of MagicMirror is required by user
|
|
* },
|
|
* {
|
|
* ...
|
|
* }
|
|
* ]
|
|
*/
|
|
|
|
class Updater {
|
|
constructor (config) {
|
|
this.updates = config.updates;
|
|
this.timeout = config.updateTimeout;
|
|
this.autoRestart = config.updateAutorestart;
|
|
this.moduleList = {};
|
|
this.updating = false;
|
|
this.version = global.version;
|
|
this.root_path = global.root_path;
|
|
Log.info("Updater Class Loaded!");
|
|
}
|
|
|
|
// [main command] parse if module update is needed
|
|
async parse (modules) {
|
|
var parser = modules.map(async (module) => {
|
|
if (this.moduleList[module.module] === undefined) {
|
|
this.moduleList[module.module] = {};
|
|
this.moduleList[module.module].name = module.module;
|
|
this.moduleList[module.module].updateCommand = await this.applyCommand(module.module);
|
|
this.moduleList[module.module].inProgress = false;
|
|
this.moduleList[module.module].error = null;
|
|
this.moduleList[module.module].updated = false;
|
|
this.moduleList[module.module].needRestart = false;
|
|
}
|
|
if (!this.moduleList[module.module].inProgress) {
|
|
if (!this.updating) {
|
|
if (!this.moduleList[module.module].updateCommand) {
|
|
this.updating = false;
|
|
} else {
|
|
this.updating = true;
|
|
this.moduleList[module.module].inProgress = true;
|
|
Object.assign(this.moduleList[module.module], await this.updateProcess(this.moduleList[module.module]));
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
await Promise.all(parser);
|
|
let updater = Object.values(this.moduleList);
|
|
Log.debug("Update Result:", updater);
|
|
return updater;
|
|
}
|
|
|
|
/*
|
|
* module updater with his proper command
|
|
* return object as result
|
|
* {
|
|
* error: <boolean>, // if error detected
|
|
* updated: <boolean>, // if updated successfully
|
|
* needRestart: <boolean> // if magicmirror restart required
|
|
* };
|
|
*/
|
|
updateProcess (module) {
|
|
let Result = {
|
|
error: false,
|
|
updated: false,
|
|
needRestart: false
|
|
};
|
|
let Command = null;
|
|
const Path = `${this.root_path}/modules/`;
|
|
const modulePath = Path + module.name;
|
|
|
|
if (module.updateCommand) {
|
|
Command = module.updateCommand;
|
|
} else {
|
|
Log.warn(`Update of ${module.name} is not supported.`);
|
|
return Result;
|
|
}
|
|
Log.info(`Updating ${module.name}...`);
|
|
|
|
return new Promise((resolve) => {
|
|
Exec(Command, { cwd: modulePath, timeout: this.timeout }, (error, stdout) => {
|
|
if (error) {
|
|
Log.error(`exec error: ${error}`);
|
|
Result.error = true;
|
|
} else {
|
|
Log.info(`Update logs of ${module.name}: ${stdout}`);
|
|
Result.updated = true;
|
|
if (this.autoRestart) {
|
|
Log.info("Update done");
|
|
setTimeout(() => this.nodeRestart(), 3000);
|
|
} else {
|
|
Log.info("Update done, don't forget to restart MagicMirror!");
|
|
Result.needRestart = true;
|
|
}
|
|
}
|
|
resolve(Result);
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Restart the current MagicMirror process after a successful auto-update.
|
|
* Under PM2 just exit and let PM2 respawn — spawning a child here would
|
|
* race against PM2 and cause an EADDRINUSE conflict (see issue #4165).
|
|
* Standalone: spawn a detached clone of the current process, then exit.
|
|
*/
|
|
nodeRestart () {
|
|
Log.info("Restarting MagicMirror...");
|
|
|
|
const isManagedByPm2 = process.env.pm_id !== undefined;
|
|
if (isManagedByPm2) {
|
|
Log.info("Running under PM2 — exiting for PM2 to respawn.");
|
|
process.exit(0);
|
|
return;
|
|
}
|
|
|
|
this._spawnDetachedSelf();
|
|
process.exit();
|
|
}
|
|
|
|
/**
|
|
* Spawn a detached clone of the current Node process (same binary + argv),
|
|
* wired to the parent's stdout/stderr.
|
|
*/
|
|
_spawnDetachedSelf () {
|
|
const nodeBinary = process.argv[0];
|
|
const nodeArgs = process.argv.slice(1);
|
|
const spawnOptions = {
|
|
cwd: this.root_path,
|
|
detached: true,
|
|
stdio: ["ignore", process.stdout, process.stderr]
|
|
};
|
|
|
|
const child = Spawn(nodeBinary, nodeArgs, spawnOptions);
|
|
child.unref();
|
|
}
|
|
|
|
// check if module is MagicMirror
|
|
isMagicMirror (module) {
|
|
if (module === "MagicMirror") return true;
|
|
return false;
|
|
}
|
|
|
|
// search update module command
|
|
applyCommand (module) {
|
|
if (this.isMagicMirror(module.module) || !this.updates.length) return null;
|
|
let command = null;
|
|
this.updates.forEach((updater) => {
|
|
if (updater[module]) command = updater[module];
|
|
});
|
|
return command;
|
|
}
|
|
}
|
|
|
|
module.exports = Updater;
|