Includes: API (Express/TypeScript), Astro site, Python workers, document generators, FCC compliance tools, Canada CRTC formation, Ansible infrastructure, and deployment scripts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
111 lines
3.2 KiB
TypeScript
111 lines
3.2 KiB
TypeScript
// Environment configuration — singleton, loaded once at startup.
|
|
|
|
function required(name: string): string {
|
|
const v = process.env[name];
|
|
if (!v) throw new Error(`Missing required env var: ${name}`);
|
|
return v;
|
|
}
|
|
|
|
function optional(name: string, fallback: string): string {
|
|
return process.env[name] ?? fallback;
|
|
}
|
|
|
|
export interface Config {
|
|
port: number;
|
|
nodeEnv: string;
|
|
postgres: {
|
|
connectionString: string;
|
|
};
|
|
erpnext: {
|
|
url: string;
|
|
apiKey: string;
|
|
apiSecret: string;
|
|
siteName: string;
|
|
};
|
|
listmonk: {
|
|
url: string;
|
|
user: string;
|
|
password: string;
|
|
};
|
|
minio: {
|
|
endpoint: string;
|
|
port: number;
|
|
accessKey: string;
|
|
secretKey: string;
|
|
};
|
|
stripe: {
|
|
secretKey: string;
|
|
webhookSecret: string;
|
|
};
|
|
smtp: {
|
|
host: string;
|
|
port: number;
|
|
user: string;
|
|
pass: string;
|
|
from: string;
|
|
};
|
|
}
|
|
|
|
// Env-var guard for production: refuse to boot with placeholder secrets.
|
|
// Catches the common footgun of deploying with `change-this-in-production`
|
|
// still in effect on ADMIN_JWT_SECRET, WEBHOOK_SECRET, etc.
|
|
function refuseInsecureProduction(): void {
|
|
if (process.env.NODE_ENV !== "production") return;
|
|
const bad: string[] = [];
|
|
const check = (name: string, sentinels: string[] = []) => {
|
|
const v = process.env[name] ?? "";
|
|
if (!v) bad.push(`${name} is unset`);
|
|
else if (sentinels.includes(v)) bad.push(`${name} is still set to a placeholder`);
|
|
};
|
|
check("ADMIN_JWT_SECRET", ["change-this-in-production"]);
|
|
check("WEBHOOK_SECRET", ["change-this-in-production"]);
|
|
check("SHKEEPER_API_KEY");
|
|
check("STRIPE_WEBHOOK_SECRET");
|
|
if (bad.length) {
|
|
throw new Error(
|
|
`[config] Refusing to start in production with insecure settings:\n` +
|
|
bad.map(b => ` - ${b}`).join("\n"),
|
|
);
|
|
}
|
|
}
|
|
|
|
function loadConfig(): Config {
|
|
refuseInsecureProduction();
|
|
return {
|
|
port: parseInt(optional("PORT", "3001"), 10),
|
|
nodeEnv: optional("NODE_ENV", "development"),
|
|
postgres: {
|
|
connectionString: required("DATABASE_URL"),
|
|
},
|
|
erpnext: {
|
|
url: optional("ERPNEXT_URL", "http://erpnext:8000"),
|
|
apiKey: optional("ERPNEXT_API_KEY", ""),
|
|
apiSecret: optional("ERPNEXT_API_SECRET", ""),
|
|
siteName: optional("ERPNEXT_SITE_NAME", optional("ERPNEXT_HOST_HEADER", "performancewest.net")),
|
|
},
|
|
listmonk: {
|
|
url: optional("LISTMONK_URL", "http://listmonk:9000"),
|
|
user: optional("LISTMONK_USER", "api"),
|
|
password: optional("LISTMONK_PASSWORD", ""),
|
|
},
|
|
minio: {
|
|
endpoint: optional("MINIO_ENDPOINT", "localhost"),
|
|
port: parseInt(optional("MINIO_PORT", "9000"), 10),
|
|
accessKey: optional("MINIO_ACCESS_KEY", ""),
|
|
secretKey: optional("MINIO_SECRET_KEY", ""),
|
|
},
|
|
stripe: {
|
|
secretKey: optional("STRIPE_SECRET_KEY", ""),
|
|
webhookSecret: optional("STRIPE_WEBHOOK_SECRET", ""),
|
|
},
|
|
smtp: {
|
|
host: optional("SMTP_HOST", "mail.smtp2go.com"),
|
|
port: parseInt(optional("SMTP_PORT", "587"), 10),
|
|
user: optional("SMTP_USER", ""),
|
|
pass: optional("SMTP_PASS", ""),
|
|
from: optional("SMTP_FROM", "Performance West <noreply@performancewest.net>"),
|
|
},
|
|
};
|
|
}
|
|
|
|
export const config = loadConfig();
|