Make env-var parsing handle Coolify's full quote-escaping scenarios; try multiple unescaping candidates before failing

This commit is contained in:
oonyeje 2026-07-07 23:54:10 +00:00
parent 4d01664787
commit 0cd6cc68fb

View File

@ -41,27 +41,59 @@ type RouteMap = Record<string, RouteEntry>;
// ----- Helpers -------------------------------------------------------------- // ----- Helpers --------------------------------------------------------------
/** /**
* Normalize a raw env-var value. Some hosting platforms (Coolify included) * Normalize a raw env-var value. Different hosting platforms apply different
* wrap pasted JSON values in extra quotes when the user doesn't quote-escape * (and surprising) transformations when you paste a JSON value into an
* properly, producing strings like: * env-var field. We've observed at least:
* "{\"square\":{...}}"
* which JSON.parse cannot read at position 1.
* *
* This helper: * 1. Raw JSON pasted in: {"square":{"project":"x"}}
* 1. Trims whitespace * 2. Wrapped in a single layer of quotes: "{"square":{"project":"x"}}"
* 2. Strips a single surrounding layer of `"` or `'` * 3. Wrapped AND every inner quote escaped: "{\"square\":{\"project\":\"x\"}}"
* 3. Unescapes `\"` -> `"`, `\'` -> `'`, and `\\` -> `\` * 4. Inner quotes escaped, no outer wrapping: {\"square\":{\"project\":\"x\"}}
*
* This helper walks through each plausible unescaping and returns the first
* one that produces a syntactically valid JSON string. If none succeed, the
* raw value is returned and JSON.parse will throw a clear error.
*/ */
function normalizeEnvValue(raw: string): string { function normalizeEnvValue(raw: string): string {
if (!raw) return raw;
let value = raw.trim(); let value = raw.trim();
if (
(value.startsWith('"') && value.endsWith('"')) || const candidates: string[] = [
(value.startsWith("'") && value.endsWith("'")) value,
) { // Strip a single surrounding layer of `"` or `'`
value = value.slice(1, -1); stripWrappingQuotes(value),
value = value.replace(/\\"/g, '"').replace(/\\'/g, "'").replace(/\\\\/g, '\\'); // Unescape every \" -> " (handles Coolify double-escape)
value.replace(/\\"/g, '"').replace(/\\'/g, "'").replace(/\\\\/g, '\\'),
// Combined: strip wrapping quotes AND unescape inner escapes
stripWrappingQuotes(value).replace(/\\"/g, '"').replace(/\\'/g, "'").replace(/\\\\/g, '\\'),
];
// Dedupe and try each
const seen = new Set<string>();
for (const candidate of candidates) {
const trimmed = candidate.trim();
if (seen.has(trimmed)) continue;
seen.add(trimmed);
try {
JSON.parse(trimmed);
return trimmed;
} catch {
// try next
}
} }
return value.trim();
return value;
}
function stripWrappingQuotes(s: string): string {
if (
(s.startsWith('"') && s.endsWith('"')) ||
(s.startsWith("'") && s.endsWith("'"))
) {
return s.slice(1, -1);
}
return s;
} }
// ----- Config --------------------------------------------------------------- // ----- Config ---------------------------------------------------------------
@ -81,7 +113,7 @@ try {
const raw = (process.env.PROJECT_CREDENTIALS ?? '').slice(0, 300); const raw = (process.env.PROJECT_CREDENTIALS ?? '').slice(0, 300);
console.error('[startup] PROJECT_CREDENTIALS is not valid JSON:', message); console.error('[startup] PROJECT_CREDENTIALS is not valid JSON:', message);
console.error('[startup] Received value (first 300 chars):', raw); console.error('[startup] Received value (first 300 chars):', raw);
console.error('[startup] Hint: in Coolify, paste the JSON without surrounding quotes. If the platform wraps it, set the env var to a single-line value with no leading/trailing whitespace.'); console.error('[startup] Tried stripping wrapping quotes and unescaping \\"->". If you still see this, paste the JSON without any escaping.');
process.exit(1); process.exit(1);
} }