/** * Upgrade script */ const dotenv = require('dotenv'); const fs = require('fs'); const { exit } = require('process'); // Suppress default warnings const originalConsoleWarn = console.warn; console.warn = () => {}; const loader = require('./loader'); console.warn = originalConsoleWarn; // Old Paths const apiEnvPath = loader.resolve('api/.env'); const clientEnvPath = loader.resolve('client/.env'); // Load into env dotenv.config({ path: loader.resolve(apiEnvPath), }); dotenv.config({ path: loader.resolve(clientEnvPath), }); // JS was doing spooky actions at a distance, lets prevent that const initEnv = JSON.parse(JSON.stringify(process.env)); // New Paths const rootEnvPath = loader.resolve('.env'); const devEnvPath = loader.resolve('.env.development'); const prodEnvPath = loader.resolve('.env.production'); if (fs.existsSync(rootEnvPath)) { console.error('Root env file already exists! Aborting'); exit(1); } // Validate old configs if (!fs.existsSync(apiEnvPath)) { console.error('Api env doesn\'t exit! Did you mean to run install?'); exit(1); } if (!fs.existsSync(clientEnvPath)) { console.error('Client env doesn\'t exit! But api/.env does. Manual upgrade required'); exit(1); } /** * Refactor the ENV if it has a prod_/dev_ version * * @param {*} varDev * @param {*} varProd * @param {*} varName */ function refactorPairedEnvVar(varDev, varProd, varName) { // Lets validate if either of these are undefined, if so lets use the non-undefined one if (initEnv[varDev] === undefined && initEnv[varProd] === undefined) { console.error(`Both ${varDev} and ${varProd} are undefined! Manual intervention required!`); } else if (initEnv[varDev] === undefined) { fs.appendFileSync(rootEnvPath, `\n${varName}=${initEnv[varProd]}`); } else if (initEnv[varProd] === undefined) { fs.appendFileSync(rootEnvPath, `\n${varName}=${initEnv[varDev]}`); } else if (initEnv[varDev] === initEnv[varProd]) { fs.appendFileSync(rootEnvPath, `\n${varName}=${initEnv[varDev]}`); } else { fs.appendFileSync(rootEnvPath, `${varName}=${initEnv[varProd]}\n`); fs.appendFileSync(devEnvPath, `${varName}=${initEnv[varDev]}\n`); } } /** * Upgrade the env files! * 1. /api/.env will merge into /.env * 2. /client/.env will merge into /.env * 3. Any prod_/dev_ keys will be split up into .env.development / .env.production files (if they are different) */ if (fs.existsSync(apiEnvPath)) { fs.copyFileSync(apiEnvPath, rootEnvPath); fs.copyFileSync(apiEnvPath, rootEnvPath + '.api.bak'); fs.unlinkSync(apiEnvPath); } // Clean up Domain variables fs.appendFileSync( rootEnvPath, '\n\n##########################\n# Domain Variables:\n# Note: DOMAIN_ vars are passed to vite\n##########################\n', ); refactorPairedEnvVar('CLIENT_URL_DEV', 'CLIENT_URL_PROD', 'DOMAIN_CLIENT'); refactorPairedEnvVar('SERVER_URL_DEV', 'SERVER_URL_PROD', 'DOMAIN_SERVER'); // Remove the old vars const removeEnvs = { NODE_ENV: 'remove', OPENAI_KEY: 'remove', CLIENT_URL_DEV: 'remove', CLIENT_URL_PROD: 'remove', SERVER_URL_DEV: 'remove', SERVER_URL_PROD: 'remove', JWT_SECRET_DEV: 'remove', // Lets regen JWT_SECRET_PROD: 'remove', // Lets regen VITE_APP_TITLE: 'remove', // Comments to remove: '#JWT:': 'remove', '# Add a secure secret for production if deploying to live domain.': 'remove', '# Site URLs:': 'remove', '# Don\'t forget to set Node env to development in the Server configuration section above': 'remove', '# if you want to run in dev mode': 'remove', '# Change these values to domain if deploying:': 'remove', '# Set Node env to development if running in dev mode.': 'remove', }; loader.writeEnvFile(rootEnvPath, removeEnvs); /** * Lets make things more secure! * 1. Add CREDS_KEY * 2. Add CREDS_IV * 3. Add JWT_SECRET */ fs.appendFileSync( rootEnvPath, '\n\n##########################\n# Secure Keys:\n##########################\n', ); loader.addSecureEnvVar(rootEnvPath, 'CREDS_KEY', 32); loader.addSecureEnvVar(rootEnvPath, 'CREDS_IV', 16); loader.addSecureEnvVar(rootEnvPath, 'JWT_SECRET', 32); // Lets update the openai key name, not the best spot in the env file but who cares ¯\_(ツ)_/¯ loader.writeEnvFile(rootEnvPath, { OPENAI_API_KEY: initEnv['OPENAI_KEY'] }); // TODO: we need to copy over the value of: APP_TITLE fs.appendFileSync( rootEnvPath, '\n\n##########################\n# Frontend Vite Variables:\n##########################\n', ); const frontend = { APP_TITLE: initEnv['VITE_APP_TITLE'] || '"LibreChat"', ALLOW_REGISTRATION: 'true', }; loader.writeEnvFile(rootEnvPath, frontend); // Ensure .env.development and .env.production files end with a newline if (fs.existsSync(devEnvPath)) { fs.appendFileSync(devEnvPath, '\n'); } if (fs.existsSync(prodEnvPath)) { fs.appendFileSync(prodEnvPath, '\n'); } // Remove client file fs.copyFileSync(clientEnvPath, rootEnvPath + '.client.bak'); fs.unlinkSync(clientEnvPath); console.log('###############################################'); console.log('Upgrade completed! Please review the new .env file and make any changes as needed.'); console.log('###############################################'); // if the .env.development file exists, lets tell the user if (fs.existsSync(devEnvPath)) { console.log( 'NOTE: A .env.development file was created. This will take precedence over the .env file when running in dev mode.', ); console.log('###############################################'); }