LibreChat/config/upgrade.js
Dan Orlando 3634d8691a
Feat/startup config api (#518)
* feat: add api for config

* feat: add data service to client

* feat: update client pages with values from config endpoint

* test: update tests

* Update configurations and documentation to remove VITE_SHOW_GOOGLE_LOGIN_OPTION and change VITE_APP_TITLE to APP_TITLE

* include APP_TITLE with startup config

* Add test for new route

* update backend-review pipeline

* comment out test until we can figure out testing routes in CI

* update: .env.example

---------

Co-authored-by: fuegovic <32828263+fuegovic@users.noreply.github.com>
2023-06-15 12:36:34 -04:00

151 lines
No EOL
5.3 KiB
JavaScript

/**
* 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('###############################################')
}