From 14d7d16f73ae0b221f9cdda6521f589d7b6469eb Mon Sep 17 00:00:00 2001 From: svemagie <869694+svemagie@users.noreply.github.com> Date: Sun, 8 Mar 2026 01:19:47 +0100 Subject: [PATCH] fix(startup): add strict mongo preflight in production --- README.md | 3 +- package.json | 2 +- scripts/preflight-mongo-connection.mjs | 61 ++++++++++++++++++++++++++ start.example.sh | 3 ++ 4 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 scripts/preflight-mongo-connection.mjs diff --git a/README.md b/README.md index 26e61e13..3a99f626 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ - Preferred: set a full `MONGO_URL` (example: `mongodb://user:pass@host:27017/indiekit?authSource=admin`). - If `MONGO_URL` is not set, set `MONGO_USERNAME` and `MONGO_PASSWORD` explicitly; config builds the URL from `MONGO_USERNAME`, `MONGO_PASSWORD`, `MONGO_HOST`, `MONGO_PORT`, `MONGO_DATABASE`, `MONGO_AUTH_SOURCE`. - Startup scripts now fail fast when `MONGO_URL` is absent and `MONGO_USERNAME` is missing, to avoid silent auth mismatches. +- Startup now runs `scripts/preflight-mongo-connection.mjs` before boot. In `NODE_ENV=production` this is strict and aborts start on Mongo auth/connect failures. - For `MongoServerError: Authentication failed`, first verify `MONGO_PASSWORD`, then try `MONGO_AUTH_SOURCE=admin`. ## Content paths @@ -65,7 +66,7 @@ - `start.sh` is intentionally ignored by Git (`.gitignore`) so server secrets are not committed. - Use `start.example.sh` as the tracked template and keep real credentials in environment variables (or `.env` on the server). - Startup scripts parse `.env` with the `dotenv` parser (not shell `source`), so values containing spaces are handled safely. -- Startup scripts run patch helpers before boot (`scripts/patch-lightningcss.mjs`, `scripts/patch-endpoint-media-scope.mjs`, `scripts/patch-endpoint-files-upload-route.mjs`, `scripts/patch-frontend-serviceworker-file.mjs`, `scripts/patch-conversations-collection-guards.mjs`). +- Startup scripts run preflight + patch helpers before boot (`scripts/preflight-mongo-connection.mjs`, `scripts/patch-lightningcss.mjs`, `scripts/patch-endpoint-media-scope.mjs`, `scripts/patch-endpoint-files-upload-route.mjs`, `scripts/patch-frontend-serviceworker-file.mjs`, `scripts/patch-conversations-collection-guards.mjs`). - The media scope patch fixes a known upstream issue where file uploads can fail if the token scope is `create update delete` without explicit `media`. - The files upload route patch fixes browser multi-upload by posting to `/files/upload` (session-authenticated) instead of direct `/media` calls without bearer token. - The frontend serviceworker patch ensures `@indiekit/frontend/lib/serviceworker.js` exists at runtime to avoid ENOENT in the offline/service worker route. diff --git a/package.json b/package.json index 676b6364..9f96dc78 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "index.js", "scripts": { "postinstall": "node scripts/patch-lightningcss.mjs && node scripts/patch-endpoint-media-scope.mjs && node scripts/patch-endpoint-files-upload-route.mjs && node scripts/patch-frontend-serviceworker-file.mjs && node scripts/patch-conversations-collection-guards.mjs", - "serve": "node scripts/patch-lightningcss.mjs && node scripts/patch-endpoint-media-scope.mjs && node scripts/patch-endpoint-files-upload-route.mjs && node scripts/patch-frontend-serviceworker-file.mjs && node scripts/patch-conversations-collection-guards.mjs && node node_modules/@indiekit/indiekit/bin/cli.js serve --config indiekit.config.mjs", + "serve": "node scripts/preflight-mongo-connection.mjs && node scripts/patch-lightningcss.mjs && node scripts/patch-endpoint-media-scope.mjs && node scripts/patch-endpoint-files-upload-route.mjs && node scripts/patch-frontend-serviceworker-file.mjs && node scripts/patch-conversations-collection-guards.mjs && node node_modules/@indiekit/indiekit/bin/cli.js serve --config indiekit.config.mjs", "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], diff --git a/scripts/preflight-mongo-connection.mjs b/scripts/preflight-mongo-connection.mjs new file mode 100644 index 00000000..58250c3c --- /dev/null +++ b/scripts/preflight-mongo-connection.mjs @@ -0,0 +1,61 @@ +import { MongoClient } from "mongodb"; + +import config from "../indiekit.config.mjs"; + +const strictMode = + process.env.REQUIRE_MONGO === "1" || + (process.env.REQUIRE_MONGO !== "0" && process.env.NODE_ENV === "production"); + +const hasMongoUrl = Boolean(process.env.MONGO_URL); +const mongoUser = process.env.MONGO_USERNAME || process.env.MONGO_USER || ""; +const hasMongoPassword = Boolean(process.env.MONGO_PASSWORD); + +if (!hasMongoUrl && (!mongoUser || !hasMongoPassword)) { + const message = + "[preflight] Missing Mongo credentials: set MONGO_URL or MONGO_USERNAME + MONGO_PASSWORD."; + + if (strictMode) { + console.error(message); + process.exit(1); + } + + console.warn(`${message} Continuing because strict mode is disabled.`); + process.exit(0); +} + +const mongodbUrl = config.application?.mongodbUrl; + +if (!mongodbUrl) { + const message = "[preflight] MongoDB URL could not be resolved from configuration."; + + if (strictMode) { + console.error(message); + process.exit(1); + } + + console.warn(`${message} Continuing because strict mode is disabled.`); + process.exit(0); +} + +const client = new MongoClient(mongodbUrl, { connectTimeoutMS: 5000 }); + +try { + await client.connect(); + await client.db().command({ ping: 1 }); + console.log("[preflight] MongoDB connection OK"); +} catch (error) { + const message = `[preflight] MongoDB connection failed: ${error.message}`; + + if (strictMode) { + console.error(message); + process.exit(1); + } + + console.warn(`${message} Continuing because strict mode is disabled.`); +} finally { + try { + await client.close(); + } catch { + // no-op + } +} diff --git a/start.example.sh b/start.example.sh index c048e3ed..257a0331 100644 --- a/start.example.sh +++ b/start.example.sh @@ -34,6 +34,9 @@ fi export NODE_ENV="${NODE_ENV:-production}" +# Verify MongoDB credentials/connectivity before launching server. +/usr/local/bin/node scripts/preflight-mongo-connection.mjs + # Ensure runtime dependency patches are applied even if node_modules already exists. /usr/local/bin/node scripts/patch-lightningcss.mjs /usr/local/bin/node scripts/patch-endpoint-media-scope.mjs