| | require('dotenv').config(); |
| | const fs = require('fs'); |
| | const path = require('path'); |
| | require('module-alias')({ base: path.resolve(__dirname, '..') }); |
| | const cors = require('cors'); |
| | const axios = require('axios'); |
| | const express = require('express'); |
| | const passport = require('passport'); |
| | const compression = require('compression'); |
| | const cookieParser = require('cookie-parser'); |
| | const { logger } = require('@librechat/data-schemas'); |
| | const mongoSanitize = require('express-mongo-sanitize'); |
| | const { |
| | isEnabled, |
| | ErrorController, |
| | performStartupChecks, |
| | handleJsonParseError, |
| | initializeFileStorage, |
| | } = require('@librechat/api'); |
| | const { connectDb, indexSync } = require('~/db'); |
| | const initializeOAuthReconnectManager = require('./services/initializeOAuthReconnectManager'); |
| | const createValidateImageRequest = require('./middleware/validateImageRequest'); |
| | const { jwtLogin, ldapLogin, passportLogin } = require('~/strategies'); |
| | const { updateInterfacePermissions } = require('~/models/interface'); |
| | const { checkMigrations } = require('./services/start/migration'); |
| | const initializeMCPs = require('./services/initializeMCPs'); |
| | const configureSocialLogins = require('./socialLogins'); |
| | const { getAppConfig } = require('./services/Config'); |
| | const staticCache = require('./utils/staticCache'); |
| | const noIndex = require('./middleware/noIndex'); |
| | const { seedDatabase } = require('~/models'); |
| | const routes = require('./routes'); |
| |
|
| | const { PORT, HOST, ALLOW_SOCIAL_LOGIN, DISABLE_COMPRESSION, TRUST_PROXY } = process.env ?? {}; |
| |
|
| | |
| | const port = isNaN(Number(PORT)) ? 3080 : Number(PORT); |
| | const host = HOST || 'localhost'; |
| | const trusted_proxy = Number(TRUST_PROXY) || 1; |
| |
|
| | const app = express(); |
| |
|
| | const startServer = async () => { |
| | if (typeof Bun !== 'undefined') { |
| | axios.defaults.headers.common['Accept-Encoding'] = 'gzip'; |
| | } |
| | await connectDb(); |
| |
|
| | logger.info('Connected to MongoDB'); |
| | indexSync().catch((err) => { |
| | logger.error('[indexSync] Background sync failed:', err); |
| | }); |
| |
|
| | app.disable('x-powered-by'); |
| | app.set('trust proxy', trusted_proxy); |
| |
|
| | await seedDatabase(); |
| | const appConfig = await getAppConfig(); |
| | initializeFileStorage(appConfig); |
| | await performStartupChecks(appConfig); |
| | await updateInterfacePermissions(appConfig); |
| |
|
| | const indexPath = path.join(appConfig.paths.dist, 'index.html'); |
| | let indexHTML = fs.readFileSync(indexPath, 'utf8'); |
| |
|
| | |
| | |
| | if (process.env.DOMAIN_CLIENT) { |
| | const clientUrl = new URL(process.env.DOMAIN_CLIENT); |
| | const baseHref = clientUrl.pathname.endsWith('/') |
| | ? clientUrl.pathname |
| | : `${clientUrl.pathname}/`; |
| | if (baseHref !== '/') { |
| | logger.info(`Setting base href to ${baseHref}`); |
| | indexHTML = indexHTML.replace(/base href="\/"/, `base href="${baseHref}"`); |
| | } |
| | } |
| |
|
| | app.get('/health', (_req, res) => res.status(200).send('OK')); |
| |
|
| | |
| | app.use(noIndex); |
| | app.use(express.json({ limit: '3mb' })); |
| | app.use(express.urlencoded({ extended: true, limit: '3mb' })); |
| | app.use(handleJsonParseError); |
| | app.use(mongoSanitize()); |
| | app.use(cors()); |
| | app.use(cookieParser()); |
| |
|
| | if (!isEnabled(DISABLE_COMPRESSION)) { |
| | app.use(compression()); |
| | } else { |
| | console.warn('Response compression has been disabled via DISABLE_COMPRESSION.'); |
| | } |
| |
|
| | app.use(staticCache(appConfig.paths.dist)); |
| | app.use(staticCache(appConfig.paths.fonts)); |
| | app.use(staticCache(appConfig.paths.assets)); |
| |
|
| | if (!ALLOW_SOCIAL_LOGIN) { |
| | console.warn('Social logins are disabled. Set ALLOW_SOCIAL_LOGIN=true to enable them.'); |
| | } |
| |
|
| | |
| | app.use(passport.initialize()); |
| | passport.use(jwtLogin()); |
| | passport.use(passportLogin()); |
| |
|
| | |
| | if (process.env.LDAP_URL && process.env.LDAP_USER_SEARCH_BASE) { |
| | passport.use(ldapLogin); |
| | } |
| |
|
| | if (isEnabled(ALLOW_SOCIAL_LOGIN)) { |
| | await configureSocialLogins(app); |
| | } |
| |
|
| | app.use('/oauth', routes.oauth); |
| | |
| | app.use('/api/auth', routes.auth); |
| | app.use('/api/actions', routes.actions); |
| | app.use('/api/keys', routes.keys); |
| | app.use('/api/user', routes.user); |
| | app.use('/api/search', routes.search); |
| | app.use('/api/edit', routes.edit); |
| | app.use('/api/messages', routes.messages); |
| | app.use('/api/convos', routes.convos); |
| | app.use('/api/presets', routes.presets); |
| | app.use('/api/prompts', routes.prompts); |
| | app.use('/api/categories', routes.categories); |
| | app.use('/api/endpoints', routes.endpoints); |
| | app.use('/api/balance', routes.balance); |
| | app.use('/api/models', routes.models); |
| | app.use('/api/plugins', routes.plugins); |
| | app.use('/api/config', routes.config); |
| | app.use('/api/assistants', routes.assistants); |
| | app.use('/api/files', await routes.files.initialize()); |
| | app.use('/images/', createValidateImageRequest(appConfig.secureImageLinks), routes.staticRoute); |
| | app.use('/api/share', routes.share); |
| | app.use('/api/roles', routes.roles); |
| | app.use('/api/agents', routes.agents); |
| | app.use('/api/banner', routes.banner); |
| | app.use('/api/memories', routes.memories); |
| | app.use('/api/permissions', routes.accessPermissions); |
| |
|
| | app.use('/api/tags', routes.tags); |
| | app.use('/api/mcp', routes.mcp); |
| |
|
| | app.use(ErrorController); |
| |
|
| | app.use((req, res) => { |
| | res.set({ |
| | 'Cache-Control': process.env.INDEX_CACHE_CONTROL || 'no-cache, no-store, must-revalidate', |
| | Pragma: process.env.INDEX_PRAGMA || 'no-cache', |
| | Expires: process.env.INDEX_EXPIRES || '0', |
| | }); |
| |
|
| | const lang = req.cookies.lang || req.headers['accept-language']?.split(',')[0] || 'en-US'; |
| | const saneLang = lang.replace(/"/g, '"'); |
| | let updatedIndexHtml = indexHTML.replace(/lang="en-US"/g, `lang="${saneLang}"`); |
| |
|
| | res.type('html'); |
| | res.send(updatedIndexHtml); |
| | }); |
| |
|
| | app.listen(port, host, async () => { |
| | if (host === '0.0.0.0') { |
| | logger.info( |
| | `Server listening on all interfaces at port ${port}. Use http://localhost:${port} to access it`, |
| | ); |
| | } else { |
| | logger.info(`Server listening at http://${host == '0.0.0.0' ? 'localhost' : host}:${port}`); |
| | } |
| |
|
| | await initializeMCPs(); |
| | await initializeOAuthReconnectManager(); |
| | await checkMigrations(); |
| | }); |
| | }; |
| |
|
| | startServer(); |
| |
|
| | let messageCount = 0; |
| | process.on('uncaughtException', (err) => { |
| | if (!err.message.includes('fetch failed')) { |
| | logger.error('There was an uncaught error:', err); |
| | } |
| |
|
| | if (err.message && err.message?.toLowerCase()?.includes('abort')) { |
| | logger.warn('There was an uncatchable abort error.'); |
| | return; |
| | } |
| |
|
| | if (err.message.includes('GoogleGenerativeAI')) { |
| | logger.warn( |
| | '\n\n`GoogleGenerativeAI` errors cannot be caught due to an upstream issue, see: https://github.com/google-gemini/generative-ai-js/issues/303', |
| | ); |
| | return; |
| | } |
| |
|
| | if (err.message.includes('fetch failed')) { |
| | if (messageCount === 0) { |
| | logger.warn('Meilisearch error, search will be disabled'); |
| | messageCount++; |
| | } |
| |
|
| | return; |
| | } |
| |
|
| | if (err.message.includes('OpenAIError') || err.message.includes('ChatCompletionMessage')) { |
| | logger.error( |
| | '\n\nAn Uncaught `OpenAIError` error may be due to your reverse-proxy setup or stream configuration, or a bug in the `openai` node package.', |
| | ); |
| | return; |
| | } |
| |
|
| | if (err.stack && err.stack.includes('@librechat/agents')) { |
| | logger.error( |
| | '\n\nAn error occurred in the agents system. The error has been logged and the app will continue running.', |
| | { |
| | message: err.message, |
| | stack: err.stack, |
| | }, |
| | ); |
| | return; |
| | } |
| |
|
| | process.exit(1); |
| | }); |
| |
|
| | |
| | module.exports = app; |
| |
|