// config.js — environment-based JSON config + .env overrides (farmcontrol-api style) import fs from "fs"; import path from "path"; import { fileURLToPath } from "url"; import dotenv from "dotenv"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // Monorepo: repo-root .env then packages/server/.env (later files only fill missing keys) dotenv.config({ path: path.resolve(__dirname, "../../.env") }); dotenv.config({ path: path.resolve(__dirname, ".env") }); const CONFIG_PATH = path.resolve(__dirname, "config.json"); const NODE_ENV = process.env.NODE_ENV || "development"; function ensureShape(envConfig) { if (!envConfig.server) envConfig.server = {}; if (!envConfig.app) envConfig.app = {}; if (!envConfig.database) envConfig.database = {}; if (!envConfig.database.mongo) envConfig.database.mongo = {}; if (!envConfig.auth) envConfig.auth = {}; if (!envConfig.auth.oidc) envConfig.auth.oidc = {}; if (!envConfig.device) envConfig.device = {}; if (!envConfig.mqtt) envConfig.mqtt = {}; if (!envConfig.s3) envConfig.s3 = {}; if (!envConfig.hub) envConfig.hub = {}; if (!Array.isArray(envConfig.patches)) envConfig.patches = []; } function loadConfig() { try { if (!fs.existsSync(CONFIG_PATH)) { throw new Error(`Configuration file not found at ${CONFIG_PATH}`); } const configData = fs.readFileSync(CONFIG_PATH, "utf8"); const all = JSON.parse(configData); if (!all[NODE_ENV]) { throw new Error( `Configuration for environment '${NODE_ENV}' not found in config.json`, ); } const envConfig = JSON.parse(JSON.stringify(all[NODE_ENV])); ensureShape(envConfig); if (process.env.PORT) { envConfig.server.port = Number(process.env.PORT); } if (process.env.LOG_LEVEL) { envConfig.server.logLevel = process.env.LOG_LEVEL; } if (process.env.CLIENT_URL) { envConfig.app.urlClient = process.env.CLIENT_URL; } if (process.env.MONGODB_URI) { envConfig.database.mongo.url = process.env.MONGODB_URI; } if (process.env.SESSION_SECRET) { envConfig.auth.sessionSecret = process.env.SESSION_SECRET; } if (process.env.OIDC_ISSUER) { envConfig.auth.oidc.issuer = process.env.OIDC_ISSUER; } if (process.env.OIDC_CLIENT_ID) { envConfig.auth.oidc.clientId = process.env.OIDC_CLIENT_ID; } if (process.env.OIDC_CLIENT_SECRET) { envConfig.auth.oidc.clientSecret = process.env.OIDC_CLIENT_SECRET; } if (process.env.OIDC_REDIRECT_URI) { envConfig.auth.oidc.redirectUri = process.env.OIDC_REDIRECT_URI; } if (process.env.OIDC_APP_CALLBACK_URI) { envConfig.auth.oidc.appCallbackUri = process.env.OIDC_APP_CALLBACK_URI; } if (process.env.APP_REDIRECT_URI) { envConfig.auth.appRedirectUri = process.env.APP_REDIRECT_URI; } if (process.env.HUB_API_KEY) { envConfig.hub.apiKey = process.env.HUB_API_KEY; } if (process.env.DEMO_TTN_APP_ID) { envConfig.device.ttnApplicationId = process.env.DEMO_TTN_APP_ID; } if (process.env.DEMO_TTN_DEV_ID) { envConfig.device.ttnDeviceId = process.env.DEMO_TTN_DEV_ID; } if (process.env.DEVICE_TTN_APPLICATION_ID) { envConfig.device.ttnApplicationId = process.env.DEVICE_TTN_APPLICATION_ID; } if (process.env.DEVICE_TTN_DEVICE_ID) { envConfig.device.ttnDeviceId = process.env.DEVICE_TTN_DEVICE_ID; } if (process.env.DEVICE_NAME) { envConfig.device.name = process.env.DEVICE_NAME; } if (process.env.MQTT_CONFIG_PATH) { envConfig.mqtt.configPath = process.env.MQTT_CONFIG_PATH; } if (process.env.S3_BUCKET) { envConfig.s3.bucket = process.env.S3_BUCKET; } if (process.env.AWS_REGION) { envConfig.s3.region = process.env.AWS_REGION; } if (process.env.S3_PREFIX) { envConfig.s3.prefix = process.env.S3_PREFIX; } if (process.env.S3_ENDPOINT) { envConfig.s3.endpoint = process.env.S3_ENDPOINT; } if (process.env.S3_FORCE_PATH_STYLE != null) { const v = String(process.env.S3_FORCE_PATH_STYLE).toLowerCase(); envConfig.s3.forcePathStyle = v === "1" || v === "true" || v === "yes"; } if (process.env.S3_ACCESS_KEY_ID) { envConfig.s3.accessKeyId = process.env.S3_ACCESS_KEY_ID; } else if (process.env.AWS_ACCESS_KEY_ID) { envConfig.s3.accessKeyId = process.env.AWS_ACCESS_KEY_ID; } if (process.env.S3_SECRET_ACCESS_KEY) { envConfig.s3.secretAccessKey = process.env.S3_SECRET_ACCESS_KEY; } else if (process.env.AWS_SECRET_ACCESS_KEY) { envConfig.s3.secretAccessKey = process.env.AWS_SECRET_ACCESS_KEY; } if (process.env.S3_SESSION_TOKEN) { envConfig.s3.sessionToken = process.env.S3_SESSION_TOKEN; } else if (process.env.AWS_SESSION_TOKEN) { envConfig.s3.sessionToken = process.env.AWS_SESSION_TOKEN; } if (!envConfig.auth.sessionSecret) { throw new Error( "SESSION_SECRET environment variable is required, or set auth.sessionSecret in config.json for this environment.", ); } if (NODE_ENV === "production") { if (!envConfig.database.mongo.url?.trim()) { throw new Error( "database.mongo.url must be set for production (config.json or MONGODB_URI).", ); } if (!envConfig.app.urlClient?.trim()) { throw new Error( "app.urlClient must be set for production (config.json or CLIENT_URL).", ); } } return envConfig; } catch (err) { console.error("Error loading config:", err); throw err; } } export function getEnvironment() { return NODE_ENV; } const config = loadConfig(); export default config;