diff --git a/server/utils/AiProviders/ollama/index.js b/server/utils/AiProviders/ollama/index.js index 6bd857b4e9831bb73b1e64272a475bbfb1a6d9a1..a6f60ca444288bf0b373715b4dfdf4d04209b7c8 100644 --- a/server/utils/AiProviders/ollama/index.js +++ b/server/utils/AiProviders/ollama/index.js @@ -32,6 +32,7 @@ class OllamaAILLM { return new ChatOllama({ baseUrl: this.basePath, model: this.model, + useMLock: true, temperature, }); } diff --git a/server/utils/helpers/portAvailabilityChecker.js b/server/utils/helpers/portAvailabilityChecker.js new file mode 100644 index 0000000000000000000000000000000000000000..66d6d1af7e9b1142a21fab44290620f5eb09a83c --- /dev/null +++ b/server/utils/helpers/portAvailabilityChecker.js @@ -0,0 +1,46 @@ +// Get all loopback addresses that are available for use or binding. +function getLocalHosts() { + const os = require("os"); + const interfaces = os.networkInterfaces(); + const results = new Set([undefined, "0.0.0.0"]); + + for (const _interface of Object.values(interfaces)) { + for (const config of _interface) { + results.add(config.address); + } + } + + return Array.from(results); +} + +function checkPort(options = {}) { + const net = require("net"); + return new Promise((resolve, reject) => { + const server = net.createServer(); + server.unref(); + server.on("error", reject); + + server.listen(options, () => { + server.close(() => { + resolve(true); + }); + }); + }); +} + +async function isPortInUse(port, host) { + try { + await checkPort({ port, host }); + return true; + } catch (error) { + if (!["EADDRNOTAVAIL", "EINVAL"].includes(error.code)) { + return false; + } + } + return false; +} + +module.exports = { + isPortInUse, + getLocalHosts, +}; diff --git a/server/utils/helpers/updateENV.js b/server/utils/helpers/updateENV.js index 6e0e5daa69a702231e60c20e1b587848e98b4eae..12c45af2686a534b6b0bcb48919a49209e139619 100644 --- a/server/utils/helpers/updateENV.js +++ b/server/utils/helpers/updateENV.js @@ -462,14 +462,28 @@ function isDownloadedModel(input = "") { return files.includes(input); } -function validDockerizedUrl(input = "") { +async function validDockerizedUrl(input = "") { if (process.env.ANYTHING_LLM_RUNTIME !== "docker") return null; + try { - const { hostname } = new URL(input); - if (["localhost", "127.0.0.1", "0.0.0.0"].includes(hostname.toLowerCase())) - return "Localhost, 127.0.0.1, or 0.0.0.0 origins cannot be reached from inside the AnythingLLM container. Please use host.docker.internal, a real machine ip, or domain to connect to your service."; - return null; - } catch {} + const { isPortInUse, getLocalHosts } = require("./portAvailabilityChecker"); + const localInterfaces = getLocalHosts(); + const url = new URL(input); + const hostname = url.hostname.toLowerCase(); + const port = parseInt(url.port, 10); + + // If not a loopback, skip this check. + if (!localInterfaces.includes(hostname)) return null; + if (isNaN(port)) return "Invalid URL: Port is not specified or invalid"; + + const isPortAvailableFromDocker = await isPortInUse(port, hostname); + if (isPortAvailableFromDocker) + return "Port is not running a reachable service on loopback address from inside the AnythingLLM container. Please use host.docker.internal (for linux use 172.17.0.1), a real machine ip, or domain to connect to your service."; + } catch (error) { + console.error(error.message); + return "An error occurred while validating the URL"; + } + return null; } @@ -504,10 +518,8 @@ async function updateENV(newENVs = {}, force = false, userId = null) { const { envKey, checks, postUpdate = [] } = KEY_MAPPING[key]; const prevValue = process.env[envKey]; const nextValue = newENVs[key]; - const errors = checks - .map((validityCheck) => validityCheck(nextValue, force)) - .filter((err) => typeof err === "string"); + const errors = await executeValidationChecks(checks, nextValue, force); if (errors.length > 0) { error += errors.join("\n"); break; @@ -524,6 +536,13 @@ async function updateENV(newENVs = {}, force = false, userId = null) { return { newValues, error: error?.length > 0 ? error : false }; } +async function executeValidationChecks(checks, value, force) { + const results = await Promise.all( + checks.map((validator) => validator(value, force)) + ); + return results.filter((err) => typeof err === "string"); +} + async function logChangesToEventLog(newValues = {}, userId = null) { const { EventLogs } = require("../../models/eventLogs"); const eventMapping = {