env.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  1. import importlib.metadata
  2. import json
  3. import logging
  4. import os
  5. import pkgutil
  6. import sys
  7. import shutil
  8. from pathlib import Path
  9. import markdown
  10. from bs4 import BeautifulSoup
  11. from open_webui.constants import ERROR_MESSAGES
  12. ####################################
  13. # Load .env file
  14. ####################################
  15. OPEN_WEBUI_DIR = Path(__file__).parent # the path containing this file
  16. print(OPEN_WEBUI_DIR)
  17. BACKEND_DIR = OPEN_WEBUI_DIR.parent # the path containing this file
  18. BASE_DIR = BACKEND_DIR.parent # the path containing the backend/
  19. print(BACKEND_DIR)
  20. print(BASE_DIR)
  21. try:
  22. from dotenv import find_dotenv, load_dotenv
  23. load_dotenv(find_dotenv(str(BASE_DIR / ".env")))
  24. except ImportError:
  25. print("dotenv not installed, skipping...")
  26. DOCKER = os.environ.get("DOCKER", "False").lower() == "true"
  27. # device type embedding models - "cpu" (default), "cuda" (nvidia gpu required) or "mps" (apple silicon) - choosing this right can lead to better performance
  28. USE_CUDA = os.environ.get("USE_CUDA_DOCKER", "false")
  29. if USE_CUDA.lower() == "true":
  30. try:
  31. import torch
  32. assert torch.cuda.is_available(), "CUDA not available"
  33. DEVICE_TYPE = "cuda"
  34. except Exception as e:
  35. cuda_error = (
  36. "Error when testing CUDA but USE_CUDA_DOCKER is true. "
  37. f"Resetting USE_CUDA_DOCKER to false: {e}"
  38. )
  39. os.environ["USE_CUDA_DOCKER"] = "false"
  40. USE_CUDA = "false"
  41. DEVICE_TYPE = "cpu"
  42. else:
  43. DEVICE_TYPE = "cpu"
  44. try:
  45. if torch.backends.mps.is_available() and torch.backends.mps.is_built():
  46. DEVICE_TYPE = "mps"
  47. except Exception:
  48. pass
  49. ####################################
  50. # LOGGING
  51. ####################################
  52. log_levels = ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"]
  53. GLOBAL_LOG_LEVEL = os.environ.get("GLOBAL_LOG_LEVEL", "").upper()
  54. if GLOBAL_LOG_LEVEL in log_levels:
  55. logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL, force=True)
  56. else:
  57. GLOBAL_LOG_LEVEL = "INFO"
  58. log = logging.getLogger(__name__)
  59. log.info(f"GLOBAL_LOG_LEVEL: {GLOBAL_LOG_LEVEL}")
  60. if "cuda_error" in locals():
  61. log.exception(cuda_error)
  62. log_sources = [
  63. "AUDIO",
  64. "COMFYUI",
  65. "CONFIG",
  66. "DB",
  67. "IMAGES",
  68. "MAIN",
  69. "MODELS",
  70. "OLLAMA",
  71. "OPENAI",
  72. "RAG",
  73. "WEBHOOK",
  74. "SOCKET",
  75. ]
  76. SRC_LOG_LEVELS = {}
  77. for source in log_sources:
  78. log_env_var = source + "_LOG_LEVEL"
  79. SRC_LOG_LEVELS[source] = os.environ.get(log_env_var, "").upper()
  80. if SRC_LOG_LEVELS[source] not in log_levels:
  81. SRC_LOG_LEVELS[source] = GLOBAL_LOG_LEVEL
  82. log.info(f"{log_env_var}: {SRC_LOG_LEVELS[source]}")
  83. log.setLevel(SRC_LOG_LEVELS["CONFIG"])
  84. WEBUI_NAME = os.environ.get("WEBUI_NAME", "Open WebUI")
  85. if WEBUI_NAME != "Open WebUI":
  86. WEBUI_NAME += " (Open WebUI)"
  87. WEBUI_FAVICON_URL = "https://openwebui.com/favicon.png"
  88. ####################################
  89. # ENV (dev,test,prod)
  90. ####################################
  91. ENV = os.environ.get("ENV", "dev")
  92. FROM_INIT_PY = os.environ.get("FROM_INIT_PY", "False").lower() == "true"
  93. if FROM_INIT_PY:
  94. PACKAGE_DATA = {"version": importlib.metadata.version("open-webui")}
  95. else:
  96. try:
  97. PACKAGE_DATA = json.loads((BASE_DIR / "package.json").read_text())
  98. except Exception:
  99. PACKAGE_DATA = {"version": "0.0.0"}
  100. VERSION = PACKAGE_DATA["version"]
  101. # Function to parse each section
  102. def parse_section(section):
  103. items = []
  104. for li in section.find_all("li"):
  105. # Extract raw HTML string
  106. raw_html = str(li)
  107. # Extract text without HTML tags
  108. text = li.get_text(separator=" ", strip=True)
  109. # Split into title and content
  110. parts = text.split(": ", 1)
  111. title = parts[0].strip() if len(parts) > 1 else ""
  112. content = parts[1].strip() if len(parts) > 1 else text
  113. items.append({"title": title, "content": content, "raw": raw_html})
  114. return items
  115. try:
  116. changelog_path = BASE_DIR / "CHANGELOG.md"
  117. with open(str(changelog_path.absolute()), "r", encoding="utf8") as file:
  118. changelog_content = file.read()
  119. except Exception:
  120. changelog_content = (pkgutil.get_data("open_webui", "CHANGELOG.md") or b"").decode()
  121. # Convert markdown content to HTML
  122. html_content = markdown.markdown(changelog_content)
  123. # Parse the HTML content
  124. soup = BeautifulSoup(html_content, "html.parser")
  125. # Initialize JSON structure
  126. changelog_json = {}
  127. # Iterate over each version
  128. for version in soup.find_all("h2"):
  129. version_number = version.get_text().strip().split(" - ")[0][1:-1] # Remove brackets
  130. date = version.get_text().strip().split(" - ")[1]
  131. version_data = {"date": date}
  132. # Find the next sibling that is a h3 tag (section title)
  133. current = version.find_next_sibling()
  134. while current and current.name != "h2":
  135. if current.name == "h3":
  136. section_title = current.get_text().lower() # e.g., "added", "fixed"
  137. section_items = parse_section(current.find_next_sibling("ul"))
  138. version_data[section_title] = section_items
  139. # Move to the next element
  140. current = current.find_next_sibling()
  141. changelog_json[version_number] = version_data
  142. CHANGELOG = changelog_json
  143. ####################################
  144. # SAFE_MODE
  145. ####################################
  146. SAFE_MODE = os.environ.get("SAFE_MODE", "false").lower() == "true"
  147. ####################################
  148. # ENABLE_FORWARD_USER_INFO_HEADERS
  149. ####################################
  150. ENABLE_FORWARD_USER_INFO_HEADERS = (
  151. os.environ.get("ENABLE_FORWARD_USER_INFO_HEADERS", "False").lower() == "true"
  152. )
  153. ####################################
  154. # WEBUI_BUILD_HASH
  155. ####################################
  156. WEBUI_BUILD_HASH = os.environ.get("WEBUI_BUILD_HASH", "dev-build")
  157. ####################################
  158. # DATA/FRONTEND BUILD DIR
  159. ####################################
  160. DATA_DIR = Path(os.getenv("DATA_DIR", BACKEND_DIR / "data")).resolve()
  161. if FROM_INIT_PY:
  162. NEW_DATA_DIR = Path(os.getenv("DATA_DIR", OPEN_WEBUI_DIR / "data")).resolve()
  163. NEW_DATA_DIR.mkdir(parents=True, exist_ok=True)
  164. # Check if the data directory exists in the package directory
  165. if DATA_DIR.exists() and DATA_DIR != NEW_DATA_DIR:
  166. log.info(f"Moving {DATA_DIR} to {NEW_DATA_DIR}")
  167. for item in DATA_DIR.iterdir():
  168. dest = NEW_DATA_DIR / item.name
  169. if item.is_dir():
  170. shutil.copytree(item, dest, dirs_exist_ok=True)
  171. else:
  172. shutil.copy2(item, dest)
  173. # Zip the data directory
  174. shutil.make_archive(DATA_DIR.parent / "open_webui_data", "zip", DATA_DIR)
  175. # Remove the old data directory
  176. shutil.rmtree(DATA_DIR)
  177. DATA_DIR = Path(os.getenv("DATA_DIR", OPEN_WEBUI_DIR / "data"))
  178. STATIC_DIR = Path(os.getenv("STATIC_DIR", OPEN_WEBUI_DIR / "static"))
  179. FONTS_DIR = Path(os.getenv("FONTS_DIR", OPEN_WEBUI_DIR / "static" / "fonts"))
  180. FRONTEND_BUILD_DIR = Path(os.getenv("FRONTEND_BUILD_DIR", BASE_DIR / "build")).resolve()
  181. if FROM_INIT_PY:
  182. FRONTEND_BUILD_DIR = Path(
  183. os.getenv("FRONTEND_BUILD_DIR", OPEN_WEBUI_DIR / "frontend")
  184. ).resolve()
  185. ####################################
  186. # Database
  187. ####################################
  188. # Check if the file exists
  189. if os.path.exists(f"{DATA_DIR}/ollama.db"):
  190. # Rename the file
  191. os.rename(f"{DATA_DIR}/ollama.db", f"{DATA_DIR}/webui.db")
  192. log.info("Database migrated from Ollama-WebUI successfully.")
  193. else:
  194. pass
  195. DATABASE_URL = os.environ.get("DATABASE_URL", f"sqlite:///{DATA_DIR}/webui.db")
  196. # Replace the postgres:// with postgresql://
  197. if "postgres://" in DATABASE_URL:
  198. DATABASE_URL = DATABASE_URL.replace("postgres://", "postgresql://")
  199. DATABASE_POOL_SIZE = os.environ.get("DATABASE_POOL_SIZE", 0)
  200. if DATABASE_POOL_SIZE == "":
  201. DATABASE_POOL_SIZE = 0
  202. else:
  203. try:
  204. DATABASE_POOL_SIZE = int(DATABASE_POOL_SIZE)
  205. except Exception:
  206. DATABASE_POOL_SIZE = 0
  207. DATABASE_POOL_MAX_OVERFLOW = os.environ.get("DATABASE_POOL_MAX_OVERFLOW", 0)
  208. if DATABASE_POOL_MAX_OVERFLOW == "":
  209. DATABASE_POOL_MAX_OVERFLOW = 0
  210. else:
  211. try:
  212. DATABASE_POOL_MAX_OVERFLOW = int(DATABASE_POOL_MAX_OVERFLOW)
  213. except Exception:
  214. DATABASE_POOL_MAX_OVERFLOW = 0
  215. DATABASE_POOL_TIMEOUT = os.environ.get("DATABASE_POOL_TIMEOUT", 30)
  216. if DATABASE_POOL_TIMEOUT == "":
  217. DATABASE_POOL_TIMEOUT = 30
  218. else:
  219. try:
  220. DATABASE_POOL_TIMEOUT = int(DATABASE_POOL_TIMEOUT)
  221. except Exception:
  222. DATABASE_POOL_TIMEOUT = 30
  223. DATABASE_POOL_RECYCLE = os.environ.get("DATABASE_POOL_RECYCLE", 3600)
  224. if DATABASE_POOL_RECYCLE == "":
  225. DATABASE_POOL_RECYCLE = 3600
  226. else:
  227. try:
  228. DATABASE_POOL_RECYCLE = int(DATABASE_POOL_RECYCLE)
  229. except Exception:
  230. DATABASE_POOL_RECYCLE = 3600
  231. RESET_CONFIG_ON_START = (
  232. os.environ.get("RESET_CONFIG_ON_START", "False").lower() == "true"
  233. )
  234. ENABLE_REALTIME_CHAT_SAVE = (
  235. os.environ.get("ENABLE_REALTIME_CHAT_SAVE", "False").lower() == "true"
  236. )
  237. ####################################
  238. # REDIS
  239. ####################################
  240. REDIS_URL = os.environ.get("REDIS_URL", "redis://localhost:6379/0")
  241. ####################################
  242. # WEBUI_AUTH (Required for security)
  243. ####################################
  244. WEBUI_AUTH = os.environ.get("WEBUI_AUTH", "True").lower() == "true"
  245. WEBUI_AUTH_TRUSTED_EMAIL_HEADER = os.environ.get(
  246. "WEBUI_AUTH_TRUSTED_EMAIL_HEADER", None
  247. )
  248. WEBUI_AUTH_TRUSTED_NAME_HEADER = os.environ.get("WEBUI_AUTH_TRUSTED_NAME_HEADER", None)
  249. BYPASS_MODEL_ACCESS_CONTROL = (
  250. os.environ.get("BYPASS_MODEL_ACCESS_CONTROL", "False").lower() == "true"
  251. )
  252. ####################################
  253. # WEBUI_SECRET_KEY
  254. ####################################
  255. WEBUI_SECRET_KEY = os.environ.get(
  256. "WEBUI_SECRET_KEY",
  257. os.environ.get(
  258. "WEBUI_JWT_SECRET_KEY", "t0p-s3cr3t"
  259. ), # DEPRECATED: remove at next major version
  260. )
  261. WEBUI_SESSION_COOKIE_SAME_SITE = os.environ.get(
  262. "WEBUI_SESSION_COOKIE_SAME_SITE",
  263. os.environ.get("WEBUI_SESSION_COOKIE_SAME_SITE", "lax"),
  264. )
  265. WEBUI_SESSION_COOKIE_SECURE = os.environ.get(
  266. "WEBUI_SESSION_COOKIE_SECURE",
  267. os.environ.get("WEBUI_SESSION_COOKIE_SECURE", "false").lower() == "true",
  268. )
  269. if WEBUI_AUTH and WEBUI_SECRET_KEY == "":
  270. raise ValueError(ERROR_MESSAGES.ENV_VAR_NOT_FOUND)
  271. ENABLE_WEBSOCKET_SUPPORT = (
  272. os.environ.get("ENABLE_WEBSOCKET_SUPPORT", "True").lower() == "true"
  273. )
  274. WEBSOCKET_MANAGER = os.environ.get("WEBSOCKET_MANAGER", "")
  275. WEBSOCKET_REDIS_URL = os.environ.get("WEBSOCKET_REDIS_URL", REDIS_URL)
  276. AIOHTTP_CLIENT_TIMEOUT = os.environ.get("AIOHTTP_CLIENT_TIMEOUT", "")
  277. if AIOHTTP_CLIENT_TIMEOUT == "":
  278. AIOHTTP_CLIENT_TIMEOUT = None
  279. else:
  280. try:
  281. AIOHTTP_CLIENT_TIMEOUT = int(AIOHTTP_CLIENT_TIMEOUT)
  282. except Exception:
  283. AIOHTTP_CLIENT_TIMEOUT = 300
  284. AIOHTTP_CLIENT_TIMEOUT_OPENAI_MODEL_LIST = os.environ.get(
  285. "AIOHTTP_CLIENT_TIMEOUT_OPENAI_MODEL_LIST", ""
  286. )
  287. if AIOHTTP_CLIENT_TIMEOUT_OPENAI_MODEL_LIST == "":
  288. AIOHTTP_CLIENT_TIMEOUT_OPENAI_MODEL_LIST = None
  289. else:
  290. try:
  291. AIOHTTP_CLIENT_TIMEOUT_OPENAI_MODEL_LIST = int(
  292. AIOHTTP_CLIENT_TIMEOUT_OPENAI_MODEL_LIST
  293. )
  294. except Exception:
  295. AIOHTTP_CLIENT_TIMEOUT_OPENAI_MODEL_LIST = 5
  296. ####################################
  297. # OFFLINE_MODE
  298. ####################################
  299. OFFLINE_MODE = os.environ.get("OFFLINE_MODE", "false").lower() == "true"
  300. if OFFLINE_MODE:
  301. os.environ["HF_HUB_OFFLINE"] = "1"