env.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. from pathlib import Path
  2. import os
  3. import logging
  4. import sys
  5. import json
  6. import importlib.metadata
  7. import pkgutil
  8. from urllib.parse import urlparse
  9. from datetime import datetime
  10. import markdown
  11. from bs4 import BeautifulSoup
  12. from constants import ERROR_MESSAGES
  13. ####################################
  14. # Load .env file
  15. ####################################
  16. BACKEND_DIR = Path(__file__).parent # the path containing this file
  17. BASE_DIR = BACKEND_DIR.parent # the path containing the backend/
  18. print(BASE_DIR)
  19. try:
  20. from dotenv import load_dotenv, find_dotenv
  21. load_dotenv(find_dotenv(str(BASE_DIR / ".env")))
  22. except ImportError:
  23. print("dotenv not installed, skipping...")
  24. ####################################
  25. # LOGGING
  26. ####################################
  27. log_levels = ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"]
  28. GLOBAL_LOG_LEVEL = os.environ.get("GLOBAL_LOG_LEVEL", "").upper()
  29. if GLOBAL_LOG_LEVEL in log_levels:
  30. logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL, force=True)
  31. else:
  32. GLOBAL_LOG_LEVEL = "INFO"
  33. log = logging.getLogger(__name__)
  34. log.info(f"GLOBAL_LOG_LEVEL: {GLOBAL_LOG_LEVEL}")
  35. log_sources = [
  36. "AUDIO",
  37. "COMFYUI",
  38. "CONFIG",
  39. "DB",
  40. "IMAGES",
  41. "MAIN",
  42. "MODELS",
  43. "OLLAMA",
  44. "OPENAI",
  45. "RAG",
  46. "WEBHOOK",
  47. ]
  48. SRC_LOG_LEVELS = {}
  49. for source in log_sources:
  50. log_env_var = source + "_LOG_LEVEL"
  51. SRC_LOG_LEVELS[source] = os.environ.get(log_env_var, "").upper()
  52. if SRC_LOG_LEVELS[source] not in log_levels:
  53. SRC_LOG_LEVELS[source] = GLOBAL_LOG_LEVEL
  54. log.info(f"{log_env_var}: {SRC_LOG_LEVELS[source]}")
  55. log.setLevel(SRC_LOG_LEVELS["CONFIG"])
  56. WEBUI_NAME = os.environ.get("WEBUI_NAME", "Open WebUI")
  57. if WEBUI_NAME != "Open WebUI":
  58. WEBUI_NAME += " (Open WebUI)"
  59. WEBUI_URL = os.environ.get("WEBUI_URL", "http://localhost:3000")
  60. WEBUI_FAVICON_URL = "https://openwebui.com/favicon.png"
  61. ####################################
  62. # ENV (dev,test,prod)
  63. ####################################
  64. ENV = os.environ.get("ENV", "dev")
  65. try:
  66. PACKAGE_DATA = json.loads((BASE_DIR / "package.json").read_text())
  67. except Exception:
  68. try:
  69. PACKAGE_DATA = {"version": importlib.metadata.version("open-webui")}
  70. except importlib.metadata.PackageNotFoundError:
  71. PACKAGE_DATA = {"version": "0.0.0"}
  72. VERSION = PACKAGE_DATA["version"]
  73. # Function to parse each section
  74. def parse_section(section):
  75. items = []
  76. for li in section.find_all("li"):
  77. # Extract raw HTML string
  78. raw_html = str(li)
  79. # Extract text without HTML tags
  80. text = li.get_text(separator=" ", strip=True)
  81. # Split into title and content
  82. parts = text.split(": ", 1)
  83. title = parts[0].strip() if len(parts) > 1 else ""
  84. content = parts[1].strip() if len(parts) > 1 else text
  85. items.append({"title": title, "content": content, "raw": raw_html})
  86. return items
  87. try:
  88. changelog_path = BASE_DIR / "CHANGELOG.md"
  89. with open(str(changelog_path.absolute()), "r", encoding="utf8") as file:
  90. changelog_content = file.read()
  91. except Exception:
  92. changelog_content = (pkgutil.get_data("open_webui", "CHANGELOG.md") or b"").decode()
  93. # Convert markdown content to HTML
  94. html_content = markdown.markdown(changelog_content)
  95. # Parse the HTML content
  96. soup = BeautifulSoup(html_content, "html.parser")
  97. # Initialize JSON structure
  98. changelog_json = {}
  99. # Iterate over each version
  100. for version in soup.find_all("h2"):
  101. version_number = version.get_text().strip().split(" - ")[0][1:-1] # Remove brackets
  102. date = version.get_text().strip().split(" - ")[1]
  103. version_data = {"date": date}
  104. # Find the next sibling that is a h3 tag (section title)
  105. current = version.find_next_sibling()
  106. while current and current.name != "h2":
  107. if current.name == "h3":
  108. section_title = current.get_text().lower() # e.g., "added", "fixed"
  109. section_items = parse_section(current.find_next_sibling("ul"))
  110. version_data[section_title] = section_items
  111. # Move to the next element
  112. current = current.find_next_sibling()
  113. changelog_json[version_number] = version_data
  114. CHANGELOG = changelog_json
  115. ####################################
  116. # SAFE_MODE
  117. ####################################
  118. SAFE_MODE = os.environ.get("SAFE_MODE", "false").lower() == "true"
  119. ####################################
  120. # WEBUI_BUILD_HASH
  121. ####################################
  122. WEBUI_BUILD_HASH = os.environ.get("WEBUI_BUILD_HASH", "dev-build")
  123. ####################################
  124. # DATA/FRONTEND BUILD DIR
  125. ####################################
  126. DATA_DIR = Path(os.getenv("DATA_DIR", BACKEND_DIR / "data")).resolve()
  127. FRONTEND_BUILD_DIR = Path(os.getenv("FRONTEND_BUILD_DIR", BASE_DIR / "build")).resolve()
  128. RESET_CONFIG_ON_START = (
  129. os.environ.get("RESET_CONFIG_ON_START", "False").lower() == "true"
  130. )
  131. if RESET_CONFIG_ON_START:
  132. try:
  133. os.remove(f"{DATA_DIR}/config.json")
  134. with open(f"{DATA_DIR}/config.json", "w") as f:
  135. f.write("{}")
  136. except Exception:
  137. pass
  138. try:
  139. CONFIG_DATA = json.loads((DATA_DIR / "config.json").read_text())
  140. except Exception:
  141. CONFIG_DATA = {}
  142. ####################################
  143. # Database
  144. ####################################
  145. # Check if the file exists
  146. if os.path.exists(f"{DATA_DIR}/ollama.db"):
  147. # Rename the file
  148. os.rename(f"{DATA_DIR}/ollama.db", f"{DATA_DIR}/webui.db")
  149. log.info("Database migrated from Ollama-WebUI successfully.")
  150. else:
  151. pass
  152. DATABASE_URL = os.environ.get("DATABASE_URL", f"sqlite:///{DATA_DIR}/webui.db")
  153. # Replace the postgres:// with postgresql://
  154. if "postgres://" in DATABASE_URL:
  155. DATABASE_URL = DATABASE_URL.replace("postgres://", "postgresql://")
  156. ####################################
  157. # WEBUI_AUTH (Required for security)
  158. ####################################
  159. WEBUI_AUTH = os.environ.get("WEBUI_AUTH", "True").lower() == "true"
  160. WEBUI_AUTH_TRUSTED_EMAIL_HEADER = os.environ.get(
  161. "WEBUI_AUTH_TRUSTED_EMAIL_HEADER", None
  162. )
  163. WEBUI_AUTH_TRUSTED_NAME_HEADER = os.environ.get("WEBUI_AUTH_TRUSTED_NAME_HEADER", None)
  164. ####################################
  165. # WEBUI_SECRET_KEY
  166. ####################################
  167. WEBUI_SECRET_KEY = os.environ.get(
  168. "WEBUI_SECRET_KEY",
  169. os.environ.get(
  170. "WEBUI_JWT_SECRET_KEY", "t0p-s3cr3t"
  171. ), # DEPRECATED: remove at next major version
  172. )
  173. WEBUI_SESSION_COOKIE_SAME_SITE = os.environ.get(
  174. "WEBUI_SESSION_COOKIE_SAME_SITE",
  175. os.environ.get("WEBUI_SESSION_COOKIE_SAME_SITE", "lax"),
  176. )
  177. WEBUI_SESSION_COOKIE_SECURE = os.environ.get(
  178. "WEBUI_SESSION_COOKIE_SECURE",
  179. os.environ.get("WEBUI_SESSION_COOKIE_SECURE", "false").lower() == "true",
  180. )
  181. if WEBUI_AUTH and WEBUI_SECRET_KEY == "":
  182. raise ValueError(ERROR_MESSAGES.ENV_VAR_NOT_FOUND)