From 27835a0fb4c3ac7fe2465289bbebd53d79c2a251 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Thie=C3=9Fen?= Date: Mon, 1 Jan 2024 19:08:47 +0100 Subject: [PATCH] feat: Notify systemd for start, stop, watchdog --- lib/controller.ts | 18 ++++++++++++++++++ package-lock.json | 21 +++++++++++++++++++++ package.json | 3 +++ scripts/install.sh | 4 +++- test/controller.test.js | 11 +++++++++++ 5 files changed, 56 insertions(+), 1 deletion(-) diff --git a/lib/controller.ts b/lib/controller.ts index 0519158aca..d989e4ed35 100644 --- a/lib/controller.ts +++ b/lib/controller.ts @@ -39,6 +39,14 @@ const AllExtensions = [ type ExtensionArgs = [Zigbee, MQTT, State, PublishEntityState, EventBus, (enable: boolean, name: string) => Promise, () => void, (extension: Extension) => Promise]; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +let sdNotify: any = null; +try { + sdNotify = process.env.NOTIFY_SOCKET ? require('sd-notify') : null; +} catch { + // sd-notify is optional +} + export class Controller { private eventBus: EventBus; private zigbee: Zigbee; @@ -162,6 +170,12 @@ export class Controller { (data) => utils.publishLastSeen(data, settings.get(), false, this.publishEntityState)); logger.info(`Zigbee2MQTT started!`); + + const watchdogInterval = sdNotify?.watchdogInterval() || 0; + if (watchdogInterval > 0) { + sdNotify.startWatchdogMode(Math.floor(watchdogInterval / 2)); + } + sdNotify?.ready(); } @bind async enableDisableExtension(enable: boolean, name: string): Promise { @@ -186,6 +200,8 @@ export class Controller { } async stop(restart = false): Promise { + sdNotify?.stopping(); + // Call extensions await this.callExtensions('stop', this.extensions); this.eventBus.removeListeners(this); @@ -202,6 +218,8 @@ export class Controller { logger.error('Failed to stop Zigbee2MQTT'); await this.exit(1, restart); } + + sdNotify?.stopWatchdogMode(); } async exit(code: number, restart = false): Promise { diff --git a/package-lock.json b/package-lock.json index 61773493f1..c7c33527cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -67,6 +67,9 @@ }, "engines": { "node": "^18 || ^20 || ^21" + }, + "optionalDependencies": { + "sd-notify": "^2.8.0" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -8764,6 +8767,24 @@ "node": ">=10" } }, + "node_modules/sd-notify": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/sd-notify/-/sd-notify-2.8.0.tgz", + "integrity": "sha512-e+D1v0Y6UzmqXcPlaTkHk1QMdqk36mF/jIYv5gwry/N2Tb8/UNnpfG6ktGLpeBOR6TCC5hPKgqA+0hTl9sm2tA==", + "hasInstallScript": true, + "optional": true, + "os": [ + "linux", + "darwin", + "win32" + ], + "dependencies": { + "bindings": "1.5.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", diff --git a/package.json b/package.json index 2d09148b3b..bbd3430c50 100644 --- a/package.json +++ b/package.json @@ -111,5 +111,8 @@ }, "bin": { "zigbee2mqtt": "cli.js" + }, + "optionalDependencies": { + "sd-notify": "^2.8.0" } } diff --git a/scripts/install.sh b/scripts/install.sh index 00f8b76d97..38a4c1eac5 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -36,10 +36,12 @@ Description=zigbee2mqtt After=network.target [Service] -ExecStart=/usr/bin/npm start +Type=notify +ExecStart=/usr/bin/node index.js WorkingDirectory=/opt/zigbee2mqtt StandardOutput=inherit StandardError=inherit +WatchdogSec=10s Restart=always User=pi diff --git a/test/controller.test.js b/test/controller.test.js index aa6b6e60d3..9b6054a89e 100644 --- a/test/controller.test.js +++ b/test/controller.test.js @@ -1,3 +1,4 @@ +process.env.NOTIFY_SOCKET = "mocked"; const data = require('./stub/data'); const logger = require('./stub/logger'); const zigbeeHerdsman = require('./stub/zigbeeHerdsman'); @@ -16,6 +17,16 @@ const mocksClear = [ const fs = require('fs'); +jest.mock('sd-notify', () => { + return { + watchdogInterval: () => {return 3000;}, + startWatchdogMode: (interval) => {}, + stopWatchdogMode: () => {}, + ready: () => {}, + stopping: () => {}, + }; +}, {virtual: true}); + describe('Controller', () => { let controller; let mockExit;