@@ -9,41 +9,76 @@ require('source-map-support').install();
9
9
10
10
let controller ;
11
11
let stopping = false ;
12
+ let watchdog = process . env . Z2M_WATCHDOG != undefined ;
13
+ let watchdogCount = 0 ;
14
+ let unsolicitedStop = false ;
15
+ // csv in minutes, default: 1min, 5min, 15min, 30min, 60min
16
+ let watchdogDelays = [ 60000 , 300000 , 900000 , 1800000 , 3600000 ] ;
17
+
18
+ if ( watchdog && process . env . Z2M_WATCHDOG !== 'default' ) {
19
+ if ( / ^ (?: (?: [ 0 - 9 ] * [ . ] ) ? [ 0 - 9 ] + ) + (?: , ? (?: [ 0 - 9 ] * [ . ] ) ? [ 0 - 9 ] + ) * $ / . test ( process . env . Z2M_WATCHDOG ) ) {
20
+ watchdogDelays = process . env . Z2M_WATCHDOG . split ( ',' ) . map ( ( v ) => parseFloat ( v ) * 60000 ) ;
21
+ } else {
22
+ console . log ( `Invalid watchdog delays (must use number-only CSV format representing minutes, example: 'Z2M_WATCHDOG=1,5,15,30,60'.` ) ;
23
+ process . exit ( 1 ) ;
24
+ }
25
+ }
12
26
13
27
const hashFile = path . join ( __dirname , 'dist' , '.hash' ) ;
14
28
29
+ async function triggerWatchdog ( code ) {
30
+ const delay = watchdogDelays [ watchdogCount ] ;
31
+ watchdogCount += 1 ;
32
+
33
+ if ( delay ) {
34
+ // garbage collector
35
+ controller = undefined ;
36
+
37
+ console . log ( `WATCHDOG: Waiting ${ delay / 60000 } min before next start try.` ) ;
38
+ await new Promise ( ( resolve ) => setTimeout ( resolve , delay ) ) ;
39
+ await start ( ) ;
40
+ } else {
41
+ process . exit ( code ) ;
42
+ }
43
+ }
44
+
15
45
async function restart ( ) {
16
46
await stop ( true ) ;
17
47
await start ( ) ;
18
48
}
19
49
20
- async function exit ( code , restart ) {
50
+ async function exit ( code , restart = false ) {
21
51
if ( ! restart ) {
22
- process . exit ( code ) ;
52
+ if ( watchdog && unsolicitedStop ) {
53
+ await triggerWatchdog ( code ) ;
54
+ } else {
55
+ process . exit ( code ) ;
56
+ }
23
57
}
24
58
}
25
59
26
60
async function currentHash ( ) {
27
61
const git = require ( 'git-last-commit' ) ;
62
+
28
63
return new Promise ( ( resolve ) => {
29
- git . getLastCommit ( ( err , commit ) => {
30
- if ( err ) resolve ( 'unknown' ) ;
31
- else resolve ( commit . shortHash ) ;
32
- } ) ;
64
+ git . getLastCommit ( ( err , commit ) => err ? resolve ( 'unknown' ) : resolve ( commit . shortHash ) ) ;
33
65
} ) ;
34
66
}
35
67
36
68
async function writeHash ( ) {
37
69
const hash = await currentHash ( ) ;
70
+
38
71
fs . writeFileSync ( hashFile , hash ) ;
39
72
}
40
73
41
74
async function build ( reason ) {
42
75
return new Promise ( ( resolve , reject ) => {
43
76
process . stdout . write ( `Building Zigbee2MQTT... (${ reason } )` ) ;
44
77
rimrafSync ( 'dist' ) ;
78
+
45
79
const env = { ...process . env } ;
46
80
const _600mb = 629145600 ;
81
+
47
82
if ( _600mb > os . totalmem ( ) && ! env . NODE_OPTIONS ) {
48
83
// Prevent OOM on tsc compile for system with low memory
49
84
// https://github.com/Koenkk/zigbee2mqtt/issues/12034
@@ -53,10 +88,11 @@ async function build(reason) {
53
88
exec ( 'npm run build' , { env, cwd : __dirname } , async ( err , stdout , stderr ) => {
54
89
if ( err ) {
55
90
process . stdout . write ( ', failed\n' ) ;
91
+
56
92
if ( err . code === 134 ) {
57
- process . stderr . write (
58
- '\n\nBuild failed; ran out-of-memory, free some memory (RAM) and start again\n\n' ) ;
93
+ process . stderr . write ( '\n\nBuild failed; ran out-of-memory, free some memory (RAM) and start again\n\n' ) ;
59
94
}
95
+
60
96
reject ( err ) ;
61
97
} else {
62
98
process . stdout . write ( ', finished\n' ) ;
@@ -79,42 +115,65 @@ async function checkDist() {
79
115
}
80
116
81
117
async function start ( ) {
118
+ console . log ( `Starting Zigbee2MQTT ${ watchdog ? `with watchdog (${ watchdogDelays } )` : `without watchdog` } .` ) ;
82
119
await checkDist ( ) ;
83
120
84
121
const version = engines . node ;
122
+
85
123
if ( ! semver . satisfies ( process . version , version ) ) {
86
- console . log ( `\t\tZigbee2MQTT requires node version ${ version } , you are running ${ process . version } !\n` ) ; // eslint-disable-line
124
+ console . log ( `\t\tZigbee2MQTT requires node version ${ version } , you are running ${ process . version } !\n` ) ;
87
125
}
88
126
89
127
// Validate settings
90
128
const settings = require ( './dist/util/settings' ) ;
129
+
91
130
settings . reRead ( ) ;
131
+
92
132
const errors = settings . validate ( ) ;
133
+
93
134
if ( errors . length > 0 ) {
135
+ unsolicitedStop = false ;
136
+
94
137
console . log ( `\n\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!` ) ;
95
138
console . log ( ' READ THIS CAREFULLY\n' ) ;
96
139
console . log ( `Refusing to start because configuration is not valid, found the following errors:` ) ;
140
+
97
141
for ( const error of errors ) {
98
142
console . log ( `- ${ error } ` ) ;
99
143
}
100
- console . log ( `\nIf you don't know how to solve this, read https://www.zigbee2mqtt.io/guide/configuration` ) ; // eslint-disable-line
144
+
145
+ console . log ( `\nIf you don't know how to solve this, read https://www.zigbee2mqtt.io/guide/configuration` ) ;
101
146
console . log ( `\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\n` ) ;
102
- exit ( 1 ) ;
147
+
148
+ return exit ( 1 ) ;
103
149
}
104
150
105
151
const Controller = require ( './dist/controller' ) ;
106
152
controller = new Controller ( restart , exit ) ;
153
+
107
154
await controller . start ( ) ;
155
+
156
+ // consider next controller.stop() call as unsolicited, only after successful first start
157
+ unsolicitedStop = true ;
158
+ watchdogCount = 0 ; // reset
108
159
}
109
160
110
161
async function stop ( restart ) {
162
+ // `handleQuit` or `restart` never unsolicited
163
+ unsolicitedStop = false ;
164
+
111
165
await controller . stop ( restart ) ;
112
166
}
113
167
114
168
async function handleQuit ( ) {
115
- if ( ! stopping && controller ) {
116
- stopping = true ;
117
- await stop ( false ) ;
169
+ if ( ! stopping ) {
170
+ if ( controller ) {
171
+ stopping = true ;
172
+
173
+ await stop ( false ) ;
174
+ } else {
175
+ process . exit ( 0 ) ;
176
+ }
118
177
}
119
178
}
120
179
@@ -129,5 +188,6 @@ if (require.main === module || require.main.filename.endsWith(path.sep + 'cli.js
129
188
} else {
130
189
process . on ( 'SIGINT' , handleQuit ) ;
131
190
process . on ( 'SIGTERM' , handleQuit ) ;
191
+
132
192
module . exports = { start} ;
133
193
}
0 commit comments