mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-16 16:30:15 +01:00
* ci(backend-review.yml): add linter step to the backend review workflow * chore(backend-review.yml): remove prettier from lint-action configuration * chore: apply new linting workflow * chore(lint-staged.config.js): reorder lint-staged tasks for JavaScript and TypeScript files * chore(eslint): update ignorePatterns in .eslintrc.js chore(lint-action): remove prettier option in backend-review.yml chore(package.json): add lint and lint:fix scripts * chore(lint-staged.config.js): remove prettier --write command for js, jsx, ts, tsx files * chore(titleConvo.js): remove unnecessary console.log statement chore(titleConvo.js): add missing comma in options object * chore: apply linting to all files * chore(lint-staged.config.js): update lint-staged configuration to include prettier formatting
243 lines
5.8 KiB
JavaScript
243 lines
5.8 KiB
JavaScript
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;
|