2018-12-01 00:01:09 +01:00
|
|
|
/*!
|
2017-01-11 16:24:33 +01:00
|
|
|
* Copyright JS Foundation and other contributors, http://js.foundation
|
2013-09-05 16:02:48 +02:00
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
**/
|
|
|
|
|
2014-03-06 23:32:23 +01:00
|
|
|
var when = require('when');
|
|
|
|
|
2018-08-04 23:23:06 +02:00
|
|
|
var externalAPI = require("./api");
|
|
|
|
|
2013-09-05 16:02:48 +02:00
|
|
|
var redNodes = require("./nodes");
|
2014-09-22 15:33:26 +02:00
|
|
|
var storage = require("./storage");
|
2018-04-18 18:09:31 +02:00
|
|
|
var library = require("./library");
|
2015-11-11 23:11:02 +01:00
|
|
|
var events = require("./events");
|
|
|
|
var settings = require("./settings");
|
2018-10-19 00:49:47 +02:00
|
|
|
var exec = require("./exec");
|
2017-01-09 23:22:49 +01:00
|
|
|
|
|
|
|
var express = require("express");
|
2015-11-11 23:11:02 +01:00
|
|
|
var path = require('path');
|
|
|
|
var fs = require("fs");
|
2016-02-22 18:47:16 +01:00
|
|
|
var os = require("os");
|
2013-09-05 16:02:48 +02:00
|
|
|
|
2018-11-16 11:04:53 +01:00
|
|
|
var redUtil = require("@node-red/util");
|
2018-12-01 00:01:09 +01:00
|
|
|
var log = redUtil.log;
|
|
|
|
var i18n = redUtil.i18n;
|
2018-04-19 12:23:08 +02:00
|
|
|
|
2015-02-04 23:28:17 +01:00
|
|
|
var runtimeMetricInterval = null;
|
|
|
|
|
2016-03-12 01:03:50 +01:00
|
|
|
var started = false;
|
|
|
|
|
2015-11-24 23:38:42 +01:00
|
|
|
var stubbedExpressApp = {
|
|
|
|
get: function() {},
|
|
|
|
post: function() {},
|
|
|
|
put: function() {},
|
2016-05-26 11:38:24 +02:00
|
|
|
delete: function() {}
|
2015-11-24 23:38:42 +01:00
|
|
|
}
|
|
|
|
var adminApi = {
|
|
|
|
auth: {
|
2016-05-26 11:38:24 +02:00
|
|
|
needsPermission: function() {}
|
2015-11-24 23:38:42 +01:00
|
|
|
},
|
|
|
|
adminApp: stubbedExpressApp,
|
|
|
|
server: {}
|
|
|
|
}
|
|
|
|
|
2017-01-09 23:22:49 +01:00
|
|
|
var nodeApp;
|
2018-12-04 16:59:43 +01:00
|
|
|
var adminApp;
|
2018-12-01 00:01:09 +01:00
|
|
|
var server;
|
|
|
|
|
2017-01-09 23:22:49 +01:00
|
|
|
|
2018-12-01 00:01:09 +01:00
|
|
|
/**
|
|
|
|
* Initialise the runtime module.
|
|
|
|
* @param {Object} settings - the runtime settings object
|
|
|
|
* @param {HTTPServer} server - the http server instance for the server to use
|
|
|
|
* @param {AdminAPI} adminApi - an instance of @node-red/editor-api. <B>TODO</B>: This needs to be
|
|
|
|
* better abstracted.
|
|
|
|
* @memberof @node-red/runtime
|
|
|
|
*/
|
2018-12-04 16:59:43 +01:00
|
|
|
function init(userSettings,httpServer,_adminApi,__util) {
|
2018-12-01 00:01:09 +01:00
|
|
|
server = httpServer;
|
2015-11-16 12:31:55 +01:00
|
|
|
userSettings.version = getVersion();
|
2015-11-11 23:11:02 +01:00
|
|
|
settings.init(userSettings);
|
2017-01-09 23:22:49 +01:00
|
|
|
|
|
|
|
nodeApp = express();
|
2018-12-04 16:59:43 +01:00
|
|
|
adminApp = express();
|
2017-01-09 23:22:49 +01:00
|
|
|
|
2015-11-24 23:38:42 +01:00
|
|
|
if (_adminApi) {
|
|
|
|
adminApi = _adminApi;
|
|
|
|
}
|
|
|
|
redNodes.init(runtime);
|
2018-04-18 18:09:31 +02:00
|
|
|
library.init(runtime);
|
2018-08-04 23:23:06 +02:00
|
|
|
externalAPI.init(runtime);
|
2018-10-19 00:49:47 +02:00
|
|
|
exec.init(runtime);
|
2018-12-04 16:59:43 +01:00
|
|
|
if (__util) {
|
|
|
|
log = __util.log;
|
|
|
|
i18n = __util.i18n;
|
|
|
|
} else {
|
|
|
|
log = redUtil.log;
|
|
|
|
i18n = redUtil.i18n;
|
|
|
|
}
|
2015-11-11 23:11:02 +01:00
|
|
|
}
|
2015-02-04 23:28:17 +01:00
|
|
|
|
2015-11-16 12:31:55 +01:00
|
|
|
var version;
|
|
|
|
|
|
|
|
function getVersion() {
|
|
|
|
if (!version) {
|
2018-08-17 23:10:54 +02:00
|
|
|
version = require(path.join(__dirname,"..","package.json")).version;
|
2015-11-16 12:31:55 +01:00
|
|
|
/* istanbul ignore else */
|
|
|
|
try {
|
2018-08-04 23:23:06 +02:00
|
|
|
fs.statSync(path.join(__dirname,"..","..","..","..",".git"));
|
2015-11-16 12:31:55 +01:00
|
|
|
version += "-git";
|
|
|
|
} catch(err) {
|
|
|
|
// No git directory
|
|
|
|
}
|
2015-11-11 23:11:02 +01:00
|
|
|
}
|
2015-11-16 12:31:55 +01:00
|
|
|
return version;
|
2014-11-04 12:34:49 +01:00
|
|
|
}
|
|
|
|
|
2018-12-01 00:01:09 +01:00
|
|
|
/**
|
|
|
|
* Start the runtime.
|
|
|
|
* @return {Promise} - resolves when the runtime is started. This does not mean the
|
|
|
|
* flows will be running as they are started asynchronously.
|
|
|
|
* @memberof @node-red/runtime
|
|
|
|
*/
|
2014-11-04 12:34:49 +01:00
|
|
|
function start() {
|
2018-08-17 23:10:54 +02:00
|
|
|
return i18n.registerMessageCatalog("runtime",path.resolve(path.join(__dirname,"..","locales")),"runtime.json")
|
2016-09-21 11:22:04 +02:00
|
|
|
.then(function() { return storage.init(runtime)})
|
2015-03-22 21:55:38 +01:00
|
|
|
.then(function() { return settings.load(storage)})
|
2015-03-14 00:37:59 +01:00
|
|
|
.then(function() {
|
2015-07-02 14:25:15 +02:00
|
|
|
|
2015-02-04 23:28:17 +01:00
|
|
|
if (log.metric()) {
|
|
|
|
runtimeMetricInterval = setInterval(function() {
|
|
|
|
reportMetrics();
|
2015-03-21 18:42:06 +01:00
|
|
|
}, settings.runtimeMetricInterval||15000);
|
2015-02-04 23:28:17 +01:00
|
|
|
}
|
2016-12-23 12:31:23 +01:00
|
|
|
log.info("\n\n"+log._("runtime.welcome")+"\n===================\n");
|
2014-11-04 12:34:49 +01:00
|
|
|
if (settings.version) {
|
2015-05-08 15:21:01 +02:00
|
|
|
log.info(log._("runtime.version",{component:"Node-RED",version:"v"+settings.version}));
|
2014-08-28 01:35:07 +02:00
|
|
|
}
|
2015-05-08 15:21:01 +02:00
|
|
|
log.info(log._("runtime.version",{component:"Node.js ",version:process.version}));
|
2017-01-15 00:57:39 +01:00
|
|
|
if (settings.UNSUPPORTED_VERSION) {
|
|
|
|
log.error("*****************************************************************");
|
|
|
|
log.error("* "+log._("runtime.unsupported_version",{component:"Node.js",version:process.version,requires: ">=4"})+" *");
|
|
|
|
log.error("*****************************************************************");
|
2017-07-08 18:27:45 +02:00
|
|
|
events.emit("runtime-event",{id:"runtime-unsupported-version",payload:{type:"error",text:"notification.errors.unsupportedVersion"},retain:true});
|
2017-01-15 00:57:39 +01:00
|
|
|
}
|
2016-02-22 18:47:16 +01:00
|
|
|
log.info(os.type()+" "+os.release()+" "+os.arch()+" "+os.endianness());
|
2015-04-08 21:17:24 +02:00
|
|
|
return redNodes.load().then(function() {
|
2015-07-02 14:25:15 +02:00
|
|
|
|
2014-11-04 12:34:49 +01:00
|
|
|
var i;
|
2015-03-30 22:49:20 +02:00
|
|
|
var nodeErrors = redNodes.getNodeList(function(n) { return n.err!=null;});
|
|
|
|
var nodeMissing = redNodes.getNodeList(function(n) { return n.module && n.enabled && !n.loaded && !n.err;});
|
2014-11-04 12:34:49 +01:00
|
|
|
if (nodeErrors.length > 0) {
|
2016-05-26 11:38:24 +02:00
|
|
|
log.warn("------------------------------------------------------");
|
|
|
|
for (i=0;i<nodeErrors.length;i+=1) {
|
2018-01-16 00:20:20 +01:00
|
|
|
if (nodeErrors[i].err.code === "type_already_registered") {
|
|
|
|
log.warn("["+nodeErrors[i].id+"] "+log._("server.type-already-registered",{type:nodeErrors[i].err.details.type,module: nodeErrors[i].err.details.moduleA}));
|
|
|
|
} else {
|
|
|
|
log.warn("["+nodeErrors[i].id+"] "+nodeErrors[i].err);
|
|
|
|
}
|
2014-08-28 01:35:07 +02:00
|
|
|
}
|
2016-05-26 11:38:24 +02:00
|
|
|
log.warn("------------------------------------------------------");
|
2014-09-22 15:33:26 +02:00
|
|
|
}
|
2014-11-04 12:34:49 +01:00
|
|
|
if (nodeMissing.length > 0) {
|
2015-05-08 15:21:01 +02:00
|
|
|
log.warn(log._("server.missing-modules"));
|
2014-11-04 12:34:49 +01:00
|
|
|
var missingModules = {};
|
|
|
|
for (i=0;i<nodeMissing.length;i++) {
|
|
|
|
var missing = nodeMissing[i];
|
2017-01-25 12:07:02 +01:00
|
|
|
missingModules[missing.module] = missingModules[missing.module]||{
|
|
|
|
module:missing.module,
|
|
|
|
version:missing.pending_version||missing.version,
|
|
|
|
types:[]
|
|
|
|
}
|
|
|
|
missingModules[missing.module].types = missingModules[missing.module].types.concat(missing.types);
|
2014-11-04 12:34:49 +01:00
|
|
|
}
|
2017-07-08 18:27:45 +02:00
|
|
|
var moduleList = [];
|
2014-11-04 12:34:49 +01:00
|
|
|
var promises = [];
|
2017-07-08 18:27:45 +02:00
|
|
|
var installingModules = [];
|
2014-11-04 12:34:49 +01:00
|
|
|
for (i in missingModules) {
|
|
|
|
if (missingModules.hasOwnProperty(i)) {
|
2017-01-25 12:07:02 +01:00
|
|
|
log.warn(" - "+i+" ("+missingModules[i].version+"): "+missingModules[i].types.join(", "));
|
2015-02-04 11:27:02 +01:00
|
|
|
if (settings.autoInstallModules && i != "node-red") {
|
2017-07-08 18:27:45 +02:00
|
|
|
installingModules.push({id:i,version:missingModules[i].version});
|
2014-09-22 15:33:26 +02:00
|
|
|
}
|
2014-08-28 01:35:07 +02:00
|
|
|
}
|
|
|
|
}
|
2014-11-04 12:34:49 +01:00
|
|
|
if (!settings.autoInstallModules) {
|
2015-05-08 15:21:01 +02:00
|
|
|
log.info(log._("server.removing-modules"));
|
2014-11-26 17:41:31 +01:00
|
|
|
redNodes.cleanModuleList();
|
2017-07-08 18:27:45 +02:00
|
|
|
} else if (installingModules.length > 0) {
|
|
|
|
reinstallAttempts = 0;
|
|
|
|
reinstallModules(installingModules);
|
2014-11-04 12:34:49 +01:00
|
|
|
}
|
|
|
|
}
|
2015-12-07 00:29:58 +01:00
|
|
|
if (settings.settingsFile) {
|
|
|
|
log.info(log._("runtime.paths.settings",{path:settings.settingsFile}));
|
|
|
|
}
|
2017-07-26 20:45:49 +02:00
|
|
|
if (settings.httpStatic) {
|
|
|
|
log.info(log._("runtime.paths.httpStatic",{path:path.resolve(settings.httpStatic)}));
|
|
|
|
}
|
2018-08-16 15:36:11 +02:00
|
|
|
return redNodes.loadContextsPlugin().then(function () {
|
2018-07-20 16:26:47 +02:00
|
|
|
redNodes.loadFlows().then(redNodes.startFlows).catch(function(err) {});
|
|
|
|
started = true;
|
|
|
|
});
|
2014-11-04 12:34:49 +01:00
|
|
|
});
|
2016-05-26 11:38:24 +02:00
|
|
|
});
|
2014-08-28 01:35:07 +02:00
|
|
|
}
|
2014-11-04 12:34:49 +01:00
|
|
|
|
2017-07-08 18:27:45 +02:00
|
|
|
var reinstallAttempts;
|
|
|
|
var reinstallTimeout;
|
|
|
|
function reinstallModules(moduleList) {
|
|
|
|
var promises = [];
|
|
|
|
var failedModules = [];
|
|
|
|
for (var i=0;i<moduleList.length;i++) {
|
|
|
|
if (settings.autoInstallModules && i != "node-red") {
|
|
|
|
promises.push(redNodes.installModule(moduleList[i].id,moduleList[i].version));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
when.settle(promises).then(function(results) {
|
|
|
|
var reinstallList = [];
|
|
|
|
for (var i=0;i<results.length;i++) {
|
|
|
|
if (results[i].state === 'rejected') {
|
|
|
|
reinstallList.push(moduleList[i]);
|
|
|
|
} else {
|
|
|
|
events.emit("runtime-event",{id:"node/added",retain:false,payload:results[i].value.nodes});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (reinstallList.length > 0) {
|
|
|
|
reinstallAttempts++;
|
|
|
|
// First 5 at 1x timeout, next 5 at 2x, next 5 at 4x, then 8x
|
|
|
|
var timeout = (settings.autoInstallModulesRetry||30000) * Math.pow(2,Math.min(Math.floor(reinstallAttempts/5),3));
|
|
|
|
reinstallTimeout = setTimeout(function() {
|
|
|
|
reinstallModules(reinstallList);
|
|
|
|
},timeout);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2015-02-04 23:28:17 +01:00
|
|
|
function reportMetrics() {
|
|
|
|
var memUsage = process.memoryUsage();
|
2015-03-06 11:17:00 +01:00
|
|
|
|
2015-03-21 18:42:06 +01:00
|
|
|
log.log({
|
|
|
|
level: log.METRIC,
|
|
|
|
event: "runtime.memory.rss",
|
|
|
|
value: memUsage.rss
|
|
|
|
});
|
|
|
|
log.log({
|
|
|
|
level: log.METRIC,
|
|
|
|
event: "runtime.memory.heapTotal",
|
|
|
|
value: memUsage.heapTotal
|
|
|
|
});
|
|
|
|
log.log({
|
|
|
|
level: log.METRIC,
|
|
|
|
event: "runtime.memory.heapUsed",
|
|
|
|
value: memUsage.heapUsed
|
|
|
|
});
|
2015-02-04 23:28:17 +01:00
|
|
|
}
|
|
|
|
|
2018-12-01 00:01:09 +01:00
|
|
|
/**
|
|
|
|
* Stops the runtime.
|
|
|
|
* @return {Promise} - resolves when the runtime is stopped.
|
|
|
|
* @memberof @node-red/runtime
|
|
|
|
*/
|
2013-10-13 20:14:39 +02:00
|
|
|
function stop() {
|
2015-02-04 23:28:17 +01:00
|
|
|
if (runtimeMetricInterval) {
|
|
|
|
clearInterval(runtimeMetricInterval);
|
|
|
|
runtimeMetricInterval = null;
|
|
|
|
}
|
2017-07-08 18:27:45 +02:00
|
|
|
if (reinstallTimeout) {
|
|
|
|
clearTimeout(reinstallTimeout);
|
|
|
|
}
|
2016-03-12 01:03:50 +01:00
|
|
|
started = false;
|
2018-05-30 03:24:27 +02:00
|
|
|
return redNodes.stopFlows().then(function(){
|
|
|
|
return redNodes.closeContextsPlugin();
|
|
|
|
});
|
2013-10-13 20:14:39 +02:00
|
|
|
}
|
|
|
|
|
2018-08-04 23:23:06 +02:00
|
|
|
// This is the internal api
|
|
|
|
var runtime = {
|
2015-11-16 12:31:55 +01:00
|
|
|
version: getVersion,
|
2018-04-19 12:23:08 +02:00
|
|
|
get log() { return log },
|
|
|
|
get i18n() { return i18n },
|
2015-11-11 23:11:02 +01:00
|
|
|
settings: settings,
|
|
|
|
storage: storage,
|
|
|
|
events: events,
|
2015-11-24 23:38:42 +01:00
|
|
|
nodes: redNodes,
|
2018-04-18 18:09:31 +02:00
|
|
|
library: library,
|
2018-10-19 00:49:47 +02:00
|
|
|
exec: exec,
|
2018-08-17 23:10:54 +02:00
|
|
|
util: require("@node-red/util").util,
|
2016-03-12 01:03:50 +01:00
|
|
|
get adminApi() { return adminApi },
|
2018-12-04 16:59:43 +01:00
|
|
|
get adminApp() { return adminApp },
|
2017-01-09 23:22:49 +01:00
|
|
|
get nodeApp() { return nodeApp },
|
2018-12-01 00:01:09 +01:00
|
|
|
get server() { return server },
|
2016-03-12 01:03:50 +01:00
|
|
|
isStarted: function() {
|
|
|
|
return started;
|
|
|
|
}
|
2018-08-04 23:23:06 +02:00
|
|
|
};
|
|
|
|
|
2018-12-01 00:01:09 +01:00
|
|
|
/**
|
|
|
|
* This module provides the core runtime component of Node-RED.
|
|
|
|
* It does *not* include the Node-RED editor. All interaction with
|
|
|
|
* this module is done using the api provided.
|
|
|
|
*
|
|
|
|
* @namespace @node-red/runtime
|
|
|
|
*/
|
2018-08-04 23:23:06 +02:00
|
|
|
module.exports = {
|
|
|
|
init: init,
|
|
|
|
start: start,
|
|
|
|
stop: stop,
|
|
|
|
|
2018-12-01 00:01:09 +01:00
|
|
|
/**
|
|
|
|
* @memberof @node-red/runtime
|
|
|
|
* @mixes @node-red/runtime_comms
|
|
|
|
*/
|
2018-08-04 23:23:06 +02:00
|
|
|
comms: externalAPI.comms,
|
2018-12-01 00:01:09 +01:00
|
|
|
/**
|
|
|
|
* @memberof @node-red/runtime
|
|
|
|
* @mixes @node-red/runtime_flows
|
|
|
|
*/
|
2018-08-04 23:23:06 +02:00
|
|
|
flows: externalAPI.flows,
|
2018-12-01 00:01:09 +01:00
|
|
|
/**
|
|
|
|
* @memberof @node-red/runtime
|
|
|
|
* @mixes @node-red/runtime_library
|
|
|
|
*/
|
2018-08-04 23:23:06 +02:00
|
|
|
library: externalAPI.library,
|
2018-12-01 00:01:09 +01:00
|
|
|
/**
|
|
|
|
* @memberof @node-red/runtime
|
|
|
|
* @mixes @node-red/runtime_nodes
|
|
|
|
*/
|
2018-08-04 23:23:06 +02:00
|
|
|
nodes: externalAPI.nodes,
|
2018-12-01 00:01:09 +01:00
|
|
|
/**
|
|
|
|
* @memberof @node-red/runtime
|
|
|
|
* @mixes @node-red/runtime_settings
|
|
|
|
*/
|
2018-08-04 23:23:06 +02:00
|
|
|
settings: externalAPI.settings,
|
2018-12-01 00:01:09 +01:00
|
|
|
/**
|
|
|
|
* @memberof @node-red/runtime
|
|
|
|
* @mixes @node-red/runtime_projects
|
|
|
|
*/
|
2018-08-04 23:23:06 +02:00
|
|
|
projects: externalAPI.projects,
|
2018-12-01 00:01:09 +01:00
|
|
|
/**
|
|
|
|
* @memberof @node-red/runtime
|
|
|
|
* @mixes @node-red/runtime_context
|
|
|
|
*/
|
2018-08-04 23:23:06 +02:00
|
|
|
context: externalAPI.context,
|
|
|
|
|
2018-12-01 00:01:09 +01:00
|
|
|
/**
|
|
|
|
* Returns whether the runtime is started
|
|
|
|
* @param {Object} opts
|
|
|
|
* @param {User} opts.user - the user calling the api
|
|
|
|
* @return {Promise<Boolean>} - whether the runtime is started
|
|
|
|
* @function
|
|
|
|
* @memberof @node-red/runtime
|
|
|
|
*/
|
2018-08-04 23:23:06 +02:00
|
|
|
isStarted: externalAPI.isStarted,
|
2018-12-01 00:01:09 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns version number of the runtime
|
|
|
|
* @param {Object} opts
|
|
|
|
* @param {User} opts.user - the user calling the api
|
|
|
|
* @return {Promise<String>} - the runtime version number
|
|
|
|
* @function
|
|
|
|
* @memberof @node-red/runtime
|
|
|
|
*/
|
|
|
|
version: externalAPI.version,
|
|
|
|
|
|
|
|
storage: storage,
|
|
|
|
events: events,
|
2018-12-04 16:59:43 +01:00
|
|
|
util: require("@node-red/util").util,
|
2018-12-01 00:01:09 +01:00
|
|
|
get httpNode() { return nodeApp },
|
2018-12-04 16:59:43 +01:00
|
|
|
get httpAdmin() { return adminApp },
|
|
|
|
get server() { return server },
|
|
|
|
|
|
|
|
"_": runtime
|
2015-03-06 11:17:00 +01:00
|
|
|
}
|
2018-12-01 00:01:09 +01:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A user accessing the API
|
|
|
|
* @typedef User
|
|
|
|
* @type {object}
|
|
|
|
*/
|