UNPKG

@iobroker/js-controller-common-db

Version:

The Library contains the common utils for the ioBroker controller which can be used by db classes too, as they do not rely on the db (circular dependencies).

1,399 lines 91.3 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 __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default")); var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var tools_exports = {}; __export(tools_exports, { ERRORS: () => ERRORS, FORBIDDEN_CHARS: () => FORBIDDEN_CHARS, MAX_CERT_VALIDITY: () => MAX_CERT_VALIDITY, appName: () => appName, appNameLowerCase: () => appNameLowerCase, appendStackTrace: () => appendStackTrace, checkNonEditable: () => checkNonEditable, compressFileGZip: () => compressFileGZip, copyAttributes: () => copyAttributes, createUuid: () => createUuid, decrypt: () => decrypt, decryptPhrase: () => decryptPhrase, detectPackageManagerWithFallback: () => detectPackageManagerWithFallback, encrypt: () => encrypt, ensureDNSOrder: () => ensureDNSOrder, execAsync: () => execAsync, findIPs: () => findIPs, formatAliasValue: () => formatAliasValue, generateDefaultCertificates: () => generateDefaultCertificates, getAdapterDir: () => getAdapterDir, getAllEnums: () => getAllEnums, getAllInstances: () => getAllInstances, getCertificateInfo: () => getCertificateInfo, getConfigFileName: () => getConfigFileName, getControllerDir: () => getControllerDir, getControllerPid: () => getControllerPid, getDefaultDataDir: () => getDefaultDataDir, getDefaultNodeArgs: () => getDefaultNodeArgs, getDefaultRequireResolvePaths: () => getDefaultRequireResolvePaths, getDiskInfo: () => getDiskInfo, getDockerInformation: () => getDockerInformation, getFile: () => getFile, getHostInfo: () => getHostInfo, getHostName: () => getHostName, getHostObject: () => getHostObject, getInstalledInfo: () => getInstalledInfo, getInstanceIndicatorObjects: () => getInstanceIndicatorObjects, getInstances: () => getInstances, getJson: () => getJson, getJsonAsync: () => getJsonAsync, getListenAllAddress: () => getListenAllAddress, getLocalAddress: () => getLocalAddress, getLogger: () => getLogger, getNewestDockerImageVersion: () => getNewestDockerImageVersion, getPids: () => getPids, getPidsFileName: () => getPidsFileName, getRepositoryFile: () => getRepositoryFile, getRepositoryFileAsync: () => getRepositoryFileAsync, getRootDir: () => getRootDir, installNodeModule: () => installNodeModule, isAdapterEsmModule: () => isAdapterEsmModule, isArray: () => isArray, isControllerUiUpgradeSupported: () => isControllerUiUpgradeSupported, isDevInstallation: () => isDevInstallation, isDevServerInstallation: () => isDevServerInstallation, isDocker: () => isDocker, isGithubPathname: () => isGithubPathname, isHostRunning: () => isHostRunning, isIoBrokerInstalledAsSystemd: () => isIoBrokerInstalledAsSystemd, isListenAllAddress: () => isListenAllAddress, isLocalAddress: () => isLocalAddress, isObject: () => isObject, isShortGithubUrl: () => isShortGithubUrl, isSingleHost: () => isSingleHost, isValidPattern: () => isValidPattern, maybeArrayToString: () => maybeArrayToString, measureEventLoopLag: () => measureEventLoopLag, parseDependencies: () => parseDependencies, parseGithubPathname: () => parseGithubPathname, parseShortGithubUrl: () => parseShortGithubUrl, pattern2RegEx: () => pattern2RegEx, pipeLinewise: () => pipeLinewise, promisify: () => promisify, promisifyNoError: () => promisifyNoError, rebuildNodeModules: () => rebuildNodeModules, removeIdFromAllEnums: () => removeIdFromAllEnums, removePreservedProperties: () => removePreservedProperties, resolveAdapterMainFile: () => resolveAdapterMainFile, sendDiagInfo: () => sendDiagInfo, setExecutableCapabilities: () => setExecutableCapabilities, setQualityForInstance: () => setQualityForInstance, uninstallNodeModule: () => uninstallNodeModule, upToDate: () => upToDate, updateLicenses: () => updateLicenses, validateDataDir: () => validateDataDir, validateGeneralObjectProperties: () => validateGeneralObjectProperties }); module.exports = __toCommonJS(tools_exports); var import_fs_extra = __toESM(require("fs-extra"), 1); var import_node_path = __toESM(require("node:path"), 1); var import_semver = __toESM(require("semver"), 1); var import_node_os = __toESM(require("node:os"), 1); var import_node_forge = __toESM(require("node-forge"), 1); var import_deep_clone = __toESM(require("deep-clone"), 1); var import_promisify_child_process = require("promisify-child-process"); var import_node_readline = require("node:readline"); var import_node_stream = require("node:stream"); var import_pak = require("@alcalzone/pak"); var import_exitCodes = require("../../lib/common/exitCodes.js"); var import_node_zlib = __toESM(require("node:zlib"), 1); var import_password = require("../../lib/common/password.js"); var import_jsonwebtoken = __toESM(require("jsonwebtoken"), 1); var import_axios = __toESM(require("axios"), 1); var import_node_crypto = __toESM(require("node:crypto"), 1); var import_node_child_process = require("node:child_process"); var import_node_url = require("node:url"); var import_node_events = __toESM(require("node:events"), 1); var import_maybeCallback = require("../../lib/common/maybeCallback.js"); var import_node = __toESM(require("node.extend"), 1); var import_node_dns = require("node:dns"); var import_aliasProcessing = require("../../lib/common/aliasProcessing.js"); var url = __toESM(require("node:url"), 1); var import_node_module = require("node:module"); __reExport(tools_exports, require("../../lib/common/maybeCallback.js"), module.exports); const import_meta = {}; const thisDir = url.fileURLToPath(new URL(".", import_meta.url || `file://${__filename}`)); const require2 = (0, import_node_module.createRequire)(import_meta.url || `file://${__filename}`); var ERRORS; (function(ERRORS2) { ERRORS2["ERROR_NOT_FOUND"] = "Not exists"; ERRORS2["ERROR_EMPTY_OBJECT"] = "null object"; ERRORS2["ERROR_NO_OBJECT"] = "no object"; ERRORS2["ERROR_DB_CLOSED"] = "DB closed"; })(ERRORS || (ERRORS = {})); import_node_events.default.EventEmitter.prototype.setMaxListeners(100); let npmVersion; let diskusage; const randomID = Math.round(Math.random() * 1e13); const VENDOR_FILE = "/etc/iob-vendor.json"; const OFFICIAL_DOCKER_FILE = "/opt/scripts/.docker_config/.thisisdocker"; const DOCKER_INFO_URL = "https://hub.docker.com/v2/namespaces/iobroker/repositories/iobroker/tags?page_size=1"; const DOCKER_HUB_BUILD_TIME_MS = 6 * 60 * 60 * 1e3; const DOCKER_VERSION_UI_UPGRADE = "8.1.0"; let lastCalculationOfIps; let ownIpArr = []; const FORBIDDEN_CHARS = /[^._\-/ :!#$%&()+=@^{}|~\p{Ll}\p{Lu}\p{Nd}]+/gu; function copyAttributes(oldObj, newObj, originalObj, isNonEdit) { for (const attr of Object.keys(oldObj)) { if (oldObj[attr] === void 0 || oldObj[attr] === null || typeof oldObj[attr] !== "object" || oldObj[attr] instanceof Array) { if (oldObj[attr] === "__no_change__" && originalObj && !isNonEdit) { if (originalObj[attr] !== void 0) { newObj[attr] = (0, import_deep_clone.default)(originalObj[attr]); } else { console.log(`Attribute ${attr} ignored by copying`); } } else if (oldObj[attr] === "__delete__" && !isNonEdit) { if (newObj[attr] !== void 0) { delete newObj[attr]; } } else { newObj[attr] = oldObj[attr]; } } else { newObj[attr] = newObj[attr] || {}; copyAttributes(oldObj[attr], newObj[attr], originalObj && originalObj[attr], isNonEdit || attr === "nonEdit"); } } } function checkNonEditable(oldObject, newObject) { if (!oldObject) { return true; } if (!oldObject.nonEdit && !newObject.nonEdit) { return true; } if (oldObject.nonEdit?.passHash) { if (newObject.nonEdit?.password) { const hash = import_node_crypto.default.createHash("sha256").update(newObject.nonEdit.password.toString()).digest("base64"); if (oldObject.nonEdit.passHash !== hash) { delete newObject.nonEdit; return false; } oldObject.nonEdit = (0, import_deep_clone.default)(newObject.nonEdit); delete oldObject.nonEdit.password; delete newObject.nonEdit.password; oldObject.nonEdit.passHash = hash; newObject.nonEdit.passHash = hash; copyAttributes(newObject.nonEdit, newObject, newObject); if (newObject.nonEdit.passHash) { delete newObject.nonEdit.passHash; } if (newObject.nonEdit?.password) { delete newObject.nonEdit.password; } return true; } newObject.nonEdit = oldObject.nonEdit; } else if (newObject.nonEdit) { oldObject.nonEdit = (0, import_deep_clone.default)(newObject.nonEdit); if (newObject.nonEdit.password) { const hash = import_node_crypto.default.createHash("sha256").update(newObject.nonEdit.password.toString()).digest("base64"); delete oldObject.nonEdit.password; delete newObject.nonEdit.password; oldObject.nonEdit.passHash = hash; newObject.nonEdit.passHash = hash; } } copyAttributes(oldObject.nonEdit, newObject, oldObject); if (newObject.nonEdit?.passHash) { delete newObject.nonEdit.passHash; } if (newObject.nonEdit?.password) { delete newObject.nonEdit.password; } return true; } function upToDate(repoVersion, installedVersion) { return import_semver.default.gte(installedVersion, repoVersion); } function decryptPhrase(password2, data, callback) { const decipher = import_node_crypto.default.createDecipher("aes192", password2); try { let decrypted = ""; decipher.on("readable", () => { const data2 = decipher.read(); if (data2) { decrypted += data2.toString("utf8"); } }); decipher.on("error", (error) => { console.error(`Cannot decode secret: ${error.message}`); callback(null); }); decipher.on("end", () => callback(decrypted)); decipher.write(data, "hex"); decipher.end(); } catch (e) { console.error(`Cannot decode secret: ${e.message}`); callback(null); } } async function isSingleHost(objects) { const res = await objects.getObjectList({ startkey: "system.host.", endkey: "system.host.\u9999" }); const hostObjs = res.rows.filter((obj) => obj.value && obj.value.type === "host"); return hostObjs.length <= 1; } async function isHostRunning(objects, states) { const res = await objects.getObjectList({ startkey: "system.host.", endkey: "system.host.\u9999" }); res.rows = res.rows.filter((obj) => obj.value?.type === "host"); for (const hostObj of res.rows) { const state = await states.getState(`${hostObj.id}.alive`); if (state && state.val) { return true; } } return false; } function isDevInstallation() { return import_fs_extra.default.pathExistsSync(`${getControllerDir()}/../../packages/controller`); } function getAppName() { if (isDevInstallation()) { return "ioBroker"; } return "iobroker"; } const appNameLowerCase = "iobroker"; const appName = getAppName(); function findIPs() { if (!lastCalculationOfIps || Date.now() - lastCalculationOfIps > 1e4) { lastCalculationOfIps = Date.now(); ownIpArr = []; try { const ifaces = import_node_os.default.networkInterfaces(); for (const iface of Object.values(ifaces)) { iface?.forEach((details) => !details.internal && ownIpArr.push(details.address)); } } catch (e) { console.error(`Can not find local IPs: ${e.message}`); } } return ownIpArr; } function findPath(path2, url2) { if (!url2) { return ""; } if (url2.startsWith("http://") || url2.startsWith("https://")) { return url2; } if (path2.startsWith("http://") || path2.startsWith("https://")) { return (path2 + url2).replace(/\/\//g, "/").replace("http:/", "http://").replace("https:/", "https://"); } if (url2[0] === "/") { return `${thisDir}/..${url2}`; } return `${thisDir}/../${path2}${url2}`; } async function getMac() { const macRegex = /(?:[a-z0-9]{2}[:-]){5}[a-z0-9]{2}/gi; const zeroRegex = /(?:[0]{2}[:-]){5}[0]{2}/; const command = process.platform.indexOf("win") === 0 ? "getmac" : "ifconfig || ip link"; const { stdout, stderr } = await execAsync(command); if (typeof stderr === "string") { throw new Error(stderr); } if (typeof stdout !== "string") { throw new Error(`Unexpected stdout: ${stdout?.toString()}`); } let macAddress; let match; let result = null; while (true) { match = macRegex.exec(stdout); if (!match) { break; } macAddress = match[0]; if (!zeroRegex.test(macAddress) && !result) { result = macAddress; } } if (result === null) { throw new Error(`Could not determine the mac address from: ${stdout}`); } return result.replace(/-/g, ":").toLowerCase(); } async function getNewestDockerImageVersion() { const res = await import_axios.default.get(DOCKER_INFO_URL); const dockerResult = res.data.results[0]; const isNew = new Date(dockerResult.last_updated).getTime() > new Date(process.env.BUILD).getTime() + DOCKER_HUB_BUILD_TIME_MS; return { version: dockerResult.name, lastUpdated: dockerResult.last_updated, isNew }; } function getDockerInformation() { try { const versionString = import_fs_extra.default.readFileSync(OFFICIAL_DOCKER_FILE, { encoding: "utf-8" }).trim(); return { isDocker: true, isOfficial: true, officialVersion: versionString }; } catch { } return { isDocker: isDocker(), isOfficial: false }; } function isControllerUiUpgradeSupported() { const dockerInfo = getDockerInformation(); if (dockerInfo.isDocker) { if (!dockerInfo.isOfficial) { return false; } if (!import_semver.default.valid(dockerInfo.officialVersion) || import_semver.default.lt(dockerInfo.officialVersion, DOCKER_VERSION_UI_UPGRADE)) { return false; } return true; } return !["win32", "darwin"].includes(import_node_os.default.platform()); } function isDocker() { try { import_fs_extra.default.statSync("/.dockerenv"); return true; } catch { } try { import_fs_extra.default.statSync(OFFICIAL_DOCKER_FILE); return true; } catch { } try { return import_fs_extra.default.readFileSync("/proc/self/cgroup", "utf8").includes("docker"); } catch { return false; } } async function uuid(givenMac) { givenMac = givenMac ?? ""; const _isDocker = isDocker(); if (require2("ci-info").isCI) { return "55travis-pipe-line-cior-githubaction"; } let mac = givenMac !== null ? givenMac || "" : null; let u; if (!_isDocker && mac === "") { const ifaces = import_node_os.default.networkInterfaces(); for (const iface of Object.values(ifaces)) { if (iface) { for (const entry of iface) { if (entry.mac !== "00:00:00:00:00:00") { mac = entry.mac; break; } } } if (mac) { break; } } } if (!_isDocker && mac === "") { const mac2 = await getMac(); return uuid(mac2); } if (!_isDocker && mac) { const md5sum = import_node_crypto.default.createHash("md5"); md5sum.update(mac); mac = md5sum.digest("hex"); u = `${mac.substring(0, 8)}-${mac.substring(8, 12)}-${mac.substring(12, 16)}-${mac.substring(16, 20)}-${mac.substring(20)}`; } else { let a; let b; b = a = ""; while (a++ < 36) { b += a * 51 & 52 ? (a ^ 15 ? 8 ^ Math.random() * (a ^ 20 ? 16 : 4) : 4).toString(16) : "-"; } u = b; } return u; } async function updateUuid(newUuid, _objects) { let _uuid = await uuid(""); _uuid = newUuid || _uuid; if (import_fs_extra.default.existsSync(VENDOR_FILE)) { try { const vendor = await import_fs_extra.default.readJSON(VENDOR_FILE); if (vendor.vendor?.uuidPrefix?.length === 2 && !_uuid.startsWith(vendor.vendor.uuidPrefix)) { _uuid = vendor.vendor.uuidPrefix + _uuid; } } catch { console.error(`Cannot parse ${VENDOR_FILE}`); } } try { await _objects.setObject("system.meta.uuid", { type: "meta", common: { name: "uuid", type: "uuid" }, ts: new Date().getTime(), from: `system.host.${getHostName()}.tools`, native: { uuid: _uuid } }); } catch (e) { throw new Error(`Object system.meta.uuid cannot be updated: ${e.message}`); } const obj = await _objects.getObject("system.meta.uuid"); if (obj.native.uuid !== _uuid) { console.error("object system.meta.uuid cannot be updated: write protected"); } else { console.log(`object system.meta.uuid created: ${_uuid}`); } return _uuid; } async function createUuid(objects) { const userObj = await objects.getObject("system.user.admin"); if (!userObj) { await new Promise((resolve) => { (0, import_password.password)(appName).hash(null, null, async (err, res) => { err && console.error(err); await objects.setObject("system.user.admin", { type: "user", common: { name: "admin", password: res, dontDelete: true, enabled: true }, ts: new Date().getTime(), from: `system.host.${getHostName()}.tools`, native: {} }); console.log("object system.user.admin created"); resolve(); }); }); } const obj = await objects.getObject("system.meta.uuid"); if (!obj?.native?.uuid) { return updateUuid("", objects); } const PROBLEM_UUIDS = [ "ab265f4a-67f9-a46a-c0b2-61e4b95cefe5", "7abd3182-d399-f7bd-da19-9550d8babede", "deb6f2a8-fe69-5491-0a50-a9f9b8f3419c", "ec66c85e-fc36-f6f9-f1c9-f5a2882d23c7", "e6203b03-f5f4-253a-e4f6-b295fc543ab7", "d659fa3d-7ef9-202a-ea23-acd0aff67b24" ]; if (!PROBLEM_UUIDS.includes(obj.native.uuid)) { return; } const licObj = objects.getObject("system.adapter.vis.0"); if (!licObj || !licObj.native || !licObj.native.license) { return updateUuid("", objects); } let data; try { data = import_jsonwebtoken.default.decode(licObj.native.license); } catch { data = null; } if (!data || typeof data === "string" || !data.uuid) { return updateUuid("", objects); } if (data.uuid !== obj.native.uuid) { return updateUuid(data.correct ? data.uuid : "", objects); } console.warn(`Your iobroker.vis license must be updated. Please contact info@iobroker.net to get a new license!`); console.warn(`Provide following information in email: ${data.email}, invoice: ${data.invoice}`); } async function getFile(urlOrPath, fileName, callback) { if (urlOrPath.substring(0, "http://".length) === "http://" || urlOrPath.substring(0, "https://".length) === "https://") { const tmpFile = `${thisDir}/../tmp/${fileName || `${Math.floor(Math.random() * 268435454)}.zip`}`; try { const res = await import_axios.default.get(urlOrPath, { responseType: "stream", headers: { "User-Agent": `${appName}, RND: ${randomID}, N: ${process.version}`, "Accept-Encoding": "gzip" } }); res.data.pipe(import_fs_extra.default.createWriteStream(tmpFile)).on("close", () => { console.log(`downloaded ${tmpFile}`); callback && callback(tmpFile); }); } catch (e) { console.log(`Cannot download "${tmpFile}": ${e.message}`); callback && callback(tmpFile); } } else { try { if (import_fs_extra.default.existsSync(urlOrPath)) { callback && callback(urlOrPath); } else if (import_fs_extra.default.existsSync(`${thisDir}/../${urlOrPath}`)) { callback && callback(`${thisDir}/../${urlOrPath}`); } else if (import_fs_extra.default.existsSync(`${thisDir}/../tmp/${urlOrPath}`)) { callback && callback(`${thisDir}/../tmp/${urlOrPath}`); } else { console.log(`File not found: ${urlOrPath}`); process.exit(import_exitCodes.EXIT_CODES.FILE_NOT_FOUND); } } catch (err) { console.log(`File "${urlOrPath}" could no be read: ${err.message}`); process.exit(import_exitCodes.EXIT_CODES.FILE_NOT_FOUND); } } } async function getJson(urlOrPath, agent, callback) { if (typeof agent === "function") { callback = agent; agent = ""; } agent = agent || ""; let sources = {}; if (urlOrPath && typeof urlOrPath === "object") { if (callback) { callback(urlOrPath); } } else if (!urlOrPath) { console.log("Empty url!"); if (callback) { callback(null); } } else { if (urlOrPath.substring(0, "http://".length) === "http://" || urlOrPath.substring(0, "https://".length) === "https://") { try { const res = await import_axios.default.get(urlOrPath, { headers: { "Accept-Encoding": "gzip", timeout: 1e4, "User-Agent": agent } }); if (res.status !== 200 || !res.data) { throw new Error(`Invalid response, body: ${res.data}, status code: ${res.status}`); } sources = res.data; if (callback) { callback(sources, urlOrPath); } } catch (e) { console.warn(`Cannot download json from ${urlOrPath}. Error: ${e.message}`); if (callback) { callback(null, urlOrPath); } return; } } else { if (import_fs_extra.default.existsSync(urlOrPath)) { try { sources = import_fs_extra.default.readJSONSync(urlOrPath); } catch (e) { console.log(`Cannot parse json file from ${urlOrPath}. Error: ${e.message}`); if (callback) { callback(null, urlOrPath); } return; } if (callback) { callback(sources, urlOrPath); } } else if (import_fs_extra.default.existsSync(`${thisDir}/../${urlOrPath}`)) { try { sources = import_fs_extra.default.readJSONSync(`${thisDir}/../${urlOrPath}`); } catch (e) { console.log(`Cannot parse json file from ${thisDir}/../${urlOrPath}. Error: ${e.message}`); if (callback) { callback(null, urlOrPath); } return; } if (callback) { callback(sources, urlOrPath); } } else if (import_fs_extra.default.existsSync(`${thisDir}/../tmp/${urlOrPath}`)) { try { sources = import_fs_extra.default.readJSONSync(`${thisDir}/../tmp/${urlOrPath}`); } catch (e) { console.log(`Cannot parse json file from ${thisDir}/../tmp/${urlOrPath}. Error: ${e.message}`); if (callback) { callback(null, urlOrPath); } return; } if (callback) { callback(sources, urlOrPath); } } else { if (callback) { callback(null, urlOrPath); } } } } } async function getJsonAsync(urlOrPath, agent) { agent = agent || ""; let sources = {}; if (urlOrPath && typeof urlOrPath === "object") { return urlOrPath; } else if (!urlOrPath) { console.log("Empty url!"); return null; } if (urlOrPath.startsWith("http://") || urlOrPath.startsWith("https://")) { try { const result = await (0, import_axios.default)(urlOrPath, { timeout: 1e4, headers: { "User-Agent": agent }, validateStatus: (status) => status !== 200 }); return result.data; } catch (e) { console.warn(`Cannot download json from ${urlOrPath}. Error: ${e.message}`); return null; } } else { if (import_fs_extra.default.existsSync(urlOrPath)) { try { sources = import_fs_extra.default.readJSONSync(urlOrPath); } catch (e) { console.warn(`Cannot parse json file from ${urlOrPath}. Error: ${e.message}`); return null; } return sources; } else if (import_fs_extra.default.existsSync(`${thisDir}/../${urlOrPath}`)) { try { sources = import_fs_extra.default.readJSONSync(`${thisDir}/../${urlOrPath}`); } catch (e) { console.warn(`Cannot parse json file from ${thisDir}/../${urlOrPath}. Error: ${e.message}`); return null; } return sources; } else if (import_fs_extra.default.existsSync(`${thisDir}/../tmp/${urlOrPath}`)) { try { sources = import_fs_extra.default.readJSONSync(`${thisDir}/../tmp/${urlOrPath}`); } catch (e) { console.log(`Cannot parse json file from ${thisDir}/../tmp/${urlOrPath}. Error: ${e.message}`); return null; } return sources; } return null; } } function scanDirectory(dirName, list, regExp) { if (import_fs_extra.default.existsSync(dirName)) { let dirs; try { dirs = import_fs_extra.default.readdirSync(dirName); } catch (e) { console.log(`Cannot read or parse ${dirName}: ${e.message}`); return; } for (let i = 0; i < dirs.length; i++) { try { const fullPath = import_node_path.default.join(dirName, dirs[i]); const fileIoName = import_node_path.default.join(fullPath, "io-package.json"); const fileName = import_node_path.default.join(fullPath, "package.json"); if (regExp.test(dirs[i]) && import_fs_extra.default.existsSync(fileIoName)) { const ioPackage = import_fs_extra.default.readJSONSync(fileIoName); const package_ = import_fs_extra.default.existsSync(fileName) ? import_fs_extra.default.readJSONSync(fileName) : {}; const localIcon = ioPackage.common.icon ? `/adapter/${dirs[i].substring(appName.length + 1)}/${ioPackage.common.icon}` : ""; list[ioPackage.common.name] = { controller: ioPackage.common.controller || false, version: ioPackage.common.version, icon: ioPackage.common.extIcon || localIcon, localIcon, title: ioPackage.common.title, titleLang: ioPackage.common.titleLang, desc: ioPackage.common.desc, platform: ioPackage.common.platform, keywords: ioPackage.common.keywords, readme: ioPackage.common.readme, type: ioPackage.common.type, license: ioPackage.common.license ? ioPackage.common.license : package_.licenses && package_.licenses.length ? package_.licenses[0].type : "", licenseUrl: package_.licenses && package_.licenses.length ? package_.licenses[0].url : "" }; } } catch (e) { console.log(`Cannot read or parse ${thisDir}/../node_modules/${dirs[i]}/io-package.json: ${e.message}`); } } } } function getInstalledInfo(hostRunningVersion) { const result = {}; const fullPath = getControllerDir(); if (!fullPath) { console.error("Could not determine controller directory on getInstalledInfo."); return result; } let ioPackage; try { ioPackage = import_fs_extra.default.readJSONSync(import_node_path.default.join(fullPath, "io-package.json")); } catch (e) { console.error(`Cannot get installed host information: ${e.message}`); } const package_ = import_fs_extra.default.existsSync(import_node_path.default.join(fullPath, "package.json")) ? import_fs_extra.default.readJSONSync(import_node_path.default.join(fullPath, "package.json")) : {}; const regExp = new RegExp(`^${appName}\\.`, "i"); if (ioPackage) { result[ioPackage.common.name] = { controller: true, type: ioPackage.common.type, version: ioPackage.common.version, icon: ioPackage.common.extIcon || ioPackage.common.icon, title: ioPackage.common.title, titleLang: ioPackage.common.titleLang, desc: ioPackage.common.desc, platform: ioPackage.common.platform, keywords: ioPackage.common.keywords, readme: ioPackage.common.readme, runningVersion: hostRunningVersion, license: ioPackage.common.license ? ioPackage.common.license : package_.licenses && package_.licenses.length ? package_.licenses[0].type : "", licenseUrl: package_.licenses && package_.licenses.length ? package_.licenses[0].url : "" }; } scanDirectory(import_node_path.default.join(fullPath, "node_modules"), result, regExp); scanDirectory(import_node_path.default.join(fullPath, ".."), result, regExp); return result; } function getNpmVersion(adapter, callback) { adapter = adapter ? `${appName}.${adapter}` : appName; adapter = adapter.toLowerCase(); const cliCommand = `npm view ${adapter}@latest version`; (0, import_node_child_process.exec)(cliCommand, { timeout: 2e3, windowsHide: true }, (error, stdout, _stderr) => { let version; if (error) { if (typeof callback === "function") { callback(error); return; } } else if (stdout) { version = import_semver.default.valid(stdout.trim()); } if (typeof callback === "function") { callback(null, version); } }); } function getIoPack(sources, name, callback) { getJson(sources[name].meta, "", (ioPack) => { const packUrl = sources[name].meta.replace("io-package.json", "package.json"); if (!ioPack) { if (sources._helper) { sources._helper.failCounter.push(name); } if (callback) { callback(sources, name); } } else { setImmediate(() => { getJson(packUrl, "", (pack) => { const version = sources[name].version; const type = sources[name].type; if (sources[name].url && name !== "js-controller") { if (ioPack && ioPack.common) { sources[name] = (0, import_node.default)(true, sources[name], ioPack.common); if (type) { sources[name].type = type; } if (pack && pack.licenses && pack.licenses.length) { if (!sources[name].license) { sources[name].license = pack.licenses[0].type; } if (!sources[name].licenseUrl) { sources[name].licenseUrl = pack.licenses[0].url; } } } if (callback) { callback(sources, name); } } else { if (ioPack && ioPack.common) { sources[name] = (0, import_node.default)(true, sources[name], ioPack.common); if (pack && pack.licenses && pack.licenses.length) { if (!sources[name].license) { sources[name].license = pack.licenses[0].type; } if (!sources[name].licenseUrl) { sources[name].licenseUrl = pack.licenses[0].url; } } } if (type) { sources[name].type = type; } if (version) { sources[name].version = version; if (callback) { callback(sources, name); } } else { if (sources[name].meta.substring(0, "http://".length) === "http://" || sources[name].meta.substring(0, "https://".length) === "https://") { getNpmVersion(name, (_err, version2) => { if (version2) { sources[name].version = version2; } else { sources[name].version = "npm error"; } if (callback) { callback(sources, name); } }); } else { if (callback) { callback(sources, name); } } } } }); }); } }); } function _getRepositoryFile(sources, path2, callback) { if (!sources._helper) { let count = 0; for (const _name in sources) { if (!Object.prototype.hasOwnProperty.call(sources, _name)) { continue; } count++; } sources._helper = { failCounter: [] }; sources._helper.timeout = setTimeout(() => { if (sources._helper) { delete sources._helper; for (const __name of Object.keys(sources)) { if (sources[__name].processed !== void 0) { delete sources[__name].processed; } } if (callback) { callback(new Error(`Timeout by read all package.json (${count}) seconds`), sources); } callback = void 0; } }, count * 1e3); } for (const name of Object.keys(sources)) { if (sources[name].processed || name === "_helper") { continue; } sources[name].processed = true; if (sources[name].url) { sources[name].url = findPath(path2, sources[name].url); } if (sources[name].meta) { sources[name].meta = findPath(path2, sources[name].meta); } if (sources[name].icon) { sources[name].icon = findPath(path2, sources[name].icon); } if (!sources[name].name && sources[name].meta) { getIoPack(sources, name, (_ignore) => { if (sources._helper) { if (sources._helper.failCounter.length > 10) { clearTimeout(sources._helper.timeout); delete sources._helper; for (const _name of Object.keys(sources)) { if (sources[_name].processed !== void 0) { delete sources[_name].processed; } } if (callback) { callback(new Error("Looks like there is no internet."), sources); } callback = void 0; } else { setImmediate(() => _getRepositoryFile(sources, path2, callback)); } } }); return; } } if (sources._helper) { let err; if (sources._helper.failCounter.length) { err = new Error(`Following packages cannot be read: ${sources._helper.failCounter.join(", ")}`); } clearTimeout(sources._helper.timeout); delete sources._helper; for (const __name of Object.keys(sources)) { if (sources[__name].processed !== void 0) { delete sources[__name].processed; } } if (callback) { callback(err, sources); } callback = void 0; } } async function _checkRepositoryFileHash(urlOrPath, additionalInfo, callback) { if (urlOrPath.startsWith("http://") || urlOrPath.startsWith("https://")) { urlOrPath = urlOrPath.replace(/\.json$/, "-hash.json"); let json = null; try { const res = await import_axios.default.get(urlOrPath, { headers: { "Accept-Encoding": "gzip", timeout: 1e4 } }); if (!res.data || res.status !== 200) { throw new Error(`Invalid response, body: ${res.data}, status code: ${res.status}`); } json = res.data; } catch (e) { console.warn(`Cannot download json from ${urlOrPath}. Error: ${e.message}`); } if (json && json.hash) { if (additionalInfo && additionalInfo.sources && json.hash === additionalInfo.hash) { console.log("hash unchanged, use cached sources"); callback(null, additionalInfo.sources, json.hash); } else { console.log("hash changed or no sources cached => force download of new sources"); callback(null, null, json.hash); } } else { console.log("failed to download new sources, use cached sources"); callback(null, additionalInfo.sources, ""); } } else { callback(null, null, 0); } } function getRepositoryFile(urlOrPath, additionalInfo, callback) { let sources = {}; let _path = ""; if (typeof additionalInfo === "function") { callback = additionalInfo; additionalInfo = {}; } additionalInfo = additionalInfo || {}; if (urlOrPath) { const parts = urlOrPath.split("/"); _path = `${parts.splice(0, parts.length - 1).join("/")}/`; } if (urlOrPath && typeof urlOrPath === "object") { if (typeof callback === "function") { callback(null, urlOrPath); } } else if (!urlOrPath) { try { const controllerDir = getControllerDir(); if (controllerDir) { sources = import_fs_extra.default.readJSONSync(import_node_path.default.join(controllerDir, getDefaultDataDir(), "sources.json")); } } catch { sources = {}; } try { const controllerDir = getControllerDir(); if (controllerDir) { const sourcesDist = import_fs_extra.default.readJSONSync(import_node_path.default.join(controllerDir, "conf", "sources-dist.json")); sources = (0, import_node.default)(true, sourcesDist, sources); } } catch { } for (const s of Object.keys(sources)) { if (additionalInfo[s] && additionalInfo[s].published) { sources[s].published = additionalInfo[s].published; } } _getRepositoryFile(sources, _path, (err) => { if (err) { console.error(`[${new Date().toString()}] ${err.message}`); } if (typeof callback === "function") { callback(err, sources); } }); } else { let agent = ""; if (additionalInfo) { agent = `${additionalInfo.name}, RND: ${additionalInfo.randomID || randomID}, Node:${additionalInfo.node}, V:${additionalInfo.controller}`; } _checkRepositoryFileHash(urlOrPath, additionalInfo, (err, sources2, actualSourcesHash) => { if (!err && sources2) { typeof callback === "function" && callback(err, sources2, actualSourcesHash); } else { getJson(urlOrPath, agent, (sources3) => { if (sources3) { for (const s of Object.keys(sources3)) { if (additionalInfo[s] && additionalInfo[s].published) { sources3[s].published = additionalInfo[s].published; } } setImmediate(() => _getRepositoryFile(sources3, _path, (err2) => { err2 && console.error(`[${new Date().toString()}] ${err2.message}`); typeof callback === "function" && callback(err2, sources3, actualSourcesHash); })); } else { console.log(`failed to download new sources, ${additionalInfo.sources ? "use cached sources" : "no cached sources available"}`); return (0, import_maybeCallback.maybeCallbackWithError)(callback, `Cannot read "${urlOrPath}"`, additionalInfo.sources, ""); } }); } }); } } async function getRepositoryFileAsync(url2, hash, force, _actualRepo) { let _hash; let data; if (url2.startsWith("http://") || url2.startsWith("https://")) { try { _hash = await (0, import_axios.default)({ url: url2.replace(/\.json$/, "-hash.json"), timeout: 1e4 }); } catch { } if (_actualRepo && !force && hash && _hash?.data && _hash.data.hash === hash) { data = _actualRepo; } else { const agent = `${appName}, RND: ${randomID}, Node:${process.version}, V:${require2("@iobroker/js-controller-common/package.json").version}`; try { data = await (0, import_axios.default)({ url: url2, timeout: 1e4, headers: { "User-Agent": agent } }); data = data.data; } catch (e) { throw new Error(`Cannot download repository file from "${url2}": ${e.message}`); } } } else { if (import_fs_extra.default.existsSync(url2)) { try { data = JSON.parse(import_fs_extra.default.readFileSync(url2).toString("utf8")); } catch (e) { throw new Error(`Error: Cannot read or parse file "${url2}": ${e}`); } } else { throw new Error(`Error: Cannot find file "${url2}"`); } } return { json: data, changed: _hash?.data ? hash !== _hash.data.hash : true, hash: _hash && _hash.data ? _hash.data.hash : "" }; } async function sendDiagInfo(obj) { const objStr = JSON.stringify(obj); console.log(`Send diag info: ${objStr}`); const params = new import_node_url.URLSearchParams(); params.append("data", objStr); const config = { headers: { "Content-Type": "application/x-www-form-urlencoded" }, timeout: 4e3 }; try { await import_axios.default.post(`http://download.${appName}.net/diag.php`, params, config); } catch (e) { console.log(`Cannot send diag info: ${e.message}`); } } function getAdapterDir(adapter) { if (adapter.toLowerCase().startsWith(`${appNameLowerCase}.`)) { adapter = adapter.substring(appName.length + 1); } if (/\.\d+$/.test(adapter)) { adapter = adapter.slice(0, adapter.lastIndexOf(".")); } const possibilities = [`${appName.toLowerCase()}.${adapter}/package.json`, `${appName}.${adapter}/package.json`]; let adapterPath; for (const possibility of possibilities) { if (import_fs_extra.default.existsSync(import_node_path.default.join(getControllerDir(), "..", possibility))) { adapterPath = import_node_path.default.join(getControllerDir(), "..", possibility); } else if (import_fs_extra.default.existsSync(import_node_path.default.join(getControllerDir(), "node_modules", possibility))) { adapterPath = import_node_path.default.join(getControllerDir(), "node_modules", possibility); } else { try { adapterPath = require2.resolve(possibility); } catch { } } if (adapterPath) { break; } } if (!adapterPath) { return null; } const parts = import_node_path.default.normalize(adapterPath).split(/[\\/]/g); parts.pop(); return parts.join("/"); } function getHostName() { if (process.env.IOB_HOSTNAME) { return process.env.IOB_HOSTNAME; } try { const configName = getConfigFileName(); const config = import_fs_extra.default.readJSONSync(configName); return config.system?.hostname || import_node_os.default.hostname(); } catch { return import_node_os.default.hostname(); } } function getSystemNpmVersion(callback) { const { exec: exec2 } = require2("node:child_process"); const newEnv = Object.assign({}, process.env); newEnv.PATH = (newEnv.PATH || newEnv.Path || newEnv.path).split(import_node_path.default.delimiter).filter((dir) => { dir = dir.toLowerCase(); return !dir.includes("iobroker") || !dir.includes(import_node_path.default.join("node_modules", ".bin")); }).join(import_node_path.default.delimiter); try { let timeout = setTimeout(() => { timeout = null; if (callback) { callback(new Error("timeout")); callback = void 0; } }, 1e4); exec2("npm -v", { encoding: "utf8", env: newEnv, windowsHide: true }, (error, stdout) => { if (timeout) { clearTimeout(timeout); timeout = null; } if (stdout) { stdout = import_semver.default.valid(stdout.trim()); } if (callback) { callback(error, stdout); callback = void 0; } }); } catch (e) { if (callback) { callback(e); callback = void 0; } } } const getSystemNpmVersionAsync = promisify(getSystemNpmVersion); async function detectPackageManagerWithFallback(cwd) { try { return await (0, import_pak.detectPackageManager)(typeof cwd === "string" ? { cwd } : { cwd: isDevServerInstallation() && require2.main?.path || thisDir, setCwdToPackageRoot: true }); } catch { } const ioBrokerRootDir = getRootDir(); const pak = new import_pak.packageManagers.npm(); pak.cwd = cwd || ioBrokerRootDir; return pak; } async function installNodeModule(npmUrl, options = {}) { const pak = await detectPackageManagerWithFallback(options.cwd); if (!options.debug) { pak.loglevel = "error"; } const installOpts = { additionalArgs: [] }; if (options.debug) { const stdall = new import_node_stream.PassThrough(); pak.stdall = stdall; pipeLinewise(stdall, process.stdout); installOpts.additionalArgs.push("--foreground-scripts"); } else { const stdout = new import_node_stream.PassThrough(); pak.stdout = stdout; pipeLinewise(stdout, process.stdout); } if (options.unsafePerm) { installOpts.additionalArgs.push("--unsafe-perm"); } return pak.install([npmUrl], installOpts); } async function uninstallNodeModule(packageName, options = {}) { const pak = await detectPackageManagerWithFallback(options.cwd); if (!options.debug) { pak.loglevel = "error"; } if (options.debug) { const stdall = new import_node_stream.PassThrough(); pak.stdall = stdall; pipeLinewise(stdall, process.stdout); } else { const stdout = new import_node_stream.PassThrough(); pak.stdout = stdout; pipeLinewise(stdout, process.stdout); } return pak.uninstall([packageName]); } async function rebuildNodeModules(options = {}) { const pak = await detectPackageManagerWithFallback(options.cwd); if (!options.debug) { pak.loglevel = "error"; } if (options.debug) { const stdall = new import_node_stream.PassThrough(); pak.stdall = stdall; pipeLinewise(stdall, process.stdout); } else { const stdout = new import_node_stream.PassThrough(); pak.stdout = stdout; pipeLinewise(stdout, process.stdout); } return pak.rebuild(options.module ? [options.module] : void 0); } async function getDiskInfo() { const platform = process.platform; if (diskusage) { try { const path2 = platform === "win32" ? thisDir.substring(0, 2) : "/"; const info = diskusage.checkSync(path2); return { "Disk size": info.total, "Disk free": info.free }; } catch (e) { console.log(e.message); } } else { if (platform === "win32") { const disk = thisDir.substring(0, 2).toUpperCase(); const { stdout } = await execAsync("wmic logicaldisk get size,freespace,caption"); if (typeof stdout === "string") { const lines = stdout.split("\n"); const line = lines.find((line2) => { const parts = line2.split(/\s+/); return parts[0].toUpperCase() === disk; }); if (line) { const parts = line.split(/\s+/); return { "Disk size": parseInt(parts[2]), "Disk free": parseInt(parts[1]) }; } } } else { const { stdout } = await execAsync(`df -k ${getRootDir()}`); try { if (typeof stdout === "string") { const parts = stdout.split("\n")[1].split(/\s+/); return { "Disk size": parseInt(parts[1]) * 1024, "Disk free": parseInt(parts[3]) * 1024 }; } } catch { } } } return null; } function getCertificateInfo(cert) { let info = null; if (!cert) { return null; } const pki = import_node_forge.default.pki; let certFile = null; try { if (typeof cert === "string" && cert.length < 1024 && import_fs_extra.default.existsSync(cert)) { certFile = cert; cert = import_fs_extra.default.readFileSync(cert, "utf8"); } const crt = pki.certificateFromPem(cert); info = { certificateFilename: certFile, certificate: cert, serialNumber: crt.serialNumber, signature: pki.oids[crt.signatureOid], keyLength: crt.publicKey.n.toString(2).length, issuer: crt.issuer, subject: crt.subject, dnsNames: crt.getExtension("subjectAltName").altNames, keyUsage: crt.getExtension("keyUsage"), extKeyUsage: crt.getExtension("extKeyUsage"), validityNotBefore: crt.validity.notBefore, validityNotAfter: crt.validity.notAfter }; delete info.keyUsage.value; delete info.extKeyUsage.value; return info; } catch { return null; } } const MAX_CERT_VALIDITY = 365 * 24 * 60 * 60 * 1e3; function generateDefaultCertificates() { const pki = import_node_forge.default.pki; const keys = pki.rsa.generateKeyPair({ bits: 2048, e: 65537 }); const cert = pki.createCertificate(); cert.publicKey = keys.publicKey; cert.serialNumber = `0${makeid(17)}`; cert.validity.notBefore = new Date(); cert.validity.notAfter = new Date(Date.now() + MAX_CERT_VALIDITY); const subAttrs = [ { name: "commonName", value: getHostName() }, { name: "organizationName", value: "ioBroker GmbH" }, { shortName: "OU", value: "iobroker" } ]; const issAttrs = [ { name: "commonName", value: "iobroker" }, { name: "organizationName", value: "ioBroker GmbH" }, { shortName: "OU", value: "iobroker" } ]; cert.setSubject(subAttrs); cert.setIssuer(issAttrs); cert.setExtensions([ { name: "basicConstraints", critical: true, cA: false }, { name: "keyUsage", critical: true, digitalSignature: true, contentCommitment: true, keyEncipherment: true, dataEncipherment: true, keyAgreement: true, keyCertSign: true, cRLSign: true, encipherOnly: true, decipherOnly: true }, { name: "subjectAltName", altNames: [ { type: 2, value: import_node_os.default.hostname() } ] }, { name: "subjectKeyIdentifier" }, { name: "extKeyUsage", serverAuth: true, clientAuth: true, codeSigning: false, emailProtection: false, ti