// config.js - Configuration handling import fs from "fs"; import path from "path"; import { fileURLToPath } from "url"; import log4js from "log4js"; import { startWaiting, stopWaiting } from "./spinner.js"; // Determine environment const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const isPackaged = __dirname.includes("app.asar"); if (!process.env.NODE_ENV) { process.env.NODE_ENV = "production"; } const NODE_ENV = process.env.NODE_ENV; // Configure paths function getProductionConfigPath() { const homeDir = process.env.HOME || process.env.USERPROFILE || ""; switch (process.platform) { case "win32": return path.join( process.env.PROGRAMDATA || "C:\\ProgramData", "farmcontrol-server", "config.json", ); case "darwin": return path.join( homeDir, "Library", "Application Support", "farmcontrol-server", "config.json", ); case "linux": default: return "/etc/farmcontrol-server/config.json"; } } function getDevelopmentConfigPath() { // If we're in a packaged app (asar), we can't write to the app directory. // We should use a writable user data directory instead. if (isPackaged) { const homeDir = process.env.HOME || process.env.USERPROFILE || ""; switch (process.platform) { case "win32": return path.join( process.env.APPDATA || path.join(homeDir, "AppData", "Roaming"), "farmcontrol-server", "config.json", ); case "darwin": return path.join( homeDir, "Library", "Application Support", "farmcontrol-server", "config.json", ); default: return path.join( homeDir, ".config", "farmcontrol-server", "config.json", ); } } return path.resolve(__dirname, "../config.json"); } const CONFIG_PATH = NODE_ENV === "production" ? getProductionConfigPath() : getDevelopmentConfigPath(); const logger = log4js.getLogger("Config"); logger.level = "info"; function getDefaultProductionDataDir() { const homeDir = process.env.HOME || process.env.USERPROFILE || ""; switch (process.platform) { case "win32": return path.join( process.env.PROGRAMDATA || "C:\\ProgramData", "farmcontrol-server", "data", ); case "darwin": return path.join( homeDir, "Library", "Application Support", "farmcontrol-server", "data", ); case "linux": default: return "/var/lib/farmcontrol-server"; } } function getDefaultDevelopmentDataDir() { if (isPackaged) { return path.join(path.dirname(getDevelopmentConfigPath()), "data"); } return path.resolve(__dirname, "../data"); } function resolveDataDir(envConfig) { let dataDir = envConfig.dataDir; const wasMissing = !dataDir; if (!dataDir) { dataDir = NODE_ENV === "production" ? getDefaultProductionDataDir() : getDefaultDevelopmentDataDir(); } dataDir = path.resolve(dataDir); if (!fs.existsSync(dataDir)) { fs.mkdirSync(dataDir, { recursive: true }); logger.info(`Created data directory at ${dataDir}`); } return { dataDir, shouldPersist: wasMissing && NODE_ENV === "production" }; } const DEFAULT_CONFIG = { development: { logLevel: "debug", url: "https://dev-wss.tombutcher.work", apiUrl: "https://dev.tombutcher.work/api", host: { id: "", authCode: "", }, }, production: { logLevel: "info", url: "https://ws.farmcontrol.app", apiUrl: "https://api.farmcontrol.app", host: { id: "", authCode: "", }, }, }; // Load config file export function loadConfig() { try { if (!fs.existsSync(CONFIG_PATH)) { logger.info( `Configuration file not found at ${CONFIG_PATH}. Creating a new one.`, ); const configDir = path.dirname(CONFIG_PATH); if (!fs.existsSync(configDir)) { fs.mkdirSync(configDir, { recursive: true }); } fs.writeFileSync( CONFIG_PATH, JSON.stringify(DEFAULT_CONFIG, null, 2), "utf8", ); } const configData = fs.readFileSync(CONFIG_PATH, "utf8"); const config = JSON.parse(configData); if (!config[NODE_ENV]) { throw new Error( `Configuration for environment '${NODE_ENV}' not found in config.json`, ); } const { dataDir, shouldPersist } = resolveDataDir(config[NODE_ENV]); config[NODE_ENV] = { ...config[NODE_ENV], dataDir }; if (shouldPersist) { fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), "utf8"); logger.info(`Set data directory to ${dataDir} in config`); } return config[NODE_ENV]; } catch (err) { logger.error("Error loading config:", err); throw err; } } // Save config file export async function saveConfig(newConfig) { startWaiting("Saving...", logger); try { let config = {}; if (fs.existsSync(CONFIG_PATH)) { const configData = fs.readFileSync(CONFIG_PATH, "utf8"); config = JSON.parse(configData); } else { const configDir = path.dirname(CONFIG_PATH); if (!fs.existsSync(configDir)) { fs.mkdirSync(configDir, { recursive: true }); } } // Update current environment config[NODE_ENV] = newConfig; // Write back to file with 2-space indentation fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), "utf8"); await stopWaiting(); logger.info(`Configuration for '${NODE_ENV}' saved successfully.`); } catch (err) { await stopWaiting(); logger.error("Error saving config:", err); throw err; } } // Get current environment export function getEnvironment() { return NODE_ENV; } // Get the path to the active config.json file export function getConfigPath() { return CONFIG_PATH; } // Get the configured data directory, optionally with subpaths export function getDataDir(...subpaths) { const config = loadConfig(); return subpaths.length > 0 ? path.join(config.dataDir, ...subpaths) : config.dataDir; } // Ensure a subdirectory exists within the data directory export function ensureDataDir(...subpaths) { const dir = getDataDir(...subpaths); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } return dir; }