mirror of
https://github.com/MichMich/MagicMirror.git
synced 2025-07-02 22:08:42 +00:00
## [2.32.0] - 2025-07-01 Thanks to: @bughaver, @bugsounet, @khassel, @KristjanESPERANTO, @plebcity, @rejas, @sdetweil. > ⚠️ This release needs nodejs version `v22.14.0 or higher` ### Added - [config] Allow to change module order for final renderer (or dynamically with CSS): Feature `order` in config (#3762) - [clock] Added option 'disableNextEvent' to hide next sun event (#3769) - [clock] Implement short syntax for clock week (#3775) ### Changed - [refactor] Simplify module loading process (#3766) - Use `node --run` instead of `npm run` (#3764) and adapt `start:dev` script (#3773) - [workflow] Run linter and spellcheck with LTS node version (#3767) - [workflow] Split "Run test" step into two steps for more clarity (#3767) - [linter] Review linter setup (#3783) - Fix command to lint markdown in `CONTRIBUTING.md` - Re-activate JSDoc linting and fix linting issues - Refactor ESLint config to use `defineConfig` and `globalIgnores` - Replace `eslint-plugin-import` with `eslint-plugin-import-x` - Switch Stylelint config to flat format and simplify Stylelint scripts - [workflow] Replace Node.js version v23 with v24 (#3770) - [refactor] Replace deprecated constants `fs.F_OK` and `fs.R_OK` (#3789) - [refactor] Replace `ansis` with built-in function `util.styleText` (#3793) - [core] Integrate stuff from `vendor` and `fonts` folders into main `package.json`, simplifies install and maintaining dependencies (#3795, #3805) - [l10n] Complete translations (with the help of translation tools) (#3794) - [refactor] Refactored `calendarfetcherutils` in Calendar module to handle timezones better (#3806) - Removed as many of the date conversions as possible - Use `moment-timezone` when calculating recurring events, this will fix problems from the past with offsets and DST not being handled properly - Added some tests to test the behavior of the refactored methods to make sure the correct event dates are returned - [linter] Enable ESLint rule `no-console` and replace `console` with `Log` in some files (#3810) - [tests] Review and refactor translation tests (#3792) ### Fixed - [fix] Handle spellcheck issues (#3783) - [calendar] fix fullday event rrule until with timezone offset (#3781) - [feat] Add rule `no-undef` in config file validation to fix #3785 (#3786) - [fonts] Fix `roboto.css` to avoid error message `Unknown descriptor 'var(' in @font-face rule.` in firefox console (#3787) - [tests] Fix and refactor e2e test `Same keys` in `translations_spec.js` (#3809) - [tests] Fix e2e tests newsfeed and calendar to exit without open handles (#3817) ### Updated - [core] Update dependencies including electron to v36 (#3774, #3788, #3811, #3804, #3815, #3823) - [core] Update package type to `commonjs` - [logger] Review factory code part: use `switch/case` instead of `if/else if` (#3812) --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Michael Teeuw <michael@xonaymedia.nl> Co-authored-by: Kristjan ESPERANTO <35647502+KristjanESPERANTO@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ross Younger <crazyscot@gmail.com> Co-authored-by: Veeck <github@veeck.de> Co-authored-by: Bugsounet - Cédric <github@bugsounet.fr> Co-authored-by: jkriegshauser <joshuakr@nvidia.com> Co-authored-by: illimarkangur <116028111+illimarkangur@users.noreply.github.com> Co-authored-by: sam detweiler <sdetweil@gmail.com> Co-authored-by: vppencilsharpener <tim.pray@gmail.com> Co-authored-by: veeck <michael.veeck@nebenan.de> Co-authored-by: Paranoid93 <6515818+Paranoid93@users.noreply.github.com> Co-authored-by: Brian O'Connor <btoconnor@users.noreply.github.com> Co-authored-by: WallysWellies <59727507+WallysWellies@users.noreply.github.com> Co-authored-by: Jason Stieber <jrstieber@gmail.com> Co-authored-by: jargordon <50050429+jargordon@users.noreply.github.com> Co-authored-by: Daniel <32464403+dkallen78@users.noreply.github.com> Co-authored-by: Ryan Williams <65094007+ryan-d-williams@users.noreply.github.com> Co-authored-by: Panagiotis Skias <panagiotis.skias@gmail.com> Co-authored-by: Marc Landis <dirk.rettschlag@gmail.com> Co-authored-by: HeikoGr <20295490+HeikoGr@users.noreply.github.com> Co-authored-by: Pedro Lamas <pedrolamas@gmail.com> Co-authored-by: veeck <gitkraken@veeck.de> Co-authored-by: Magnus <34011212+MagMar94@users.noreply.github.com> Co-authored-by: Ikko Eltociear Ashimine <eltociear@gmail.com> Co-authored-by: DevIncomin <56730075+Developer-Incoming@users.noreply.github.com> Co-authored-by: Nathan <n8nyoung@gmail.com> Co-authored-by: mixasgr <mixasgr@users.noreply.github.com> Co-authored-by: Savvas Adamtziloglou <savvas-gr@greeklug.gr> Co-authored-by: Konstantinos <geraki@gmail.com> Co-authored-by: OWL4C <124401812+OWL4C@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: Koen Konst <koenspero@gmail.com> Co-authored-by: Koen Konst <c.h.konst@avisi.nl>
233 lines
7.1 KiB
JavaScript
233 lines
7.1 KiB
JavaScript
const Exec = require("node:child_process").exec;
|
|
const Spawn = require("node:child_process").spawn;
|
|
const fs = require("node:fs");
|
|
|
|
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.usePM2 = false; // don't use pm2 by default
|
|
this.PM2Id = null; // pm2 process number
|
|
this.version = global.version;
|
|
this.root_path = global.root_path;
|
|
Log.info("updatenotification: 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("updatenotification 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(`updatenotification: Update of ${module.name} is not supported.`);
|
|
return Result;
|
|
}
|
|
Log.info(`updatenotification: Updating ${module.name}...`);
|
|
|
|
return new Promise((resolve) => {
|
|
Exec(Command, { cwd: modulePath, timeout: this.timeout }, (error, stdout, stderr) => {
|
|
if (error) {
|
|
Log.error(`updatenotification: exec error: ${error}`);
|
|
Result.error = true;
|
|
} else {
|
|
Log.info(`updatenotification: Update logs of ${module.name}: ${stdout}`);
|
|
Result.updated = true;
|
|
if (this.autoRestart) {
|
|
Log.info("updatenotification: Update done");
|
|
setTimeout(() => this.restart(), 3000);
|
|
} else {
|
|
Log.info("updatenotification: Update done, don't forget to restart MagicMirror!");
|
|
Result.needRestart = true;
|
|
}
|
|
}
|
|
resolve(Result);
|
|
});
|
|
});
|
|
}
|
|
|
|
// restart rules (pm2 or node --run start)
|
|
restart () {
|
|
if (this.usePM2) this.pm2Restart();
|
|
else this.nodeRestart();
|
|
}
|
|
|
|
// restart MagicMiror with "pm2": use PM2Id for restart it
|
|
pm2Restart () {
|
|
Log.info("updatenotification: PM2 will restarting MagicMirror...");
|
|
const pm2 = require("pm2");
|
|
pm2.restart(this.PM2Id, (err, proc) => {
|
|
if (err) {
|
|
Log.error("updatenotification:[PM2] restart Error", err);
|
|
}
|
|
});
|
|
}
|
|
|
|
// restart MagicMiror with "node --run start"
|
|
nodeRestart () {
|
|
Log.info("updatenotification: Restarting MagicMirror...");
|
|
const out = process.stdout;
|
|
const err = process.stderr;
|
|
const subprocess = Spawn("node --run start", { cwd: this.root_path, shell: true, detached: true, stdio: ["ignore", out, err] });
|
|
subprocess.unref(); // detach the newly launched process from the master process
|
|
process.exit();
|
|
}
|
|
|
|
// Check using pm2
|
|
check_PM2_Process () {
|
|
Log.info("updatenotification: Checking PM2 using...");
|
|
return new Promise((resolve) => {
|
|
if (fs.existsSync("/.dockerenv")) {
|
|
Log.info("updatenotification: Running in docker container, not using PM2 ...");
|
|
resolve(false);
|
|
return;
|
|
}
|
|
|
|
if (process.env.unique_id === undefined) {
|
|
Log.info("updatenotification: [PM2] You are not using pm2");
|
|
resolve(false);
|
|
return;
|
|
}
|
|
|
|
Log.debug(`updatenotification: [PM2] Search for pm2 id: ${process.env.pm_id} -- name: ${process.env.name} -- unique_id: ${process.env.unique_id}`);
|
|
|
|
const pm2 = require("pm2");
|
|
pm2.connect((err) => {
|
|
if (err) {
|
|
Log.error("updatenotification: [PM2]", err);
|
|
resolve(false);
|
|
return;
|
|
}
|
|
pm2.list((err, list) => {
|
|
if (err) {
|
|
Log.error("updatenotification: [PM2] Can't get process List!");
|
|
resolve(false);
|
|
return;
|
|
}
|
|
list.forEach((pm) => {
|
|
Log.debug(`updatenotification: [PM2] found pm2 process id: ${pm.pm_id} -- name: ${pm.name} -- unique_id: ${pm.pm2_env.unique_id}`);
|
|
if (pm.pm2_env.status === "online" && process.env.name === pm.name && +process.env.pm_id === +pm.pm_id && process.env.unique_id === pm.pm2_env.unique_id) {
|
|
this.PM2Id = pm.pm_id;
|
|
this.usePM2 = true;
|
|
Log.info(`updatenotification: [PM2] You are using pm2 with id: ${this.PM2Id} (${pm.name})`);
|
|
resolve(true);
|
|
} else {
|
|
Log.debug(`updatenotification: [PM2] pm2 process id: ${pm.pm_id} don't match...`);
|
|
}
|
|
});
|
|
pm2.disconnect();
|
|
if (!this.usePM2) {
|
|
Log.info("updatenotification: [PM2] You are not using pm2");
|
|
resolve(false);
|
|
}
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
// 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;
|