ai / config /loader.js
Marco Beretta
LibreChat upload repo
3b6afc0
raw
history blame
6.08 kB
const dotenv = require('dotenv');
const path = require('path');
const fs = require('fs');
const crypto = require('crypto');
/**
* This class is responsible for loading the environment variables
*
* Inspired by: https://thekenyandev.com/blog/environment-variables-strategy-for-node/
*/
class Env {
constructor() {
this.envMap = {
default: '.env',
development: '.env.development',
test: '.env.test',
production: '.env.production',
};
this.init();
this.isProduction = process.env.NODE_ENV === 'production';
this.domains = {
client: process.env.DOMAIN_CLIENT,
server: process.env.DOMAIN_SERVER,
};
}
/**
* Initialize the environment variables
*/
init() {
let hasDefault = false;
// Load the default env file if it exists
if (fs.existsSync(this.envMap.default)) {
hasDefault = true;
dotenv.config({
path: this.resolve(this.envMap.default),
});
} else {
console.warn('The default .env file was not found');
}
const environment = this.currentEnvironment();
// Load the environment specific env file
const envFile = this.envMap[environment];
// check if the file exists
if (fs.existsSync(envFile)) {
dotenv.config({
path: this.resolve(envFile),
});
} else if (!hasDefault) {
console.warn('No env files found, have you completed the install process?');
}
}
/**
* Validate Config
*/
validate() {
const requiredKeys = [
'NODE_ENV',
'JWT_SECRET',
'DOMAIN_CLIENT',
'DOMAIN_SERVER',
'CREDS_KEY',
'CREDS_IV',
];
const missingKeys = requiredKeys
.map((key) => {
const variable = process.env[key];
if (variable === undefined || variable === null) {
return key;
}
})
.filter((value) => value !== undefined);
// Throw an error if any required keys are missing
if (missingKeys.length) {
const message = `
The following required env variables are missing:
${missingKeys.toString()}.
Please add them to your env file or run 'npm run install'
`;
throw new Error(message);
}
// Check JWT secret for default
if (process.env.JWT_SECRET === 'secret') {
console.warn('Warning: JWT_SECRET is set to default value');
}
}
/**
* Resolve the location of the env file
*
* @param {String} envFile
* @returns
*/
resolve(envFile) {
return path.resolve(process.cwd(), envFile);
}
/**
* Add secure keys to the env
*
* @param {String} filePath The path of the .env you are updating
* @param {String} key The env you are adding
* @param {Number} length The length of the secure key
*/
addSecureEnvVar(filePath, key, length) {
const env = {};
env[key] = this.generateSecureRandomString(length);
this.writeEnvFile(filePath, env);
}
/**
* Write the change to the env file
*/
writeEnvFile(filePath, env) {
const content = fs.readFileSync(filePath, 'utf-8');
const lines = content.split('\n');
const updatedLines = lines
.map((line) => {
if (line.trim().startsWith('#')) {
// Allow comment removal
if (env[line] === 'remove') {
return null; // Mark the line for removal
}
// Preserve comments
return line;
}
const [key, value] = line.split('=');
if (key && value && Object.prototype.hasOwnProperty.call(env, key.trim())) {
if (env[key.trim()] === 'remove') {
return null; // Mark the line for removal
}
return `${key.trim()}=${env[key.trim()]}`;
}
return line;
})
.filter((line) => line !== null); // Remove lines marked for removal
// Add any new environment variables that are not in the file yet
Object.entries(env).forEach(([key, value]) => {
if (value !== 'remove' && !updatedLines.some((line) => line.startsWith(`${key}=`))) {
updatedLines.push(`${key}=${value}`);
}
});
// Loop through updatedLines and wrap values with spaces in double quotes
const fixedLines = updatedLines.map((line) => {
// lets only split the first = sign
const [key, value] = line.split(/=(.+)/);
if (typeof value === 'undefined' || line.trim().startsWith('#')) {
return line;
}
// Skip lines with quotes and numbers already
// Todo: this could be one regex
const wrappedValue =
value.includes(' ') && !value.includes('"') && !value.includes('\'') && !/\d/.test(value)
? `"${value}"`
: value;
return `${key}=${wrappedValue}`;
});
const updatedContent = fixedLines.join('\n');
fs.writeFileSync(filePath, updatedContent);
}
/**
* Generate Secure Random Strings
*
* @param {Number} length The length of the random string
* @returns
*/
generateSecureRandomString(length = 32) {
return crypto.randomBytes(length).toString('hex');
}
/**
* Get all the environment variables
*/
all() {
return process.env;
}
/**
* Get an environment variable
*
* @param {String} variable
* @returns
*/
get(variable) {
return process.env[variable];
}
/**
* Get the current environment name
*
* @returns {String}
*/
currentEnvironment() {
return this.get('NODE_ENV');
}
/**
* Are we running in development?
*
* @returns {Boolean}
*/
isDevelopment() {
return this.currentEnvironment() === 'development';
}
/**
* Are we running tests?
*
* @returns {Boolean}
*/
isTest() {
return this.currentEnvironment() === 'test';
}
/**
* Are we running in production?
*
* @returns {Boolean}
*/
isProduction() {
return this.currentEnvironment() === 'production';
}
/**
* Are we running in CI?
*
* @returns {Boolean}
*/
isCI() {
return this.currentEnvironment() === 'ci';
}
}
const env = new Env();
module.exports = env;