import * as http from "http"; import { Logger } from "./Logger"; import { WebServer } from "./WebServer"; import { WebSocketServer } from "./WebSocketServer"; import { DatabaseConnection } from "./DatabaseConnection"; import { Crypt } from "./Crypt"; import { Config } from "./Config"; import { amqpwrapper } from "./amqpwrapper"; import { WellknownIds, Role, Rights, User, Base, NoderedUtil } from "@openiap/openflow-api"; import { OAuthProvider } from "./OAuthProvider"; import { Span } from "@opentelemetry/api"; import { QueueClient } from "./QueueClient"; import { Message } from "./Messages/Message"; Logger.configure(false, false); Config.db = new DatabaseConnection(Config.mongodb_url, Config.mongodb_db, true); let amqp: amqpwrapper = null; async function initamqp(parent: Span) { const span: Span = Logger.otel.startSubSpan("initamqp", parent); try { amqp = new amqpwrapper(Config.amqp_url); amqpwrapper.SetInstance(amqp); await amqp.connect(span); } catch (error) { span?.recordException(error); Logger.instanse.error("index", "initamqp", error); return false; } finally { Logger.otel.endSpan(span); } } async function ValidateUserForm(parent: Span) { const span: Span = Logger.otel.startSubSpan("ValidateUserForm", parent); try { var forms = await Config.db.query({ query: { _id: Config.validate_user_form, _type: "form" }, top: 1, collectionname: "forms", jwt: Crypt.rootToken() }, null); if (forms.length == 0) { Logger.instanse.info("index", "ValidateUserForm", "validate_user_form " + Config.validate_user_form + " does not exists!"); Config.validate_user_form = ""; } } catch (error) { span?.recordException(error); Logger.instanse.error("index", "ValidateUserForm", error); return false; } finally { Logger.otel.endSpan(span); } } function doHouseKeeping() { // Message.lastHouseKeeping = new Date(); if (Message.lastHouseKeeping == null) { Message.lastHouseKeeping = new Date(); Message.lastHouseKeeping.setDate(Message.lastHouseKeeping.getDate() - 1); } amqpwrapper.Instance().send("openflow", "", { "command": "housekeeping", "lastrun": (new Date()).toISOString() }, 20000, null, "", 1); var dt = new Date(Message.lastHouseKeeping.toISOString()); var msg2 = new Message(); msg2.jwt = Crypt.rootToken(); var h = dt.getHours(); var skipUpdateUsage: boolean = !(dt.getHours() == 1 || dt.getHours() == 13); msg2._Housekeeping(false, skipUpdateUsage, skipUpdateUsage, null).catch((error) => Logger.instanse.error("index", "doHouseKeeping", error)); // var dt = new Date(new Date().toISOString()); // var msg = new Message(); msg.jwt = Crypt.rootToken(); // var skipUpdateUsage: boolean = !(dt.getHours() == 1 || dt.getHours() == 13); // await msg.Housekeeping(false, skipUpdateUsage, skipUpdateUsage, null); } async function initDatabase(parent: Span): Promise { const span: Span = Logger.otel.startSubSpan("initDatabase", parent); try { Logger.instanse.info("index", "initDatabase", "Begin validating builtin roles"); const jwt: string = Crypt.rootToken(); const admins: Role = await Logger.DBHelper.EnsureRole(jwt, "admins", WellknownIds.admins, span); const users: Role = await Logger.DBHelper.EnsureRole(jwt, "users", WellknownIds.users, span); const root: User = await Logger.DBHelper.EnsureUser(jwt, "root", "root", WellknownIds.root, null, null, span); Base.addRight(root, WellknownIds.admins, "admins", [Rights.full_control]); Base.removeRight(root, WellknownIds.admins, [Rights.delete]); Base.addRight(root, WellknownIds.root, "root", [Rights.full_control]); Base.removeRight(root, WellknownIds.root, [Rights.delete]); await Logger.DBHelper.Save(root, jwt, span); const robot_agent_users: Role = await Logger.DBHelper.EnsureRole(jwt, "robot agent users", WellknownIds.robot_agent_users, span); Base.addRight(robot_agent_users, WellknownIds.admins, "admins", [Rights.full_control]); Base.removeRight(robot_agent_users, WellknownIds.admins, [Rights.delete]); Base.addRight(robot_agent_users, WellknownIds.root, "root", [Rights.full_control]); if (Config.multi_tenant) { Logger.instanse.silly("index", "initDatabase", "[root][users] Running in multi tenant mode, remove " + robot_agent_users.name + " from self"); Base.removeRight(robot_agent_users, robot_agent_users._id, [Rights.full_control]); } else if (Config.update_acl_based_on_groups) { Base.removeRight(robot_agent_users, robot_agent_users._id, [Rights.full_control]); Base.addRight(robot_agent_users, robot_agent_users._id, "robot agent users", [Rights.read]); } await Logger.DBHelper.Save(robot_agent_users, jwt, span); Base.addRight(admins, WellknownIds.admins, "admins", [Rights.full_control]); Base.removeRight(admins, WellknownIds.admins, [Rights.delete]); await Logger.DBHelper.Save(admins, jwt, span); Base.addRight(users, WellknownIds.admins, "admins", [Rights.full_control]); Base.removeRight(users, WellknownIds.admins, [Rights.delete]); users.AddMember(root); if (Config.multi_tenant) { Base.removeRight(users, users._id, [Rights.full_control]); } else { Base.removeRight(users, users._id, [Rights.full_control]); Base.addRight(users, users._id, "users", [Rights.read]); } await Logger.DBHelper.Save(users, jwt, span); const personal_nodered_users: Role = await Logger.DBHelper.EnsureRole(jwt, "personal nodered users", WellknownIds.personal_nodered_users, span); personal_nodered_users.AddMember(admins); Base.addRight(personal_nodered_users, WellknownIds.admins, "admins", [Rights.full_control]); Base.removeRight(personal_nodered_users, WellknownIds.admins, [Rights.delete]); if (Config.multi_tenant) { Logger.instanse.silly("index", "initDatabase", "[root][users] Running in multi tenant mode, remove " + personal_nodered_users.name + " from self"); Base.removeRight(personal_nodered_users, personal_nodered_users._id, [Rights.full_control]); } else if (Config.update_acl_based_on_groups) { Base.removeRight(personal_nodered_users, personal_nodered_users._id, [Rights.full_control]); Base.addRight(personal_nodered_users, personal_nodered_users._id, "personal nodered users", [Rights.read]); } await Logger.DBHelper.Save(personal_nodered_users, jwt, span); const nodered_admins: Role = await Logger.DBHelper.EnsureRole(jwt, "nodered admins", WellknownIds.nodered_admins, span); nodered_admins.AddMember(admins); Base.addRight(nodered_admins, WellknownIds.admins, "admins", [Rights.full_control]); Base.removeRight(nodered_admins, WellknownIds.admins, [Rights.delete]); await Logger.DBHelper.Save(nodered_admins, jwt, span); const nodered_users: Role = await Logger.DBHelper.EnsureRole(jwt, "nodered users", WellknownIds.nodered_users, span); nodered_users.AddMember(admins); Base.addRight(nodered_users, WellknownIds.admins, "admins", [Rights.full_control]); Base.removeRight(nodered_users, WellknownIds.admins, [Rights.delete]); if (Config.multi_tenant) { Logger.instanse.silly("index", "initDatabase", "[root][users] Running in multi tenant mode, remove " + nodered_users.name + " from self"); Base.removeRight(nodered_users, nodered_users._id, [Rights.full_control]); } else if (Config.update_acl_based_on_groups) { Base.removeRight(nodered_users, nodered_users._id, [Rights.full_control]); Base.addRight(nodered_users, nodered_users._id, "nodered users", [Rights.read]); } await Logger.DBHelper.Save(nodered_users, jwt, span); const nodered_api_users: Role = await Logger.DBHelper.EnsureRole(jwt, "nodered api users", WellknownIds.nodered_api_users, span); nodered_api_users.AddMember(admins); Base.addRight(nodered_api_users, WellknownIds.admins, "admins", [Rights.full_control]); Base.removeRight(nodered_api_users, WellknownIds.admins, [Rights.delete]); if (Config.multi_tenant) { Logger.instanse.silly("index", "initDatabase", "[root][users] Running in multi tenant mode, remove " + nodered_api_users.name + " from self"); Base.removeRight(nodered_api_users, nodered_api_users._id, [Rights.full_control]); } else if (Config.update_acl_based_on_groups) { Base.removeRight(nodered_api_users, nodered_api_users._id, [Rights.full_control]); Base.addRight(nodered_api_users, nodered_api_users._id, "nodered api users", [Rights.read]); } await Logger.DBHelper.Save(nodered_api_users, jwt, span); if (Config.multi_tenant) { try { const resellers: Role = await Logger.DBHelper.EnsureRole(jwt, "resellers", WellknownIds.resellers, span); Base.addRight(resellers, WellknownIds.admins, "admins", [Rights.full_control]); Base.removeRight(resellers, WellknownIds.admins, [Rights.delete]); Base.removeRight(resellers, WellknownIds.resellers, [Rights.full_control]); resellers.AddMember(admins); await Logger.DBHelper.Save(resellers, jwt, span); const customer_admins: Role = await Logger.DBHelper.EnsureRole(jwt, "customer admins", WellknownIds.customer_admins, span); Base.addRight(customer_admins, WellknownIds.admins, "admins", [Rights.full_control]); Base.removeRight(customer_admins, WellknownIds.admins, [Rights.delete]); Base.removeRight(customer_admins, WellknownIds.customer_admins, [Rights.full_control]); await Logger.DBHelper.Save(customer_admins, jwt, span); } catch (error) { Logger.instanse.error("index", "initDatabase", error); } } const robot_admins: Role = await Logger.DBHelper.EnsureRole(jwt, "robot admins", WellknownIds.robot_admins, span); robot_admins.AddMember(admins); Base.addRight(robot_admins, WellknownIds.admins, "admins", [Rights.full_control]); Base.removeRight(robot_admins, WellknownIds.admins, [Rights.delete]); await Logger.DBHelper.Save(robot_admins, jwt, span); const robot_users: Role = await Logger.DBHelper.EnsureRole(jwt, "robot users", WellknownIds.robot_users, span); robot_users.AddMember(admins); robot_users.AddMember(users); Base.addRight(robot_users, WellknownIds.admins, "admins", [Rights.full_control]); Base.removeRight(robot_users, WellknownIds.admins, [Rights.delete]); if (Config.multi_tenant) { Logger.instanse.silly("index", "initDatabase", "[root][users] Running in multi tenant mode, remove " + robot_users.name + " from self"); Base.removeRight(robot_users, robot_users._id, [Rights.full_control]); } else if (Config.update_acl_based_on_groups) { Base.removeRight(robot_users, robot_users._id, [Rights.full_control]); Base.addRight(robot_users, robot_users._id, "robot users", [Rights.read, Rights.invoke, Rights.update]); } await Logger.DBHelper.Save(robot_users, jwt, span); if (!admins.IsMember(root._id)) { admins.AddMember(root); await Logger.DBHelper.Save(admins, jwt, span); } const filestore_admins: Role = await Logger.DBHelper.EnsureRole(jwt, "filestore admins", WellknownIds.filestore_admins, span); filestore_admins.AddMember(admins); Base.addRight(filestore_admins, WellknownIds.admins, "admins", [Rights.full_control]); Base.removeRight(filestore_admins, WellknownIds.admins, [Rights.delete]); if (Config.multi_tenant) { Logger.instanse.silly("index", "initDatabase", "[root][users] Running in multi tenant mode, remove " + filestore_admins.name + " from self"); Base.removeRight(filestore_admins, filestore_admins._id, [Rights.full_control]); } await Logger.DBHelper.Save(filestore_admins, jwt, span); const filestore_users: Role = await Logger.DBHelper.EnsureRole(jwt, "filestore users", WellknownIds.filestore_users, span); filestore_users.AddMember(admins); if (!Config.multi_tenant) { filestore_users.AddMember(users); } Base.addRight(filestore_users, WellknownIds.admins, "admins", [Rights.full_control]); Base.removeRight(filestore_users, WellknownIds.admins, [Rights.delete]); if (Config.multi_tenant) { Logger.instanse.silly("index", "initDatabase", "[root][users] Running in multi tenant mode, remove " + filestore_users.name + " from self"); Base.removeRight(filestore_users, filestore_users._id, [Rights.full_control]); } else if (Config.update_acl_based_on_groups) { Base.removeRight(filestore_users, filestore_users._id, [Rights.full_control]); Base.addRight(filestore_users, filestore_users._id, "filestore users", [Rights.read]); } await Logger.DBHelper.Save(filestore_users, jwt, span); const workitem_queue_admins: Role = await Logger.DBHelper.EnsureRole(jwt, "workitem queue admins", "625440c4231309af5f2052cd", span); workitem_queue_admins.AddMember(admins); Base.addRight(workitem_queue_admins, WellknownIds.admins, "admins", [Rights.full_control]); Base.removeRight(workitem_queue_admins, WellknownIds.admins, [Rights.delete]); if (Config.multi_tenant) { Base.removeRight(workitem_queue_admins, WellknownIds.admins, [Rights.full_control]); } await Logger.DBHelper.Save(workitem_queue_admins, jwt, span); const workitem_queue_users: Role = await Logger.DBHelper.EnsureRole(jwt, "workitem queue users", "62544134231309e2cd2052ce", span); Base.addRight(workitem_queue_users, WellknownIds.admins, "admins", [Rights.full_control]); Base.removeRight(workitem_queue_users, WellknownIds.admins, [Rights.delete]); if (Config.multi_tenant) { Base.removeRight(workitem_queue_users, WellknownIds.admins, [Rights.full_control]); } await Logger.DBHelper.Save(workitem_queue_users, jwt, span); await Config.db.ensureindexes(span); if (Config.auto_hourly_housekeeping) { const crypto = require('crypto'); const randomNum = crypto.randomInt(1, 100); // Every 15 minutes, give and take a few minutes, send out a message to do house keeping, if ready Logger.instanse.verbose("index", "initDatabase", "Housekeeping every 15 minutes plus " + randomNum + " seconds"); housekeeping = setInterval(async () => { if (Config.enable_openflow_amqp) { if (!Message.ReadyForHousekeeping()) { return; } amqpwrapper.Instance().send("openflow", "", { "command": "housekeeping" }, 10000, null, "", 1); await new Promise(resolve => { setTimeout(resolve, 10000) }); if (Message.ReadyForHousekeeping()) { doHouseKeeping(); } else { Logger.instanse.verbose("index", "initDatabase", "SKIP housekeeping"); } } else { doHouseKeeping(); } }, (15 * 60 * 1000) + (randomNum * 1000)); // If I'm first and noone else has run it, lets trigger it now const randomNum2 = crypto.randomInt(1, 10); Logger.instanse.info("index", "initDatabase", "Trigger first Housekeeping in " + randomNum2 + " seconds"); setTimeout(async () => { if (Config.enable_openflow_amqp) { if (!Message.ReadyForHousekeeping()) { return; } amqpwrapper.Instance().send("openflow", "", { "command": "housekeeping" }, 10000, null, "", 1); await new Promise(resolve => { setTimeout(resolve, 10000) }); if (Message.ReadyForHousekeeping()) { doHouseKeeping(); } else { Logger.instanse.verbose("index", "initDatabase", "SKIP housekeeping"); } } }, randomNum2 * 1000); } return true; } catch (error) { span?.recordException(error); Logger.instanse.error("index", "initDatabase", error); return false; } finally { Logger.otel.endSpan(span); } } process.on('beforeExit', (code) => { Logger.instanse.error("index", "beforeExit", code as any); }); process.on('exit', (code) => { Logger.instanse.error("index", "exit", code as any); }); const unhandledRejections = new Map(); process.on('unhandledRejection', (reason, promise) => { Logger.instanse.error("index", "unhandledRejection", reason as any); // ('Unhandled Rejection at: Promise', promise, 'reason:', reason); unhandledRejections.set(promise, reason); }); process.on('rejectionHandled', (promise) => { unhandledRejections.delete(promise); }); process.on('uncaughtException', (err, origin) => { Logger.instanse.error("index", "uncaughtException", err); // (`Caught exception: ${err}\n` + // `Exception origin: ${origin}` // ); }); process.on('uncaughtExceptionMonitor', (err, origin) => { Logger.instanse.error("index", "uncaughtExceptionMonitor", err); // (`Caught exception Monitor: ${err}\n` + // `Exception origin: ${origin}` // ); }); process.on('warning', (warning) => { try { Logger.instanse.warn("index", "uncaughtExceptionMonitor", warning.name + ": " + warning.message); // (warning.name + ": " + warning.message); // (warning.stack); } catch (error) { } }); // The signals we want to handle // NOTE: although it is tempting, the SIGKILL signal (9) cannot be intercepted and handled var signals = { 'SIGHUP': 1, 'SIGINT': 2, 'SIGTERM': 15 }; var housekeeping = null; function handle(signal, value) { Logger.instanse.info("index", "handle", `process received a ${signal} signal with value ${value}`); try { Config.db.shutdown(); Logger.otel.shutdown(); Logger.License.shutdown() // Auth.shutdown(); if (housekeeping != null) { try { clearInterval(housekeeping); } catch (error) { } } setTimeout(() => { process.exit(128 + value); }, 1000); server.close((err) => { Logger.instanse.info("index", "handle", `server stopped by ${signal} with value ${value}`); Logger.instanse.error("index", "handle", err); process.exit(128 + value); }) } catch (error) { Logger.instanse.error("index", "handle", error); Logger.instanse.info("index", "handle", `server stopped by ${signal} with value ${value}`); process.exit(128 + value); } } Object.keys(signals).forEach((signal) => process.on(signal, handle)); let GrafanaProxy: any = null; try { GrafanaProxy = require("./ee/grafana-proxy"); } catch (error) { } const originalStdoutWrite = process.stdout.write.bind(process.stdout); const originalStderrWrite = process.stderr.write.bind(process.stderr); (process.stdout.write as any) = (chunk: string, encoding?: string, callback?: (err?: Error | null) => void): boolean => { return originalStdoutWrite(chunk, encoding, callback); }; (process.stderr.write as any) = (chunk: string, encoding?: string, callback?: (err?: Error | null) => void): boolean => { return originalStderrWrite(chunk, encoding, callback); }; var server: http.Server = null; (async function (): Promise { const span: Span = Logger.otel.startSpan("openflow.startup"); try { await initamqp(span); Logger.instanse.info("index", "configure", "VERSION: " + Config.version); if (Logger.License.validlicense) { if (NoderedUtil.IsNullEmpty(Logger.License.data.domain)) { Logger.instanse.info("index", "configure", "License valid to " + Logger.License.data.expirationDate); } else { Logger.instanse.info("index", "configure", "License valid for " + Logger.License.data.domain + " until the " + Logger.License.data.expirationDate); } } server = await WebServer.configure(Config.baseurl(), span); if (GrafanaProxy != null) { const grafana = await GrafanaProxy.GrafanaProxy.configure(WebServer.app, span); } OAuthProvider.configure(WebServer.app, span); WebSocketServer.configure(server, span); await QueueClient.configure(span); await ValidateUserForm(span); if (!await initDatabase(span)) { process.exit(404); } WebServer.Listen(); Config.db.queuemonitoring(); } catch (error) { span?.recordException(error); Logger.instanse.error("index", "configure", error); process.exit(404); } finally { Logger.otel.endSpan(span); } })();