mirror of
https://github.com/open-webui/open-webui
synced 2025-02-01 15:24:25 +00:00
412923dc91
Introducing two new env config options to control cookies settings regarding authentication. These values are taken into use when setting 'token' and 'oauth_id_token'. To maintain backwards compatibility, the original session cookie values are used as fallback. Separation is done to prevent issues with the session cookie. When the config value was set as 'strict', the oauth flow was broken (since the session cookie was not provided after the callback). Providing a separate config for auth & session cookies allows us to keep the 'strict' settings for auth related cookies, while also allowing the session cookie to behave as intended (e.g., by configuring it as 'lax'). The original config was added in commit #af4f8aa. However a later commit #a2e889c reused this config option for other type of cookies, which was not the original intent.
413 lines
11 KiB
Python
413 lines
11 KiB
Python
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
|
|
####################################
|
|
|
|
log_levels = ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"]
|
|
|
|
GLOBAL_LOG_LEVEL = os.environ.get("GLOBAL_LOG_LEVEL", "").upper()
|
|
if GLOBAL_LOG_LEVEL in log_levels:
|
|
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)
|
|
|
|
log_sources = [
|
|
"AUDIO",
|
|
"COMFYUI",
|
|
"CONFIG",
|
|
"DB",
|
|
"IMAGES",
|
|
"MAIN",
|
|
"MODELS",
|
|
"OLLAMA",
|
|
"OPENAI",
|
|
"RAG",
|
|
"WEBHOOK",
|
|
"SOCKET",
|
|
]
|
|
|
|
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 log_levels:
|
|
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"
|
|
|
|
|
|
####################################
|
|
# 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)
|
|
|
|
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_OPENAI_MODEL_LIST = os.environ.get(
|
|
"AIOHTTP_CLIENT_TIMEOUT_OPENAI_MODEL_LIST", ""
|
|
)
|
|
|
|
if AIOHTTP_CLIENT_TIMEOUT_OPENAI_MODEL_LIST == "":
|
|
AIOHTTP_CLIENT_TIMEOUT_OPENAI_MODEL_LIST = None
|
|
else:
|
|
try:
|
|
AIOHTTP_CLIENT_TIMEOUT_OPENAI_MODEL_LIST = int(
|
|
AIOHTTP_CLIENT_TIMEOUT_OPENAI_MODEL_LIST
|
|
)
|
|
except Exception:
|
|
AIOHTTP_CLIENT_TIMEOUT_OPENAI_MODEL_LIST = 5
|
|
|
|
####################################
|
|
# OFFLINE_MODE
|
|
####################################
|
|
|
|
OFFLINE_MODE = os.environ.get("OFFLINE_MODE", "false").lower() == "true"
|
|
|
|
if OFFLINE_MODE:
|
|
os.environ["HF_HUB_OFFLINE"] = "1"
|