Tom Butcher 3ca730ecee
All checks were successful
farmcontrol/farmcontrol-server/pipeline/head This commit looks good
Add data directory management and enhance command line output
- Introduce functions to determine and create default data directories for both production and development environments in config.js.
- Update commandline.js to display the configured data directory and config path in the command line output.
- Modify filemanager.js to ensure the existence of a dedicated files directory within the data directory.
- Enhance localserver.js to include the data directory and config path in the server response, providing better context for users.
2026-07-27 18:15:29 +01:00

251 lines
6.3 KiB
JavaScript

// 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;
}