const Exec = require("child_process").exec; const Spawn = require("child_process").spawn; const commandExists = require("command-exists"); 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 * { * : * }, * { * ... * } * ], * updateTimeout: 2 * 60 * 1000, // max update duration * updateAutorestart: false // autoRestart MM when update done ? * * [main command]: parse(): * 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 = , // name of the module * updateCommand = , // update command (if found) * inProgress = , // an update if in progress for this module * error = , // an error if detected when updating * updated = , // updated successfully * needRestart = // 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; this.PM2 = null; 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: , // if error detected // updated: , // if updated successfully // needRestart: // 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 npm start) restart () { if (this.usePM2) this.pm2Restart(); else this.npmRestart(); } // restart MagicMiror with "pm2" pm2Restart () { Log.info("updatenotification: PM2 will restarting MagicMirror..."); Exec(`pm2 restart ${this.PM2}`, (err, std, sde) => { if (err) { Log.error("updatenotification:[PM2] restart Error", err); } }); } // restart MagicMiror with "npm start" npmRestart () { Log.info("updatenotification: Restarting MagicMirror..."); const out = process.stdout; const err = process.stderr; const subprocess = Spawn("npm start", { cwd: this.root_path, shell: true, detached: true, stdio: ["ignore", out, err] }); subprocess.unref(); process.exit(); } // Check using pm2 check_PM2_Process () { Log.info("updatenotification: Checking PM2 using..."); return new Promise((resolve) => { commandExists("pm2") .then(async () => { var PM2_List = await this.PM2_GetList(); if (!PM2_List) { Log.error("updatenotification: [PM2] Can't get process List!"); this.usePM2 = false; resolve(false); return; } PM2_List.forEach((pm) => { if (pm.pm2_env.version === this.version && pm.pm2_env.status === "online" && pm.pm2_env.PWD.includes(this.root_path)) { this.PM2 = pm.name; this.usePM2 = true; Log.info("updatenotification: You are using pm2 with", this.PM2); resolve(true); } }); if (!this.PM2) { Log.info("updatenotification: You are not using pm2"); this.usePM2 = false; resolve(false); } }) .catch(() => { Log.info("updatenotification: You are not using pm2"); this.usePM2 = false; resolve(false); }); }); } // Get the list of pm2 process PM2_GetList () { return new Promise((resolve) => { Exec("pm2 jlist", (err, std, sde) => { if (err) { resolve(null); return; } try { let result = JSON.parse(std); resolve(result); } catch (e) { Log.error("updatenotification: [PM2] can't GetList!"); Log.debug("updatenotification: [PM2] GetList is not an JSON format", e); resolve(null); } }); }); } // 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)) return null; let command = null; this.updates.forEach((updater) => { if (updater[module]) command = updater[module]; }); return command; } } module.exports = Updater;