forked from MagicMirrorOrg/MagicMirror
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
3rd party modules updater for updatenotification (MagicMirrorOrg#3150)
Added my (modified) updater main core into updatenotification default module Missing: callback display in MM² (i will code it after) new part of configuration added: ``` updates: [ // array of module update commands { // with embed npm script "MMM-Test": "npm run update" }, { // with "complex" process "MMM-OtherSample": "rm -rf package-lock.json && git reset --hard && git pull && npm install" }, { // with git pull && npm install "MMM-OtherSample2": "git pull && npm install" }, { // with a simple git pull "MMM-OtherSample3": "git pull" } ], updateTimeout: 2 * 60 * 1000, // max update duration updateAutorestart: false // autoRestart MM when update done ? ``` @khassel: i need your help I don't use docker, maybe you can help me for this: How can i check if MM² is running inside a docker ? (from MM² main core) Actually, I check if we use pm2 or not. I have to check if docker is used or not too last time you tell me: "you can't use updater with docker", so I want to check and deny any update if docker used --------- Co-authored-by: bugsounet <bugsounet@bugsounet.fr>
- Loading branch information
Showing
10 changed files
with
323 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,224 @@ | ||
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 | ||
* { | ||
* <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; | ||
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: <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 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; | ||
} | ||
let result = JSON.parse(std); | ||
resolve(result); | ||
}); | ||
}); | ||
} | ||
|
||
// 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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.