fix(backend): harden mongo startup and files upload locales

This commit is contained in:
svemagie
2026-03-08 01:51:18 +01:00
parent 14d7d16f73
commit 7a61f4da53
6 changed files with 130 additions and 12 deletions

View File

@@ -22,10 +22,11 @@
## MongoDB
- 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`.
- Preferred: 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`.
- You can still use a full `MONGO_URL` (example: `mongodb://user:pass@host:27017/indiekit?authSource=admin`).
- If both `MONGO_URL` and `MONGO_USERNAME`/`MONGO_PASSWORD` are set, decomposed credentials take precedence by default to avoid stale URL mismatches. Set `MONGO_PREFER_URL=1` to force `MONGO_URL` precedence.
- 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.
- Startup now runs `scripts/preflight-mongo-connection.mjs` before boot. Preflight is strict by default and aborts start on Mongo auth/connect failures; set `REQUIRE_MONGO=0` to bypass strict mode intentionally.
- For `MongoServerError: Authentication failed`, first verify `MONGO_PASSWORD`, then try `MONGO_AUTH_SOURCE=admin`.
## Content paths
@@ -66,8 +67,9 @@
- `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 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`).
- 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-endpoint-files-upload-locales.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 files upload locale patch adds missing `files.upload.dropText`/`files.upload.browse`/`files.upload.submitMultiple` labels in endpoint locale files so UI text does not render raw translation keys.
- The frontend serviceworker patch ensures `@indiekit/frontend/lib/serviceworker.js` exists at runtime to avoid ENOENT in the offline/service worker route.
- The conversations guard patch prevents `Cannot read properties of undefined (reading 'find')` when the `conversation_items` collection is temporarily unavailable.

View File

@@ -7,6 +7,9 @@ const mongoPort = process.env.MONGO_PORT || "27017";
const mongoDatabase =
process.env.MONGO_DATABASE || process.env.MONGO_DB || "indiekit";
const mongoAuthSource = process.env.MONGO_AUTH_SOURCE || "admin";
const hasMongoUrl = Boolean(process.env.MONGO_URL);
const hasMongoCredentials = Boolean(mongoUsername && mongoPassword);
const preferMongoUrl = process.env.MONGO_PREFER_URL === "1";
const mongoCredentials =
mongoUsername && mongoPassword
? `${encodeURIComponent(mongoUsername)}:${encodeURIComponent(
@@ -17,9 +20,11 @@ const mongoQuery =
mongoCredentials && mongoAuthSource
? `?authSource=${encodeURIComponent(mongoAuthSource)}`
: "";
const mongoUrlFromParts = `mongodb://${mongoCredentials}${mongoHost}:${mongoPort}/${mongoDatabase}${mongoQuery}`;
const mongoUrl =
process.env.MONGO_URL ||
`mongodb://${mongoCredentials}${mongoHost}:${mongoPort}/${mongoDatabase}${mongoQuery}`;
hasMongoUrl && (!hasMongoCredentials || preferMongoUrl)
? process.env.MONGO_URL
: mongoUrlFromParts;
const githubUsername = process.env.GITHUB_USERNAME || "svemagie";
const githubContentToken =

View File

@@ -4,8 +4,8 @@
"description": "",
"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/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",
"postinstall": "node scripts/patch-lightningcss.mjs && node scripts/patch-endpoint-media-scope.mjs && node scripts/patch-endpoint-files-upload-route.mjs && node scripts/patch-endpoint-files-upload-locales.mjs && node scripts/patch-frontend-serviceworker-file.mjs && node scripts/patch-conversations-collection-guards.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-endpoint-files-upload-locales.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": [],

View File

@@ -0,0 +1,93 @@
import { access, readdir, readFile, writeFile } from "node:fs/promises";
import path from "node:path";
const localeDirCandidates = [
"node_modules/@indiekit/endpoint-files/locales",
"node_modules/@indiekit/indiekit/node_modules/@indiekit/endpoint-files/locales",
];
const defaultLabels = {
dropText: "Drag files here or",
browse: "Browse files",
submitMultiple: "Upload files",
};
const localeLabels = {
de: {
dropText: "Dateien hierher ziehen oder",
browse: "Dateien auswaehlen",
submitMultiple: "Dateien hochladen",
},
};
async function exists(filePath) {
try {
await access(filePath);
return true;
} catch {
return false;
}
}
let checkedDirs = 0;
let checkedFiles = 0;
let patchedFiles = 0;
for (const localeDir of localeDirCandidates) {
if (!(await exists(localeDir))) {
continue;
}
checkedDirs += 1;
const files = (await readdir(localeDir)).filter((file) => file.endsWith(".json"));
for (const fileName of files) {
const filePath = path.join(localeDir, fileName);
const source = await readFile(filePath, "utf8");
let json;
try {
json = JSON.parse(source);
} catch {
continue;
}
checkedFiles += 1;
if (!json.files || typeof json.files !== "object") {
json.files = {};
}
if (!json.files.upload || typeof json.files.upload !== "object") {
json.files.upload = {};
}
const locale = fileName.replace(/\.json$/, "");
const labels = localeLabels[locale] || defaultLabels;
let changed = false;
for (const [key, value] of Object.entries(labels)) {
if (!json.files.upload[key]) {
json.files.upload[key] = value;
changed = true;
}
}
if (!changed) {
continue;
}
await writeFile(filePath, `${JSON.stringify(json, null, 2)}\n`, "utf8");
patchedFiles += 1;
}
}
if (checkedDirs === 0) {
console.log("[postinstall] No endpoint-files locale directories found");
} else if (patchedFiles === 0) {
console.log("[postinstall] endpoint-files upload locale keys already patched");
} else {
console.log(
`[postinstall] Patched endpoint-files upload locale keys in ${patchedFiles}/${checkedFiles} locale file(s)`,
);
}

View File

@@ -2,9 +2,7 @@ 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 strictMode = process.env.REQUIRE_MONGO !== "0";
const hasMongoUrl = Boolean(process.env.MONGO_URL);
const mongoUser = process.env.MONGO_USERNAME || process.env.MONGO_USER || "";
@@ -37,6 +35,19 @@ if (!mongodbUrl) {
process.exit(0);
}
try {
const parsedUrl = new URL(mongodbUrl);
const database = parsedUrl.pathname.replace(/^\//, "") || "(default)";
const authSource = parsedUrl.searchParams.get("authSource") || "(default)";
const username = parsedUrl.username ? decodeURIComponent(parsedUrl.username) : "(none)";
console.log(
`[preflight] Mongo target ${parsedUrl.hostname}:${parsedUrl.port || "27017"}/${database} user=${username} authSource=${authSource}`,
);
} catch {
// Keep preflight behavior unchanged if URL parsing fails.
}
const client = new MongoClient(mongodbUrl, { connectTimeoutMS: 5000 });
try {
@@ -46,6 +57,12 @@ try {
} catch (error) {
const message = `[preflight] MongoDB connection failed: ${error.message}`;
if (hasMongoUrl && mongoUser && hasMongoPassword) {
console.warn(
"[preflight] Both MONGO_URL and MONGO_USERNAME/MONGO_PASSWORD are set. Effective precedence follows indiekit.config.mjs.",
);
}
if (strictMode) {
console.error(message);
process.exit(1);

View File

@@ -12,7 +12,7 @@ if [ -f .env ]; then
const parsed = dotenv.parse(fs.readFileSync(".env"));
for (const [key, value] of Object.entries(parsed)) {
const safe = String(value).split("\x27").join("\x27\"\x27\"\x27");
process.stdout.write(`export ${key}=\x27${safe}\x27\\n`);
process.stdout.write(`export ${key}=\x27${safe}\x27\n`);
}
')"
fi
@@ -41,6 +41,7 @@ export NODE_ENV="${NODE_ENV:-production}"
/usr/local/bin/node scripts/patch-lightningcss.mjs
/usr/local/bin/node scripts/patch-endpoint-media-scope.mjs
/usr/local/bin/node scripts/patch-endpoint-files-upload-route.mjs
/usr/local/bin/node scripts/patch-endpoint-files-upload-locales.mjs
/usr/local/bin/node scripts/patch-frontend-serviceworker-file.mjs
/usr/local/bin/node scripts/patch-conversations-collection-guards.mjs