UNPKG

homebridge-config-ui-x

Version:

A web based management, configuration and control platform for Homebridge.

356 lines • 16.1 kB
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; import { createHash, randomBytes } from 'node:crypto'; import { createReadStream } from 'node:fs'; import { stat } from 'node:fs/promises'; import { homedir, platform, totalmem } from 'node:os'; import { resolve } from 'node:path'; import process from 'node:process'; import { isDeepStrictEqual } from 'node:util'; import { Injectable } from '@nestjs/common'; import { pathExists, pathExistsSync, readJson, readJSONSync, readJsonSync, writeJsonSync } from 'fs-extra/esm'; import { satisfies } from 'semver'; import { FEATURE_FLAGS } from '../feature-flags/feature-flags.registry.js'; let ConfigService = class ConfigService { name = 'homebridge-config-ui-x'; configPath = process.env.UIX_CONFIG_PATH || resolve(homedir(), '.homebridge/config.json'); storagePath = process.env.UIX_STORAGE_PATH || resolve(homedir(), '.homebridge'); customPluginPath = process.env.UIX_CUSTOM_PLUGIN_PATH; strictPluginResolution = (process.env.UIX_STRICT_PLUGIN_RESOLUTION === '1'); secretPath = resolve(this.storagePath, '.uix-secrets'); authPath = resolve(this.storagePath, 'auth.json'); accessoryLayoutPath = resolve(this.storagePath, 'accessories', 'uiAccessoriesLayout.json'); configBackupPath = resolve(this.storagePath, 'backups/config-backups'); instanceBackupPath = resolve(this.storagePath, 'backups/instance-backups'); homebridgeInsecureMode = Boolean(process.env.UIX_INSECURE_MODE === '1'); homebridgeVersion; runningHomebridgeModulePath; minimumNodeVersion = '22.12.0'; runningInDocker = Boolean(process.env.HOMEBRIDGE_CONFIG_UI === '1'); runningInSynologyPackage = Boolean(process.env.HOMEBRIDGE_SYNOLOGY_PACKAGE === '1'); runningInPackageMode = Boolean(process.env.HOMEBRIDGE_APT_PACKAGE === '1'); runningInLinux = (!this.runningInDocker && !this.runningInSynologyPackage && !this.runningInPackageMode && platform() === 'linux'); runningInFreeBSD = (platform() === 'freebsd'); canShutdownRestartHost = (this.runningInLinux || process.env.UIX_CAN_SHUTDOWN_RESTART_HOST === '1'); enableTerminalAccess = (this.runningInDocker && process.env.HOMEBRIDGE_CONFIG_UI_TERMINAL !== '0') || (this.runningInPackageMode && process.env.HOMEBRIDGE_CONFIG_UI_TERMINAL !== '0') || this.runningInSynologyPackage || Boolean(process.env.HOMEBRIDGE_CONFIG_UI_TERMINAL === '1'); usePluginBundles = (process.env.UIX_USE_PLUGIN_BUNDLES === '1'); recommendChildBridges = (totalmem() > 2e+9); runningOnRaspberryPi = false; runningOnRaspbianImage = false; startupScript = resolve(this.storagePath, 'startup.sh'); dockerOfflineUpdate = this.runningInDocker && satisfies(process.env.CONFIG_UI_VERSION, '>=4.6.2 <=4.44.1', { includePrerelease: true }); package = readJsonSync(resolve(process.env.UIX_BASE_PATH, 'package.json')); hbStartupSettings = pathExistsSync(resolve(this.storagePath, '.uix-hb-service-homebridge-startup.json')) ? readJsonSync(resolve(this.storagePath, '.uix-hb-service-homebridge-startup.json')) : {}; setupWizardComplete = true; customWallpaperHash; hbServiceUiRestartRequired = false; homebridgeConfig; ui; bridgeFreeze; uiFreeze; secrets; instanceId; constructor() { const homebridgeConfig = readJSONSync(this.configPath); this.parseConfig(homebridgeConfig); this.checkIfRunningOnRaspberryPi(); this.checkIfRunningOnRaspbianImage(); } parseConfig(homebridgeConfig) { this.homebridgeConfig = homebridgeConfig; if (!this.homebridgeConfig.bridge) { this.homebridgeConfig.bridge = {}; } this.ui = Array.isArray(this.homebridgeConfig.platforms) ? this.homebridgeConfig.platforms.find(x => x.platform === 'config') : undefined; if (!this.ui) { this.ui = { name: 'Config', }; } process.env.UIX_PLUGIN_NAME = this.ui.name || 'homebridge-config-ui-x'; if (this.runningInDocker) { this.setConfigForDocker(); } this.setConfig(); if (!this.ui.port) { this.ui.port = 8080; } if (!this.ui.sessionTimeout) { this.ui.sessionTimeout = this.ui.auth === 'none' ? 1296000 : 28800; } if (this.ui.scheduledBackupPath) { this.instanceBackupPath = this.ui.scheduledBackupPath; } else { this.instanceBackupPath = resolve(this.storagePath, 'backups/instance-backups'); } this.secrets = this.getSecrets(); this.instanceId = this.getInstanceId(); this.freezeUiSettings(); this.getCustomWallpaperHash(); } uiSettings(authorized = false) { const toReturn = { env: { canShutdownRestartHost: this.canShutdownRestartHost, customWallpaperHash: this.customWallpaperHash, dockerOfflineUpdate: this.dockerOfflineUpdate, featureFlags: this.getFeatureFlags(), homebridgeVersion: this.homebridgeVersion || null, homebridgeInstanceName: this.homebridgeConfig.bridge.name, instanceId: this.instanceId, lang: this.ui.lang === 'auto' ? null : this.ui.lang, packageName: this.package.name, packageVersion: this.package.version, platform: platform(), port: this.ui.port, setupWizardComplete: this.setupWizardComplete, scheduledBackupDisable: Boolean(this.ui.scheduledBackupDisable), scheduledBackupPath: this.ui.scheduledBackupPath || this.instanceBackupPath, }, formAuth: Boolean(this.ui.auth !== 'none'), sessionTimeout: this.ui.sessionTimeout || 28800, sessionTimeoutInactivityBased: Boolean(this.ui.sessionTimeoutInactivityBased), lightingMode: this.ui.lightingMode || 'auto', serverTimestamp: new Date().toISOString(), theme: this.ui.theme || 'deep-purple', menuMode: this.ui.menuMode || 'default', }; if (!authorized) { return toReturn; } return { ...toReturn, env: { ...toReturn.env, enableAccessories: this.homebridgeInsecureMode, enableTerminalAccess: this.enableTerminalAccess, enableMdnsAdvertise: Boolean(this.ui.enableMdnsAdvertise), nodeVersion: process.version, recommendChildBridges: this.recommendChildBridges, runningInDocker: this.runningInDocker, runningInSynologyPackage: this.runningInSynologyPackage, runningInPackageMode: this.runningInPackageMode, runningInLinux: this.runningInLinux, runningInFreeBSD: this.runningInFreeBSD, runningOnRaspberryPi: this.runningOnRaspberryPi, runningOnRaspbianImage: this.runningOnRaspbianImage, temperatureUnits: this.ui.tempUnits || 'c', temp: this.ui.temp, log: { maxSize: this.ui.log?.maxSize, truncateSize: this.ui.log?.truncateSize, }, ssl: { key: this.ui.ssl?.key, cert: this.ui.ssl?.cert, pfx: this.ui.ssl?.pfx, hasPassphrase: Boolean(this.ui.ssl?.passphrase), selfSigned: this.ui.ssl?.selfSigned, selfSignedHostnames: this.ui.ssl?.selfSignedHostnames, }, accessoryControl: { debug: this.ui.accessoryControl?.debug, instanceBlacklist: this.ui.accessoryControl?.instanceBlacklist || [], }, plugins: { hideUpdatesFor: this.ui.plugins?.hideUpdatesFor || [], showBetasFor: this.ui.plugins?.showBetasFor || [], hideChildBridgeSetupFor: this.ui.plugins?.hideChildBridgeSetupFor || [], }, nodeUpdatePolicy: this.ui.nodeUpdatePolicy || 'all', homebridgeUpdatePolicy: this.ui.homebridgeUpdatePolicy || 'all', homebridgeUiUpdatePolicy: this.ui.homebridgeUiUpdatePolicy || 'all', scheduledRestartCron: this.ui.scheduledRestartCron || null, bridges: this.ui.bridges || [], linux: { shutdown: this.ui.linux?.shutdown, restart: this.ui.linux?.restart, }, terminal: { persistence: this.ui.terminal?.persistence, hideWarning: this.ui.terminal?.hideWarning, bufferSize: this.ui.terminal?.bufferSize || globalThis.terminal.bufferSize, fontSize: this.ui.terminal?.fontSize, fontWeight: this.ui.terminal?.fontWeight, lightingMode: this.ui.terminal?.lightingMode, }, }, menuMode: this.ui.menuMode || 'default', wallpaper: this.ui.wallpaper, host: this.ui.host || '', proxyHost: this.ui.proxyHost || '', homebridgePackagePath: this.ui.homebridgePackagePath, disableServerMetricsMonitoring: this.ui.disableServerMetricsMonitoring, keepOrphans: this.hbStartupSettings?.keepOrphans || false, }; } async uiRestartRequired() { if (this.hbServiceUiRestartRequired) { return true; } const currentPackage = await readJson(resolve(process.env.UIX_BASE_PATH, 'package.json')); if (currentPackage.version !== this.package.version) { return true; } return !(isDeepStrictEqual(this.ui, this.uiFreeze) && isDeepStrictEqual(this.homebridgeConfig.bridge, this.bridgeFreeze)); } freezeUiSettings() { if (!this.uiFreeze) { this.uiFreeze = {}; Object.assign(this.uiFreeze, this.ui); } if (!this.bridgeFreeze) { this.bridgeFreeze = {}; Object.assign(this.bridgeFreeze, this.homebridgeConfig.bridge); } } setConfigForDocker() { this.ui.restart = 'killall -15 homebridge; sleep 5.1; killall -9 homebridge; kill -9 $(pidof homebridge-config-ui-x);'; this.homebridgeInsecureMode = Boolean(process.env.HOMEBRIDGE_INSECURE === '1'); this.ui.sudo = false; this.ui.log = { method: 'file', path: '/homebridge/logs/homebridge.log', maxSize: this.ui.log?.maxSize, truncateSize: this.ui.log?.truncateSize, }; if (!this.ui.port && process.env.HOMEBRIDGE_CONFIG_UI_PORT) { this.ui.port = Number.parseInt(process.env.HOMEBRIDGE_CONFIG_UI_PORT, 10); } this.ui.theme = this.ui.theme || process.env.HOMEBRIDGE_CONFIG_UI_THEME || 'deep-purple'; this.ui.auth = this.ui.auth || process.env.HOMEBRIDGE_CONFIG_UI_AUTH || 'form'; this.ui.wallpaper = this.ui.wallpaper || process.env.HOMEBRIDGE_CONFIG_UI_LOGIN_WALLPAPER || undefined; } setConfig() { this.homebridgeInsecureMode = Boolean(process.env.UIX_INSECURE_MODE === '1'); this.ui.restart = undefined; this.ui.sudo = (platform() === 'linux' && !this.runningInDocker && !this.runningInSynologyPackage && !this.runningInPackageMode) || platform() === 'freebsd'; this.ui.log = { method: 'native', path: resolve(this.storagePath, 'homebridge.log'), maxSize: this.ui.log?.maxSize, truncateSize: this.ui.log?.truncateSize, }; } getSecrets() { if (pathExistsSync(this.secretPath)) { try { const secrets = readJsonSync(this.secretPath); if (!secrets.secretKey) { return this.generateSecretToken(); } else { return secrets; } } catch (e) { return this.generateSecretToken(); } } else { return this.generateSecretToken(); } } generateSecretToken() { const secrets = { secretKey: randomBytes(32).toString('hex'), }; writeJsonSync(this.secretPath, secrets); return secrets; } getInstanceId() { return createHash('sha256').update(this.secrets.secretKey).digest('hex'); } async getCustomWallpaperHash() { try { if (this.ui.wallpaper) { const filePath = resolve(this.storagePath, this.ui.wallpaper); const fileStat = await stat(filePath); const hash = createHash('sha256'); hash.update(`${fileStat.birthtime}${fileStat.ctime}${fileStat.size}${fileStat.blocks}`); this.customWallpaperHash = `${hash.digest('hex')}.jpg`; } } catch (e) { } } removeWallpaperCache() { this.customWallpaperHash = undefined; } streamCustomWallpaper() { return createReadStream(resolve(this.storagePath, this.ui.wallpaper)); } async checkIfRunningOnRaspberryPi() { try { this.runningOnRaspberryPi = await pathExists('/usr/bin/vcgencmd') && await pathExists('/usr/bin/raspi-config'); } catch (e) { this.runningOnRaspberryPi = false; } } async checkIfRunningOnRaspbianImage() { try { this.runningOnRaspbianImage = platform() === 'linux' && await pathExists('/etc/hb-ui-port'); } catch (e) { this.runningOnRaspbianImage = false; } } getFeatureFlags() { const featureFlags = {}; if (!this.homebridgeVersion) { FEATURE_FLAGS.forEach((flag) => { featureFlags[flag.key] = false; }); return featureFlags; } FEATURE_FLAGS.forEach((flag) => { try { featureFlags[flag.key] = satisfies(this.homebridgeVersion, flag.range, { includePrerelease: true }); } catch (error) { featureFlags[flag.key] = false; } }); return featureFlags; } isMatterEnabled() { const config = this.homebridgeConfig; if (!config) { return false; } const matterEnabled = (matter) => !!matter && matter.enabled !== false; if (matterEnabled(config.bridge?.matter)) { return true; } if (Array.isArray(config.platforms) && config.platforms.some(p => matterEnabled(p?._bridge?.matter))) { return true; } if (Array.isArray(config.accessories) && config.accessories.some(a => matterEnabled(a?._bridge?.matter))) { return true; } return false; } getNodeUpdatePolicy() { return this.ui?.nodeUpdatePolicy || 'all'; } }; ConfigService = __decorate([ Injectable(), __metadata("design:paramtypes", []) ], ConfigService); export { ConfigService }; //# sourceMappingURL=config.service.js.map