flightdeck 0.0.3 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/flightdeck.$configPath.schema.json +2 -2
- package/dist/flightdeck.bin.js +56 -31
- package/dist/flightdeck.main.schema.json +2 -2
- package/dist/lib.d.ts +6 -2
- package/dist/lib.js +55 -30
- package/package.json +1 -1
- package/src/flightdeck.bin.ts +1 -1
- package/src/flightdeck.lib.ts +71 -51
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"additionalProperties": false
|
|
30
30
|
}
|
|
31
31
|
},
|
|
32
|
-
"
|
|
32
|
+
"flightdeckRootDir": {
|
|
33
33
|
"type": "string"
|
|
34
34
|
},
|
|
35
35
|
"downloadPackageToUpdatesCmd": {
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"secret",
|
|
44
44
|
"packageName",
|
|
45
45
|
"services",
|
|
46
|
-
"
|
|
46
|
+
"flightdeckRootDir",
|
|
47
47
|
"downloadPackageToUpdatesCmd"
|
|
48
48
|
],
|
|
49
49
|
"additionalProperties": false,
|
package/dist/flightdeck.bin.js
CHANGED
|
@@ -26,13 +26,15 @@ class FlightDeck {
|
|
|
26
26
|
defaultServicesReadyToUpdate;
|
|
27
27
|
servicesReadyToUpdate;
|
|
28
28
|
servicesShouldRestart;
|
|
29
|
-
|
|
29
|
+
logger;
|
|
30
|
+
serviceLoggers;
|
|
30
31
|
servicesLive;
|
|
31
32
|
servicesDead;
|
|
32
33
|
live = new Future(() => {
|
|
33
34
|
});
|
|
34
35
|
dead = new Future(() => {
|
|
35
36
|
});
|
|
37
|
+
restartTimes = [];
|
|
36
38
|
currentServiceDir;
|
|
37
39
|
updateServiceDir;
|
|
38
40
|
backupServiceDir;
|
|
@@ -48,6 +50,31 @@ class FlightDeck {
|
|
|
48
50
|
]));
|
|
49
51
|
this.servicesReadyToUpdate = { ...this.defaultServicesReadyToUpdate };
|
|
50
52
|
this.servicesShouldRestart = true;
|
|
53
|
+
this.logger = {
|
|
54
|
+
info: (...args) => {
|
|
55
|
+
console.log(`${this.options.packageName}:`, ...args);
|
|
56
|
+
},
|
|
57
|
+
warn: (...args) => {
|
|
58
|
+
console.warn(`${this.options.packageName}:`, ...args);
|
|
59
|
+
},
|
|
60
|
+
error: (...args) => {
|
|
61
|
+
console.error(`${this.options.packageName}:`, ...args);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
this.serviceLoggers = fromEntries(servicesEntries.map(([serviceName]) => [
|
|
65
|
+
serviceName,
|
|
66
|
+
{
|
|
67
|
+
info: (...args) => {
|
|
68
|
+
console.log(`${this.options.packageName}::${serviceName}:`, ...args);
|
|
69
|
+
},
|
|
70
|
+
warn: (...args) => {
|
|
71
|
+
console.warn(`${this.options.packageName}::${serviceName}:`, ...args);
|
|
72
|
+
},
|
|
73
|
+
error: (...args) => {
|
|
74
|
+
console.error(`${this.options.packageName}::${serviceName}:`, ...args);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
]));
|
|
51
78
|
this.servicesLive = servicesEntries.map(() => new Future(() => {
|
|
52
79
|
}));
|
|
53
80
|
this.servicesDead = servicesEntries.map(() => new Future(() => {
|
|
@@ -62,7 +89,6 @@ class FlightDeck {
|
|
|
62
89
|
req.on(`data`, (chunk) => {
|
|
63
90
|
data.push(chunk instanceof Buffer ? chunk : Buffer.from(chunk));
|
|
64
91
|
}).on(`end`, () => {
|
|
65
|
-
console.log(req.headers);
|
|
66
92
|
const authHeader = req.headers.authorization;
|
|
67
93
|
try {
|
|
68
94
|
if (typeof req.url === `undefined`)
|
|
@@ -70,19 +96,18 @@ class FlightDeck {
|
|
|
70
96
|
if (authHeader !== `Bearer ${secret}`)
|
|
71
97
|
throw 401;
|
|
72
98
|
const url = new URL(req.url, ORIGIN);
|
|
73
|
-
|
|
99
|
+
this.logger.info(req.method, url.pathname);
|
|
74
100
|
switch (req.method) {
|
|
75
101
|
case `POST`:
|
|
76
102
|
{
|
|
77
|
-
console.log(`received post, url is ${url.pathname}`);
|
|
78
103
|
switch (url.pathname) {
|
|
79
104
|
case `/`:
|
|
80
105
|
{
|
|
81
106
|
res.writeHead(200);
|
|
82
107
|
res.end();
|
|
83
|
-
this.
|
|
108
|
+
this.getLatestRelease();
|
|
84
109
|
if (toEntries(this.servicesReadyToUpdate).every(([, isReady]) => isReady)) {
|
|
85
|
-
|
|
110
|
+
this.logger.info(`All services are ready to update!`);
|
|
86
111
|
this.stopAllServices();
|
|
87
112
|
return;
|
|
88
113
|
}
|
|
@@ -107,7 +132,7 @@ class FlightDeck {
|
|
|
107
132
|
throw 405;
|
|
108
133
|
}
|
|
109
134
|
} catch (thrown) {
|
|
110
|
-
|
|
135
|
+
this.logger.error(thrown, req.url);
|
|
111
136
|
if (typeof thrown === `number`) {
|
|
112
137
|
res.writeHead(thrown);
|
|
113
138
|
res.end();
|
|
@@ -117,25 +142,25 @@ class FlightDeck {
|
|
|
117
142
|
}
|
|
118
143
|
});
|
|
119
144
|
}).listen(PORT, () => {
|
|
120
|
-
|
|
145
|
+
this.logger.info(`Server started on port ${PORT}`);
|
|
121
146
|
});
|
|
122
147
|
this.startAllServices();
|
|
123
148
|
}
|
|
124
149
|
startAllServices() {
|
|
125
|
-
|
|
150
|
+
this.logger.info(`Starting all services...`);
|
|
126
151
|
for (const [serviceName] of toEntries(this.services)) {
|
|
127
152
|
this.startService(serviceName);
|
|
128
153
|
}
|
|
129
154
|
}
|
|
130
155
|
startService(serviceName) {
|
|
131
|
-
|
|
132
|
-
if (this.safety
|
|
156
|
+
this.logger.info(`Starting service ${this.options.packageName}::${serviceName}, try ${this.safety}/2...`);
|
|
157
|
+
if (this.safety >= 2) {
|
|
133
158
|
throw new Error(`Out of tries...`);
|
|
134
159
|
}
|
|
135
160
|
this.safety++;
|
|
136
161
|
if (!existsSync(this.currentServiceDir)) {
|
|
137
|
-
|
|
138
|
-
this.
|
|
162
|
+
this.logger.info(`Tried to start service but failed: could not find ${this.currentServiceDir}`);
|
|
163
|
+
this.getLatestRelease();
|
|
139
164
|
this.applyUpdate();
|
|
140
165
|
this.startService(serviceName);
|
|
141
166
|
return;
|
|
@@ -148,13 +173,13 @@ class FlightDeck {
|
|
|
148
173
|
});
|
|
149
174
|
this.services[serviceName] = new ChildSocket(serviceProcess, `${this.options.packageName}::${serviceName}`, console);
|
|
150
175
|
this.services[serviceName].onAny((...messages) => {
|
|
151
|
-
|
|
176
|
+
this.logger.info(`\uD83D\uDCAC`, ...messages);
|
|
152
177
|
});
|
|
153
178
|
this.services[serviceName].on(`readyToUpdate`, () => {
|
|
154
|
-
|
|
179
|
+
this.serviceLoggers[serviceName].info(`Ready to update.`);
|
|
155
180
|
this.servicesReadyToUpdate[serviceName] = true;
|
|
156
181
|
if (toEntries(this.servicesReadyToUpdate).every(([, isReady]) => isReady)) {
|
|
157
|
-
|
|
182
|
+
this.logger.info(`All services are ready to update.`);
|
|
158
183
|
this.stopAllServices();
|
|
159
184
|
}
|
|
160
185
|
});
|
|
@@ -169,15 +194,15 @@ class FlightDeck {
|
|
|
169
194
|
this.dead.use(Promise.all(this.servicesDead));
|
|
170
195
|
});
|
|
171
196
|
this.services[serviceName].process.on(`close`, (exitCode) => {
|
|
172
|
-
|
|
197
|
+
this.serviceLoggers[serviceName].info(`Exited with code ${exitCode}`);
|
|
173
198
|
this.services[serviceName] = null;
|
|
174
199
|
if (!this.servicesShouldRestart) {
|
|
175
|
-
|
|
200
|
+
this.serviceLoggers[serviceName].info(`Will not be restarted.`);
|
|
176
201
|
return;
|
|
177
202
|
}
|
|
178
203
|
const updatesAreReady = existsSync(this.updateServiceDir);
|
|
179
204
|
if (updatesAreReady) {
|
|
180
|
-
|
|
205
|
+
this.serviceLoggers[serviceName].info(`Updating before startup...`);
|
|
181
206
|
this.restartTimes = [];
|
|
182
207
|
this.applyUpdate();
|
|
183
208
|
this.startService(serviceName);
|
|
@@ -187,21 +212,21 @@ class FlightDeck {
|
|
|
187
212
|
this.restartTimes = this.restartTimes.filter((time) => time > fiveMinutesAgo);
|
|
188
213
|
this.restartTimes.push(now);
|
|
189
214
|
if (this.restartTimes.length < 5) {
|
|
190
|
-
|
|
215
|
+
this.serviceLoggers[serviceName].info(`Crashed. Restarting...`);
|
|
191
216
|
this.startService(serviceName);
|
|
192
217
|
} else {
|
|
193
|
-
|
|
218
|
+
this.serviceLoggers[serviceName].info(`Crashed 5 times in 5 minutes. Not restarting.`);
|
|
194
219
|
}
|
|
195
220
|
}
|
|
196
221
|
});
|
|
197
222
|
this.safety = 0;
|
|
198
223
|
}
|
|
199
224
|
applyUpdate() {
|
|
200
|
-
|
|
225
|
+
this.logger.info(`Applying update...`);
|
|
201
226
|
if (existsSync(this.updateServiceDir)) {
|
|
202
227
|
const runningServices = toEntries(this.services).filter(([, service]) => service);
|
|
203
228
|
if (runningServices.length > 0) {
|
|
204
|
-
|
|
229
|
+
this.logger.error(`Tried to apply update but failed. The following services are currently running: [${runningServices.map(([serviceName]) => serviceName).join(`, `)}]`);
|
|
205
230
|
return;
|
|
206
231
|
}
|
|
207
232
|
if (existsSync(this.currentServiceDir)) {
|
|
@@ -216,29 +241,29 @@ class FlightDeck {
|
|
|
216
241
|
this.restartTimes = [];
|
|
217
242
|
this.servicesReadyToUpdate = { ...this.defaultServicesReadyToUpdate };
|
|
218
243
|
} else {
|
|
219
|
-
|
|
244
|
+
this.logger.error(`Tried to apply update but failed: could not find update directory ${this.updateServiceDir}`);
|
|
220
245
|
}
|
|
221
246
|
}
|
|
222
|
-
|
|
223
|
-
|
|
247
|
+
getLatestRelease() {
|
|
248
|
+
this.logger.info(`Getting latest release...`);
|
|
224
249
|
try {
|
|
225
250
|
execSync(this.options.downloadPackageToUpdatesCmd.join(` `));
|
|
226
251
|
} catch (thrown) {
|
|
227
252
|
if (thrown instanceof Error) {
|
|
228
|
-
|
|
253
|
+
this.logger.error(`Failed to get the latest release: ${thrown.message}`);
|
|
229
254
|
}
|
|
230
255
|
return;
|
|
231
256
|
}
|
|
232
257
|
}
|
|
233
258
|
stopAllServices() {
|
|
234
|
-
|
|
259
|
+
this.logger.info(`Stopping all services...`);
|
|
235
260
|
for (const [serviceName] of toEntries(this.services)) {
|
|
236
261
|
this.stopService(serviceName);
|
|
237
262
|
}
|
|
238
263
|
}
|
|
239
264
|
stopService(serviceName) {
|
|
240
265
|
if (this.services[serviceName]) {
|
|
241
|
-
|
|
266
|
+
this.serviceLoggers[serviceName].info(`Stopping service...`);
|
|
242
267
|
this.services[serviceName].process.kill();
|
|
243
268
|
this.services[serviceName] = null;
|
|
244
269
|
this.servicesDead[this.serviceIdx[serviceName]].use(Promise.resolve());
|
|
@@ -250,7 +275,7 @@ class FlightDeck {
|
|
|
250
275
|
}
|
|
251
276
|
this.live.use(Promise.all(this.servicesLive));
|
|
252
277
|
} else {
|
|
253
|
-
|
|
278
|
+
this.serviceLoggers[serviceName].error(`Tried to stop service, but it wasn't running.`);
|
|
254
279
|
}
|
|
255
280
|
}
|
|
256
281
|
shutdown() {
|
|
@@ -265,7 +290,7 @@ var FLIGHTDECK_MANUAL = {
|
|
|
265
290
|
secret: z.string(),
|
|
266
291
|
packageName: z.string(),
|
|
267
292
|
services: z.record(z.object({ run: z.array(z.string()), waitFor: z.boolean() })),
|
|
268
|
-
|
|
293
|
+
flightdeckRootDir: z.string(),
|
|
269
294
|
downloadPackageToUpdatesCmd: z.array(z.string())
|
|
270
295
|
}),
|
|
271
296
|
options: {
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"additionalProperties": false
|
|
30
30
|
}
|
|
31
31
|
},
|
|
32
|
-
"
|
|
32
|
+
"flightdeckRootDir": {
|
|
33
33
|
"type": "string"
|
|
34
34
|
},
|
|
35
35
|
"downloadPackageToUpdatesCmd": {
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"secret",
|
|
44
44
|
"packageName",
|
|
45
45
|
"services",
|
|
46
|
-
"
|
|
46
|
+
"flightdeckRootDir",
|
|
47
47
|
"downloadPackageToUpdatesCmd"
|
|
48
48
|
],
|
|
49
49
|
"additionalProperties": false,
|
package/dist/lib.d.ts
CHANGED
|
@@ -36,11 +36,15 @@ declare class FlightDeck<S extends string = string> {
|
|
|
36
36
|
[service in S]: boolean;
|
|
37
37
|
};
|
|
38
38
|
servicesShouldRestart: boolean;
|
|
39
|
-
protected
|
|
39
|
+
protected logger: Pick<Console, `error` | `info` | `warn`>;
|
|
40
|
+
protected serviceLoggers: {
|
|
41
|
+
readonly [service in S]: Pick<Console, `error` | `info` | `warn`>;
|
|
42
|
+
};
|
|
40
43
|
servicesLive: Future<void>[];
|
|
41
44
|
servicesDead: Future<void>[];
|
|
42
45
|
live: Future<unknown>;
|
|
43
46
|
dead: Future<unknown>;
|
|
47
|
+
protected restartTimes: number[];
|
|
44
48
|
readonly currentServiceDir: string;
|
|
45
49
|
readonly updateServiceDir: string;
|
|
46
50
|
readonly backupServiceDir: string;
|
|
@@ -48,7 +52,7 @@ declare class FlightDeck<S extends string = string> {
|
|
|
48
52
|
protected startAllServices(): void;
|
|
49
53
|
protected startService(serviceName: S): void;
|
|
50
54
|
protected applyUpdate(): void;
|
|
51
|
-
protected
|
|
55
|
+
protected getLatestRelease(): void;
|
|
52
56
|
stopAllServices(): void;
|
|
53
57
|
stopService(serviceName: S): void;
|
|
54
58
|
shutdown(): void;
|
package/dist/lib.js
CHANGED
|
@@ -30,13 +30,15 @@ class FlightDeck {
|
|
|
30
30
|
defaultServicesReadyToUpdate;
|
|
31
31
|
servicesReadyToUpdate;
|
|
32
32
|
servicesShouldRestart;
|
|
33
|
-
|
|
33
|
+
logger;
|
|
34
|
+
serviceLoggers;
|
|
34
35
|
servicesLive;
|
|
35
36
|
servicesDead;
|
|
36
37
|
live = new Future(() => {
|
|
37
38
|
});
|
|
38
39
|
dead = new Future(() => {
|
|
39
40
|
});
|
|
41
|
+
restartTimes = [];
|
|
40
42
|
currentServiceDir;
|
|
41
43
|
updateServiceDir;
|
|
42
44
|
backupServiceDir;
|
|
@@ -52,6 +54,31 @@ class FlightDeck {
|
|
|
52
54
|
]));
|
|
53
55
|
this.servicesReadyToUpdate = { ...this.defaultServicesReadyToUpdate };
|
|
54
56
|
this.servicesShouldRestart = true;
|
|
57
|
+
this.logger = {
|
|
58
|
+
info: (...args) => {
|
|
59
|
+
console.log(`${this.options.packageName}:`, ...args);
|
|
60
|
+
},
|
|
61
|
+
warn: (...args) => {
|
|
62
|
+
console.warn(`${this.options.packageName}:`, ...args);
|
|
63
|
+
},
|
|
64
|
+
error: (...args) => {
|
|
65
|
+
console.error(`${this.options.packageName}:`, ...args);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
this.serviceLoggers = fromEntries(servicesEntries.map(([serviceName]) => [
|
|
69
|
+
serviceName,
|
|
70
|
+
{
|
|
71
|
+
info: (...args) => {
|
|
72
|
+
console.log(`${this.options.packageName}::${serviceName}:`, ...args);
|
|
73
|
+
},
|
|
74
|
+
warn: (...args) => {
|
|
75
|
+
console.warn(`${this.options.packageName}::${serviceName}:`, ...args);
|
|
76
|
+
},
|
|
77
|
+
error: (...args) => {
|
|
78
|
+
console.error(`${this.options.packageName}::${serviceName}:`, ...args);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
]));
|
|
55
82
|
this.servicesLive = servicesEntries.map(() => new Future(() => {
|
|
56
83
|
}));
|
|
57
84
|
this.servicesDead = servicesEntries.map(() => new Future(() => {
|
|
@@ -66,7 +93,6 @@ class FlightDeck {
|
|
|
66
93
|
req.on(`data`, (chunk) => {
|
|
67
94
|
data.push(chunk instanceof Buffer ? chunk : Buffer.from(chunk));
|
|
68
95
|
}).on(`end`, () => {
|
|
69
|
-
console.log(req.headers);
|
|
70
96
|
const authHeader = req.headers.authorization;
|
|
71
97
|
try {
|
|
72
98
|
if (typeof req.url === `undefined`)
|
|
@@ -74,19 +100,18 @@ class FlightDeck {
|
|
|
74
100
|
if (authHeader !== `Bearer ${secret}`)
|
|
75
101
|
throw 401;
|
|
76
102
|
const url = new URL(req.url, ORIGIN);
|
|
77
|
-
|
|
103
|
+
this.logger.info(req.method, url.pathname);
|
|
78
104
|
switch (req.method) {
|
|
79
105
|
case `POST`:
|
|
80
106
|
{
|
|
81
|
-
console.log(`received post, url is ${url.pathname}`);
|
|
82
107
|
switch (url.pathname) {
|
|
83
108
|
case `/`:
|
|
84
109
|
{
|
|
85
110
|
res.writeHead(200);
|
|
86
111
|
res.end();
|
|
87
|
-
this.
|
|
112
|
+
this.getLatestRelease();
|
|
88
113
|
if (toEntries(this.servicesReadyToUpdate).every(([, isReady]) => isReady)) {
|
|
89
|
-
|
|
114
|
+
this.logger.info(`All services are ready to update!`);
|
|
90
115
|
this.stopAllServices();
|
|
91
116
|
return;
|
|
92
117
|
}
|
|
@@ -111,7 +136,7 @@ class FlightDeck {
|
|
|
111
136
|
throw 405;
|
|
112
137
|
}
|
|
113
138
|
} catch (thrown) {
|
|
114
|
-
|
|
139
|
+
this.logger.error(thrown, req.url);
|
|
115
140
|
if (typeof thrown === `number`) {
|
|
116
141
|
res.writeHead(thrown);
|
|
117
142
|
res.end();
|
|
@@ -121,25 +146,25 @@ class FlightDeck {
|
|
|
121
146
|
}
|
|
122
147
|
});
|
|
123
148
|
}).listen(PORT, () => {
|
|
124
|
-
|
|
149
|
+
this.logger.info(`Server started on port ${PORT}`);
|
|
125
150
|
});
|
|
126
151
|
this.startAllServices();
|
|
127
152
|
}
|
|
128
153
|
startAllServices() {
|
|
129
|
-
|
|
154
|
+
this.logger.info(`Starting all services...`);
|
|
130
155
|
for (const [serviceName] of toEntries(this.services)) {
|
|
131
156
|
this.startService(serviceName);
|
|
132
157
|
}
|
|
133
158
|
}
|
|
134
159
|
startService(serviceName) {
|
|
135
|
-
|
|
136
|
-
if (this.safety
|
|
160
|
+
this.logger.info(`Starting service ${this.options.packageName}::${serviceName}, try ${this.safety}/2...`);
|
|
161
|
+
if (this.safety >= 2) {
|
|
137
162
|
throw new Error(`Out of tries...`);
|
|
138
163
|
}
|
|
139
164
|
this.safety++;
|
|
140
165
|
if (!existsSync(this.currentServiceDir)) {
|
|
141
|
-
|
|
142
|
-
this.
|
|
166
|
+
this.logger.info(`Tried to start service but failed: could not find ${this.currentServiceDir}`);
|
|
167
|
+
this.getLatestRelease();
|
|
143
168
|
this.applyUpdate();
|
|
144
169
|
this.startService(serviceName);
|
|
145
170
|
return;
|
|
@@ -152,13 +177,13 @@ class FlightDeck {
|
|
|
152
177
|
});
|
|
153
178
|
this.services[serviceName] = new ChildSocket(serviceProcess, `${this.options.packageName}::${serviceName}`, console);
|
|
154
179
|
this.services[serviceName].onAny((...messages) => {
|
|
155
|
-
|
|
180
|
+
this.logger.info(`\uD83D\uDCAC`, ...messages);
|
|
156
181
|
});
|
|
157
182
|
this.services[serviceName].on(`readyToUpdate`, () => {
|
|
158
|
-
|
|
183
|
+
this.serviceLoggers[serviceName].info(`Ready to update.`);
|
|
159
184
|
this.servicesReadyToUpdate[serviceName] = true;
|
|
160
185
|
if (toEntries(this.servicesReadyToUpdate).every(([, isReady]) => isReady)) {
|
|
161
|
-
|
|
186
|
+
this.logger.info(`All services are ready to update.`);
|
|
162
187
|
this.stopAllServices();
|
|
163
188
|
}
|
|
164
189
|
});
|
|
@@ -173,15 +198,15 @@ class FlightDeck {
|
|
|
173
198
|
this.dead.use(Promise.all(this.servicesDead));
|
|
174
199
|
});
|
|
175
200
|
this.services[serviceName].process.on(`close`, (exitCode) => {
|
|
176
|
-
|
|
201
|
+
this.serviceLoggers[serviceName].info(`Exited with code ${exitCode}`);
|
|
177
202
|
this.services[serviceName] = null;
|
|
178
203
|
if (!this.servicesShouldRestart) {
|
|
179
|
-
|
|
204
|
+
this.serviceLoggers[serviceName].info(`Will not be restarted.`);
|
|
180
205
|
return;
|
|
181
206
|
}
|
|
182
207
|
const updatesAreReady = existsSync(this.updateServiceDir);
|
|
183
208
|
if (updatesAreReady) {
|
|
184
|
-
|
|
209
|
+
this.serviceLoggers[serviceName].info(`Updating before startup...`);
|
|
185
210
|
this.restartTimes = [];
|
|
186
211
|
this.applyUpdate();
|
|
187
212
|
this.startService(serviceName);
|
|
@@ -191,21 +216,21 @@ class FlightDeck {
|
|
|
191
216
|
this.restartTimes = this.restartTimes.filter((time) => time > fiveMinutesAgo);
|
|
192
217
|
this.restartTimes.push(now);
|
|
193
218
|
if (this.restartTimes.length < 5) {
|
|
194
|
-
|
|
219
|
+
this.serviceLoggers[serviceName].info(`Crashed. Restarting...`);
|
|
195
220
|
this.startService(serviceName);
|
|
196
221
|
} else {
|
|
197
|
-
|
|
222
|
+
this.serviceLoggers[serviceName].info(`Crashed 5 times in 5 minutes. Not restarting.`);
|
|
198
223
|
}
|
|
199
224
|
}
|
|
200
225
|
});
|
|
201
226
|
this.safety = 0;
|
|
202
227
|
}
|
|
203
228
|
applyUpdate() {
|
|
204
|
-
|
|
229
|
+
this.logger.info(`Applying update...`);
|
|
205
230
|
if (existsSync(this.updateServiceDir)) {
|
|
206
231
|
const runningServices = toEntries(this.services).filter(([, service]) => service);
|
|
207
232
|
if (runningServices.length > 0) {
|
|
208
|
-
|
|
233
|
+
this.logger.error(`Tried to apply update but failed. The following services are currently running: [${runningServices.map(([serviceName]) => serviceName).join(`, `)}]`);
|
|
209
234
|
return;
|
|
210
235
|
}
|
|
211
236
|
if (existsSync(this.currentServiceDir)) {
|
|
@@ -220,29 +245,29 @@ class FlightDeck {
|
|
|
220
245
|
this.restartTimes = [];
|
|
221
246
|
this.servicesReadyToUpdate = { ...this.defaultServicesReadyToUpdate };
|
|
222
247
|
} else {
|
|
223
|
-
|
|
248
|
+
this.logger.error(`Tried to apply update but failed: could not find update directory ${this.updateServiceDir}`);
|
|
224
249
|
}
|
|
225
250
|
}
|
|
226
|
-
|
|
227
|
-
|
|
251
|
+
getLatestRelease() {
|
|
252
|
+
this.logger.info(`Getting latest release...`);
|
|
228
253
|
try {
|
|
229
254
|
execSync(this.options.downloadPackageToUpdatesCmd.join(` `));
|
|
230
255
|
} catch (thrown) {
|
|
231
256
|
if (thrown instanceof Error) {
|
|
232
|
-
|
|
257
|
+
this.logger.error(`Failed to get the latest release: ${thrown.message}`);
|
|
233
258
|
}
|
|
234
259
|
return;
|
|
235
260
|
}
|
|
236
261
|
}
|
|
237
262
|
stopAllServices() {
|
|
238
|
-
|
|
263
|
+
this.logger.info(`Stopping all services...`);
|
|
239
264
|
for (const [serviceName] of toEntries(this.services)) {
|
|
240
265
|
this.stopService(serviceName);
|
|
241
266
|
}
|
|
242
267
|
}
|
|
243
268
|
stopService(serviceName) {
|
|
244
269
|
if (this.services[serviceName]) {
|
|
245
|
-
|
|
270
|
+
this.serviceLoggers[serviceName].info(`Stopping service...`);
|
|
246
271
|
this.services[serviceName].process.kill();
|
|
247
272
|
this.services[serviceName] = null;
|
|
248
273
|
this.servicesDead[this.serviceIdx[serviceName]].use(Promise.resolve());
|
|
@@ -254,7 +279,7 @@ class FlightDeck {
|
|
|
254
279
|
}
|
|
255
280
|
this.live.use(Promise.all(this.servicesLive));
|
|
256
281
|
} else {
|
|
257
|
-
|
|
282
|
+
this.serviceLoggers[serviceName].error(`Tried to stop service, but it wasn't running.`);
|
|
258
283
|
}
|
|
259
284
|
}
|
|
260
285
|
shutdown() {
|
package/package.json
CHANGED
package/src/flightdeck.bin.ts
CHANGED
|
@@ -16,7 +16,7 @@ const FLIGHTDECK_MANUAL = {
|
|
|
16
16
|
services: z.record(
|
|
17
17
|
z.object({ run: z.array(z.string()), waitFor: z.boolean() }),
|
|
18
18
|
),
|
|
19
|
-
|
|
19
|
+
flightdeckRootDir: z.string(),
|
|
20
20
|
downloadPackageToUpdatesCmd: z.array(z.string()),
|
|
21
21
|
}),
|
|
22
22
|
options: {
|
package/src/flightdeck.lib.ts
CHANGED
|
@@ -34,13 +34,18 @@ export class FlightDeck<S extends string = string> {
|
|
|
34
34
|
public servicesReadyToUpdate: { [service in S]: boolean }
|
|
35
35
|
public servicesShouldRestart: boolean
|
|
36
36
|
|
|
37
|
-
protected
|
|
37
|
+
protected logger: Pick<Console, `error` | `info` | `warn`>
|
|
38
|
+
protected serviceLoggers: {
|
|
39
|
+
readonly [service in S]: Pick<Console, `error` | `info` | `warn`>
|
|
40
|
+
}
|
|
38
41
|
|
|
39
42
|
public servicesLive: Future<void>[]
|
|
40
43
|
public servicesDead: Future<void>[]
|
|
41
44
|
public live = new Future(() => {})
|
|
42
45
|
public dead = new Future(() => {})
|
|
43
46
|
|
|
47
|
+
protected restartTimes: number[] = []
|
|
48
|
+
|
|
44
49
|
public readonly currentServiceDir: string
|
|
45
50
|
public readonly updateServiceDir: string
|
|
46
51
|
public readonly backupServiceDir: string
|
|
@@ -64,6 +69,38 @@ export class FlightDeck<S extends string = string> {
|
|
|
64
69
|
)
|
|
65
70
|
this.servicesReadyToUpdate = { ...this.defaultServicesReadyToUpdate }
|
|
66
71
|
this.servicesShouldRestart = true
|
|
72
|
+
|
|
73
|
+
this.logger = {
|
|
74
|
+
info: (...args: any[]) => {
|
|
75
|
+
console.log(`${this.options.packageName}:`, ...args)
|
|
76
|
+
},
|
|
77
|
+
warn: (...args: any[]) => {
|
|
78
|
+
console.warn(`${this.options.packageName}:`, ...args)
|
|
79
|
+
},
|
|
80
|
+
error: (...args: any[]) => {
|
|
81
|
+
console.error(`${this.options.packageName}:`, ...args)
|
|
82
|
+
},
|
|
83
|
+
}
|
|
84
|
+
this.serviceLoggers = fromEntries(
|
|
85
|
+
servicesEntries.map(([serviceName]) => [
|
|
86
|
+
serviceName,
|
|
87
|
+
{
|
|
88
|
+
info: (...args: any[]) => {
|
|
89
|
+
console.log(`${this.options.packageName}::${serviceName}:`, ...args)
|
|
90
|
+
},
|
|
91
|
+
warn: (...args: any[]) => {
|
|
92
|
+
console.warn(`${this.options.packageName}::${serviceName}:`, ...args)
|
|
93
|
+
},
|
|
94
|
+
error: (...args: any[]) => {
|
|
95
|
+
console.error(
|
|
96
|
+
`${this.options.packageName}::${serviceName}:`,
|
|
97
|
+
...args,
|
|
98
|
+
)
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
]),
|
|
102
|
+
)
|
|
103
|
+
|
|
67
104
|
this.servicesLive = servicesEntries.map(() => new Future(() => {}))
|
|
68
105
|
this.servicesDead = servicesEntries.map(() => new Future(() => {}))
|
|
69
106
|
this.live.use(Promise.all(this.servicesLive))
|
|
@@ -92,29 +129,27 @@ export class FlightDeck<S extends string = string> {
|
|
|
92
129
|
data.push(chunk instanceof Buffer ? chunk : Buffer.from(chunk))
|
|
93
130
|
})
|
|
94
131
|
.on(`end`, () => {
|
|
95
|
-
console.log(req.headers)
|
|
96
132
|
const authHeader = req.headers.authorization
|
|
97
133
|
try {
|
|
98
134
|
if (typeof req.url === `undefined`) throw 400
|
|
99
135
|
if (authHeader !== `Bearer ${secret}`) throw 401
|
|
100
136
|
const url = new URL(req.url, ORIGIN)
|
|
101
|
-
|
|
137
|
+
this.logger.info(req.method, url.pathname)
|
|
102
138
|
switch (req.method) {
|
|
103
139
|
case `POST`:
|
|
104
140
|
{
|
|
105
|
-
console.log(`received post, url is ${url.pathname}`)
|
|
106
141
|
switch (url.pathname) {
|
|
107
142
|
case `/`:
|
|
108
143
|
{
|
|
109
144
|
res.writeHead(200)
|
|
110
145
|
res.end()
|
|
111
|
-
this.
|
|
146
|
+
this.getLatestRelease()
|
|
112
147
|
if (
|
|
113
148
|
toEntries(this.servicesReadyToUpdate).every(
|
|
114
149
|
([, isReady]) => isReady,
|
|
115
150
|
)
|
|
116
151
|
) {
|
|
117
|
-
|
|
152
|
+
this.logger.info(`All services are ready to update!`)
|
|
118
153
|
this.stopAllServices()
|
|
119
154
|
return
|
|
120
155
|
}
|
|
@@ -141,7 +176,7 @@ export class FlightDeck<S extends string = string> {
|
|
|
141
176
|
throw 405
|
|
142
177
|
}
|
|
143
178
|
} catch (thrown) {
|
|
144
|
-
|
|
179
|
+
this.logger.error(thrown, req.url)
|
|
145
180
|
if (typeof thrown === `number`) {
|
|
146
181
|
res.writeHead(thrown)
|
|
147
182
|
res.end()
|
|
@@ -151,32 +186,32 @@ export class FlightDeck<S extends string = string> {
|
|
|
151
186
|
}
|
|
152
187
|
})
|
|
153
188
|
}).listen(PORT, () => {
|
|
154
|
-
|
|
189
|
+
this.logger.info(`Server started on port ${PORT}`)
|
|
155
190
|
})
|
|
156
191
|
|
|
157
192
|
this.startAllServices()
|
|
158
193
|
}
|
|
159
194
|
|
|
160
195
|
protected startAllServices(): void {
|
|
161
|
-
|
|
196
|
+
this.logger.info(`Starting all services...`)
|
|
162
197
|
for (const [serviceName] of toEntries(this.services)) {
|
|
163
198
|
this.startService(serviceName)
|
|
164
199
|
}
|
|
165
200
|
}
|
|
166
201
|
|
|
167
202
|
protected startService(serviceName: S): void {
|
|
168
|
-
|
|
203
|
+
this.logger.info(
|
|
169
204
|
`Starting service ${this.options.packageName}::${serviceName}, try ${this.safety}/2...`,
|
|
170
205
|
)
|
|
171
|
-
if (this.safety
|
|
206
|
+
if (this.safety >= 2) {
|
|
172
207
|
throw new Error(`Out of tries...`)
|
|
173
208
|
}
|
|
174
209
|
this.safety++
|
|
175
210
|
if (!existsSync(this.currentServiceDir)) {
|
|
176
|
-
|
|
177
|
-
`Tried to start service but failed:
|
|
211
|
+
this.logger.info(
|
|
212
|
+
`Tried to start service but failed: could not find ${this.currentServiceDir}`,
|
|
178
213
|
)
|
|
179
|
-
this.
|
|
214
|
+
this.getLatestRelease()
|
|
180
215
|
this.applyUpdate()
|
|
181
216
|
this.startService(serviceName)
|
|
182
217
|
|
|
@@ -197,17 +232,15 @@ export class FlightDeck<S extends string = string> {
|
|
|
197
232
|
console,
|
|
198
233
|
)
|
|
199
234
|
this.services[serviceName].onAny((...messages) => {
|
|
200
|
-
|
|
235
|
+
this.logger.info(`💬`, ...messages)
|
|
201
236
|
})
|
|
202
237
|
this.services[serviceName].on(`readyToUpdate`, () => {
|
|
203
|
-
|
|
204
|
-
`Service ${this.options.packageName}::${serviceName} is ready to update.`,
|
|
205
|
-
)
|
|
238
|
+
this.serviceLoggers[serviceName].info(`Ready to update.`)
|
|
206
239
|
this.servicesReadyToUpdate[serviceName] = true
|
|
207
240
|
if (
|
|
208
241
|
toEntries(this.servicesReadyToUpdate).every(([, isReady]) => isReady)
|
|
209
242
|
) {
|
|
210
|
-
|
|
243
|
+
this.logger.info(`All services are ready to update.`)
|
|
211
244
|
this.stopAllServices()
|
|
212
245
|
}
|
|
213
246
|
})
|
|
@@ -220,21 +253,15 @@ export class FlightDeck<S extends string = string> {
|
|
|
220
253
|
this.dead.use(Promise.all(this.servicesDead))
|
|
221
254
|
})
|
|
222
255
|
this.services[serviceName].process.on(`close`, (exitCode) => {
|
|
223
|
-
|
|
224
|
-
`${this.options.packageName}::${serviceName} exited with code ${exitCode}`,
|
|
225
|
-
)
|
|
256
|
+
this.serviceLoggers[serviceName].info(`Exited with code ${exitCode}`)
|
|
226
257
|
this.services[serviceName] = null
|
|
227
258
|
if (!this.servicesShouldRestart) {
|
|
228
|
-
|
|
229
|
-
`Service ${this.options.packageName}::${serviceName} will not be restarted.`,
|
|
230
|
-
)
|
|
259
|
+
this.serviceLoggers[serviceName].info(`Will not be restarted.`)
|
|
231
260
|
return
|
|
232
261
|
}
|
|
233
262
|
const updatesAreReady = existsSync(this.updateServiceDir)
|
|
234
263
|
if (updatesAreReady) {
|
|
235
|
-
|
|
236
|
-
`${this.options.packageName}::${serviceName} will be updated before startup...`,
|
|
237
|
-
)
|
|
264
|
+
this.serviceLoggers[serviceName].info(`Updating before startup...`)
|
|
238
265
|
this.restartTimes = []
|
|
239
266
|
this.applyUpdate()
|
|
240
267
|
this.startService(serviceName)
|
|
@@ -247,13 +274,11 @@ export class FlightDeck<S extends string = string> {
|
|
|
247
274
|
this.restartTimes.push(now)
|
|
248
275
|
|
|
249
276
|
if (this.restartTimes.length < 5) {
|
|
250
|
-
|
|
251
|
-
`Service ${this.options.packageName}::${serviceName} crashed. Restarting...`,
|
|
252
|
-
)
|
|
277
|
+
this.serviceLoggers[serviceName].info(`Crashed. Restarting...`)
|
|
253
278
|
this.startService(serviceName)
|
|
254
279
|
} else {
|
|
255
|
-
|
|
256
|
-
`
|
|
280
|
+
this.serviceLoggers[serviceName].info(
|
|
281
|
+
`Crashed 5 times in 5 minutes. Not restarting.`,
|
|
257
282
|
)
|
|
258
283
|
}
|
|
259
284
|
}
|
|
@@ -262,17 +287,14 @@ export class FlightDeck<S extends string = string> {
|
|
|
262
287
|
}
|
|
263
288
|
|
|
264
289
|
protected applyUpdate(): void {
|
|
265
|
-
|
|
266
|
-
`Installing latest version of service ${this.options.packageName}...`,
|
|
267
|
-
)
|
|
268
|
-
|
|
290
|
+
this.logger.info(`Applying update...`)
|
|
269
291
|
if (existsSync(this.updateServiceDir)) {
|
|
270
292
|
const runningServices = toEntries(this.services).filter(
|
|
271
293
|
([, service]) => service,
|
|
272
294
|
)
|
|
273
295
|
if (runningServices.length > 0) {
|
|
274
|
-
|
|
275
|
-
`Tried to apply update
|
|
296
|
+
this.logger.error(
|
|
297
|
+
`Tried to apply update but failed. The following services are currently running: [${runningServices.map(([serviceName]) => serviceName).join(`, `)}]`,
|
|
276
298
|
)
|
|
277
299
|
return
|
|
278
300
|
}
|
|
@@ -290,27 +312,27 @@ export class FlightDeck<S extends string = string> {
|
|
|
290
312
|
this.restartTimes = []
|
|
291
313
|
this.servicesReadyToUpdate = { ...this.defaultServicesReadyToUpdate }
|
|
292
314
|
} else {
|
|
293
|
-
|
|
315
|
+
this.logger.error(
|
|
316
|
+
`Tried to apply update but failed: could not find update directory ${this.updateServiceDir}`,
|
|
317
|
+
)
|
|
294
318
|
}
|
|
295
319
|
}
|
|
296
320
|
|
|
297
|
-
protected
|
|
298
|
-
|
|
299
|
-
`Downloading latest version of service ${this.options.packageName}...`,
|
|
300
|
-
)
|
|
321
|
+
protected getLatestRelease(): void {
|
|
322
|
+
this.logger.info(`Getting latest release...`)
|
|
301
323
|
|
|
302
324
|
try {
|
|
303
325
|
execSync(this.options.downloadPackageToUpdatesCmd.join(` `))
|
|
304
326
|
} catch (thrown) {
|
|
305
327
|
if (thrown instanceof Error) {
|
|
306
|
-
|
|
328
|
+
this.logger.error(`Failed to get the latest release: ${thrown.message}`)
|
|
307
329
|
}
|
|
308
330
|
return
|
|
309
331
|
}
|
|
310
332
|
}
|
|
311
333
|
|
|
312
334
|
public stopAllServices(): void {
|
|
313
|
-
|
|
335
|
+
this.logger.info(`Stopping all services...`)
|
|
314
336
|
for (const [serviceName] of toEntries(this.services)) {
|
|
315
337
|
this.stopService(serviceName)
|
|
316
338
|
}
|
|
@@ -318,9 +340,7 @@ export class FlightDeck<S extends string = string> {
|
|
|
318
340
|
|
|
319
341
|
public stopService(serviceName: S): void {
|
|
320
342
|
if (this.services[serviceName]) {
|
|
321
|
-
|
|
322
|
-
`Stopping service ${this.options.packageName}::${serviceName}...`,
|
|
323
|
-
)
|
|
343
|
+
this.serviceLoggers[serviceName].info(`Stopping service...`)
|
|
324
344
|
this.services[serviceName].process.kill()
|
|
325
345
|
this.services[serviceName] = null
|
|
326
346
|
this.servicesDead[this.serviceIdx[serviceName]].use(Promise.resolve())
|
|
@@ -330,8 +350,8 @@ export class FlightDeck<S extends string = string> {
|
|
|
330
350
|
}
|
|
331
351
|
this.live.use(Promise.all(this.servicesLive))
|
|
332
352
|
} else {
|
|
333
|
-
|
|
334
|
-
`
|
|
353
|
+
this.serviceLoggers[serviceName].error(
|
|
354
|
+
`Tried to stop service, but it wasn't running.`,
|
|
335
355
|
)
|
|
336
356
|
}
|
|
337
357
|
}
|