UNPKG

iobroker.js-controller

Version:

Updated by reinstall.js on 2018-06-11T15:19:56.688Z

1,288 lines • 176 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var main_exports = {}; __export(main_exports, { init: () => init }); module.exports = __toCommonJS(main_exports); var __import_meta_url = typeof document === "undefined" ? new (require("url".replace("", ""))).URL("file:" + __filename).href : document.currentScript && document.currentScript.src || new URL("main.js", document.baseURI).href; var import_node_schedule = __toESM(require("node-schedule"), 1); var import_node_os = __toESM(require("node:os"), 1); var import_fs_extra = __toESM(require("fs-extra"), 1); var import_node_path = __toESM(require("node:path"), 1); var import_node_child_process = __toESM(require("node:child_process"), 1); var import_semver = __toESM(require("semver"), 1); var import_restart = __toESM(require("./lib/restart.js"), 1); var import_pidusage = __toESM(require("pidusage"), 1); var import_deep_clone = __toESM(require("deep-clone"), 1); var import_node_util = require("node:util"); var import_multihostServer = require("./lib/multihostServer.js"); var import_js_controller_common = require("@iobroker/js-controller-common"); var import_constants = require("@iobroker/js-controller-common-db/constants"); var import_plugin_base = require("@iobroker/plugin-base"); var import_blocklistManager = require("./lib/blocklistManager.js"); var import_js_controller_cli = require("@iobroker/js-controller-cli"); var import_decache = __toESM(require("decache"), 1); var import_cron_parser = __toESM(require("cron-parser"), 1); var import_utils = require("./lib/utils.js"); var import_adapterAutoUpgradeManager = require("./lib/adapterAutoUpgradeManager.js"); var import_tools = require("@iobroker/js-controller-common-db/tools"); var import_adapterUpgradeManager = require("./lib/adapterUpgradeManager.js"); var import_promises = require("node:timers/promises"); var import_objects = require("./lib/objects.js"); var url = __toESM(require("node:url"), 1); var import_node_module = require("node:module"); const thisDir = url.fileURLToPath(new URL(".", __import_meta_url || `file://${__filename}`)); const require2 = (0, import_node_module.createRequire)(__import_meta_url || `file://${__filename}`); const VIS_ADAPTERS = ["vis", "vis-2"]; const ioPackage = import_fs_extra.default.readJSONSync(import_node_path.default.join(import_js_controller_common.tools.getControllerDir(), "io-package.json")); const version = ioPackage.common.version; const controllerVersions = {}; let pluginHandler; let notificationHandler; let blocklistManager; let autoUpgradeManager; let requestedRepoUpdates = []; let upload; if (import_node_os.default.platform() === "win32") { require2("loadavg-windows"); } import_js_controller_common.tools.ensureDNSOrder(); let Objects; let States; let logger; let isDaemon = false; let callbackId = 1; const callbacks = {}; const hostname = import_js_controller_common.tools.getHostName(); const controllerDir = import_js_controller_common.tools.getControllerDir(); let hostObjectPrefix = `system.host.${hostname}`; let hostLogPrefix = `host.${hostname}`; const compactGroupObjectPrefix = ".compactgroup"; const logList = []; let detectIpsCount = 0; let objectsDisconnectTimeout = null; let statesDisconnectTimeout = null; let connected = null; let lastDiskSizeCheck = 0; let restartTimeout = null; let connectTimeout = null; let reportInterval = null; let primaryHostInterval = null; let isPrimary = false; let isRebootRequired = false; const PRIMARY_HOST_LOCK_TIME = 6e4; const VENDOR_BOOTSTRAP_FILE = "/opt/iobroker/iob-vendor-secret.json"; const VENDOR_FILE = "/etc/iob-vendor.json"; const procs = {}; const subscribe = {}; const stopTimeouts = {}; let states = null; let objects = null; let storeTimer = null; let mhTimer = null; let isStopping = null; let allInstancesStopped = true; let stopTimeout = 1e4; let uncaughtExceptionCount = 0; let installQueue = []; let started = false; let inputCount = 0; let outputCount = 0; let eventLoopLags = []; let mhService = null; const uptimeStart = Date.now(); let compactGroupController = false; let compactGroup = null; const compactProcs = {}; const scheduledInstances = {}; let diskWarningLevel = import_utils.DEFAULT_DISK_WARNING_LEVEL; let updateIPsTimer = null; let lastDiagSend = null; const config = getConfig(); function getErrorText(code) { return import_js_controller_common.EXIT_CODES[code]; } function getConfig() { const configFile = import_js_controller_common.tools.getConfigFileName(); if (!import_fs_extra.default.existsSync(configFile)) { if (process.argv.indexOf("start") !== -1) { isDaemon = true; logger = (0, import_js_controller_common.logger)("info", [import_js_controller_common.tools.appName], true); } else { logger = (0, import_js_controller_common.logger)("info", [import_js_controller_common.tools.appName]); } logger.error(`${hostLogPrefix} conf/${import_js_controller_common.tools.appName.toLowerCase()}.json missing - call node ${import_js_controller_common.tools.appName.toLowerCase()}.js setup`); process.exit(import_js_controller_common.EXIT_CODES.MISSING_CONFIG_JSON); } else { const _config = import_fs_extra.default.readJSONSync(configFile); if (!_config.states) { _config.states = { type: "jsonl" }; } if (!_config.objects) { _config.objects = { type: "jsonl" }; } if (!_config.system) { _config.system = {}; } return _config; } } function _startMultihost(_config, secret) { const cpus = import_node_os.default.cpus(); mhService = new import_multihostServer.MHServer(hostname, logger, _config, { node: process.version, arch: import_node_os.default.arch(), model: cpus && cpus[0] && cpus[0].model ? cpus[0].model : "unknown", cpus: cpus ? cpus.length : 1, mem: import_node_os.default.totalmem(), ostype: import_node_os.default.type() }, secret); } async function startMultihost(__config) { if (compactGroupController) { return; } if (mhTimer) { clearTimeout(mhTimer); mhTimer = null; } const _config = __config || getConfig(); if (_config.multihostService?.enabled) { if (mhService) { try { mhService.close(() => { mhService = null; setImmediate(() => startMultihost(_config)); }); return; } catch (e) { logger.warn(`${hostLogPrefix} Cannot stop multihost discovery server: ${e.message}`); } } const hasLocalObjectsServer = await (0, import_js_controller_common.isLocalObjectsDbServer)(_config.objects.type, _config.objects.host, true); const hasLocalStatesServer = await (0, import_js_controller_common.isLocalStatesDbServer)(_config.states.type, _config.states.host, true); if (!_config.objects.host || hasLocalObjectsServer) { logger.warn(`${hostLogPrefix} Multihost Master on this system is not possible, because IP address for objects is ${_config.objects.host}. Please allow remote connections to the server by adjusting the IP.`); return false; } else if (!_config.states.host || hasLocalStatesServer) { logger.warn(`${hostLogPrefix} Multihost Master on this system is not possible, because IP address for states is ${_config.states.host}. Please allow remote connections to the server by adjusting the IP.`); return false; } if (_config.multihostService.secure) { if (typeof _config.multihostService.password === "string" && _config.multihostService.password.length) { let obj; let errText; try { obj = await objects.getObject(import_constants.SYSTEM_CONFIG_ID); } catch (e) { errText = e.message; } if (obj?.native?.secret) { if (!_config.multihostService.password.startsWith(`$/aes-192-cbc:`)) { import_js_controller_common.tools.decryptPhrase(obj.native.secret, _config.multihostService.password, (secret) => _startMultihost(_config, secret)); } else { try { const secret = import_js_controller_common.tools.decrypt(obj.native.secret, _config.multihostService.password); _startMultihost(_config, secret); } catch (e) { logger.error(`${hostLogPrefix} Cannot decrypt password for multihost discovery server: ${e.message}`); } } } else { logger.error(`${hostLogPrefix} Cannot start multihost discovery server: no system.config found (err: ${errText})`); } } else { logger.error(`${hostLogPrefix} Cannot start multihost discovery server: secure mode was configured, but no secret was set. Please check the configuration!`); } } else { _startMultihost(_config, false); } if (!_config.multihostService.persist) { mhTimer = setTimeout(async () => { if (mhService) { try { mhService.close(); mhService = null; logger.info(`${hostLogPrefix} Multihost discovery server stopped after 15 minutes, because only temporarily activated`); _config.multihostService.persist = false; _config.multihostService.enabled = false; const configFile = import_js_controller_common.tools.getConfigFileName(); await import_fs_extra.default.writeFile(configFile, JSON.stringify(_config, null, 2)); } catch (e) { logger.warn(`${hostLogPrefix} Cannot stop multihost discovery: ${e.message}`); } } mhTimer = null; }, 15 * 6e4); } return true; } else if (mhService) { try { mhService.close(); mhService = null; } catch (e) { logger.warn(`${hostLogPrefix} Cannot stop multihost discovery: ${e.message}`); } return false; } } function startUpdateIPs() { if (!updateIPsTimer) { updateIPsTimer = setInterval(() => { if (Date.now() - uptimeStart > 5 * 6e4) { clearInterval(updateIPsTimer); updateIPsTimer = setInterval(() => setIPs(), 36e5); } setIPs(); }, 3e4); } } function logRedirect(isActive, id, reason) { console.log(`================================== > LOG REDIRECT ${id} => ${isActive} [${reason}]`); if (isActive) { if (!logList.includes(id)) { logList.push(id); } } else { const pos = logList.indexOf(id); if (pos !== -1) { logList.splice(pos, 1); } } } function handleDisconnect() { if (!connected || restartTimeout || isStopping) { return; } if (statesDisconnectTimeout) { clearTimeout(statesDisconnectTimeout); statesDisconnectTimeout = null; } if (objectsDisconnectTimeout) { clearTimeout(objectsDisconnectTimeout); objectsDisconnectTimeout = null; } connected = false; logger.warn(`${hostLogPrefix} Slave controller detected disconnection. Stop all instances.`); if (compactGroupController) { stop(true); } else { stop(true, () => { restartTimeout = setTimeout(() => { processMessage({ command: "cmdExec", message: { data: "_restart" }, from: hostObjectPrefix }); setTimeout(() => process.exit(import_js_controller_common.EXIT_CODES.JS_CONTROLLER_STOPPED), 1e3); }, 1e4); }); } } function createStates(onConnect) { states = new States({ namespace: hostLogPrefix, connection: config.states, logger, hostname, change: async (id, stateOrMessage) => { if (!states || !objects) { logger.error(`${hostLogPrefix} Could not handle state change of "${id}", because not connected`); return; } inputCount++; if (!id) { return logger.error(`${hostLogPrefix} change event with no ID: ${JSON.stringify(stateOrMessage)}`); } if (id.startsWith(import_constants.SYSTEM_ADAPTER_PREFIX) && id.endsWith(".logging")) { const state = stateOrMessage; logRedirect(state ? state.val : false, id.substring(0, id.length - ".logging".length), id); } else if (!compactGroupController && id === `messagebox.${hostObjectPrefix}`) { const obj = stateOrMessage; if (obj) { if (obj.callback && obj.callback.ack && obj.callback.id && callbacks[`_${obj.callback.id}`]) { callbacks[`_${obj.callback.id}`].cb(obj.message); delete callbacks[`_${obj.callback.id}`]; const now = Date.now(); for (const _id of Object.keys(callbacks)) { if (now - callbacks[_id].time > 36e5) { delete callbacks[_id]; } } } else { processMessage(obj); } } } else if (!compactGroupController && id.match(/^system.adapter.[^.]+\.\d+\.alive$/)) { const state = stateOrMessage; if (state && !state.ack) { const enabled = state.val; let obj; try { obj = await objects.getObject(id.substring( 0, id.length - 6 /*'.alive'.length*/ )); } catch (e) { logger.error(`${hostLogPrefix} Cannot read object: ${e.message}`); } if (obj?.common) { if (obj.common.enabled && !enabled || !obj.common.enabled && enabled) { obj.common.enabled = !!enabled; logger.info(`${hostLogPrefix} instance "${obj._id}" ${obj.common.enabled ? "enabled" : "disabled"} via .alive`); obj.from = hostObjectPrefix; obj.ts = Date.now(); try { await objects.setObject(obj._id, obj); } catch (e) { logger.error(`${hostLogPrefix} Cannot set object: ${e.message}`); } } } } } else if (subscribe[id]) { const state = stateOrMessage; for (const sub of subscribe[id]) { if (procs[sub]) { console.log(`Wake up ${id} ${JSON.stringify(state)}`); startInstance(sub, true); } else { logger.warn(`${hostLogPrefix} controller Adapter subscribed on ${id} does not exist!`); } } } else if (id === `${hostObjectPrefix}.logLevel`) { const state = stateOrMessage; if (!config || !config.log || !state || state.ack) { return; } let currentLevel = config.log.level; if (typeof state.val === "string" && state.val !== currentLevel && ["silly", "debug", "info", "warn", "error"].includes(state.val)) { config.log.level = state.val; for (const transport in logger.transports) { if (logger.transports[transport].level === currentLevel && // @ts-expect-error it's our custom property !logger.transports[transport]._defaultConfigLoglevel) { logger.transports[transport].level = state.val; } } logger.info(`${hostLogPrefix} Loglevel changed from "${currentLevel}" to "${state.val}"`); currentLevel = state.val; } else if (state.val && state.val !== currentLevel) { logger.info(`${hostLogPrefix} Got invalid loglevel "${state.val}", ignoring`); } await states.setState(`${hostObjectPrefix}.logLevel`, { val: currentLevel, ack: true, from: hostObjectPrefix }); } else if (id.startsWith(`${hostObjectPrefix}.plugins.`) && id.endsWith(".enabled")) { const state = stateOrMessage; if (!config || !config.log || !state || state.ack) { return; } const pluginStatesIndex = `${hostObjectPrefix}.plugins.`.length; let nameEndIndex = id.indexOf(".", pluginStatesIndex + 1); if (nameEndIndex === -1) { nameEndIndex = void 0; } const pluginName = id.substring(pluginStatesIndex, nameEndIndex); if (!pluginHandler.pluginExists(pluginName)) { return; } if (pluginHandler.isPluginActive(pluginName) !== state.val) { if (state.val) { if (!pluginHandler.isPluginInstantiated(pluginName)) { pluginHandler.instantiatePlugin(pluginName, pluginHandler.getPluginConfig(pluginName), controllerDir); pluginHandler.setDatabaseForPlugin(pluginName, objects, states); pluginHandler.initPlugin(pluginName, ioPackage); } } else { if (!pluginHandler.destroy(pluginName)) { logger.info(`${hostLogPrefix} Plugin ${pluginName} could not be disabled. Please restart ioBroker to disable it.`); } } } } else if (id === `${hostObjectPrefix}.diskWarning` && stateOrMessage && "ack" in stateOrMessage && !stateOrMessage.ack) { const warningLevel = (0, import_utils.getDiskWarningLevel)(stateOrMessage); diskWarningLevel = warningLevel; await states.setState(id, { val: warningLevel, ack: true }); } }, connected: () => { if (statesDisconnectTimeout) { clearTimeout(statesDisconnectTimeout); statesDisconnectTimeout = null; } initMessageQueue(); startAliveInterval(); initializeController(); onConnect && onConnect(); }, disconnected: () => { if (restartTimeout) { return; } statesDisconnectTimeout && clearTimeout(statesDisconnectTimeout); statesDisconnectTimeout = setTimeout(() => { statesDisconnectTimeout = null; handleDisconnect(); }, (config.states.connectTimeout || 2e3) + (!compactGroupController ? 500 : 0)); } }); } async function initializeController() { if (!states || !objects || connected) { return; } logger.info(`${hostLogPrefix} connected to Objects and States`); const notificationSettings = { states, objects, log: logger, logPrefix: hostLogPrefix, host: hostname }; notificationHandler = new import_js_controller_common.NotificationHandler(notificationSettings); if (ioPackage.notifications) { try { await notificationHandler.addConfig(ioPackage.notifications); logger.info(`${hostLogPrefix} added notifications configuration of host`); await notificationHandler.getSetupOfAllAdaptersFromHost(); } catch (e) { logger.error(`${hostLogPrefix} Could not add notifications config of this host: ${e.message}`); } } autoUpgradeManager = new import_adapterAutoUpgradeManager.AdapterAutoUpgradeManager({ objects, states, logger, logPrefix: hostLogPrefix }); blocklistManager = new import_blocklistManager.BlocklistManager({ objects }); checkSystemLocaleSupported(); if (connected === null) { connected = true; if (!isStopping) { pluginHandler.setDatabaseForPlugins(objects, states); await pluginHandler.initPlugins(ioPackage); states.subscribe(`${hostObjectPrefix}.plugins.*`); await checkHost(); startMultihost(config); setMeta(); started = true; getInstances(); } } else { connected = true; started = true; if (!isStopping) { getInstances(); } } } function createObjects(onConnect) { objects = new Objects({ namespace: hostLogPrefix, connection: config.objects, controller: true, logger, hostname, connected: async () => { if (objectsDisconnectTimeout) { clearTimeout(objectsDisconnectTimeout); objectsDisconnectTimeout = null; } try { await objects.subscribePrimaryHost(); } catch (e) { logger.error(`${hostLogPrefix} Cannot subscribe to primary host expiration: ${e.message}`); } if (!primaryHostInterval && !compactGroupController) { primaryHostInterval = setInterval(checkPrimaryHost, PRIMARY_HOST_LOCK_TIME / 2); } checkPrimaryHost(); initializeController(); onConnect && onConnect(); }, disconnected: () => { if (restartTimeout) { return; } isPrimary = false; objectsDisconnectTimeout && clearTimeout(objectsDisconnectTimeout); objectsDisconnectTimeout = setTimeout(() => { objectsDisconnectTimeout = null; handleDisconnect(); }, (config.objects.connectTimeout || 2e3) + (!compactGroupController ? 500 : 0)); }, change: async (_id, _obj) => { if (!started || !_id.match(/^system\.adapter\.[a-zA-Z0-9-_]+\.[0-9]+$/)) { return; } const obj = _obj; const id = _id; try { logger.debug(`${hostLogPrefix} object change ${id} (from: ${obj ? obj.from : null})`); const proc = procs[id]; if (proc) { if (!obj) { if (!compactGroupController && proc.config.common.compactGroup && compactProcs[proc.config.common.compactGroup]?.instances?.includes(id)) { compactProcs[proc.config.common.compactGroup].instances.splice(compactProcs[proc.config.common.compactGroup].instances.indexOf(id), 1); } await notificationHandler.clearNotifications(null, null, id); proc.config.common.enabled = false; proc.config.common.host = null; proc.config.deleted = true; logger.info(`${hostLogPrefix} object deleted ${id}`); } else { if (proc.config.common.enabled && !obj.common.enabled) { logger.info(`${hostLogPrefix} "${id}" disabled`); } if (!proc.config.common.enabled && obj.common.enabled) { logger.info(`${hostLogPrefix} "${id}" enabled`); proc.downloadRetry = 0; } if (!compactGroupController && proc.config.common.compactGroup && (proc.config.common.compactGroup !== obj.common.compactGroup || proc.config.common.runAsCompactMode !== obj.common.runAsCompactMode) && compactProcs[proc.config.common.compactGroup]?.instances?.includes(id)) { compactProcs[proc.config.common.compactGroup].instances.splice(compactProcs[proc.config.common.compactGroup].instances.indexOf(id), 1); } proc.config = obj; } if (proc.process || proc.config.common.mode === "schedule") { proc.restartExpected = true; await stopInstance(id, false); if (!procs[id]) { return; } const _ipArr = import_js_controller_common.tools.findIPs(); if (checkAndAddInstance(proc.config, _ipArr)) { if (proc.config.common.enabled && (proc.config.common.mode !== "extension" || !proc.config.native.webInstance)) { if (proc.restartTimer) { clearTimeout(proc.restartTimer); } const restartTimeout2 = (proc.config.common.stopTimeout || 500) + 2500; proc.restartTimer = setTimeout((_id2) => startInstance(_id2), restartTimeout2, id); } } else { if (!compactGroupController && proc.config.common.compactGroup && compactProcs[proc.config.common.compactGroup]?.instances?.includes(id)) { compactProcs[proc.config.common.compactGroup].instances.splice(compactProcs[proc.config.common.compactGroup].instances.indexOf(id), 1); } if (proc.restartTimer) { clearTimeout(proc.restartTimer); delete proc.restartTimer; } await notificationHandler.clearNotifications(null, null, id); delete procs[id]; } } else if (installQueue.find((obj2) => obj2.id === id)) { logger.debug(`${hostLogPrefix} ignore object change because the adapter is still in installation/rebuild queue`); } else { const _ipArr = import_js_controller_common.tools.findIPs(); if (proc.config && checkAndAddInstance(proc.config, _ipArr)) { if (proc.config.common.enabled && (proc.config.common.mode !== "extension" || !proc.config.native.webInstance)) { startInstance(id); } } else { if (!compactGroupController && proc.config.common.compactGroup && compactProcs[proc.config.common.compactGroup]?.instances?.includes(id)) { compactProcs[proc.config.common.compactGroup].instances.splice(compactProcs[proc.config.common.compactGroup].instances.indexOf(id), 1); } if (proc.restartTimer) { clearTimeout(proc.restartTimer); delete proc.restartTimer; } delete procs[id]; } } } else if (obj?.common) { const _ipArr = import_js_controller_common.tools.findIPs(); if (!checkAndAddInstance(obj, _ipArr)) { return; } const proc2 = procs[id]; if (proc2.config.common.enabled && (proc2.config.common.mode !== "extension" || !proc2.config.native.webInstance)) { const restartTimeout2 = (proc2.config.common.stopTimeout || 500) + 2500; proc2.restartTimer = setTimeout((_id2) => startInstance(_id2), restartTimeout2, id); } } } catch (err) { if (!compactGroupController || obj?.common?.runAsCompactMode && obj.common.compactGroup === compactGroup) { logger.error(`${hostLogPrefix} cannot process: ${id}: ${err} / ${err.stack}`); } } }, primaryHostLost: () => { if (!isStopping) { isPrimary = false; logger.info("The primary host is no longer active. Checking responsibilities."); checkPrimaryHost(); } } }); } function startAliveInterval() { config.system = config.system || {}; config.system.statisticsInterval = Math.round(config.system.statisticsInterval) || 15e3; config.system.checkDiskInterval = config.system.checkDiskInterval !== 0 ? Math.round(config.system.checkDiskInterval) || 3e5 : 0; if (!compactGroupController) { states.setState(`${hostObjectPrefix}.compactModeEnabled`, { ack: true, from: hostObjectPrefix, val: config.system.compact || false }); } reportInterval = setInterval(reportStatus, config.system.statisticsInterval); reportStatus(); import_js_controller_common.tools.measureEventLoopLag(1e3, (lag) => eventLoopLags.push(lag)); } async function checkSystemLocaleSupported() { if (!objects) { throw new Error("Objects database not connected"); } const isSupported = await objects.isSystemLocaleSupported(); if (!isSupported) { await notificationHandler.addMessage({ category: "system", scope: "databaseErrors", message: "Your redis server is using an unsupported locale. This can lead to unexpected behavior of your ioBroker installation as well as data loss. Please configure your Redis Server according to https://forum.iobroker.net/topic/52976/wichtiger-hinweis-f%C3%BCr-redis-installationen?_=1678099836122", instance: `system.host.${hostname}` }); } } async function checkPrimaryHost() { if (objectsDisconnectTimeout || compactGroupController) { return; } try { if (!isPrimary) { isPrimary = !!await objects.setPrimaryHost(PRIMARY_HOST_LOCK_TIME); } else { const lockExtended = !!await objects.extendPrimaryHostLock(PRIMARY_HOST_LOCK_TIME); if (!lockExtended) { isPrimary = !!await objects.setPrimaryHost(PRIMARY_HOST_LOCK_TIME); } } } catch (e) { logger.error(`${hostLogPrefix} Could not execute primary host determination: ${e.message}`); } } async function reportStatus() { if (!states) { return; } const id = hostObjectPrefix; outputCount += 10; states.setState(`${id}.alive`, { val: true, ack: true, expire: Math.floor(config.system.statisticsInterval / 1e3) + 10, from: id }); try { (0, import_pidusage.default)(process.pid, (err, stats) => { if (!err && states && states.setState && stats) { states.setState(`${id}.cpu`, { ack: true, from: id, val: Math.round(100 * stats.cpu) / 100 }); states.setState(`${id}.cputime`, { ack: true, from: id, val: stats.ctime / 1e3 }); outputCount += 2; } }); } catch (e) { logger.error(`${hostLogPrefix} Cannot read pidUsage data : ${e.message}`); } try { const mem = process.memoryUsage(); states.setState(`${id}.memRss`, { val: Math.round( mem.rss / 10485.76 /* 1MB / 100 */ ) / 100, ack: true, from: id }); states.setState(`${id}.memHeapTotal`, { val: Math.round( mem.heapTotal / 10485.76 /* 1MB / 100 */ ) / 100, ack: true, from: id }); states.setState(`${id}.memHeapUsed`, { val: Math.round( mem.heapUsed / 10485.76 /* 1MB / 100 */ ) / 100, ack: true, from: id }); } catch (e) { logger.error(`${hostLogPrefix} Cannot read memoryUsage data: ${e.message}`); } states.setState(`${id}.load`, { val: Math.round(import_node_os.default.loadavg()[0] * 100) / 100, ack: true, from: id }); states.setState(`${id}.uptime`, { val: Math.round(process.uptime()), ack: true, from: id }); states.setState(`${id}.mem`, { val: Math.round(100 - import_node_os.default.freemem() / import_node_os.default.totalmem() * 100), ack: true, from: id }); states.setState(`${id}.freemem`, { val: Math.round( import_node_os.default.freemem() / 1048576 /* 1MB */ ), ack: true, from: id }); if (import_fs_extra.default.existsSync("/proc/meminfo")) { try { const text = import_fs_extra.default.readFileSync("/proc/meminfo", "utf8"); const m = text && text.match(/MemAvailable:\s*(\d+)/); if (m && m[1]) { states.setState(`${id}.memAvailable`, { val: Math.round(parseInt(m[1], 10) * 1024e-6), ack: true, from: id }); outputCount++; } } catch (e) { logger.error(`${hostLogPrefix} Cannot read /proc/meminfo: ${e.message}`); } } if (config.system.checkDiskInterval && Date.now() - lastDiskSizeCheck >= config.system.checkDiskInterval) { lastDiskSizeCheck = Date.now(); let info = null; try { info = await import_js_controller_common.tools.getDiskInfo(); } catch (e) { logger.error(`${hostLogPrefix} Cannot read disk size: ${e.message}`); } try { if (info) { const diskSize = Math.round((info["Disk size"] || 0) / (1024 * 1024)); const diskFree = Math.round((info["Disk free"] || 0) / (1024 * 1024)); const percentageFree = diskFree / diskSize * 100; const isDiskWarningActive = percentageFree < diskWarningLevel; if (isDiskWarningActive) { await notificationHandler.addMessage({ scope: "system", category: "diskSpaceIssues", message: `Your system has only ${percentageFree.toFixed(2)} % of disk space left.`, instance: `system.host.${hostname}` }); } states.setState(`${id}.diskSize`, { val: diskSize, ack: true, from: id }); states.setState(`${id}.diskFree`, { val: diskFree, ack: true, from: id }); outputCount += 2; } } catch (e) { logger.error(`${hostLogPrefix} Cannot read disk information: ${e.message}`); } } states.setState(`${id}.inputCount`, { val: inputCount, ack: true, from: id }); states.setState(`${id}.outputCount`, { val: outputCount, ack: true, from: id }); if (eventLoopLags.length) { const eventLoopLag = Math.ceil(eventLoopLags.reduce((a, b) => a + b) / eventLoopLags.length); states.setState(`${id}.eventLoopLag`, { val: eventLoopLag, ack: true, from: id }); eventLoopLags = []; } states.setState(`${id}.compactgroupProcesses`, { val: Object.keys(compactProcs).length, ack: true, from: id }); let realProcesses = 0; let compactProcesses = 0; Object.values(procs).forEach((proc) => { if (proc.process) { if (proc.startedInCompactMode) { compactProcesses++; } else { realProcesses++; } } }); states.setState(`${id}.instancesAsProcess`, { val: realProcesses, ack: true, from: id }); states.setState(`${id}.instancesAsCompact`, { val: compactProcesses, ack: true, from: id }); inputCount = 0; outputCount = 0; if (!isStopping && compactGroupController && started && compactProcesses === 0 && realProcesses === 0) { logger.info(`${hostLogPrefix} Compact group controller ${compactGroup} does not own any processes, stop`); stop(false); } } async function changeHost(objs, oldHostname, newHostname) { for (const row of objs) { if (row?.value?.common.host === oldHostname) { const obj = row.value; obj.common.host = newHostname; logger.info(`${hostLogPrefix} Reassign instance ${obj._id.substring(import_constants.SYSTEM_ADAPTER_PREFIX.length)} from ${oldHostname} to ${newHostname}`); obj.from = `system.host.${import_js_controller_common.tools.getHostName()}`; obj.ts = Date.now(); try { await objects.setObject(obj._id, obj); } catch (e) { logger.error(`Error changing host of ${obj._id}: ${e.message}`); } } } } function cleanAutoSubscribe(instance, autoInstance, callback) { inputCount++; states.getState(`${autoInstance}.subscribes`, async (err, state) => { if (!state || !state.val) { return setImmediate(() => callback()); } let subs; try { subs = JSON.parse(state.val); } catch { logger.error(`${hostLogPrefix} Cannot parse subscribes: ${state.val}`); return setImmediate(() => callback()); } let modified = false; for (const pattern of Object.keys(subs)) { for (const id of Object.keys(subs[pattern])) { if (id === instance) { modified = true; delete subs[pattern][id]; } } if (!Object.keys(subs[pattern]).length) { modified = true; delete subs[pattern]; } } if (modified) { outputCount++; await states.setState(`${autoInstance}.subscribes`, subs); } setImmediate(() => callback()); }); } function cleanAutoSubscribes(instanceID, callback) { const instance = instanceID.substring(15); objects.getObjectView("system", "instance", { startkey: import_constants.SYSTEM_ADAPTER_PREFIX, endkey: `${import_constants.SYSTEM_ADAPTER_PREFIX}\u9999` }, (err, res) => { let count = 0; if (res) { for (const row of res.rows) { if (row.value?.common.subscribable) { count++; cleanAutoSubscribe(instance, row.id, () => !--count && callback && callback()); } } } !count && callback && callback(); }); } async function delObjects(objs) { for (const row of objs) { if (row?.id) { logger.info(`${hostLogPrefix} Delete state "${row.id}"`); try { if (row.value && row.value.type === "state") { await states.delState(row.id); await objects.delObject(row.id); } else { await objects.delObject(row.id); } } catch { } } } } async function checkHost() { const objectData = objects.getStatus(); if (compactGroupController || !objectData.server) { return; } let hostDoc; try { hostDoc = await objects.getObjectViewAsync("system", "host", { startkey: "system.host.", endkey: "system.host.\u9999" }); } catch { } if (hostDoc?.rows.length === 1 && hostDoc?.rows[0].value.common.name !== hostname) { const oldHostname = hostDoc.rows[0].value.common.name; const oldId = hostDoc.rows[0].value._id; let instanceDoc; try { instanceDoc = await objects.getObjectViewAsync("system", "instance", { startkey: import_constants.SYSTEM_ADAPTER_PREFIX, endkey: `${import_constants.SYSTEM_ADAPTER_PREFIX}\u9999` }); } catch (e) { if (e.message.startsWith("Cannot find ")) { return; } } if (!instanceDoc?.rows || instanceDoc.rows.length === 0) { logger.info(`${hostLogPrefix} no instances found`); return; } await changeHost(instanceDoc.rows, oldHostname, hostname); logger.info(`${hostLogPrefix} Delete host ${oldId}`); try { await objects.delObjectAsync(oldId); } catch { } try { const newHostDoc = await objects.getObjectViewAsync("system", "state", { startkey: `system.host.${oldHostname}.`, endkey: `system.host.${oldHostname}.\u9999`, include_docs: true }); await delObjects(newHostDoc.rows); return; } catch { } } } async function collectDiagInfo(type) { if (type !== "extended" && type !== "normal" && type !== "no-city") { return null; } let systemConfig; let err; try { systemConfig = await objects.getObject(import_constants.SYSTEM_CONFIG_ID); } catch (e) { err = e; } if (err || !systemConfig?.common) { logger.warn(`System config object is corrupt, please run "${import_js_controller_common.tools.appNameLowerCase} setup first". Error: ${err.message}`); systemConfig = systemConfig || { common: {} }; systemConfig.common = systemConfig.common || {}; } let obj; try { obj = await objects.getObjectAsync("system.meta.uuid"); } catch { } if (!obj) { obj = { native: { uuid: "not found" } }; } let doc; err = null; try { doc = await objects.getObjectViewAsync("system", "host", { startkey: "system.host.", endkey: "system.host.\u9999" }); } catch (e) { err = e; } const { noCompactInstances, noInstances } = await _getNumberOfInstances(); const diag = { uuid: obj.native.uuid, language: systemConfig.common.language, country: "", city: "", hosts: [], node: process.version, arch: import_node_os.default.arch(), docker: import_js_controller_common.tools.isDocker(), adapters: {}, statesType: config.states.type, // redis or file objectsType: config.objects.type, // redis or file noInstances, compactMode: config.system.compact, noCompactInstances }; if (type === "extended" || type === "no-city") { const cpus = import_node_os.default.cpus(); diag.country = "country" in systemConfig.common ? systemConfig.common.country : "unknown"; diag.model = cpus && cpus[0] && cpus[0].model ? cpus[0].model : "unknown"; diag.cpus = cpus ? cpus.length : 1; diag.mem = import_node_os.default.totalmem(); diag.ostype = import_node_os.default.type(); delete diag.city; } if (type === "extended") { diag.city = "city" in systemConfig.common ? systemConfig.common.city : "unknown"; } else if (type === "normal") { delete diag.city; delete diag.country; } if (!err && doc?.rows.length) { doc.rows.sort((a, b) => { try { return import_semver.default.lt(a.value.common.installedVersion ?? "0.0.0", b.value.common.installedVersion ?? "0.0.0") ? 1 : 0; } catch { logger.error(`${hostLogPrefix} Invalid versions: ${a.value.common.installedVersion ?? "0.0.0"}[${a.value.common.name ?? "unknown"}] or ${b.value.common.installedVersion ?? "0.0.0"}[${b.value.common.name ?? "unknown"}]`); return 0; } }); for (const row of doc.rows) { diag.hosts.push({ version: row.value.common.installedVersion, platform: row.value.common.platform, type: row.value.native.os.platform }); } } doc = null; err = null; try { doc = await objects.getObjectViewAsync("system", "adapter", { startkey: import_constants.SYSTEM_ADAPTER_PREFIX, endkey: `${import_constants.SYSTEM_ADAPTER_PREFIX}\u9999` }); } catch (e) { err = e; } const foundVisAdapters = /* @__PURE__ */ new Set(); if (!err && doc?.rows.length) { for (const row of doc.rows) { diag.adapters[row.value.common.name] = { version: row.value.common.version, platform: row.value.common.platform, installedFrom: row.value.common.installedFrom }; if (VIS_ADAPTERS.includes(row.value.common.name)) { foundVisAdapters.add(row.value.common.name); } } } for (const visAdapter of foundVisAdapters) { const { calcProjects } = await import("./lib/vis/states.js"); try { const points = await calcProjects({ objects, instance: 0, visAdapter }); let total = null; const tasks = []; if (points?.length) { for (const point of points) { if (point.id === `${visAdapter}.0.datapoints.total`) { total = point.val; } tasks.push({ _id: point.id, type: "state", native: {}, common: { name: "Datapoints count", role: "state", type: "number", read: true, write: false }, state: { val: point.val, ack: true } }); } } if (total !== null) { diag[visAdapter] = total; } await extendObjects(tasks); } catch (e) { logger.error(`${hostLogPrefix} cannot call visUtils: ${e.message}`); } } return diag; } function setIPs(ipList) { if (isStopping) { return; } const _ipList = ipList || import_js_controller_common.tools.findIPs(); let found = false; for (const entry of _ipList) { if (entry === "127.0.0.1" || entry === "::1/128") { continue; } found = true; break; } if (!found && detectIpsCount < 10) { detectIpsCount++; setTimeout(() => setIPs(), 3e4); } else if (found) { objects.getObject(`system.host.${hostname}`, (err, oldObj) => { const networkInterfaces = import_node_os.default.networkInterfaces(); if (!err && oldObj && oldObj.common && oldObj.native && oldObj.native.hardware && (!(0, import_node_util.isDeepStrictEqual)(oldObj.native.hardware.networkInterfaces, networkInterfaces) || !(0, import_node_util.isDeepStrictEqual)(oldObj.common.address, _ipList))) { oldObj.common.address = _ipList; oldObj.native.hardware.networkInterfaces = networkInterfaces; oldObj.from = hostObjectPrefix; oldObj.ts = Date.now(); objects.setObject(oldObj._id, oldObj, (err2) => err2 && logger.error(`${hostLogPrefix} Cannot write host object: ${err2.message}`)); } startUpdateIPs(); }); } else { logger.info(`${hostLogPrefix} No IPv4 address found after 5 minutes.`); } } async function extendObjects(tasks) { for (const task of tasks) { const state = task.state; if (state !== void 0) { delete task.state; } try { await objects.extendObjectAsync(task._id, task); if (state) { await states.setState(task._id, state); } } catch { } } } async function setMeta() { const id = hostObjectPrefix; const oldObj = await objects.getObject(id); let newObj; if (compactGroupController) { newObj = { _id: id, type: "folder", common: { name: hostname + compactGroupObjectPrefix + compactGroup, cmd: `${process.argv[0]} ${`${process.execArgv.join(" ")} `.replace(/--inspect-brk=\d+ /, "")}${process.argv.slice(1).join(" ")}`, hostname, address: import_js_controller_common.tools.findIPs() }, native: {} }; } else { newObj = (0, import_tools.getHostObject)(oldObj); } if (oldObj) { delete oldObj.cmd; delete oldObj.from; delete oldObj.ts; delete oldObj.acl; } if (!oldObj || !(0, import_node_util.isDeepStrictEqual)(newObj, oldObj)) { newObj.from = hostObjectPrefix; newObj.ts = Date.now(); try { await objects.setObject(id, newObj); setIPs(newObj.common.address); } catch (e) { logger.error(`${hostLogPrefix} Cannot write host object: ${e.message}`); } } else { setIPs(newObj.common.address); } config.system.checkDiskInterval = config.system.checkDiskInterval !== 0 ? Math.round(config.system.checkDiskInterval) || 3e5 : 0; const tasks = (0, import_objects.getHostObjects)({ id, hostname, config, isCompactGroupController: compactGroupController }); objects.getObjectView("system", "state", { startkey: `${hostObjectPrefix}.`, endkey: `${hostObjectPrefix}.\u9999`, include_docs: true }, async (err, doc) => { if (err) { logger && logger.error(`${hostLogPrefix} Could not collect ${hostObjectPrefix} states to check for obsolete states: ${err.message}`); } else if (doc?.rows) { let thishostStates = doc.rows; if (!compactGroupController) { thishostStates = doc.rows.filter((out1) => !out1.id.includes(hostObjectPrefix + compactGroupObjectPrefix)); } const pluginStatesIndex = `${hostObjectPrefix}.plugins.`.length; const notificationStatesIndex = `${hostObjectPrefix}.notifications.`.length; const toDelete = thishostStates.filter((out1) => { const found = tasks.find((out2) => out1.id === out2._id); if (found === void 0) { if (out1.id.startsWith(`${hostObjectPrefix}.plugins.`)) { let nameEndIndex = out1.id.indexOf(".", pluginStatesIndex + 1); if (nameEndIndex === -1) { nameEndIndex = void 0; } return !pluginHandler.pluginExists(out1.id.substring(pluginStatesIndex, nameEndIndex)); } else if (out1.id.startsWith(`${hostObjectPrefix}.notifications.`)) { return !notificationHandler.scopeExists(out1.id.substring(notificationStatesIndex)); } } return found === void 0; }); if (toDelete && toDelete.length > 0) { await delObjects(toDelete); logger && logger.info(`${hostLogPrefix} Some obsolete host states deleted.`); } } await extendObjects(tasks); if (!compactGroupController) { const uuid = await import_js_controller_common.tools.createUuid(objects); uuid && logger && logger.info(`${hostLogPrefix} Created UUID: ${uuid}`); if (import_fs_extra.default.existsSync(VENDOR_BOOTSTRAP_FILE)) { logger && logger.info(`${hostLogPrefix} Detected vendor file: ${import_fs_extra.default.existsSync(VENDOR_BOOTSTRAP_FILE)}`); try { const startScript = import_fs_extra.default.readJSONSync(VENDOR_BOOTSTRAP_FILE); if (startScript.password) { const { Vendor } = await import("@iobroker/js-controller-cli"); const vendor = new Vendor({ objects }); logger && logger.info(`${hostLogPrefix} Apply vendor file: ${VENDOR_FILE}`); try { await vendor.checkVendor(VENDOR_FILE, startScript.password, logger); logger && logger.info(`${hostLogPrefix} Vendor information synchronised.`); try { if (import_fs_extra.default.existsSync(VENDOR_BOOTSTRAP_FILE)) { import_fs_extra.default.unlinkSync(VENDOR_BOOTSTRAP_FILE); } } catch (e) { logger && logger.error(`${hostLogPrefix} Cannot delete file ${VENDOR_BOOTSTRAP_FILE}: ${e.message}`); } } catch (e) { logger && logger.error(`${hostLogPrefix} Cannot update vendor information: ${e.message}`); try { import_fs_extra.default.existsSync(VENDOR_BOOTSTRAP_FILE) && import_fs_extra.default.unlinkSync(VENDOR_BOOTSTRAP_FILE); } catch (e2) { logger && logger.error(`${hostLogPrefix} Cannot delete file ${VENDOR_BOOTSTRAP_FILE}: ${e2.message}`); } } } } catch (e) { logger && logger.error(`${hostLogPrefix} Cannot parse ${VENDOR_BOOTSTRAP_FILE}: ${e.message}`); try { import_fs_extra.default.existsSync(VENDOR_BOOTSTRAP_FILE) && import_fs_extra.default.unlinkSync(VENDOR_BOOTSTRAP_FILE); } catch (e2) { logger && logger.error(`${hostLogPrefix} Cannot delete file ${VENDOR_BOOTSTRAP_FILE}: ${e2.message}`); } } } } }); } function initMessageQueue() { states.subscribeMessage(hostObjectPrefix); } async function sendTo(objName, command, message, callback) { if (!states) { return; } if (message === void 0) { message = command; command = "send"; } const obj = { command, message, from: hostObjectPrefix }; if (!objName.startsWith(import_constants.SYSTEM_ADAPTER_PREFIX) && !objName.startsWith("system.host.")) { objName = `${import_constants.SYSTEM_ADAPTER_PREFIX}${objName}`; } if (callback) { if (typeof callback === "function") { obj.callback = { me