UNPKG

homebridge-config-ui-x

Version:

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

621 lines • 30.3 kB
"use strict"; 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); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.BackupService = void 0; const node_child_process_1 = require("node:child_process"); const node_events_1 = require("node:events"); const node_os_1 = require("node:os"); const node_path_1 = require("node:path"); const node_process_1 = __importDefault(require("node:process")); const node_stream_1 = require("node:stream"); const node_util_1 = require("node:util"); const common_1 = require("@nestjs/common"); const bash_color_1 = require("bash-color"); const dayjs_1 = __importDefault(require("dayjs")); const fs_extra_1 = require("fs-extra"); const systeminformation_1 = require("systeminformation"); const tar_1 = require("tar"); const unzipper_1 = require("unzipper"); const config_service_1 = require("../../core/config/config.service"); const homebridge_ipc_service_1 = require("../../core/homebridge-ipc/homebridge-ipc.service"); const logger_service_1 = require("../../core/logger/logger.service"); const scheduler_service_1 = require("../../core/scheduler/scheduler.service"); const plugins_service_1 = require("../plugins/plugins.service"); const pump = (0, node_util_1.promisify)(node_stream_1.pipeline); let BackupService = class BackupService { constructor(configService, pluginsService, schedulerService, homebridgeIpcService, logger) { this.configService = configService; this.pluginsService = pluginsService; this.schedulerService = schedulerService; this.homebridgeIpcService = homebridgeIpcService; this.logger = logger; this.scheduleInstanceBackups(); } scheduleInstanceBackups() { if (this.configService.ui.scheduledBackupDisable === true) { this.logger.debug('Scheduled backups disabled.'); return; } const scheduleRule = new this.schedulerService.RecurrenceRule(); scheduleRule.hour = Math.floor(Math.random() * 7); scheduleRule.minute = Math.floor(Math.random() * 59); scheduleRule.second = Math.floor(Math.random() * 59); this.schedulerService.scheduleJob('instance-backup', scheduleRule, () => { this.logger.log('Running scheduled instance backup...'); this.runScheduledBackupJob(); }); } async createBackup() { const instanceId = this.configService.homebridgeConfig.bridge.username.replace(/:/g, ''); const backupDir = await (0, fs_extra_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'homebridge-backup-')); const backupFileName = `homebridge-backup-${instanceId}.${new Date().getTime().toString()}.tar.gz`; const backupPath = (0, node_path_1.resolve)(backupDir, backupFileName); this.logger.log(`Creating temporary backup archive at ${backupPath}.`); try { const storagePath = await (0, fs_extra_1.realpath)(this.configService.storagePath); await (0, fs_extra_1.copy)(storagePath, (0, node_path_1.resolve)(backupDir, 'storage'), { filter: async (filePath) => { if ([ 'instance-backups', 'nssm.exe', 'homebridge.log', 'logs', 'node_modules', 'startup.sh', '.docker.env', 'docker-compose.yml', 'pnpm-lock.yaml', 'package.json', 'package-lock.json', '.npmrc', '.npm', 'FFmpeg', 'fdk-aac', '.git', 'recordings', '.homebridge.sock', '#recycle', '@eaDir', '.venv', '.cache', ].includes((0, node_path_1.basename)(filePath))) { return false; } try { const stat = await (0, fs_extra_1.lstat)(filePath); return (stat.isDirectory() || stat.isFile()); } catch (e) { return false; } }, }); const installedPlugins = await this.pluginsService.getInstalledPlugins(); await (0, fs_extra_1.writeJson)((0, node_path_1.resolve)(backupDir, 'plugins.json'), installedPlugins); await (0, fs_extra_1.writeJson)((0, node_path_1.resolve)(backupDir, 'info.json'), { timestamp: new Date().toISOString(), platform: (0, node_os_1.platform)(), uix: this.configService.package.version, node: node_process_1.default.version, }); await (0, tar_1.create)({ portable: true, gzip: true, file: backupPath, cwd: backupDir, filter: (filePath, stat) => { if (stat.size > globalThis.backup.maxBackupFileSize) { this.logger.warn(`Backup is skipping ${filePath} because it is larger than ${globalThis.backup.maxBackupFileSizeText}.`); return false; } return true; }, }, [ 'storage', 'plugins.json', 'info.json', ]); if ((0, fs_extra_1.statSync)(backupPath).size > globalThis.backup.maxBackupSize) { this.logger.error(`Backup file exceeds maximum restore file size (${globalThis.backup.maxBackupSizeText}) ${((0, fs_extra_1.statSync)(backupPath).size / (1024 * 1024)).toFixed(1)}MB.`); } } catch (e) { this.logger.log(`Backup failed, removing ${backupDir}.`); await (0, fs_extra_1.remove)((0, node_path_1.resolve)(backupDir)); throw e; } return { instanceId, backupDir, backupPath, backupFileName, }; } async ensureScheduledBackupPath() { if (this.configService.ui.scheduledBackupPath) { if (!await (0, fs_extra_1.pathExists)(this.configService.instanceBackupPath)) { throw new Error('Custom instance backup path does not exist'); } try { await (0, fs_extra_1.access)(this.configService.instanceBackupPath, fs_extra_1.constants.W_OK | fs_extra_1.constants.R_OK); } catch (e) { throw new Error(`Custom instance backup path is not writable / readable by service: ${e.message}`); } } else { return await (0, fs_extra_1.ensureDir)(this.configService.instanceBackupPath); } } async runScheduledBackupJob() { try { await this.ensureScheduledBackupPath(); } catch (e) { this.logger.warn(`Could not run scheduled backup as ${e.message}.`); return; } try { const { backupDir, backupPath, instanceId } = await this.createBackup(); await (0, fs_extra_1.copy)(backupPath, (0, node_path_1.resolve)(this.configService.instanceBackupPath, `homebridge-backup-${instanceId}.${new Date().getTime().toString()}.tar.gz`)); await (0, fs_extra_1.remove)((0, node_path_1.resolve)(backupDir)); } catch (e) { this.logger.warn(`Failed to create scheduled instance backup as ${e.message}.`); } try { const backups = await this.listScheduledBackups(); for (const backup of backups) { if ((0, dayjs_1.default)().diff((0, dayjs_1.default)(backup.timestamp), 'day') >= 7) { await (0, fs_extra_1.remove)((0, node_path_1.resolve)(this.configService.instanceBackupPath, backup.fileName)); } } } catch (e) { this.logger.warn(`Failed to remove old backups as ${e.message}.`); } } async getNextBackupTime() { if (this.configService.ui.scheduledBackupDisable === true) { return { next: false, }; } else { return { next: this.schedulerService.scheduledJobs['instance-backup']?.nextInvocation() || false, }; } } async listScheduledBackups() { try { await this.ensureScheduledBackupPath(); const dirContents = await (0, fs_extra_1.readdir)(this.configService.instanceBackupPath, { withFileTypes: true }); return dirContents .filter(x => x.isFile() && x.name.match(/^homebridge-backup-[0-9A-Za-z]{12}.\d{09,15}.tar.gz/)) .map((x) => { const split = x.name.split('.'); const instanceId = split[0].split('-')[2]; if (split.length === 4 && !Number.isNaN(split[1])) { return { id: `${instanceId}.${split[1]}`, instanceId: split[0].split('-')[2], timestamp: new Date(Number.parseInt(split[1], 10)), fileName: x.name, size: ((0, fs_extra_1.statSync)(`${this.configService.instanceBackupPath}/${x.name}`).size / (1024 * 1024)).toFixed(1), maxBackupSize: globalThis.backup.maxBackupSize / (1024 * 1024), maxBackupSizeText: globalThis.backup.maxBackupSizeText, }; } else { return null; } }) .filter(x => x !== null) .sort((a, b) => { if (a.id > b.id) { return -1; } else if (a.id < b.id) { return -2; } else { return 0; } }); } catch (e) { this.logger.warn(`Could not get scheduled backups as ${e.message}.`); throw new common_1.InternalServerErrorException(e.message); } } async getScheduledBackup(backupId) { const backupPath = (0, node_path_1.resolve)(this.configService.instanceBackupPath, `homebridge-backup-${backupId}.tar.gz`); if (!await (0, fs_extra_1.pathExists)(backupPath)) { throw new common_1.NotFoundException(); } return new common_1.StreamableFile((0, fs_extra_1.createReadStream)(backupPath)); } async deleteScheduledBackup(backupId) { const backupPath = (0, node_path_1.resolve)(this.configService.instanceBackupPath, `homebridge-backup-${backupId}.tar.gz`); if (!await (0, fs_extra_1.pathExists)(backupPath)) { throw new common_1.NotFoundException(); } try { await (0, fs_extra_1.remove)(backupPath); this.logger.warn(`Scheduled backup ${backupId} deleted by request.`); } catch (e) { this.logger.warn(`Failed to delete scheduled backup by request as ${e.message}.`); throw new common_1.InternalServerErrorException(e.message); } } async restoreScheduledBackup(backupId) { const backupPath = (0, node_path_1.resolve)(this.configService.instanceBackupPath, `homebridge-backup-${backupId}.tar.gz`); if (!await (0, fs_extra_1.pathExists)(backupPath)) { throw new common_1.NotFoundException(); } this.restoreDirectory = undefined; const restoreDir = await (0, fs_extra_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'homebridge-backup-')); await pump((0, fs_extra_1.createReadStream)(backupPath), (0, tar_1.extract)({ cwd: restoreDir, })); this.restoreDirectory = restoreDir; } async downloadBackup(reply) { const { backupDir, backupPath, backupFileName } = await this.createBackup(); async function cleanup() { await (0, fs_extra_1.remove)((0, node_path_1.resolve)(backupDir)); this.logger.log(`Backup complete, removing ${backupDir}.`); } reply.raw.setHeader('Content-type', 'application/octet-stream'); reply.raw.setHeader('Content-disposition', `attachment; filename=${backupFileName}`); reply.raw.setHeader('File-Name', backupFileName); if (reply.request.hostname === 'localhost:8080') { reply.raw.setHeader('access-control-allow-origin', 'http://localhost:4200'); } return new common_1.StreamableFile((0, fs_extra_1.createReadStream)(backupPath).on('close', cleanup.bind(this))); } async uploadBackupRestore(data) { this.restoreDirectory = undefined; const backupDir = await (0, fs_extra_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'homebridge-backup-')); await pump(data.file, (0, tar_1.extract)({ cwd: backupDir, })); this.restoreDirectory = backupDir; } async removeRestoreDirectory() { if (this.restoreDirectory) { return await (0, fs_extra_1.remove)(this.restoreDirectory); } } async triggerHeadlessRestore() { if (!await (0, fs_extra_1.pathExists)(this.restoreDirectory)) { throw new common_1.BadRequestException('No backup file uploaded'); } const client = new node_events_1.EventEmitter(); client.on('stdout', (data) => { this.logger.log(data); }); client.on('stderr', (data) => { this.logger.log(data); }); await this.restoreFromBackup(client, true); return { status: 0 }; } async restoreFromBackup(client, autoRestart = false) { if (!this.restoreDirectory) { throw new common_1.BadRequestException(); } if (!await (0, fs_extra_1.pathExists)((0, node_path_1.resolve)(this.restoreDirectory, 'info.json'))) { await this.removeRestoreDirectory(); throw new Error('Uploaded file is not a valid Homebridge Backup Archive.'); } if (!await (0, fs_extra_1.pathExists)((0, node_path_1.resolve)(this.restoreDirectory, 'plugins.json'))) { await this.removeRestoreDirectory(); throw new Error('Uploaded file is not a valid Homebridge Backup Archive.'); } if (!await (0, fs_extra_1.pathExists)((0, node_path_1.resolve)(this.restoreDirectory, 'storage'))) { await this.removeRestoreDirectory(); throw new Error('Uploaded file is not a valid Homebridge Backup Archive.'); } const backupInfo = await (0, fs_extra_1.readJson)((0, node_path_1.resolve)(this.restoreDirectory, 'info.json')); client.emit('stdout', (0, bash_color_1.cyan)('Backup Archive Information\r\n')); client.emit('stdout', `Source Node.js Version: ${backupInfo.node}\r\n`); client.emit('stdout', `Source Homebridge UI Version: v${backupInfo.uix}\r\n`); client.emit('stdout', `Source Platform: ${backupInfo.platform}\r\n`); client.emit('stdout', `Created: ${backupInfo.timestamp}\r\n`); this.logger.warn('Starting backup restore...'); client.emit('stdout', (0, bash_color_1.cyan)('\r\nRestoring backup...\r\n\r\n')); await new Promise(res => setTimeout(res, 1000)); const restoreFilter = [ (0, node_path_1.join)(this.restoreDirectory, 'storage', 'package.json'), (0, node_path_1.join)(this.restoreDirectory, 'storage', 'package-lock.json'), (0, node_path_1.join)(this.restoreDirectory, 'storage', '.npmrc'), (0, node_path_1.join)(this.restoreDirectory, 'storage', 'docker-compose.yml'), ]; const storagePath = await (0, fs_extra_1.realpath)(this.configService.storagePath); client.emit('stdout', (0, bash_color_1.yellow)(`Restoring Homebridge storage to ${storagePath}\r\n`)); await new Promise(res => setTimeout(res, 100)); await (0, fs_extra_1.copy)((0, node_path_1.resolve)(this.restoreDirectory, 'storage'), storagePath, { filter: async (filePath) => { if (restoreFilter.includes(filePath)) { client.emit('stdout', `Skipping ${(0, node_path_1.basename)(filePath)}\r\n`); return false; } try { const stat = await (0, fs_extra_1.lstat)(filePath); if (stat.isDirectory() || stat.isFile()) { client.emit('stdout', `Restoring ${(0, node_path_1.basename)(filePath)}\r\n`); return true; } else { client.emit('stdout', `Skipping ${(0, node_path_1.basename)(filePath)}\r\n`); return false; } } catch (e) { client.emit('stdout', `Skipping ${(0, node_path_1.basename)(filePath)}\r\n`); return false; } }, }); client.emit('stdout', (0, bash_color_1.yellow)('File restore complete.\r\n')); await new Promise(res => setTimeout(res, 1000)); client.emit('stdout', (0, bash_color_1.cyan)('\r\nRestoring plugins...\r\n')); const plugins = (await (0, fs_extra_1.readJson)((0, node_path_1.resolve)(this.restoreDirectory, 'plugins.json'))) .filter((x) => ![ 'homebridge-config-ui-x', ].includes(x.name) && x.publicPackage); for (const plugin of plugins) { try { client.emit('stdout', (0, bash_color_1.yellow)(`\r\nInstalling ${plugin.name}...\r\n`)); await this.pluginsService.managePlugin('install', { name: plugin.name, version: plugin.installedVersion }, client); } catch (e) { client.emit('stdout', (0, bash_color_1.red)(`Failed to install ${plugin.name}.\r\n`)); } } const restoredConfig = await (0, fs_extra_1.readJson)(this.configService.configPath); if (restoredConfig.bridge) { restoredConfig.bridge.port = this.configService.homebridgeConfig.bridge.port; } if (restoredConfig.bridge.bind) { this.checkBridgeBindConfig(restoredConfig); } if (!Array.isArray(restoredConfig.platforms)) { restoredConfig.platforms = []; } const uiConfigBlock = restoredConfig.platforms.find(x => x.platform === 'config'); if (uiConfigBlock) { uiConfigBlock.port = this.configService.ui.port; if (this.configService.serviceMode || this.configService.runningInDocker) { delete uiConfigBlock.restart; delete uiConfigBlock.sudo; delete uiConfigBlock.log; } } else { restoredConfig.platforms.push({ name: 'Config', port: this.configService.ui.port, platform: 'config', }); } await (0, fs_extra_1.writeJson)(this.configService.configPath, restoredConfig, { spaces: 4 }); await this.removeRestoreDirectory(); client.emit('stdout', (0, bash_color_1.green)('\r\nRestore Complete!\r\n')); this.configService.hbServiceUiRestartRequired = true; if (autoRestart) { this.postBackupRestoreRestart(); } return { status: 0 }; } async uploadHbfxRestore(data) { this.restoreDirectory = undefined; const backupDir = await (0, fs_extra_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'homebridge-backup-')); this.logger.log(`Extracting .hbfx file to ${backupDir}.`); await pump(data.file, (0, unzipper_1.Extract)({ path: backupDir, })); this.restoreDirectory = backupDir; } async restoreHbfxBackup(client) { if (!this.restoreDirectory) { throw new common_1.BadRequestException(); } if (!await (0, fs_extra_1.pathExists)((0, node_path_1.resolve)(this.restoreDirectory, 'package.json'))) { await this.removeRestoreDirectory(); throw new Error('Uploaded file is not a valid HBFX Backup Archive.'); } if (!await (0, fs_extra_1.pathExists)((0, node_path_1.resolve)(this.restoreDirectory, 'etc', 'config.json'))) { await this.removeRestoreDirectory(); throw new Error('Uploaded file is not a valid HBFX Backup Archive.'); } const backupInfo = await (0, fs_extra_1.readJson)((0, node_path_1.resolve)(this.restoreDirectory, 'package.json')); client.emit('stdout', (0, bash_color_1.cyan)('Backup Archive Information\r\n')); client.emit('stdout', `Backup Source: ${backupInfo.name}\r\n`); client.emit('stdout', `Version: v${backupInfo.version}\r\n`); this.logger.warn('Starting hbfx restore...'); client.emit('stdout', (0, bash_color_1.cyan)('\r\nRestoring hbfx backup...\r\n\r\n')); await new Promise(res => setTimeout(res, 1000)); const storagePath = await (0, fs_extra_1.realpath)(this.configService.storagePath); client.emit('stdout', (0, bash_color_1.yellow)(`Restoring Homebridge storage to ${storagePath}\r\n`)); await (0, fs_extra_1.copy)((0, node_path_1.resolve)(this.restoreDirectory, 'etc'), (0, node_path_1.resolve)(storagePath), { filter: (filePath) => { if ([ 'access.json', 'dashboard.json', 'layout.json', 'config.json', ].includes((0, node_path_1.basename)(filePath))) { return false; } client.emit('stdout', `Restoring ${(0, node_path_1.basename)(filePath)}\r\n`); return true; }, }); const sourceAccessoriesPath = (0, node_path_1.resolve)(this.restoreDirectory, 'etc', 'accessories'); const targetAccessoriesPath = (0, node_path_1.resolve)(storagePath, 'accessories'); if (await (0, fs_extra_1.pathExists)(sourceAccessoriesPath)) { await (0, fs_extra_1.copy)(sourceAccessoriesPath, targetAccessoriesPath, { filter: (filePath) => { client.emit('stdout', `Restoring ${(0, node_path_1.basename)(filePath)}\r\n`); return true; }, }); } const sourceConfig = await (0, fs_extra_1.readJson)((0, node_path_1.resolve)(this.restoreDirectory, 'etc', 'config.json')); const pluginMap = { 'hue': 'homebridge-hue', 'chamberlain': 'homebridge-chamberlain', 'google-home': 'homebridge-gsh', 'ikea-tradfri': 'homebridge-ikea-tradfri-gateway', 'nest': 'homebridge-nest', 'ring': 'homebridge-ring', 'roborock': 'homebridge-roborock', 'shelly': 'homebridge-shelly', 'wink': 'homebridge-wink3', 'homebridge-tuya-web': '@milo526/homebridge-tuya-web', }; if (sourceConfig.plugins?.length) { for (let plugin of sourceConfig.plugins) { if (plugin in pluginMap) { plugin = pluginMap[plugin]; } try { client.emit('stdout', (0, bash_color_1.yellow)(`\r\nInstalling ${plugin}...\r\n`)); await this.pluginsService.managePlugin('install', { name: plugin, version: 'latest' }, client); } catch (e) { client.emit('stdout', (0, bash_color_1.red)(`Failed to install ${plugin}.\r\n`)); } } } const targetConfig = JSON.parse(JSON.stringify({ bridge: sourceConfig.bridge, accessories: sourceConfig.accessories?.map((x) => { delete x.plugin_map; return x; }) || [], platforms: sourceConfig.platforms?.map((x) => { if (x.platform === 'google-home') { x.platform = 'google-smarthome'; x.notice = 'Keep your token a secret!'; } delete x.plugin_map; return x; }) || [], })); targetConfig.bridge.name = `Homebridge ${targetConfig.bridge.username.substring(targetConfig.bridge.username.length - 5).replace(/:/g, '')}`; if (targetConfig.bridge.bind) { this.checkBridgeBindConfig(targetConfig); } targetConfig.platforms.push(this.configService.ui); await (0, fs_extra_1.writeJson)(this.configService.configPath, targetConfig, { spaces: 4 }); await this.removeRestoreDirectory(); client.emit('stdout', (0, bash_color_1.green)('\r\nRestore Complete!\r\n')); this.configService.hbServiceUiRestartRequired = true; return { status: 0 }; } postBackupRestoreRestart() { setTimeout(() => { if (this.configService.serviceMode) { this.homebridgeIpcService.killHomebridge(); setTimeout(() => { node_process_1.default.kill(node_process_1.default.pid, 'SIGKILL'); }, 500); return; } if (this.configService.runningInDocker) { try { return (0, node_child_process_1.execSync)('killall -9 homebridge; kill -9 $(pidof homebridge-config-ui-x);'); } catch (e) { this.logger.error(`Failed to restart Homebridge as ${e.message}.`); this.logger.error(e); } } if (node_process_1.default.connected) { node_process_1.default.kill(node_process_1.default.ppid, 'SIGKILL'); node_process_1.default.kill(node_process_1.default.pid, 'SIGKILL'); } if (this.configService.ui.noFork) { return node_process_1.default.kill(node_process_1.default.pid, 'SIGKILL'); } if ((0, node_os_1.platform)() === 'linux' && this.configService.ui.standalone) { try { const getPidByPort = (port) => { try { return Number.parseInt((0, node_child_process_1.execSync)(`fuser ${port}/tcp 2>/dev/null`).toString('utf8').trim(), 10); } catch (e) { return null; } }; const getPidByName = () => { try { return Number.parseInt((0, node_child_process_1.execSync)('pidof homebridge').toString('utf8').trim(), 10); } catch (e) { return null; } }; const homebridgePid = getPidByPort(this.configService.homebridgeConfig.bridge.port) || getPidByName(); if (homebridgePid) { node_process_1.default.kill(homebridgePid, 'SIGKILL'); return node_process_1.default.kill(node_process_1.default.pid, 'SIGKILL'); } } catch (e) { } } if (this.configService.ui.restart) { return (0, node_child_process_1.exec)(this.configService.ui.restart, (err) => { if (err) { this.logger.log('Restart command exited with an error, failed to restart Homebridge.'); } }); } return node_process_1.default.kill(node_process_1.default.pid, 'SIGKILL'); }, 500); return { status: 0 }; } checkBridgeBindConfig(restoredConfig) { if (restoredConfig.bridge.bind) { if (typeof restoredConfig.bridge.bind === 'string') { restoredConfig.bridge.bind = [restoredConfig.bridge.bind]; } if (!Array.isArray(restoredConfig.bridge.bind)) { delete restoredConfig.bridge.bind; return; } const interfaces = (0, systeminformation_1.networkInterfaces)(); restoredConfig.bridge.bind = restoredConfig.bridge.bind.filter(x => interfaces[x]); if (!restoredConfig.bridge.bind) { delete restoredConfig.bridge.bind; } } } }; exports.BackupService = BackupService; exports.BackupService = BackupService = __decorate([ (0, common_1.Injectable)(), __metadata("design:paramtypes", [config_service_1.ConfigService, plugins_service_1.PluginsService, scheduler_service_1.SchedulerService, homebridge_ipc_service_1.HomebridgeIpcService, logger_service_1.Logger]) ], BackupService); //# sourceMappingURL=backup.service.js.map