/** * Konfigurace z environment variables. * AppFactory je predava containeru, viz AGENTS.md, sekce Variables a secrets. * Zadna hodnota se nehardcoduje a zadny secret se neloguje. * * Pravidlo: chybejici konfigurace nesmi shodit start aplikace. Container, * ktery nenastartuje, je pro AppFactory nefunkcni sluzba. */ import { randomBytes } from 'node:crypto'; import path from 'node:path'; const isProduction = process.env.NODE_ENV === 'production'; function positiveNumber(value: string | undefined, fallback: number): number { const parsed = Number(value); return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback; } /** * Tajny klic pro podpis tokenu. * * Kdyz promenna chybi, NEPADAME a ani nepouzijeme klic zapsany v kodu. * Aplikace musi nastartovat a odpovidat na /health, jinak ji AppFactory * vyhodnoti jako nefunkcni. Misto toho se vygeneruje nahodny klic pro * beh procesu a do logu jde hlasite varovani. * * Dusledek: po restartu containeru prestanou platit vydane tokeny * a uzivatele se musi prihlasit znovu. Proto se JWT_SECRET ma nastavit * jako promenna aplikace v AppFactory. */ function resolveJwtSecret(): string { const value = process.env.JWT_SECRET; if (value && value.trim().length > 0) return value; const generated = randomBytes(32).toString('base64url'); console.warn( '[config] JWT_SECRET neni nastavena. Pouzivam nahodny klic platny jen do restartu ' + 'containeru. Nastavte JWT_SECRET jako promennou aplikace v AppFactory.', ); return generated; } /** * Prefix verejne adresy, napr. "/apps/csbot-prototype". * Caddy ho pred predanim do containeru odstranuje (handle_path), ale prohlizec * ho vidi - proto se z nej sklada base pro SPA, odkazy, Swagger i webhooky. */ function normalizeRootPath(value: string | undefined): string { const trimmed = (value ?? '').trim(); if (trimmed.length === 0 || trimmed === '/') return ''; const withSlash = trimmed.startsWith('/') ? trimmed : `/${trimmed}`; return withSlash.replace(/\/+$/, ''); } export const config = { isProduction, /** Port urcuje AppFactory sablona, vychozi 3000. Nemenit bez upravy metadat. */ port: Number(process.env.PORT ?? 3000), rootPath: normalizeRootPath(process.env.ROOT_PATH), jwtSecret: resolveJwtSecret(), jwtExpiresIn: process.env.JWT_EXPIRES_IN ?? '8h', /** * Povolene originy pro CORS. V nasazeni bezi web i API na stejne domene, * takze se CORS neuplatni. Je tu kvuli lokalnimu vyvoji s Vite dev serverem. */ corsOrigins: (process.env.CORS_ORIGIN ?? 'http://localhost:5173,http://localhost:4173') .split(',') .map((o) => o.trim()) .filter(Boolean), /** * Verejna adresa bez prefixu, napr. "https://services.csbot.cz". * Sklada se z ni absolutni URL webhooku. Kdyz neni vyplnena, pouzije se * relativni tvar - nikdy se nehardcoduje produkcni domena. */ publicOrigin: (process.env.PUBLIC_ORIGIN ?? '').trim().replace(/\/+$/, ''), // ------------------------------------------------------- skripty konektoru /** * Adresar se skripty konektoru. Relativne k adresari, ze ktereho aplikace * bezi, aby to fungovalo v containeru (`/app/scripts`) i lokalne. */ scriptsDir: path.resolve(process.env.SCRIPTS_DIR ?? path.join(process.cwd(), 'scripts')), /** * Zaklad adres napojenych sluzeb, napr. "https://services.csbot.cz/apps". * Konkretni konektor lze presmerovat pres `_BASE_URL`. * Nikdy se nehardcoduje do logiky, viz AGENTS.md. */ servicesBaseUrl: (process.env.SERVICES_BASE_URL ?? 'https://services.csbot.cz/apps') .trim() .replace(/\/+$/, ''), /** Strop na jeden beh skriptu, kdyz si ho manifest neurci sam. */ scriptTimeoutMs: positiveNumber(process.env.SCRIPT_TIMEOUT_MS, 15_000), /** Vetsi odpoved cizi sluzby se zahodi, misto aby snedla pamet procesu. */ scriptMaxResponseBytes: positiveNumber(process.env.SCRIPT_MAX_RESPONSE_BYTES, 1_000_000), /** * Povoli skriptum volat na localhost a do privatnich rozsahu IP. * Jen pro lokalni vyvoj, v nasazeni musi zustat vypnute. */ allowPrivateTargets: process.env.ALLOW_PRIVATE_TARGETS === 'true', }; /** Zaklad verejne adresy aplikace vcetne prefixu proxy. */ export function publicBaseUrl(): string { return `${config.publicOrigin}${config.rootPath}`; }