// config/env.ts // Copyright (C) 2026 Robert Colbert // All Rights Reserved import path, { dirname } from "node:path"; import { fileURLToPath } from "node:url"; import { loadGadgetCodeConfig, resolvePath } from "@gadget/config"; import type PackageJson from "../../package.json"; import { GadgetLog, GadgetLogFile } from "@gadget/api"; const __dirname = dirname(fileURLToPath(import.meta.url)); // INSTALL_DIR: where the package is installed (for loading assets, etc.) export const INSTALL_DIR = path.resolve(__dirname, "..", ".."); // Load YAML configuration const yamlConfig = loadGadgetCodeConfig(); // Validate required fields if (!yamlConfig.auth?.jwtSecret) { throw new Error( "Configuration error: auth.jwtSecret is required in gadget-code.yaml\n" + "See documentation: ./docs/configuration.md", ); } if (!yamlConfig.auth?.passwordSalt) { throw new Error( "Configuration error: auth.passwordSalt is required in gadget-code.yaml\n" + "See documentation: ./docs/configuration.md", ); } if (!yamlConfig.session?.secret) { throw new Error( "Configuration error: session.secret is required in gadget-code.yaml\n" + "See documentation: ./docs/configuration.md", ); } async function readJsonFile(filePath: string): Promise { const fs = await import("node:fs"); const file = await fs.promises.readFile(filePath); return JSON.parse(file.toString("utf-8")) as T; } export interface ISiteDefinition { company: string; companyShort: string; name: string; shortName: string; tagline: string; slogan: string; description: string; domain: string; domainKey: string; host: string; } export default { NODE_ENV: process.env.NODE_ENV, timezone: yamlConfig.timezone || "America/New_York", installDir: INSTALL_DIR, pkg: await readJsonFile( path.join(INSTALL_DIR, "package.json"), ), site: { company: yamlConfig.site?.company || "Robert Colbert", companyShort: yamlConfig.site?.companyShort || "Colbert", name: yamlConfig.site?.name || "Gadget Code", shortName: yamlConfig.site?.shortName || "Gadget Code", slogan: yamlConfig.site?.slogan || "Self-hosted Agentic Engineering Platform", description: yamlConfig.site?.description || "Gadget Code - A self-hosted Agentic Engineering Platform (AEP).", domain: yamlConfig.site?.domain || "code-dev.g4dge7.com", domainKey: yamlConfig.site?.domainKey || "code-dev.g4dge7.com", host: yamlConfig.site?.host || "code-dev.g4dge7.com", }, ai: { ollama: { apiUrl: process.env.DTP_OLLAMA_API_URL || "http://localhost:11434", apiKey: process.env.DTP_OLLAMA_API_KEY || "", }, }, auth: { jwtSecret: yamlConfig.auth.jwtSecret, }, session: { secret: yamlConfig.session.secret, trustProxy: process.env.NODE_ENV === "production" || yamlConfig.session?.trustProxy === true, cookie: { secure: yamlConfig.session?.cookie?.secure === true, sameSite: yamlConfig.session?.cookie?.sameSite || false, }, }, google: { cse: { apiKey: yamlConfig.google.cse.apiKey, engineId: yamlConfig.google.cse.engineId, }, }, mongodb: { host: yamlConfig.mongodb?.host || "localhost", database: yamlConfig.mongodb?.database || "", }, qdrant: { host: yamlConfig.qdrant?.host || "localhost", port: yamlConfig.qdrant?.port || 6333, apiKey: yamlConfig.qdrant?.apiKey, collection: yamlConfig.qdrant?.collection || "gadget-chat-embeddings", providerId: yamlConfig.qdrant?.providerId || "", embeddingModel: yamlConfig.qdrant?.embeddingModel || "nomic-embed-text", vectorSize: yamlConfig.qdrant?.vectorSize || 768, }, redis: { host: yamlConfig.redis?.host || "localhost", port: yamlConfig.redis?.port || 6379, password: yamlConfig.redis?.password, keyPrefix: yamlConfig.redis?.keyPrefix || "dtp", lazyConnect: yamlConfig.redis?.lazyConnect === true, }, minio: { endpoint: yamlConfig.minio?.endpoint || "localhost", port: yamlConfig.minio?.port || 9080, useSsl: yamlConfig.minio?.useSsl === true, accessKey: yamlConfig.minio?.accessKey, secretKey: yamlConfig.minio?.secretKey, buckets: { uploads: yamlConfig.minio?.buckets?.uploads || "dtp-uploads", images: yamlConfig.minio?.buckets?.images || "dtp-images", videos: yamlConfig.minio?.buckets?.videos || "dtp-videos", audios: yamlConfig.minio?.buckets?.audios || "dtp-audios", }, }, user: { passwordSalt: yamlConfig.auth.passwordSalt, }, https: { enabled: yamlConfig.https?.enabled === true, address: yamlConfig.https?.address || "127.0.0.1", port: yamlConfig.https?.port || 3443, backlog: yamlConfig.https?.backlog || 16, keyFile: yamlConfig.https?.keyFile, crtFile: yamlConfig.https?.crtFile, uploadPath: yamlConfig.https?.uploadPath || "/tmp", }, socket: { maxHttpBufferSize: yamlConfig.socket?.maxHttpBufferSize || 1048576, }, frontend: { port: 5173, }, email: { enabled: yamlConfig.email?.enabled === true, smtp: { host: yamlConfig.email?.smtp?.host || "localhost", port: yamlConfig.email?.smtp?.port || 465, secure: yamlConfig.email?.smtp?.secure === true, from: yamlConfig.email?.smtp?.from || "Digital Telepresence Support ", user: yamlConfig.email?.smtp?.user, password: yamlConfig.email?.smtp?.password, pool: { enabled: yamlConfig.email?.smtp?.pool?.enabled === true, maxConnections: yamlConfig.email?.smtp?.pool?.maxConnections || 5, maxMessages: yamlConfig.email?.smtp?.pool?.maxMessages || 100, }, }, contact: { to: yamlConfig.email?.contact?.to || "DTP Support ", }, }, log: { https: { enabled: yamlConfig.logging?.https?.enabled === true || false, name: yamlConfig.logging?.https?.name || "gadget-code-https.log", path: yamlConfig.logging?.https?.path ? resolvePath(yamlConfig.logging.https.path) : "/var/log/dtp", format: yamlConfig.logging?.https?.format || "combined", }, console: { enabled: yamlConfig.logging?.console?.enabled === true, }, file: { enabled: yamlConfig.logging?.file?.enabled === true, path: yamlConfig.logging?.file?.path ? resolvePath(yamlConfig.logging.file.path) : path.join(INSTALL_DIR, "logs"), name: yamlConfig.logging?.file?.name || "gadget-code", maxWritesPerFile: yamlConfig.logging?.file?.maxWritesPerFile || 10000, maxFiles: yamlConfig.logging?.file?.maxFiles || 10, }, levels: { debug: yamlConfig.logging?.levels?.debug === true, info: yamlConfig.logging?.levels?.info === true, warn: yamlConfig.logging?.levels?.warn === true, }, }, }; // Configure GadgetLog for this package GadgetLog.consoleEnabled = yamlConfig.logging?.console?.enabled === true; if (yamlConfig.logging?.file?.enabled === true) { const logFileOptions = { basePath: yamlConfig.logging.file.path ? resolvePath(yamlConfig.logging.file.path) : path.join(INSTALL_DIR, "logs"), name: yamlConfig.logging.file.name || "gadget-code", maxWritesPerFile: yamlConfig.logging.file.maxWritesPerFile || 10000, maxFiles: yamlConfig.logging.file.maxFiles || 10, }; const defaultLogFile = new GadgetLogFile(logFileOptions); defaultLogFile.open(); GadgetLog.defaultFile = defaultLogFile; }