const os = require('os'); const fs = require('fs'); let runtime; let isContainerCached; let isWSLCached; const isInWsl = () => { if (isWSLCached === undefined) { isWSLCached = getIsInWSL(); } return isWSLCached; function getIsInWSL() { if (process.platform !== 'linux') { return false; } try { if (os.release().toLowerCase().includes('microsoft')) { if (isInContainer()) { return false; } return true; } return fs.readFileSync('/proc/version', 'utf8').toLowerCase().includes('microsoft') ? !isInContainer() : false; } catch (_) { return false; } } }; const isInContainer = () => { if (isContainerCached === undefined) { isContainerCached = hasDockerEnv() || hasDockerCGroup(); } return isContainerCached; function hasDockerEnv() { try { fs.statSync('/.dockerenv'); return true; } catch { return false; } } function hasDockerCGroup() { try { const s = fs.readFileSync('/proc/self/cgroup', 'utf8'); if (s.includes('docker')) { return "docker" } else if (s.includes('kubepod')) { return "kubepod" } else if (s.includes('lxc')) { return "lxc" } } catch { return false; } } } function buildDiagnosticReport(scope, callback) { const modules = {}; const nl = runtime.nodes.getNodeList(); for (let i = 0; i < nl.length; i++) { if (modules[nl[i].module]) { continue; } modules[nl[i].module] = nl[i].version } const now = new Date(); const report = { report: "diagnostics", scope: scope, version: runtime.settings.version, isStarted: runtime.isStarted(), containerised: isInContainer(), wsl: isInWsl(), time: { timestamp: now.valueOf(), utc: "" + now.toUTCString(), locale: now.toLocaleString(), }, intl: Intl.DateTimeFormat().resolvedOptions(), nodejs: { version: process.version, arch: process.arch, platform: process.platform, memoryUsage: process.memoryUsage(), }, os: { totalmem: os.totalmem(), freemem: os.freemem(), arch: os.arch(), loadavg: os.loadavg(), platform: os.platform(), release: os.release(), type: os.type(), uptime: os.uptime(), version: os.version(), }, runtime: { modules: modules, settings: { available: runtime.settings.available(), apiMaxLength: runtime.settings.apiMaxLength || "NO SETTING", //coreNodesDir: runtime.settings.coreNodesDir, disableEditor: runtime.settings.disableEditor, contextStorage: listContextModules(), debugMaxLength: runtime.settings.debugMaxLength || "NO SETTING", editorTheme: runtime.settings.editorTheme || "NO SETTING", flowFile: runtime.settings.flowFile || "NO SETTING", mqttReconnectTime: runtime.settings.mqttReconnectTime || "NO SETTING", serialReconnectTime: runtime.settings.serialReconnectTime || "NO SETTING", adminAuth: runtime.settings.adminAuth ? "HAS SETTING" : "NO SETTING", httpAdminRoot: runtime.settings.adminAuth ? "HAS SETTING" : "NO SETTING", httpAdminCors: runtime.settings.httpAdminCors ? "HAS SETTING" : "NO SETTING", httpNodeAuth: runtime.settings.httpNodeAuth ? "HAS SETTING" : "NO SETTING", httpAdminRoot: runtime.settings.httpAdminRoot || "NO SETTING", httpAdminCors: runtime.settings.httpAdminCors ? "HAS SETTING" : "NO SETTING", httpNodeRoot: runtime.settings.httpNodeRoot || "NO SETTING", httpNodeCors: runtime.settings.httpNodeCors ? "HAS SETTING" : "NO SETTING", httpStatic: runtime.settings.httpStatic ? "HAS SETTING" : "NO SETTING", httpStatic: runtime.settings.httpStaticRoot || "NO SETTING", httpStaticCors: runtime.settings.httpStaticCors ? "HAS SETTING" : "NO SETTING", uiHost: runtime.settings.uiHost ? "HAS SETTING" : "NO SETTING", uiPort: runtime.settings.uiPort ? "HAS SETTING" : "NO SETTING", userDir: runtime.settings.userDir ? "HAS SETTING" : "NO SETTING", } } } if (scope == "admin") { const moreSettings = { adminAuth_type: (runtime.settings.adminAuth && runtime.settings.adminAuth.type) ? runtime.settings.adminAuth.type : "NO SETTING", httpAdminCors: runtime.settings.httpAdminCors ? runtime.settings.httpAdminCors : "NO SETTING", httpNodeCors: runtime.settings.httpNodeCors ? runtime.settings.httpNodeCors : "NO SETTING", httpStaticCors: runtime.settings.httpStaticCors ? "HAS SETTING" : "NO SETTING", settingsFile: runtime.settings.settingsFile ? runtime.settings.settingsFile : "NO SETTING", uiHost: runtime.settings.uiHost ? runtime.settings.uiHost : "NO SETTING", uiPort: runtime.settings.uiPort ? runtime.settings.uiPort : "NO SETTING", userDir: runtime.settings.userDir ? runtime.settings.userDir : "NO SETTING", } const moreNodejs = { execPath: process.execPath, pid: process.pid, } const moreOs = { cpus: os.cpus(), homedir: os.homedir(), hostname: os.hostname(), networkInterfaces: os.networkInterfaces(), } report.runtime.settings = Object.assign({}, report.runtime.settings, moreSettings); report.nodejs = Object.assign({}, report.nodejs, moreNodejs); report.os = Object.assign({}, report.os, moreOs); } callback(report); /** gets a sanitised list containing only the module name */ function listContextModules() { const keys = Object.keys(runtime.settings.contextStorage); const result = {}; keys.forEach(e => { result[e] = { module: String(runtime.settings.contextStorage[e].module) } }) return result; } } module.exports = { init: function (_runtime) { runtime = _runtime; }, /** * Gets the node-red diagnostics report * @param {{scope: string}} opts - settings * @return {Promise} the diagnostics information * @memberof @node-red/diagnostics */ get: async function (opts) { return new Promise(function (resolve, reject) { opts = opts || {} try { runtime.log.audit({ event: "diagnostics.get", scope: opts.scope }, opts.req); buildDiagnosticReport(opts.scope, (report) => resolve(report)); } catch (error) { error.status = 500; reject(error); } }) }, }