123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444 |
- import importlib.metadata
- import json
- import logging
- import os
- import pkgutil
- import sys
- import shutil
- from pathlib import Path
- import markdown
- from bs4 import BeautifulSoup
- from open_webui.constants import ERROR_MESSAGES
- ####################################
- # Load .env file
- ####################################
- OPEN_WEBUI_DIR = Path(__file__).parent # the path containing this file
- print(OPEN_WEBUI_DIR)
- BACKEND_DIR = OPEN_WEBUI_DIR.parent # the path containing this file
- BASE_DIR = BACKEND_DIR.parent # the path containing the backend/
- print(BACKEND_DIR)
- print(BASE_DIR)
- try:
- from dotenv import find_dotenv, load_dotenv
- load_dotenv(find_dotenv(str(BASE_DIR / ".env")))
- except ImportError:
- print("dotenv not installed, skipping...")
- DOCKER = os.environ.get("DOCKER", "False").lower() == "true"
- # device type embedding models - "cpu" (default), "cuda" (nvidia gpu required) or "mps" (apple silicon) - choosing this right can lead to better performance
- USE_CUDA = os.environ.get("USE_CUDA_DOCKER", "false")
- if USE_CUDA.lower() == "true":
- try:
- import torch
- assert torch.cuda.is_available(), "CUDA not available"
- DEVICE_TYPE = "cuda"
- except Exception as e:
- cuda_error = (
- "Error when testing CUDA but USE_CUDA_DOCKER is true. "
- f"Resetting USE_CUDA_DOCKER to false: {e}"
- )
- os.environ["USE_CUDA_DOCKER"] = "false"
- USE_CUDA = "false"
- DEVICE_TYPE = "cpu"
- else:
- DEVICE_TYPE = "cpu"
- try:
- import torch
- if torch.backends.mps.is_available() and torch.backends.mps.is_built():
- DEVICE_TYPE = "mps"
- except Exception:
- pass
- ####################################
- # LOGGING
- ####################################
- GLOBAL_LOG_LEVEL = os.environ.get("GLOBAL_LOG_LEVEL", "").upper()
- if GLOBAL_LOG_LEVEL in logging.getLevelNamesMapping():
- logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL, force=True)
- else:
- GLOBAL_LOG_LEVEL = "INFO"
- log = logging.getLogger(__name__)
- log.info(f"GLOBAL_LOG_LEVEL: {GLOBAL_LOG_LEVEL}")
- if "cuda_error" in locals():
- log.exception(cuda_error)
- del cuda_error
- log_sources = [
- "AUDIO",
- "COMFYUI",
- "CONFIG",
- "DB",
- "IMAGES",
- "MAIN",
- "MODELS",
- "OLLAMA",
- "OPENAI",
- "RAG",
- "WEBHOOK",
- "SOCKET",
- "OAUTH",
- ]
- SRC_LOG_LEVELS = {}
- for source in log_sources:
- log_env_var = source + "_LOG_LEVEL"
- SRC_LOG_LEVELS[source] = os.environ.get(log_env_var, "").upper()
- if SRC_LOG_LEVELS[source] not in logging.getLevelNamesMapping():
- SRC_LOG_LEVELS[source] = GLOBAL_LOG_LEVEL
- log.info(f"{log_env_var}: {SRC_LOG_LEVELS[source]}")
- log.setLevel(SRC_LOG_LEVELS["CONFIG"])
- WEBUI_NAME = os.environ.get("WEBUI_NAME", "Open WebUI")
- if WEBUI_NAME != "Open WebUI":
- WEBUI_NAME += " (Open WebUI)"
- WEBUI_FAVICON_URL = "https://openwebui.com/favicon.png"
- TRUSTED_SIGNATURE_KEY = os.environ.get("TRUSTED_SIGNATURE_KEY", "")
- ####################################
- # ENV (dev,test,prod)
- ####################################
- ENV = os.environ.get("ENV", "dev")
- FROM_INIT_PY = os.environ.get("FROM_INIT_PY", "False").lower() == "true"
- if FROM_INIT_PY:
- PACKAGE_DATA = {"version": importlib.metadata.version("open-webui")}
- else:
- try:
- PACKAGE_DATA = json.loads((BASE_DIR / "package.json").read_text())
- except Exception:
- PACKAGE_DATA = {"version": "0.0.0"}
- VERSION = PACKAGE_DATA["version"]
- # Function to parse each section
- def parse_section(section):
- items = []
- for li in section.find_all("li"):
- # Extract raw HTML string
- raw_html = str(li)
- # Extract text without HTML tags
- text = li.get_text(separator=" ", strip=True)
- # Split into title and content
- parts = text.split(": ", 1)
- title = parts[0].strip() if len(parts) > 1 else ""
- content = parts[1].strip() if len(parts) > 1 else text
- items.append({"title": title, "content": content, "raw": raw_html})
- return items
- try:
- changelog_path = BASE_DIR / "CHANGELOG.md"
- with open(str(changelog_path.absolute()), "r", encoding="utf8") as file:
- changelog_content = file.read()
- except Exception:
- changelog_content = (pkgutil.get_data("open_webui", "CHANGELOG.md") or b"").decode()
- # Convert markdown content to HTML
- html_content = markdown.markdown(changelog_content)
- # Parse the HTML content
- soup = BeautifulSoup(html_content, "html.parser")
- # Initialize JSON structure
- changelog_json = {}
- # Iterate over each version
- for version in soup.find_all("h2"):
- version_number = version.get_text().strip().split(" - ")[0][1:-1] # Remove brackets
- date = version.get_text().strip().split(" - ")[1]
- version_data = {"date": date}
- # Find the next sibling that is a h3 tag (section title)
- current = version.find_next_sibling()
- while current and current.name != "h2":
- if current.name == "h3":
- section_title = current.get_text().lower() # e.g., "added", "fixed"
- section_items = parse_section(current.find_next_sibling("ul"))
- version_data[section_title] = section_items
- # Move to the next element
- current = current.find_next_sibling()
- changelog_json[version_number] = version_data
- CHANGELOG = changelog_json
- ####################################
- # SAFE_MODE
- ####################################
- SAFE_MODE = os.environ.get("SAFE_MODE", "false").lower() == "true"
- ####################################
- # ENABLE_FORWARD_USER_INFO_HEADERS
- ####################################
- ENABLE_FORWARD_USER_INFO_HEADERS = (
- os.environ.get("ENABLE_FORWARD_USER_INFO_HEADERS", "False").lower() == "true"
- )
- ####################################
- # WEBUI_BUILD_HASH
- ####################################
- WEBUI_BUILD_HASH = os.environ.get("WEBUI_BUILD_HASH", "dev-build")
- ####################################
- # DATA/FRONTEND BUILD DIR
- ####################################
- DATA_DIR = Path(os.getenv("DATA_DIR", BACKEND_DIR / "data")).resolve()
- if FROM_INIT_PY:
- NEW_DATA_DIR = Path(os.getenv("DATA_DIR", OPEN_WEBUI_DIR / "data")).resolve()
- NEW_DATA_DIR.mkdir(parents=True, exist_ok=True)
- # Check if the data directory exists in the package directory
- if DATA_DIR.exists() and DATA_DIR != NEW_DATA_DIR:
- log.info(f"Moving {DATA_DIR} to {NEW_DATA_DIR}")
- for item in DATA_DIR.iterdir():
- dest = NEW_DATA_DIR / item.name
- if item.is_dir():
- shutil.copytree(item, dest, dirs_exist_ok=True)
- else:
- shutil.copy2(item, dest)
- # Zip the data directory
- shutil.make_archive(DATA_DIR.parent / "open_webui_data", "zip", DATA_DIR)
- # Remove the old data directory
- shutil.rmtree(DATA_DIR)
- DATA_DIR = Path(os.getenv("DATA_DIR", OPEN_WEBUI_DIR / "data"))
- STATIC_DIR = Path(os.getenv("STATIC_DIR", OPEN_WEBUI_DIR / "static"))
- FONTS_DIR = Path(os.getenv("FONTS_DIR", OPEN_WEBUI_DIR / "static" / "fonts"))
- FRONTEND_BUILD_DIR = Path(os.getenv("FRONTEND_BUILD_DIR", BASE_DIR / "build")).resolve()
- if FROM_INIT_PY:
- FRONTEND_BUILD_DIR = Path(
- os.getenv("FRONTEND_BUILD_DIR", OPEN_WEBUI_DIR / "frontend")
- ).resolve()
- ####################################
- # Database
- ####################################
- # Check if the file exists
- if os.path.exists(f"{DATA_DIR}/ollama.db"):
- # Rename the file
- os.rename(f"{DATA_DIR}/ollama.db", f"{DATA_DIR}/webui.db")
- log.info("Database migrated from Ollama-WebUI successfully.")
- else:
- pass
- DATABASE_URL = os.environ.get("DATABASE_URL", f"sqlite:///{DATA_DIR}/webui.db")
- # Replace the postgres:// with postgresql://
- if "postgres://" in DATABASE_URL:
- DATABASE_URL = DATABASE_URL.replace("postgres://", "postgresql://")
- DATABASE_SCHEMA = os.environ.get("DATABASE_SCHEMA", None)
- DATABASE_POOL_SIZE = os.environ.get("DATABASE_POOL_SIZE", 0)
- if DATABASE_POOL_SIZE == "":
- DATABASE_POOL_SIZE = 0
- else:
- try:
- DATABASE_POOL_SIZE = int(DATABASE_POOL_SIZE)
- except Exception:
- DATABASE_POOL_SIZE = 0
- DATABASE_POOL_MAX_OVERFLOW = os.environ.get("DATABASE_POOL_MAX_OVERFLOW", 0)
- if DATABASE_POOL_MAX_OVERFLOW == "":
- DATABASE_POOL_MAX_OVERFLOW = 0
- else:
- try:
- DATABASE_POOL_MAX_OVERFLOW = int(DATABASE_POOL_MAX_OVERFLOW)
- except Exception:
- DATABASE_POOL_MAX_OVERFLOW = 0
- DATABASE_POOL_TIMEOUT = os.environ.get("DATABASE_POOL_TIMEOUT", 30)
- if DATABASE_POOL_TIMEOUT == "":
- DATABASE_POOL_TIMEOUT = 30
- else:
- try:
- DATABASE_POOL_TIMEOUT = int(DATABASE_POOL_TIMEOUT)
- except Exception:
- DATABASE_POOL_TIMEOUT = 30
- DATABASE_POOL_RECYCLE = os.environ.get("DATABASE_POOL_RECYCLE", 3600)
- if DATABASE_POOL_RECYCLE == "":
- DATABASE_POOL_RECYCLE = 3600
- else:
- try:
- DATABASE_POOL_RECYCLE = int(DATABASE_POOL_RECYCLE)
- except Exception:
- DATABASE_POOL_RECYCLE = 3600
- RESET_CONFIG_ON_START = (
- os.environ.get("RESET_CONFIG_ON_START", "False").lower() == "true"
- )
- ENABLE_REALTIME_CHAT_SAVE = (
- os.environ.get("ENABLE_REALTIME_CHAT_SAVE", "False").lower() == "true"
- )
- ####################################
- # REDIS
- ####################################
- REDIS_URL = os.environ.get("REDIS_URL", "redis://localhost:6379/0")
- ####################################
- # WEBUI_AUTH (Required for security)
- ####################################
- WEBUI_AUTH = os.environ.get("WEBUI_AUTH", "True").lower() == "true"
- WEBUI_AUTH_TRUSTED_EMAIL_HEADER = os.environ.get(
- "WEBUI_AUTH_TRUSTED_EMAIL_HEADER", None
- )
- WEBUI_AUTH_TRUSTED_NAME_HEADER = os.environ.get("WEBUI_AUTH_TRUSTED_NAME_HEADER", None)
- BYPASS_MODEL_ACCESS_CONTROL = (
- os.environ.get("BYPASS_MODEL_ACCESS_CONTROL", "False").lower() == "true"
- )
- ####################################
- # WEBUI_SECRET_KEY
- ####################################
- WEBUI_SECRET_KEY = os.environ.get(
- "WEBUI_SECRET_KEY",
- os.environ.get(
- "WEBUI_JWT_SECRET_KEY", "t0p-s3cr3t"
- ), # DEPRECATED: remove at next major version
- )
- WEBUI_SESSION_COOKIE_SAME_SITE = os.environ.get("WEBUI_SESSION_COOKIE_SAME_SITE", "lax")
- WEBUI_SESSION_COOKIE_SECURE = (
- os.environ.get("WEBUI_SESSION_COOKIE_SECURE", "false").lower() == "true"
- )
- WEBUI_AUTH_COOKIE_SAME_SITE = os.environ.get(
- "WEBUI_AUTH_COOKIE_SAME_SITE", WEBUI_SESSION_COOKIE_SAME_SITE
- )
- WEBUI_AUTH_COOKIE_SECURE = (
- os.environ.get(
- "WEBUI_AUTH_COOKIE_SECURE",
- os.environ.get("WEBUI_SESSION_COOKIE_SECURE", "false"),
- ).lower()
- == "true"
- )
- if WEBUI_AUTH and WEBUI_SECRET_KEY == "":
- raise ValueError(ERROR_MESSAGES.ENV_VAR_NOT_FOUND)
- ENABLE_WEBSOCKET_SUPPORT = (
- os.environ.get("ENABLE_WEBSOCKET_SUPPORT", "True").lower() == "true"
- )
- WEBSOCKET_MANAGER = os.environ.get("WEBSOCKET_MANAGER", "")
- WEBSOCKET_REDIS_URL = os.environ.get("WEBSOCKET_REDIS_URL", REDIS_URL)
- WEBSOCKET_REDIS_LOCK_TIMEOUT = os.environ.get("WEBSOCKET_REDIS_LOCK_TIMEOUT", 60)
- AIOHTTP_CLIENT_TIMEOUT = os.environ.get("AIOHTTP_CLIENT_TIMEOUT", "")
- if AIOHTTP_CLIENT_TIMEOUT == "":
- AIOHTTP_CLIENT_TIMEOUT = None
- else:
- try:
- AIOHTTP_CLIENT_TIMEOUT = int(AIOHTTP_CLIENT_TIMEOUT)
- except Exception:
- AIOHTTP_CLIENT_TIMEOUT = 300
- AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST = os.environ.get(
- "AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST",
- os.environ.get("AIOHTTP_CLIENT_TIMEOUT_OPENAI_MODEL_LIST", ""),
- )
- if AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST == "":
- AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST = None
- else:
- try:
- AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST = int(AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST)
- except Exception:
- AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST = 5
- ####################################
- # OFFLINE_MODE
- ####################################
- OFFLINE_MODE = os.environ.get("OFFLINE_MODE", "false").lower() == "true"
- if OFFLINE_MODE:
- os.environ["HF_HUB_OFFLINE"] = "1"
- ####################################
- # AUDIT LOGGING
- ####################################
- ENABLE_AUDIT_LOGS = os.getenv("ENABLE_AUDIT_LOGS", "false").lower() == "true"
- # Where to store log file
- AUDIT_LOGS_FILE_PATH = f"{DATA_DIR}/audit.log"
- # Maximum size of a file before rotating into a new log file
- AUDIT_LOG_FILE_ROTATION_SIZE = os.getenv("AUDIT_LOG_FILE_ROTATION_SIZE", "10MB")
- # METADATA | REQUEST | REQUEST_RESPONSE
- AUDIT_LOG_LEVEL = os.getenv("AUDIT_LOG_LEVEL", "REQUEST_RESPONSE").upper()
- try:
- MAX_BODY_LOG_SIZE = int(os.environ.get("MAX_BODY_LOG_SIZE") or 2048)
- except ValueError:
- MAX_BODY_LOG_SIZE = 2048
- # Comma separated list for urls to exclude from audit
- AUDIT_EXCLUDED_PATHS = os.getenv("AUDIT_EXCLUDED_PATHS", "/chats,/chat,/folders").split(
- ","
- )
- AUDIT_EXCLUDED_PATHS = [path.strip() for path in AUDIT_EXCLUDED_PATHS]
- AUDIT_EXCLUDED_PATHS = [path.lstrip("/") for path in AUDIT_EXCLUDED_PATHS]
|