diff --git a/msa/js-executor/.gitignore b/msa/js-executor/.gitignore
new file mode 100644
index 0000000000..c5cfd3d322
--- /dev/null
+++ b/msa/js-executor/.gitignore
@@ -0,0 +1,31 @@
+*.toDelete
+output/**
+*.class
+*~
+*.iml
+*/.idea/**
+.idea/**
+.idea
+*.log
+*.log.[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]
+*/.classpath
+.classpath
+*/.project
+.project
+.cache/**
+target/
+logs/
+build/
+.settings/
+/bin
+bin/
+**/dependency-reduced-pom.xml
+pom.xml.versionsBackup
+.DS_Store
+**/.gradle
+**/local.properties
+**/build
+**/target
+**/.env
+node_modules
+package-lock.json
diff --git a/msa/js-executor/api/jsMessageConsumer.js b/msa/js-executor/api/jsMessageConsumer.js
new file mode 100644
index 0000000000..43d3c781e4
--- /dev/null
+++ b/msa/js-executor/api/jsMessageConsumer.js
@@ -0,0 +1,25 @@
+/*
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * 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.
+ */
+
+'use strict';
+
+var logger = require('../config/logger')('JsMessageConsumer');
+
+exports.onJsInvokeMessage = function(message, producer) {
+
+ logger.info('Received message: %s', message);
+
+}
diff --git a/msa/js-executor/build.gradle b/msa/js-executor/build.gradle
new file mode 100644
index 0000000000..f29dbeca79
--- /dev/null
+++ b/msa/js-executor/build.gradle
@@ -0,0 +1,120 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * 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.
+ */
+import org.apache.tools.ant.filters.ReplaceTokens
+
+buildscript {
+ ext {
+ osPackageVersion = "3.8.0"
+ }
+ repositories {
+ jcenter()
+ }
+ dependencies {
+ classpath("com.netflix.nebula:gradle-ospackage-plugin:${osPackageVersion}")
+ }
+}
+
+apply plugin: "nebula.ospackage"
+
+buildDir = projectBuildDir
+version = projectVersion
+distsDirName = "./"
+
+// OS Package plugin configuration
+ospackage {
+ packageName = pkgName
+ version = "${project.version}"
+ release = 1
+ os = LINUX
+ type = BINARY
+
+ into pkgInstallFolder
+
+ user pkgUser
+ permissionGroup pkgUser
+
+ // Copy the executable file
+ from("target/package/linux/bin/${pkgName}") {
+ fileMode 0500
+ into "bin"
+ }
+
+ // Copy the init file
+ from("target/package/linux/init/${pkgName}") {
+ fileMode 0500
+ into "init"
+ }
+
+ // Copy the config files
+ from("target/package/linux/conf") {
+ fileType CONFIG | NOREPLACE
+ fileMode 0754
+ into "conf"
+ }
+
+}
+
+// Configure our RPM build task
+buildRpm {
+
+ arch = X86_64
+
+ version = projectVersion.replace('-', '')
+ archiveName = "${pkgName}.rpm"
+
+ preInstall file("${buildDir}/control/rpm/preinst")
+ postInstall file("${buildDir}/control/rpm/postinst")
+ preUninstall file("${buildDir}/control/rpm/prerm")
+ postUninstall file("${buildDir}/control/rpm/postrm")
+
+ user pkgUser
+ permissionGroup pkgUser
+
+ // Copy the system unit files
+ from("${buildDir}/control/${pkgName}.service") {
+ addParentDirs = false
+ fileMode 0644
+ into "/usr/lib/systemd/system"
+ }
+
+ directory(pkgLogFolder, 0755)
+ link("/etc/${pkgName}/conf", "${pkgInstallFolder}/conf")
+}
+
+// Same as the buildRpm task
+buildDeb {
+
+ arch = "amd64"
+
+ archiveName = "${pkgName}.deb"
+
+ configurationFile("${pkgInstallFolder}/conf/${pkgName}.conf")
+ configurationFile("${pkgInstallFolder}/conf/custom-environment-variables.yml")
+ configurationFile("${pkgInstallFolder}/conf/default.yml")
+ configurationFile("${pkgInstallFolder}/conf/logger.js")
+
+ preInstall file("${buildDir}/control/deb/preinst")
+ postInstall file("${buildDir}/control/deb/postinst")
+ preUninstall file("${buildDir}/control/deb/prerm")
+ postUninstall file("${buildDir}/control/deb/postrm")
+
+ user pkgUser
+ permissionGroup pkgUser
+
+ directory(pkgLogFolder, 0755)
+ link("/etc/init.d/${pkgName}", "${pkgInstallFolder}/init/${pkgName}")
+ link("/etc/${pkgName}/conf", "${pkgInstallFolder}/conf")
+}
diff --git a/msa/js-executor/config/custom-environment-variables.yml b/msa/js-executor/config/custom-environment-variables.yml
new file mode 100644
index 0000000000..d63a50461b
--- /dev/null
+++ b/msa/js-executor/config/custom-environment-variables.yml
@@ -0,0 +1,25 @@
+#
+# Copyright © 2016-2018 The Thingsboard Authors
+#
+# 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.
+#
+
+kafka:
+ request_topic: "REMOTE_JS_EVAL_REQUEST_TOPIC"
+ bootstrap:
+ # Kafka Bootstrap Servers
+ servers: "TB_KAFKA_SERVERS"
+logger:
+ level: "LOGGER_LEVEL"
+ path: "LOG_FOLDER"
+ filename: "LOGGER_FILENAME"
diff --git a/msa/js-executor/config/default.yml b/msa/js-executor/config/default.yml
new file mode 100644
index 0000000000..5933780bdf
--- /dev/null
+++ b/msa/js-executor/config/default.yml
@@ -0,0 +1,26 @@
+#
+# Copyright © 2016-2018 The Thingsboard Authors
+#
+# 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.
+#
+
+kafka:
+ request_topic: "js.eval.requests"
+ bootstrap:
+ # Kafka Bootstrap Servers
+ servers: "localhost:9092"
+
+logger:
+ level: "info"
+ path: "logs"
+ filename: "tb-js-executor-%DATE%.log"
diff --git a/msa/js-executor/config/logger.js b/msa/js-executor/config/logger.js
new file mode 100644
index 0000000000..86df82777d
--- /dev/null
+++ b/msa/js-executor/config/logger.js
@@ -0,0 +1,59 @@
+/*
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * 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.
+ */
+var config = require('config'),
+ path = require('path'),
+ DailyRotateFile = require('winston-daily-rotate-file');
+
+const { createLogger, format, transports } = require('winston');
+const { combine, timestamp, label, printf, splat } = format;
+
+var loggerTransports = [];
+
+if (process.env.NODE_ENV !== 'production') {
+ loggerTransports.push(new transports.Console({
+ handleExceptions: true
+ }));
+} else {
+ var filename = path.join(config.get('logger.path'), config.get('logger.filename'));
+ var transport = new (DailyRotateFile)({
+ filename: filename,
+ datePattern: 'YYYY-MM-DD-HH',
+ zippedArchive: true,
+ maxSize: '20m',
+ maxFiles: '14d',
+ handleExceptions: true
+ });
+ loggerTransports.push(transport);
+}
+
+const tbFormat = printf(info => {
+ return `${info.timestamp} [${info.label}] ${info.level.toUpperCase()}: ${info.message}`;
+});
+
+function _logger(moduleLabel) {
+ return createLogger({
+ level: config.get('logger.level'),
+ format:combine(
+ splat(),
+ label({ label: moduleLabel }),
+ timestamp({format: 'YYYY-MM-DD HH:mm:ss,SSS'}),
+ tbFormat
+ ),
+ transports: loggerTransports
+ });
+}
+
+module.exports = _logger;
\ No newline at end of file
diff --git a/msa/js-executor/config/tb-js-executor.conf b/msa/js-executor/config/tb-js-executor.conf
new file mode 100644
index 0000000000..71f829d270
--- /dev/null
+++ b/msa/js-executor/config/tb-js-executor.conf
@@ -0,0 +1,19 @@
+#
+# Copyright © 2016-2018 The Thingsboard Authors
+#
+# 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.
+#
+
+export NODE_CONFIG_DIR=${pkg.installFolder}/conf
+export LOG_FOLDER=${pkg.logFolder}
+export NODE_ENV=production
diff --git a/msa/js-executor/install.js b/msa/js-executor/install.js
new file mode 100644
index 0000000000..a6bf8d8ff7
--- /dev/null
+++ b/msa/js-executor/install.js
@@ -0,0 +1,42 @@
+/*
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * 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.
+ */
+const fs = require('fs');
+const fse = require('fs-extra');
+const path = require('path');
+
+let _projectRoot = null;
+
+
+(async() => {
+ await fse.move(path.join(projectRoot(), 'target', 'thingsboard-js-executor-linux'),
+ path.join(targetPackageDir('linux'), 'bin', 'tb-js-executor'),
+ {overwrite: true});
+ await fse.move(path.join(projectRoot(), 'target', 'thingsboard-js-executor-win.exe'),
+ path.join(targetPackageDir('windows'), 'bin', 'tb-js-executor.exe'),
+ {overwrite: true});
+})();
+
+
+function projectRoot() {
+ if (!_projectRoot) {
+ _projectRoot = __dirname;
+ }
+ return _projectRoot;
+}
+
+function targetPackageDir(platform) {
+ return path.join(projectRoot(), 'target', 'package', platform);
+}
diff --git a/msa/js-executor/package.json b/msa/js-executor/package.json
new file mode 100644
index 0000000000..870986a24f
--- /dev/null
+++ b/msa/js-executor/package.json
@@ -0,0 +1,35 @@
+{
+ "name": "thingsboard-js-executor",
+ "private": true,
+ "version": "2.2.0",
+ "description": "ThingsBoard JavaScript Executor Microservice",
+ "main": "server.js",
+ "bin": "server.js",
+ "scripts": {
+ "install": "pkg -t node8-linux-x64,node8-win-x64 --out-path ./target . && node install.js",
+ "test": "echo \"Error: no test specified\" && exit 1",
+ "start": "nodemon server.js",
+ "start-prod": "NODE_ENV=production nodemon server.js"
+ },
+ "dependencies": {
+ "config": "^1.30.0",
+ "js-yaml": "^3.12.0",
+ "kafka-node": "^3.0.1",
+ "winston": "^3.0.0",
+ "winston-daily-rotate-file": "^3.2.1"
+ },
+ "engine": "node >= 5.9.0",
+ "nyc": {
+ "exclude": [
+ "test",
+ "__tests__",
+ "node_modules",
+ "target"
+ ]
+ },
+ "devDependencies": {
+ "fs-extra": "^6.0.1",
+ "nodemon": "^1.17.5",
+ "pkg": "^4.3.3"
+ }
+}
diff --git a/msa/js-executor/pom.xml b/msa/js-executor/pom.xml
new file mode 100644
index 0000000000..2dd03a4a8b
--- /dev/null
+++ b/msa/js-executor/pom.xml
@@ -0,0 +1,310 @@
+
+
+ 4.0.0
+
+ org.thingsboard
+ 2.2.0-SNAPSHOT
+ msa
+
+ org.thingsboard.msa
+ js-executor
+ pom
+
+ ThingsBoard JavaScript Executor Microservice
+ https://thingsboard.io
+ Service executing JavaScript functions in sandboxed environment
+
+
+ UTF-8
+ ${basedir}/../..
+ tb-js-executor
+ thingsboard
+ /var/log/${pkg.name}
+ /usr/share/${pkg.name}
+ ${project.build.directory}/package/linux
+ ${project.build.directory}/package/windows
+
+
+
+
+ com.sun.winsw
+ winsw
+ bin
+ exe
+ provided
+
+
+
+
+
+
+ com.github.eirslett
+ frontend-maven-plugin
+ 1.0
+
+ target
+ ${basedir}
+
+
+
+ install node and npm
+
+ install-node-and-npm
+
+
+ v8.11.3
+ 5.6.0
+
+
+
+ npm install
+
+ npm
+
+
+ install
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-winsw-service
+ package
+
+ copy
+
+
+
+
+ com.sun.winsw
+ winsw
+ bin
+ exe
+ service.exe
+
+
+ ${pkg.win.dist}
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+
+
+ copy-linux-conf
+ process-resources
+
+ copy-resources
+
+
+ ${pkg.linux.dist}/conf
+
+
+ config
+ true
+
+
+
+ src/main/filters/unix.properties
+
+
+
+
+ copy-linux-init
+ process-resources
+
+ copy-resources
+
+
+ ${pkg.linux.dist}/init
+
+
+ src/main/scripts/init
+ true
+
+
+
+ src/main/filters/unix.properties
+
+
+
+
+ copy-win-conf
+ process-resources
+
+ copy-resources
+
+
+ ${pkg.win.dist}/conf
+
+
+ config
+
+ tb-js-executor.conf
+
+ true
+
+
+
+ src/main/filters/windows.properties
+
+
+
+
+ copy-control
+ process-resources
+
+ copy-resources
+
+
+ ${project.build.directory}/control
+
+
+ src/main/scripts/control
+ true
+
+
+
+ src/main/filters/unix.properties
+
+
+
+
+ copy-windows-control
+ process-resources
+
+ copy-resources
+
+
+ ${pkg.win.dist}
+
+
+ src/main/scripts/windows
+ true
+
+
+
+ src/main/filters/windows.properties
+
+
+
+
+
+
+ org.fortasoft
+ gradle-maven-plugin
+
+
+ build
+ buildDeb
+ buildRpm
+
+
+ -PprojectBuildDir=${project.build.directory}
+ -PprojectVersion=${project.version}
+ -PpkgName=${pkg.name}
+ -PpkgUser=${pkg.user}
+ -PpkgInstallFolder=${pkg.installFolder}
+ -PpkgLogFolder=${pkg.unixLogFolder}
+
+
+
+
+ package
+
+ invoke
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+ 3.0.0
+
+ ${pkg.name}
+
+ src/main/assembly/windows.xml
+
+
+
+
+ assembly
+ package
+
+ single
+
+
+
+
+
+
+
+
+ npm-start
+
+
+ npm-start
+
+
+
+
+
+ com.github.eirslett
+ frontend-maven-plugin
+ 1.0
+
+ target
+ ${basedir}
+
+
+
+ npm start
+
+ npm
+
+
+
+ start
+
+
+
+
+
+
+
+
+
+
+ jenkins
+ Jenkins Repository
+ http://repo.jenkins-ci.org/releases
+
+ false
+
+
+
+
diff --git a/msa/js-executor/server.js b/msa/js-executor/server.js
new file mode 100644
index 0000000000..2a3b24f30e
--- /dev/null
+++ b/msa/js-executor/server.js
@@ -0,0 +1,83 @@
+/*
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * 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.
+ */
+var config = require('config'),
+ kafka = require('kafka-node'),
+ Consumer = kafka.Consumer,
+ Producer = kafka.Producer,
+ JsMessageConsumer = require('./api/jsMessageConsumer');
+
+var logger = require('./config/logger')('main');
+
+var kafkaBootstrapServers = config.get('kafka.bootstrap.servers');
+var kafkaRequestTopic = config.get('kafka.request_topic');
+
+logger.info('Kafka Bootstrap Servers: %s', kafkaBootstrapServers);
+logger.info('Kafka Requests Topic: %s', kafkaRequestTopic);
+
+var kafkaClient;
+
+(async() => {
+ try {
+ logger.info('Starting ThingsBoard JavaScript Executor Microservice...');
+
+ kafkaClient = new kafka.KafkaClient({kafkaHost: kafkaBootstrapServers});
+
+ var consumer = new Consumer(
+ kafkaClient,
+ [
+ { topic: kafkaRequestTopic, partition: 0 }
+ ],
+ {
+ autoCommit: true
+ }
+ );
+
+ var producer = new Producer(kafkaClient);
+ producer.on('error', (err) => {
+ logger.error('Unexpected kafka producer error');
+ logger.error(err);
+ });
+
+ producer.on('ready', () => {
+ consumer.on('message', (message) => {
+ JsMessageConsumer.onJsInvokeMessage(message, producer);
+ });
+ });
+
+ logger.info('Started ThingsBoard JavaScript Executor Microservice.');
+ } catch (e) {
+ logger.error('Failed to start ThingsBoard JavaScript Executor Microservice: %s', e.message);
+ logger.error(e);
+ exit(-1);
+ }
+})();
+
+process.on('exit', function () {
+ exit(0);
+});
+
+function exit(status) {
+ logger.info('Exiting with status: %d ...', status);
+ if (kafkaClient) {
+ logger.info('Stopping Kafka Client...');
+ kafkaClient.close(() => {
+ logger.info('Kafka Client stopped.');
+ process.exit(status);
+ });
+ } else {
+ process.exit(status);
+ }
+}
\ No newline at end of file
diff --git a/msa/js-executor/src/main/assembly/windows.xml b/msa/js-executor/src/main/assembly/windows.xml
new file mode 100644
index 0000000000..7d137157b4
--- /dev/null
+++ b/msa/js-executor/src/main/assembly/windows.xml
@@ -0,0 +1,71 @@
+
+
+ windows
+
+
+ zip
+
+
+
+
+
+ ${pkg.win.dist}
+ logs
+
+ */**
+
+
+
+ ${pkg.win.dist}/conf
+ conf
+ windows
+
+
+
+
+
+ ${pkg.win.dist}/bin/${pkg.name}.exe
+ bin
+ ${pkg.name}.exe
+
+
+ ${pkg.win.dist}/service.exe
+
+ ${pkg.name}.exe
+
+
+ ${pkg.win.dist}/service.xml
+
+ ${pkg.name}.xml
+ windows
+
+
+ ${pkg.win.dist}/install.bat
+
+ windows
+
+
+ ${pkg.win.dist}/uninstall.bat
+
+ windows
+
+
+
diff --git a/msa/js-executor/src/main/filters/unix.properties b/msa/js-executor/src/main/filters/unix.properties
new file mode 100644
index 0000000000..8967278673
--- /dev/null
+++ b/msa/js-executor/src/main/filters/unix.properties
@@ -0,0 +1 @@
+pkg.logFolder=${pkg.unixLogFolder}
\ No newline at end of file
diff --git a/msa/js-executor/src/main/filters/windows.properties b/msa/js-executor/src/main/filters/windows.properties
new file mode 100644
index 0000000000..a6e48d91ba
--- /dev/null
+++ b/msa/js-executor/src/main/filters/windows.properties
@@ -0,0 +1,2 @@
+pkg.logFolder=${BASE}\\logs
+pkg.winWrapperLogFolder=%BASE%\\logs
diff --git a/msa/js-executor/src/main/scripts/control/deb/postinst b/msa/js-executor/src/main/scripts/control/deb/postinst
new file mode 100644
index 0000000000..0767d3f2c7
--- /dev/null
+++ b/msa/js-executor/src/main/scripts/control/deb/postinst
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+chown -R ${pkg.user}: ${pkg.logFolder}
+chown -R ${pkg.user}: ${pkg.installFolder}
+update-rc.d ${pkg.name} defaults
+
diff --git a/msa/js-executor/src/main/scripts/control/deb/postrm b/msa/js-executor/src/main/scripts/control/deb/postrm
new file mode 100644
index 0000000000..61865803c3
--- /dev/null
+++ b/msa/js-executor/src/main/scripts/control/deb/postrm
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+update-rc.d -f ${pkg.name} remove
diff --git a/msa/js-executor/src/main/scripts/control/deb/preinst b/msa/js-executor/src/main/scripts/control/deb/preinst
new file mode 100644
index 0000000000..d2ebea46d7
--- /dev/null
+++ b/msa/js-executor/src/main/scripts/control/deb/preinst
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+if ! getent group ${pkg.user} >/dev/null; then
+ addgroup --system ${pkg.user}
+fi
+
+if ! getent passwd ${pkg.user} >/dev/null; then
+ adduser --quiet \
+ --system \
+ --ingroup ${pkg.user} \
+ --quiet \
+ --disabled-login \
+ --disabled-password \
+ --home ${pkg.installFolder} \
+ --no-create-home \
+ -gecos "Thingsboard application" \
+ ${pkg.user}
+fi
diff --git a/msa/js-executor/src/main/scripts/control/deb/prerm b/msa/js-executor/src/main/scripts/control/deb/prerm
new file mode 100644
index 0000000000..898d3efd5c
--- /dev/null
+++ b/msa/js-executor/src/main/scripts/control/deb/prerm
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+if [ -e /var/run/${pkg.name}/${pkg.name}.pid ]; then
+ service ${pkg.name} stop
+fi
diff --git a/msa/js-executor/src/main/scripts/control/rpm/postinst b/msa/js-executor/src/main/scripts/control/rpm/postinst
new file mode 100644
index 0000000000..d8021e2dd9
--- /dev/null
+++ b/msa/js-executor/src/main/scripts/control/rpm/postinst
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+chown -R ${pkg.user}: ${pkg.logFolder}
+chown -R ${pkg.user}: ${pkg.installFolder}
+
+if [ $1 -eq 1 ] ; then
+ # Initial installation
+ systemctl --no-reload enable ${pkg.name}.service >/dev/null 2>&1 || :
+fi
diff --git a/msa/js-executor/src/main/scripts/control/rpm/postrm b/msa/js-executor/src/main/scripts/control/rpm/postrm
new file mode 100644
index 0000000000..8e1f8a2048
--- /dev/null
+++ b/msa/js-executor/src/main/scripts/control/rpm/postrm
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+if [ $1 -ge 1 ] ; then
+ # Package upgrade, not uninstall
+ systemctl try-restart ${pkg.name}.service >/dev/null 2>&1 || :
+fi
diff --git a/msa/js-executor/src/main/scripts/control/rpm/preinst b/msa/js-executor/src/main/scripts/control/rpm/preinst
new file mode 100644
index 0000000000..db6306e4ac
--- /dev/null
+++ b/msa/js-executor/src/main/scripts/control/rpm/preinst
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+getent group ${pkg.user} >/dev/null || groupadd -r ${pkg.user}
+getent passwd ${pkg.user} >/dev/null || \
+useradd -d ${pkg.installFolder} -g ${pkg.user} -M -r ${pkg.user} -s /sbin/nologin \
+-c "Thingsboard application"
diff --git a/msa/js-executor/src/main/scripts/control/rpm/prerm b/msa/js-executor/src/main/scripts/control/rpm/prerm
new file mode 100644
index 0000000000..accb487b8e
--- /dev/null
+++ b/msa/js-executor/src/main/scripts/control/rpm/prerm
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+if [ $1 -eq 0 ] ; then
+ # Package removal, not upgrade
+ systemctl --no-reload disable --now ${pkg.name}.service > /dev/null 2>&1 || :
+fi
diff --git a/msa/js-executor/src/main/scripts/control/tb-js-executor.service b/msa/js-executor/src/main/scripts/control/tb-js-executor.service
new file mode 100644
index 0000000000..f542dd0f92
--- /dev/null
+++ b/msa/js-executor/src/main/scripts/control/tb-js-executor.service
@@ -0,0 +1,11 @@
+[Unit]
+Description=${pkg.name}
+After=syslog.target
+
+[Service]
+User=${pkg.user}
+ExecStart=${pkg.installFolder}/init/${pkg.name}
+SuccessExitStatus=143
+
+[Install]
+WantedBy=multi-user.target
diff --git a/msa/js-executor/src/main/scripts/init/tb-js-executor b/msa/js-executor/src/main/scripts/init/tb-js-executor
new file mode 100644
index 0000000000..6e8fa52cd3
--- /dev/null
+++ b/msa/js-executor/src/main/scripts/init/tb-js-executor
@@ -0,0 +1,233 @@
+#!/bin/bash
+#
+
+
+### BEGIN INIT INFO
+# Provides: tb-js-executor
+# Required-Start: $remote_fs $syslog $network
+# Required-Stop: $remote_fs $syslog $network
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: ${project.name}
+# Description: ${project.description}
+# chkconfig: 2345 99 01
+### END INIT INFO
+
+[[ -n "$DEBUG" ]] && set -x
+
+# Initialize variables that cannot be provided by a .conf file
+WORKING_DIR="$(pwd)"
+# shellcheck disable=SC2153
+
+mainfile=${pkg.installFolder}/bin/${pkg.name}
+configfile=${pkg.name}.conf
+
+# Follow symlinks to find the real script and detect init.d script
+cd "$(dirname "$0")" || exit 1
+[[ -z "$initfile" ]] && initfile=$(pwd)/$(basename "$0")
+while [[ -L "$initfile" ]]; do
+ [[ "$initfile" =~ init\.d ]] && init_script=$(basename "$initfile")
+ initfile=$(readlink "$initfile")
+ cd "$(dirname "$initfile")" || exit 1
+ initfile=$(pwd)/$(basename "$initfile")
+done
+initfolder="$( (cd "$(dirname "initfile")" && pwd -P) )"
+cd "$WORKING_DIR" || exit 1
+
+# Initialize CONF_FOLDER location
+[[ -z "$CONF_FOLDER" ]] && CONF_FOLDER="${pkg.installFolder}/conf"
+
+# shellcheck source=/dev/null
+[[ -r "${CONF_FOLDER}/${configfile}" ]] && source "${CONF_FOLDER}/${configfile}"
+
+# Initialize PID/LOG locations if they weren't provided by the config file
+[[ -z "$PID_FOLDER" ]] && PID_FOLDER="/var/run"
+[[ -z "$LOG_FOLDER" ]] && LOG_FOLDER="${pkg.unixLogFolder}"
+! [[ "$PID_FOLDER" == /* ]] && PID_FOLDER="$(dirname "$mainfile")"/"$PID_FOLDER"
+! [[ "$LOG_FOLDER" == /* ]] && LOG_FOLDER="$(dirname "$mainfile")"/"$LOG_FOLDER"
+! [[ -x "$PID_FOLDER" ]] && PID_FOLDER="/tmp"
+! [[ -x "$LOG_FOLDER" ]] && LOG_FOLDER="/tmp"
+
+# Set up defaults
+[[ -z "$MODE" ]] && MODE="auto" # modes are "auto", "service" or "run"
+[[ -z "$USE_START_STOP_DAEMON" ]] && USE_START_STOP_DAEMON="true"
+
+# Create an identity for log/pid files
+if [[ -z "$identity" ]]; then
+ if [[ -n "$init_script" ]]; then
+ identity="${init_script}"
+ else
+ identity=$(basename "${initfile%.*}")_${initfolder//\//}
+ fi
+fi
+
+# Initialize log file name if not provided by the config file
+[[ -z "$LOG_FILENAME" ]] && LOG_FILENAME="${identity}.log"
+
+# ANSI Colors
+echoRed() { echo $'\e[0;31m'"$1"$'\e[0m'; }
+echoGreen() { echo $'\e[0;32m'"$1"$'\e[0m'; }
+echoYellow() { echo $'\e[0;33m'"$1"$'\e[0m'; }
+
+# Utility functions
+checkPermissions() {
+ touch "$pid_file" &> /dev/null || { echoRed "Operation not permitted (cannot access pid file)"; return 4; }
+ touch "$log_file" &> /dev/null || { echoRed "Operation not permitted (cannot access log file)"; return 4; }
+}
+
+isRunning() {
+ ps -p "$1" &> /dev/null
+}
+
+await_file() {
+ end=$(date +%s)
+ let "end+=10"
+ while [[ ! -s "$1" ]]
+ do
+ now=$(date +%s)
+ if [[ $now -ge $end ]]; then
+ break
+ fi
+ sleep 1
+ done
+}
+
+# Determine the script mode
+action="run"
+if [[ "$MODE" == "auto" && -n "$init_script" ]] || [[ "$MODE" == "service" ]]; then
+ action="$1"
+ shift
+fi
+
+# Build the pid and log filenames
+if [[ "$identity" == "$init_script" ]] || [[ "$identity" == "$APP_NAME" ]]; then
+ PID_FOLDER="$PID_FOLDER/${identity}"
+ pid_subfolder=$PID_FOLDER
+fi
+pid_file="$PID_FOLDER/${identity}.pid"
+log_file="$LOG_FOLDER/$LOG_FILENAME"
+
+# Determine the user to run as if we are root
+# shellcheck disable=SC2012
+[[ $(id -u) == "0" ]] && run_user=$(ls -ld "$mainfile" | awk '{print $3}')
+
+arguments=($RUN_ARGS "$@")
+
+# Action functions
+start() {
+ if [[ -f "$pid_file" ]]; then
+ pid=$(cat "$pid_file")
+ isRunning "$pid" && { echoYellow "Already running [$pid]"; return 0; }
+ fi
+ do_start "$@"
+}
+
+do_start() {
+ working_dir=$(dirname "$mainfile")
+ pushd "$working_dir" > /dev/null
+ mkdir -p "$PID_FOLDER" &> /dev/null
+ if [[ -n "$run_user" ]]; then
+ checkPermissions || return $?
+ if [[ -z "$pid_subfolder" ]]; then
+ chown "$run_user" "$pid_subfolder"
+ fi
+ chown "$run_user" "$pid_file"
+ chown "$run_user" "$log_file"
+ if [ $USE_START_STOP_DAEMON = true ] && type start-stop-daemon > /dev/null 2>&1; then
+ start-stop-daemon --start --quiet \
+ --chuid "$run_user" \
+ --name "$identity" \
+ --make-pidfile --pidfile "$pid_file" \
+ --background --no-close \
+ --startas "$mainfile" \
+ --chdir "$working_dir" \
+ -- "${arguments[@]}" \
+ >> "$log_file" 2>&1
+ await_file "$pid_file"
+ else
+ su -s /bin/sh -c "$mainfile $(printf "\"%s\" " "${arguments[@]}") >> \"$log_file\" 2>&1 & echo \$!" "$run_user" > "$pid_file"
+ fi
+ pid=$(cat "$pid_file")
+ else
+ checkPermissions || return $?
+ "$mainfile" "${arguments[@]}" >> "$log_file" 2>&1 &
+ pid=$!
+ disown $pid
+ echo "$pid" > "$pid_file"
+ fi
+ [[ -z $pid ]] && { echoRed "Failed to start"; return 1; }
+ echoGreen "Started [$pid]"
+}
+
+stop() {
+ working_dir=$(dirname "$mainfile")
+ pushd "$working_dir" > /dev/null
+ [[ -f $pid_file ]] || { echoYellow "Not running (pidfile not found)"; return 0; }
+ pid=$(cat "$pid_file")
+ isRunning "$pid" || { echoYellow "Not running (process ${pid}). Removing stale pid file."; rm -f "$pid_file"; return 0; }
+ do_stop "$pid" "$pid_file"
+}
+
+do_stop() {
+ kill -2 "$1" &> /dev/null || { echoRed "Unable to kill process $1"; return 1; }
+ for i in $(seq 1 60); do
+ isRunning "$1" || { echoGreen "Stopped [$1]"; rm -f "$2"; return 0; }
+ [[ $i -eq 30 ]] && kill -9 "$1" &> /dev/null
+ sleep 1
+ done
+ echoRed "Unable to kill process $1";
+ return 1;
+}
+
+restart() {
+ stop && start
+}
+
+orce_reload() {
+ working_dir=$(dirname "$mainfile")
+ pushd "$working_dir" > /dev/null
+ [[ -f $pid_file ]] || { echoRed "Not running (pidfile not found)"; return 7; }
+ pid=$(cat "$pid_file")
+ rm -f "$pid_file"
+ isRunning "$pid" || { echoRed "Not running (process ${pid} not found)"; return 7; }
+ do_stop "$pid" "$pid_file"
+ do_start
+}
+
+status() {
+ working_dir=$(dirname "$mainfile")
+ pushd "$working_dir" > /dev/null
+ [[ -f "$pid_file" ]] || { echoRed "Not running"; return 3; }
+ pid=$(cat "$pid_file")
+ isRunning "$pid" || { echoRed "Not running (process ${pid} not found)"; return 1; }
+ echoGreen "Running [$pid]"
+ return 0
+}
+
+run() {
+ pushd "$(dirname "$mainfile")" > /dev/null
+ "$mainfile" "${arguments[@]}"
+ result=$?
+ popd > /dev/null
+ return "$result"
+}
+
+# Call the appropriate action function
+case "$action" in
+start)
+ start "$@"; exit $?;;
+stop)
+ stop "$@"; exit $?;;
+restart)
+ restart "$@"; exit $?;;
+force-reload)
+ force_reload "$@"; exit $?;;
+status)
+ status "$@"; exit $?;;
+run)
+ run "$@"; exit $?;;
+*)
+ echo "Usage: $0 {start|stop|restart|force-reload|status|run}"; exit 1;
+esac
+
+exit 0
diff --git a/msa/js-executor/src/main/scripts/windows/install.bat b/msa/js-executor/src/main/scripts/windows/install.bat
new file mode 100644
index 0000000000..4da55425bb
--- /dev/null
+++ b/msa/js-executor/src/main/scripts/windows/install.bat
@@ -0,0 +1,31 @@
+@REM
+@REM Copyright © 2016-2018 The Thingsboard Authors
+@REM
+@REM Licensed under the Apache License, Version 2.0 (the "License");
+@REM you may not use this file except in compliance with the License.
+@REM You may obtain a copy of the License at
+@REM
+@REM http://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing, software
+@REM distributed under the License is distributed on an "AS IS" BASIS,
+@REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@REM See the License for the specific language governing permissions and
+@REM limitations under the License.
+@REM
+
+@ECHO OFF
+
+setlocal ENABLEEXTENSIONS
+
+@ECHO Installing ${pkg.name} ...
+
+SET BASE=%~dp0
+
+%BASE%${pkg.name}.exe install
+
+@ECHO ${pkg.name} installed successfully!
+
+GOTO END
+
+:END
diff --git a/msa/js-executor/src/main/scripts/windows/service.xml b/msa/js-executor/src/main/scripts/windows/service.xml
new file mode 100644
index 0000000000..bb91111586
--- /dev/null
+++ b/msa/js-executor/src/main/scripts/windows/service.xml
@@ -0,0 +1,29 @@
+
+
+ ${pkg.name}
+ ${project.name}
+ ${project.description}
+ %BASE%\bin
+ ${pkg.winWrapperLogFolder}
+ rotate
+
+
+
+ %BASE%\bin\${pkg.name}.exe
+
diff --git a/msa/js-executor/src/main/scripts/windows/uninstall.bat b/msa/js-executor/src/main/scripts/windows/uninstall.bat
new file mode 100644
index 0000000000..7061d2a052
--- /dev/null
+++ b/msa/js-executor/src/main/scripts/windows/uninstall.bat
@@ -0,0 +1,25 @@
+@REM
+@REM Copyright © 2016-2018 The Thingsboard Authors
+@REM
+@REM Licensed under the Apache License, Version 2.0 (the "License");
+@REM you may not use this file except in compliance with the License.
+@REM You may obtain a copy of the License at
+@REM
+@REM http://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing, software
+@REM distributed under the License is distributed on an "AS IS" BASIS,
+@REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@REM See the License for the specific language governing permissions and
+@REM limitations under the License.
+@REM
+
+@ECHO OFF
+
+@ECHO Stopping ${pkg.name} ...
+net stop ${pkg.name}
+
+@ECHO Uninstalling ${pkg.name} ...
+%~dp0${pkg.name}.exe uninstall
+
+@ECHO DONE.
\ No newline at end of file
diff --git a/msa/pom.xml b/msa/pom.xml
new file mode 100644
index 0000000000..85d45a1f76
--- /dev/null
+++ b/msa/pom.xml
@@ -0,0 +1,41 @@
+
+
+ 4.0.0
+
+ org.thingsboard
+ 2.2.0-SNAPSHOT
+ thingsboard
+
+ org.thingsboard
+ msa
+ pom
+
+ ThingsBoard Microservices
+ https://thingsboard.io
+
+
+ ${basedir}/..
+
+
+
+ js-executor
+
+
+
diff --git a/pom.xml b/pom.xml
index 57603eedd1..e824177ac7 100755
--- a/pom.xml
+++ b/pom.xml
@@ -93,6 +93,7 @@
ui
tools
application
+ msa