diff --git a/keyserver/flow-typed/npm/stoppable_vx.x.x.js b/keyserver/flow-typed/npm/stoppable_vx.x.x.js new file mode 100644 index 0000000000..42e7f5857a --- /dev/null +++ b/keyserver/flow-typed/npm/stoppable_vx.x.x.js @@ -0,0 +1,10 @@ +// flow-typed signature: 4ffffaa246e31defe6bd7054e7869482 +// flow-typed version: <>/stoppable_v1.1.0/flow_v0.202.1 + +declare module 'stoppable' { + declare module.exports: { + (server: T, grace?: number): T & { + +stop: (callback?: (error: ?Error, gracefully: boolean) => void) => void, + } + } +} diff --git a/keyserver/package.json b/keyserver/package.json index a0d43d2690..8bdf5c6c24 100644 --- a/keyserver/package.json +++ b/keyserver/package.json @@ -85,6 +85,7 @@ "sharp": "^0.30.5", "siwe": "^2.1.4", "sql-template-strings": "^2.2.2", + "stoppable": "^1.1.0", "tcomb": "^3.2.29", "twin-bcrypt": "^2.1.1", "uuid": "^3.4.0", diff --git a/keyserver/src/keyserver.js b/keyserver/src/keyserver.js index 82de5f814d..4f2b6236be 100644 --- a/keyserver/src/keyserver.js +++ b/keyserver/src/keyserver.js @@ -11,6 +11,7 @@ import type { $Request, $Response } from 'express'; import expressWs from 'express-ws'; import os from 'os'; import qrcode from 'qrcode'; +import stoppable from 'stoppable'; import './cron/cron.js'; import { qrCodeLinkURL } from 'lib/facts/links.js'; @@ -110,6 +111,23 @@ void (async () => { if (cluster.isMaster) { if (isPrimaryNode) { + const healthCheckApp = express(); + healthCheckApp.use(express.json({ limit: '250mb' })); + healthCheckApp.get('/health', (req: $Request, res: $Response) => { + res.send('OK'); + }); + + // We use stoppable to allow forcibly stopping the health check server + // on the master process so that non-master processes can successfully + // initialize their express servers on the same port without conflict + const healthCheckServer = stoppable( + healthCheckApp.listen( + parseInt(process.env.PORT, 10) || 3000, + listenAddress, + ), + 0, + ); + const didMigrationsSucceed: boolean = await migrate(); if (!didMigrationsSucceed) { // The following line uses exit code 2 to ensure nodemon exits @@ -117,6 +135,18 @@ void (async () => { // in https://github.com/remy/nodemon/issues/751 process.exit(2); } + + if (healthCheckServer) { + await new Promise((resolve, reject) => { + healthCheckServer.stop(err => { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); + } } if (shouldDisplayQRCodeInTerminal && isPrimaryNode) { diff --git a/yarn.lock b/yarn.lock index 6e77ece01d..1b4bbab4a5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -22492,6 +22492,11 @@ stealthy-require@^1.1.1: resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" integrity sha512-ZnWpYnYugiOVEY5GkcuJK1io5V8QmNYChG62gSit9pQVGErXtrKuPC55ITaVSukmMta5qpMU7vqLt2Lnni4f/g== +stoppable@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/stoppable/-/stoppable-1.1.0.tgz#32da568e83ea488b08e4d7ea2c3bcc9d75015d5b" + integrity sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw== + stopwords-iso@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/stopwords-iso/-/stopwords-iso-1.1.0.tgz#dc303db6b0842d4290bc1339b4eaf37b94219395"