Candro commited on
Commit
b47b2ac
·
0 Parent(s):

fresh commit

Browse files
Files changed (6) hide show
  1. .dockerignore +3 -0
  2. .gitattributes +36 -0
  3. Dockerfile +22 -0
  4. README.md +26 -0
  5. src/database/webui.db +3 -0
  6. src/env.py +790 -0
.dockerignore ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ .env
2
+ *.key
3
+ secrets/
.gitattributes ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ *.db filter=lfs diff=lfs merge=lfs -text
Dockerfile ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use a specific container image for the application
2
+ FROM ghcr.io/open-webui/open-webui:0.6.26-slim
3
+
4
+ # Set the main working directory inside the container
5
+ WORKDIR /app/backend
6
+
7
+ # Copy the database file into the container
8
+ # This database is a placeholder or dummy,
9
+ # and the core configuration is located in the Environment
10
+ # and Secret Environment settings of Hugging Face Spaces.
11
+ COPY --chown=$UID:$GID src/database/webui.db /app/backend/data/
12
+ COPY --chown=$UID:$GID src/env.py /app/backend/open_webui/
13
+
14
+ # Set the database permission
15
+ RUN chmod 777 /app/backend/data/webui.db
16
+ RUN chmod 777 /app/backend/open_webui/env.py
17
+
18
+ # Open the port so the application can be accessed
19
+ EXPOSE 8080
20
+
21
+ # Start the application using the startup script
22
+ CMD ["bash", "start.sh"]
README.md ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Simata AI
3
+ short_description: Premium AI | Tools | Code Interpreter | Web Generator | etc
4
+ emoji: ⚡
5
+ colorFrom: blue
6
+ colorTo: indigo
7
+ sdk: docker
8
+ license: mit
9
+ app_port: 8080
10
+ pinned: true
11
+ models:
12
+ - black-forest-labs/FLUX.1-Kontext-Dev
13
+ - black-forest-labs/FLUX.1-Krea-dev
14
+ - deepseek-ai/DeepSeek-R1
15
+ - google/gemma-3-270m
16
+ - Qwen/Qwen-Image
17
+ - Qwen/Qwen3-235B-A22B-Instruct-2507
18
+ - Qwen/Qwen3-Coder-480B-A35B-Instruct
19
+ - microsoft/speecht5_tts
20
+ - sentence-transformers/all-MiniLM-L6-v2
21
+ - zai-org/GLM-4.5
22
+ datasets:
23
+ - fka/awesome-chatgpt-prompts
24
+ ---
25
+
26
+ ## User-friendly AI Interface 💻
src/database/webui.db ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1f0adab66c61f3ef8491d12c7a62459dc9d482ea88b0d29719c15373177bbe8e
3
+ size 720896
src/env.py ADDED
@@ -0,0 +1,790 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import importlib.metadata
2
+ import json
3
+ import logging
4
+ import os
5
+ import pkgutil
6
+ import sys
7
+ import shutil
8
+ from uuid import uuid4
9
+ from pathlib import Path
10
+ from cryptography.hazmat.primitives import serialization
11
+
12
+ import markdown
13
+ from bs4 import BeautifulSoup
14
+ from open_webui.constants import ERROR_MESSAGES
15
+
16
+ ####################################
17
+ # Load .env file
18
+ ####################################
19
+
20
+ # Use .resolve() to get the canonical path, removing any '..' or '.' components
21
+ ENV_FILE_PATH = Path(__file__).resolve()
22
+
23
+ # OPEN_WEBUI_DIR should be the directory where env.py resides (open_webui/)
24
+ OPEN_WEBUI_DIR = ENV_FILE_PATH.parent
25
+
26
+ # BACKEND_DIR is the parent of OPEN_WEBUI_DIR (backend/)
27
+ BACKEND_DIR = OPEN_WEBUI_DIR.parent
28
+
29
+ # BASE_DIR is the parent of BACKEND_DIR (open-webui-dev/)
30
+ BASE_DIR = BACKEND_DIR.parent
31
+
32
+ try:
33
+ from dotenv import find_dotenv, load_dotenv
34
+
35
+ load_dotenv(find_dotenv(str(BASE_DIR / ".env")))
36
+ except ImportError:
37
+ print("dotenv not installed, skipping...")
38
+
39
+ DOCKER = os.environ.get("DOCKER", "False").lower() == "true"
40
+
41
+ # device type embedding models - "cpu" (default), "cuda" (nvidia gpu required) or "mps" (apple silicon) - choosing this right can lead to better performance
42
+ USE_CUDA = os.environ.get("USE_CUDA_DOCKER", "false")
43
+
44
+ if USE_CUDA.lower() == "true":
45
+ try:
46
+ import torch
47
+
48
+ assert torch.cuda.is_available(), "CUDA not available"
49
+ DEVICE_TYPE = "cuda"
50
+ except Exception as e:
51
+ cuda_error = (
52
+ "Error when testing CUDA but USE_CUDA_DOCKER is true. "
53
+ f"Resetting USE_CUDA_DOCKER to false: {e}"
54
+ )
55
+ os.environ["USE_CUDA_DOCKER"] = "false"
56
+ USE_CUDA = "false"
57
+ DEVICE_TYPE = "cpu"
58
+ else:
59
+ DEVICE_TYPE = "cpu"
60
+
61
+ try:
62
+ import torch
63
+
64
+ if torch.backends.mps.is_available() and torch.backends.mps.is_built():
65
+ DEVICE_TYPE = "mps"
66
+ except Exception:
67
+ pass
68
+
69
+ ####################################
70
+ # LOGGING
71
+ ####################################
72
+
73
+ GLOBAL_LOG_LEVEL = os.environ.get("GLOBAL_LOG_LEVEL", "").upper()
74
+ if GLOBAL_LOG_LEVEL in logging.getLevelNamesMapping():
75
+ logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL, force=True)
76
+ else:
77
+ GLOBAL_LOG_LEVEL = "INFO"
78
+
79
+ log = logging.getLogger(__name__)
80
+ log.info(f"GLOBAL_LOG_LEVEL: {GLOBAL_LOG_LEVEL}")
81
+
82
+ if "cuda_error" in locals():
83
+ log.exception(cuda_error)
84
+ del cuda_error
85
+
86
+ log_sources = [
87
+ "AUDIO",
88
+ "COMFYUI",
89
+ "CONFIG",
90
+ "DB",
91
+ "IMAGES",
92
+ "MAIN",
93
+ "MODELS",
94
+ "OLLAMA",
95
+ "OPENAI",
96
+ "RAG",
97
+ "WEBHOOK",
98
+ "SOCKET",
99
+ "OAUTH",
100
+ ]
101
+
102
+ SRC_LOG_LEVELS = {}
103
+
104
+ for source in log_sources:
105
+ log_env_var = source + "_LOG_LEVEL"
106
+ SRC_LOG_LEVELS[source] = os.environ.get(log_env_var, "").upper()
107
+ if SRC_LOG_LEVELS[source] not in logging.getLevelNamesMapping():
108
+ SRC_LOG_LEVELS[source] = GLOBAL_LOG_LEVEL
109
+ log.info(f"{log_env_var}: {SRC_LOG_LEVELS[source]}")
110
+
111
+ log.setLevel(SRC_LOG_LEVELS["CONFIG"])
112
+
113
+ WEBUI_NAME = os.environ.get("WEBUI_NAME", "Open WebUI")
114
+
115
+ WEBUI_FAVICON_URL = "https://cdn.jsdelivr.net/gh/tulungagung/aset@utama/img/favicon.png"
116
+
117
+ TRUSTED_SIGNATURE_KEY = os.environ.get("TRUSTED_SIGNATURE_KEY", "")
118
+
119
+ ####################################
120
+ # ENV (dev,test,prod)
121
+ ####################################
122
+
123
+ ENV = os.environ.get("ENV", "dev")
124
+
125
+ FROM_INIT_PY = os.environ.get("FROM_INIT_PY", "False").lower() == "true"
126
+
127
+ if FROM_INIT_PY:
128
+ PACKAGE_DATA = {"version": importlib.metadata.version("open-webui")}
129
+ else:
130
+ try:
131
+ PACKAGE_DATA = json.loads((BASE_DIR / "package.json").read_text())
132
+ except Exception:
133
+ PACKAGE_DATA = {"version": "0.0.0"}
134
+
135
+ VERSION = PACKAGE_DATA["version"]
136
+ INSTANCE_ID = os.environ.get("INSTANCE_ID", str(uuid4()))
137
+
138
+
139
+ # Function to parse each section
140
+ def parse_section(section):
141
+ items = []
142
+ for li in section.find_all("li"):
143
+ # Extract raw HTML string
144
+ raw_html = str(li)
145
+
146
+ # Extract text without HTML tags
147
+ text = li.get_text(separator=" ", strip=True)
148
+
149
+ # Split into title and content
150
+ parts = text.split(": ", 1)
151
+ title = parts[0].strip() if len(parts) > 1 else ""
152
+ content = parts[1].strip() if len(parts) > 1 else text
153
+
154
+ items.append({"title": title, "content": content, "raw": raw_html})
155
+ return items
156
+
157
+
158
+ try:
159
+ changelog_path = BASE_DIR / "CHANGELOG.md"
160
+ with open(str(changelog_path.absolute()), "r", encoding="utf8") as file:
161
+ changelog_content = file.read()
162
+
163
+ except Exception:
164
+ changelog_content = (pkgutil.get_data("open_webui", "CHANGELOG.md") or b"").decode()
165
+
166
+ # Convert markdown content to HTML
167
+ html_content = markdown.markdown(changelog_content)
168
+
169
+ # Parse the HTML content
170
+ soup = BeautifulSoup(html_content, "html.parser")
171
+
172
+ # Initialize JSON structure
173
+ changelog_json = {}
174
+
175
+ # Iterate over each version
176
+ for version in soup.find_all("h2"):
177
+ version_number = version.get_text().strip().split(" - ")[0][1:-1] # Remove brackets
178
+ date = version.get_text().strip().split(" - ")[1]
179
+
180
+ version_data = {"date": date}
181
+
182
+ # Find the next sibling that is a h3 tag (section title)
183
+ current = version.find_next_sibling()
184
+
185
+ while current and current.name != "h2":
186
+ if current.name == "h3":
187
+ section_title = current.get_text().lower() # e.g., "added", "fixed"
188
+ section_items = parse_section(current.find_next_sibling("ul"))
189
+ version_data[section_title] = section_items
190
+
191
+ # Move to the next element
192
+ current = current.find_next_sibling()
193
+
194
+ changelog_json[version_number] = version_data
195
+
196
+ CHANGELOG = changelog_json
197
+
198
+ ####################################
199
+ # SAFE_MODE
200
+ ####################################
201
+
202
+ SAFE_MODE = os.environ.get("SAFE_MODE", "false").lower() == "true"
203
+
204
+
205
+ ####################################
206
+ # ENABLE_FORWARD_USER_INFO_HEADERS
207
+ ####################################
208
+
209
+ ENABLE_FORWARD_USER_INFO_HEADERS = (
210
+ os.environ.get("ENABLE_FORWARD_USER_INFO_HEADERS", "False").lower() == "true"
211
+ )
212
+
213
+ ####################################
214
+ # WEBUI_BUILD_HASH
215
+ ####################################
216
+
217
+ WEBUI_BUILD_HASH = os.environ.get("WEBUI_BUILD_HASH", "dev-build")
218
+
219
+ ####################################
220
+ # DATA/FRONTEND BUILD DIR
221
+ ####################################
222
+
223
+ DATA_DIR = Path(os.getenv("DATA_DIR", BACKEND_DIR / "data")).resolve()
224
+
225
+ if FROM_INIT_PY:
226
+ NEW_DATA_DIR = Path(os.getenv("DATA_DIR", OPEN_WEBUI_DIR / "data")).resolve()
227
+ NEW_DATA_DIR.mkdir(parents=True, exist_ok=True)
228
+
229
+ # Check if the data directory exists in the package directory
230
+ if DATA_DIR.exists() and DATA_DIR != NEW_DATA_DIR:
231
+ log.info(f"Moving {DATA_DIR} to {NEW_DATA_DIR}")
232
+ for item in DATA_DIR.iterdir():
233
+ dest = NEW_DATA_DIR / item.name
234
+ if item.is_dir():
235
+ shutil.copytree(item, dest, dirs_exist_ok=True)
236
+ else:
237
+ shutil.copy2(item, dest)
238
+
239
+ # Zip the data directory
240
+ shutil.make_archive(DATA_DIR.parent / "open_webui_data", "zip", DATA_DIR)
241
+
242
+ # Remove the old data directory
243
+ shutil.rmtree(DATA_DIR)
244
+
245
+ DATA_DIR = Path(os.getenv("DATA_DIR", OPEN_WEBUI_DIR / "data"))
246
+
247
+ STATIC_DIR = Path(os.getenv("STATIC_DIR", OPEN_WEBUI_DIR / "static"))
248
+
249
+ FONTS_DIR = Path(os.getenv("FONTS_DIR", OPEN_WEBUI_DIR / "static" / "fonts"))
250
+
251
+ FRONTEND_BUILD_DIR = Path(os.getenv("FRONTEND_BUILD_DIR", BASE_DIR / "build")).resolve()
252
+
253
+ if FROM_INIT_PY:
254
+ FRONTEND_BUILD_DIR = Path(
255
+ os.getenv("FRONTEND_BUILD_DIR", OPEN_WEBUI_DIR / "frontend")
256
+ ).resolve()
257
+
258
+ ####################################
259
+ # Database
260
+ ####################################
261
+
262
+ # Check if the file exists
263
+ if os.path.exists(f"{DATA_DIR}/ollama.db"):
264
+ # Rename the file
265
+ os.rename(f"{DATA_DIR}/ollama.db", f"{DATA_DIR}/webui.db")
266
+ log.info("Database migrated from Ollama-WebUI successfully.")
267
+ else:
268
+ pass
269
+
270
+ DATABASE_URL = os.environ.get("DATABASE_URL", f"sqlite:///{DATA_DIR}/webui.db")
271
+
272
+ DATABASE_TYPE = os.environ.get("DATABASE_TYPE")
273
+ DATABASE_USER = os.environ.get("DATABASE_USER")
274
+ DATABASE_PASSWORD = os.environ.get("DATABASE_PASSWORD")
275
+
276
+ DATABASE_CRED = ""
277
+ if DATABASE_USER:
278
+ DATABASE_CRED += f"{DATABASE_USER}"
279
+ if DATABASE_PASSWORD:
280
+ DATABASE_CRED += f":{DATABASE_PASSWORD}"
281
+
282
+ DB_VARS = {
283
+ "db_type": DATABASE_TYPE,
284
+ "db_cred": DATABASE_CRED,
285
+ "db_host": os.environ.get("DATABASE_HOST"),
286
+ "db_port": os.environ.get("DATABASE_PORT"),
287
+ "db_name": os.environ.get("DATABASE_NAME"),
288
+ }
289
+
290
+ if all(DB_VARS.values()):
291
+ DATABASE_URL = f"{DB_VARS['db_type']}://{DB_VARS['db_cred']}@{DB_VARS['db_host']}:{DB_VARS['db_port']}/{DB_VARS['db_name']}"
292
+ elif DATABASE_TYPE == "sqlite+sqlcipher" and not os.environ.get("DATABASE_URL"):
293
+ # Handle SQLCipher with local file when DATABASE_URL wasn't explicitly set
294
+ DATABASE_URL = f"sqlite+sqlcipher:///{DATA_DIR}/webui.db"
295
+
296
+ # Replace the postgres:// with postgresql://
297
+ if "postgres://" in DATABASE_URL:
298
+ DATABASE_URL = DATABASE_URL.replace("postgres://", "postgresql://")
299
+
300
+ DATABASE_SCHEMA = os.environ.get("DATABASE_SCHEMA", None)
301
+
302
+ DATABASE_POOL_SIZE = os.environ.get("DATABASE_POOL_SIZE", None)
303
+
304
+ if DATABASE_POOL_SIZE != None:
305
+ try:
306
+ DATABASE_POOL_SIZE = int(DATABASE_POOL_SIZE)
307
+ except Exception:
308
+ DATABASE_POOL_SIZE = None
309
+
310
+ DATABASE_POOL_MAX_OVERFLOW = os.environ.get("DATABASE_POOL_MAX_OVERFLOW", 0)
311
+
312
+ if DATABASE_POOL_MAX_OVERFLOW == "":
313
+ DATABASE_POOL_MAX_OVERFLOW = 0
314
+ else:
315
+ try:
316
+ DATABASE_POOL_MAX_OVERFLOW = int(DATABASE_POOL_MAX_OVERFLOW)
317
+ except Exception:
318
+ DATABASE_POOL_MAX_OVERFLOW = 0
319
+
320
+ DATABASE_POOL_TIMEOUT = os.environ.get("DATABASE_POOL_TIMEOUT", 30)
321
+
322
+ if DATABASE_POOL_TIMEOUT == "":
323
+ DATABASE_POOL_TIMEOUT = 30
324
+ else:
325
+ try:
326
+ DATABASE_POOL_TIMEOUT = int(DATABASE_POOL_TIMEOUT)
327
+ except Exception:
328
+ DATABASE_POOL_TIMEOUT = 30
329
+
330
+ DATABASE_POOL_RECYCLE = os.environ.get("DATABASE_POOL_RECYCLE", 3600)
331
+
332
+ if DATABASE_POOL_RECYCLE == "":
333
+ DATABASE_POOL_RECYCLE = 3600
334
+ else:
335
+ try:
336
+ DATABASE_POOL_RECYCLE = int(DATABASE_POOL_RECYCLE)
337
+ except Exception:
338
+ DATABASE_POOL_RECYCLE = 3600
339
+
340
+ DATABASE_ENABLE_SQLITE_WAL = (
341
+ os.environ.get("DATABASE_ENABLE_SQLITE_WAL", "False").lower() == "true"
342
+ )
343
+
344
+ DATABASE_USER_ACTIVE_STATUS_UPDATE_INTERVAL = os.environ.get(
345
+ "DATABASE_USER_ACTIVE_STATUS_UPDATE_INTERVAL", None
346
+ )
347
+ if DATABASE_USER_ACTIVE_STATUS_UPDATE_INTERVAL is not None:
348
+ try:
349
+ DATABASE_USER_ACTIVE_STATUS_UPDATE_INTERVAL = float(
350
+ DATABASE_USER_ACTIVE_STATUS_UPDATE_INTERVAL
351
+ )
352
+ except Exception:
353
+ DATABASE_USER_ACTIVE_STATUS_UPDATE_INTERVAL = 0.0
354
+
355
+ RESET_CONFIG_ON_START = (
356
+ os.environ.get("RESET_CONFIG_ON_START", "False").lower() == "true"
357
+ )
358
+
359
+ ENABLE_REALTIME_CHAT_SAVE = (
360
+ os.environ.get("ENABLE_REALTIME_CHAT_SAVE", "False").lower() == "true"
361
+ )
362
+
363
+ ENABLE_QUERIES_CACHE = os.environ.get("ENABLE_QUERIES_CACHE", "False").lower() == "true"
364
+
365
+ ####################################
366
+ # REDIS
367
+ ####################################
368
+
369
+ REDIS_URL = os.environ.get("REDIS_URL", "")
370
+ REDIS_CLUSTER = os.environ.get("REDIS_CLUSTER", "False").lower() == "true"
371
+
372
+ REDIS_KEY_PREFIX = os.environ.get("REDIS_KEY_PREFIX", "open-webui")
373
+
374
+ REDIS_SENTINEL_HOSTS = os.environ.get("REDIS_SENTINEL_HOSTS", "")
375
+ REDIS_SENTINEL_PORT = os.environ.get("REDIS_SENTINEL_PORT", "26379")
376
+
377
+ # Maximum number of retries for Redis operations when using Sentinel fail-over
378
+ REDIS_SENTINEL_MAX_RETRY_COUNT = os.environ.get("REDIS_SENTINEL_MAX_RETRY_COUNT", "2")
379
+ try:
380
+ REDIS_SENTINEL_MAX_RETRY_COUNT = int(REDIS_SENTINEL_MAX_RETRY_COUNT)
381
+ if REDIS_SENTINEL_MAX_RETRY_COUNT < 1:
382
+ REDIS_SENTINEL_MAX_RETRY_COUNT = 2
383
+ except ValueError:
384
+ REDIS_SENTINEL_MAX_RETRY_COUNT = 2
385
+
386
+ ####################################
387
+ # UVICORN WORKERS
388
+ ####################################
389
+
390
+ # Number of uvicorn worker processes for handling requests
391
+ UVICORN_WORKERS = os.environ.get("UVICORN_WORKERS", "1")
392
+ try:
393
+ UVICORN_WORKERS = int(UVICORN_WORKERS)
394
+ if UVICORN_WORKERS < 1:
395
+ UVICORN_WORKERS = 1
396
+ except ValueError:
397
+ UVICORN_WORKERS = 1
398
+ log.info(f"Invalid UVICORN_WORKERS value, defaulting to {UVICORN_WORKERS}")
399
+
400
+ ####################################
401
+ # WEBUI_AUTH (Required for security)
402
+ ####################################
403
+
404
+ WEBUI_AUTH = os.environ.get("WEBUI_AUTH", "True").lower() == "true"
405
+
406
+ ENABLE_INITIAL_ADMIN_SIGNUP = (
407
+ os.environ.get("ENABLE_INITIAL_ADMIN_SIGNUP", "False").lower() == "true"
408
+ )
409
+ ENABLE_SIGNUP_PASSWORD_CONFIRMATION = (
410
+ os.environ.get("ENABLE_SIGNUP_PASSWORD_CONFIRMATION", "False").lower() == "true"
411
+ )
412
+
413
+ WEBUI_AUTH_TRUSTED_EMAIL_HEADER = os.environ.get(
414
+ "WEBUI_AUTH_TRUSTED_EMAIL_HEADER", None
415
+ )
416
+ WEBUI_AUTH_TRUSTED_NAME_HEADER = os.environ.get("WEBUI_AUTH_TRUSTED_NAME_HEADER", None)
417
+ WEBUI_AUTH_TRUSTED_GROUPS_HEADER = os.environ.get(
418
+ "WEBUI_AUTH_TRUSTED_GROUPS_HEADER", None
419
+ )
420
+
421
+
422
+ BYPASS_MODEL_ACCESS_CONTROL = (
423
+ os.environ.get("BYPASS_MODEL_ACCESS_CONTROL", "False").lower() == "true"
424
+ )
425
+
426
+ WEBUI_AUTH_SIGNOUT_REDIRECT_URL = os.environ.get(
427
+ "WEBUI_AUTH_SIGNOUT_REDIRECT_URL", None
428
+ )
429
+
430
+ ####################################
431
+ # WEBUI_SECRET_KEY
432
+ ####################################
433
+
434
+ WEBUI_SECRET_KEY = os.environ.get(
435
+ "WEBUI_SECRET_KEY",
436
+ os.environ.get(
437
+ "WEBUI_JWT_SECRET_KEY", "t0p-s3cr3t"
438
+ ), # DEPRECATED: remove at next major version
439
+ )
440
+
441
+ WEBUI_SESSION_COOKIE_SAME_SITE = os.environ.get("WEBUI_SESSION_COOKIE_SAME_SITE", "lax")
442
+
443
+ WEBUI_SESSION_COOKIE_SECURE = (
444
+ os.environ.get("WEBUI_SESSION_COOKIE_SECURE", "false").lower() == "true"
445
+ )
446
+
447
+ WEBUI_AUTH_COOKIE_SAME_SITE = os.environ.get(
448
+ "WEBUI_AUTH_COOKIE_SAME_SITE", WEBUI_SESSION_COOKIE_SAME_SITE
449
+ )
450
+
451
+ WEBUI_AUTH_COOKIE_SECURE = (
452
+ os.environ.get(
453
+ "WEBUI_AUTH_COOKIE_SECURE",
454
+ os.environ.get("WEBUI_SESSION_COOKIE_SECURE", "false"),
455
+ ).lower()
456
+ == "true"
457
+ )
458
+
459
+ if WEBUI_AUTH and WEBUI_SECRET_KEY == "":
460
+ raise ValueError(ERROR_MESSAGES.ENV_VAR_NOT_FOUND)
461
+
462
+ ENABLE_COMPRESSION_MIDDLEWARE = (
463
+ os.environ.get("ENABLE_COMPRESSION_MIDDLEWARE", "True").lower() == "true"
464
+ )
465
+
466
+
467
+ ####################################
468
+ # SCIM Configuration
469
+ ####################################
470
+
471
+ SCIM_ENABLED = os.environ.get("SCIM_ENABLED", "False").lower() == "true"
472
+ SCIM_TOKEN = os.environ.get("SCIM_TOKEN", "")
473
+
474
+ ####################################
475
+ # LICENSE_KEY
476
+ ####################################
477
+
478
+ LICENSE_KEY = os.environ.get("LICENSE_KEY", "")
479
+
480
+ LICENSE_BLOB = None
481
+ LICENSE_BLOB_PATH = os.environ.get("LICENSE_BLOB_PATH", DATA_DIR / "l.data")
482
+ if LICENSE_BLOB_PATH and os.path.exists(LICENSE_BLOB_PATH):
483
+ with open(LICENSE_BLOB_PATH, "rb") as f:
484
+ LICENSE_BLOB = f.read()
485
+
486
+ LICENSE_PUBLIC_KEY = os.environ.get("LICENSE_PUBLIC_KEY", "")
487
+
488
+ pk = None
489
+ if LICENSE_PUBLIC_KEY:
490
+ pk = serialization.load_pem_public_key(
491
+ f"""
492
+ -----BEGIN PUBLIC KEY-----
493
+ {LICENSE_PUBLIC_KEY}
494
+ -----END PUBLIC KEY-----
495
+ """.encode(
496
+ "utf-8"
497
+ )
498
+ )
499
+
500
+
501
+ ####################################
502
+ # MODELS
503
+ ####################################
504
+
505
+ MODELS_CACHE_TTL = os.environ.get("MODELS_CACHE_TTL", "1")
506
+ if MODELS_CACHE_TTL == "":
507
+ MODELS_CACHE_TTL = None
508
+ else:
509
+ try:
510
+ MODELS_CACHE_TTL = int(MODELS_CACHE_TTL)
511
+ except Exception:
512
+ MODELS_CACHE_TTL = 1
513
+
514
+
515
+ ####################################
516
+ # CHAT
517
+ ####################################
518
+
519
+ CHAT_RESPONSE_STREAM_DELTA_CHUNK_SIZE = os.environ.get(
520
+ "CHAT_RESPONSE_STREAM_DELTA_CHUNK_SIZE", "1"
521
+ )
522
+
523
+ if CHAT_RESPONSE_STREAM_DELTA_CHUNK_SIZE == "":
524
+ CHAT_RESPONSE_STREAM_DELTA_CHUNK_SIZE = 1
525
+ else:
526
+ try:
527
+ CHAT_RESPONSE_STREAM_DELTA_CHUNK_SIZE = int(
528
+ CHAT_RESPONSE_STREAM_DELTA_CHUNK_SIZE
529
+ )
530
+ except Exception:
531
+ CHAT_RESPONSE_STREAM_DELTA_CHUNK_SIZE = 1
532
+
533
+
534
+ CHAT_RESPONSE_MAX_TOOL_CALL_RETRIES = os.environ.get(
535
+ "CHAT_RESPONSE_MAX_TOOL_CALL_RETRIES", "10"
536
+ )
537
+
538
+ if CHAT_RESPONSE_MAX_TOOL_CALL_RETRIES == "":
539
+ CHAT_RESPONSE_MAX_TOOL_CALL_RETRIES = 10
540
+ else:
541
+ try:
542
+ CHAT_RESPONSE_MAX_TOOL_CALL_RETRIES = int(CHAT_RESPONSE_MAX_TOOL_CALL_RETRIES)
543
+ except Exception:
544
+ CHAT_RESPONSE_MAX_TOOL_CALL_RETRIES = 10
545
+
546
+
547
+ ####################################
548
+ # WEBSOCKET SUPPORT
549
+ ####################################
550
+
551
+ ENABLE_WEBSOCKET_SUPPORT = (
552
+ os.environ.get("ENABLE_WEBSOCKET_SUPPORT", "True").lower() == "true"
553
+ )
554
+
555
+
556
+ WEBSOCKET_MANAGER = os.environ.get("WEBSOCKET_MANAGER", "")
557
+
558
+ WEBSOCKET_REDIS_URL = os.environ.get("WEBSOCKET_REDIS_URL", REDIS_URL)
559
+ WEBSOCKET_REDIS_CLUSTER = (
560
+ os.environ.get("WEBSOCKET_REDIS_CLUSTER", str(REDIS_CLUSTER)).lower() == "true"
561
+ )
562
+
563
+ websocket_redis_lock_timeout = os.environ.get("WEBSOCKET_REDIS_LOCK_TIMEOUT", "60")
564
+
565
+ try:
566
+ WEBSOCKET_REDIS_LOCK_TIMEOUT = int(websocket_redis_lock_timeout)
567
+ except ValueError:
568
+ WEBSOCKET_REDIS_LOCK_TIMEOUT = 60
569
+
570
+ WEBSOCKET_SENTINEL_HOSTS = os.environ.get("WEBSOCKET_SENTINEL_HOSTS", "")
571
+ WEBSOCKET_SENTINEL_PORT = os.environ.get("WEBSOCKET_SENTINEL_PORT", "26379")
572
+
573
+
574
+ AIOHTTP_CLIENT_TIMEOUT = os.environ.get("AIOHTTP_CLIENT_TIMEOUT", "")
575
+
576
+ if AIOHTTP_CLIENT_TIMEOUT == "":
577
+ AIOHTTP_CLIENT_TIMEOUT = None
578
+ else:
579
+ try:
580
+ AIOHTTP_CLIENT_TIMEOUT = int(AIOHTTP_CLIENT_TIMEOUT)
581
+ except Exception:
582
+ AIOHTTP_CLIENT_TIMEOUT = 300
583
+
584
+
585
+ AIOHTTP_CLIENT_SESSION_SSL = (
586
+ os.environ.get("AIOHTTP_CLIENT_SESSION_SSL", "True").lower() == "true"
587
+ )
588
+
589
+ AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST = os.environ.get(
590
+ "AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST",
591
+ os.environ.get("AIOHTTP_CLIENT_TIMEOUT_OPENAI_MODEL_LIST", "10"),
592
+ )
593
+
594
+ if AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST == "":
595
+ AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST = None
596
+ else:
597
+ try:
598
+ AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST = int(AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST)
599
+ except Exception:
600
+ AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST = 10
601
+
602
+
603
+ AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER_DATA = os.environ.get(
604
+ "AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER_DATA", "10"
605
+ )
606
+
607
+ if AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER_DATA == "":
608
+ AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER_DATA = None
609
+ else:
610
+ try:
611
+ AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER_DATA = int(
612
+ AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER_DATA
613
+ )
614
+ except Exception:
615
+ AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER_DATA = 10
616
+
617
+
618
+ AIOHTTP_CLIENT_SESSION_TOOL_SERVER_SSL = (
619
+ os.environ.get("AIOHTTP_CLIENT_SESSION_TOOL_SERVER_SSL", "True").lower() == "true"
620
+ )
621
+
622
+
623
+ ####################################
624
+ # SENTENCE TRANSFORMERS
625
+ ####################################
626
+
627
+
628
+ SENTENCE_TRANSFORMERS_BACKEND = os.environ.get("SENTENCE_TRANSFORMERS_BACKEND", "")
629
+ if SENTENCE_TRANSFORMERS_BACKEND == "":
630
+ SENTENCE_TRANSFORMERS_BACKEND = "torch"
631
+
632
+
633
+ SENTENCE_TRANSFORMERS_MODEL_KWARGS = os.environ.get(
634
+ "SENTENCE_TRANSFORMERS_MODEL_KWARGS", ""
635
+ )
636
+ if SENTENCE_TRANSFORMERS_MODEL_KWARGS == "":
637
+ SENTENCE_TRANSFORMERS_MODEL_KWARGS = None
638
+ else:
639
+ try:
640
+ SENTENCE_TRANSFORMERS_MODEL_KWARGS = json.loads(
641
+ SENTENCE_TRANSFORMERS_MODEL_KWARGS
642
+ )
643
+ except Exception:
644
+ SENTENCE_TRANSFORMERS_MODEL_KWARGS = None
645
+
646
+
647
+ SENTENCE_TRANSFORMERS_CROSS_ENCODER_BACKEND = os.environ.get(
648
+ "SENTENCE_TRANSFORMERS_CROSS_ENCODER_BACKEND", ""
649
+ )
650
+ if SENTENCE_TRANSFORMERS_CROSS_ENCODER_BACKEND == "":
651
+ SENTENCE_TRANSFORMERS_CROSS_ENCODER_BACKEND = "torch"
652
+
653
+
654
+ SENTENCE_TRANSFORMERS_CROSS_ENCODER_MODEL_KWARGS = os.environ.get(
655
+ "SENTENCE_TRANSFORMERS_CROSS_ENCODER_MODEL_KWARGS", ""
656
+ )
657
+ if SENTENCE_TRANSFORMERS_CROSS_ENCODER_MODEL_KWARGS == "":
658
+ SENTENCE_TRANSFORMERS_CROSS_ENCODER_MODEL_KWARGS = None
659
+ else:
660
+ try:
661
+ SENTENCE_TRANSFORMERS_CROSS_ENCODER_MODEL_KWARGS = json.loads(
662
+ SENTENCE_TRANSFORMERS_CROSS_ENCODER_MODEL_KWARGS
663
+ )
664
+ except Exception:
665
+ SENTENCE_TRANSFORMERS_CROSS_ENCODER_MODEL_KWARGS = None
666
+
667
+ ####################################
668
+ # OFFLINE_MODE
669
+ ####################################
670
+
671
+ ENABLE_VERSION_UPDATE_CHECK = (
672
+ os.environ.get("ENABLE_VERSION_UPDATE_CHECK", "true").lower() == "true"
673
+ )
674
+ OFFLINE_MODE = os.environ.get("OFFLINE_MODE", "false").lower() == "true"
675
+
676
+ if OFFLINE_MODE:
677
+ os.environ["HF_HUB_OFFLINE"] = "1"
678
+ ENABLE_VERSION_UPDATE_CHECK = False
679
+
680
+ ####################################
681
+ # AUDIT LOGGING
682
+ ####################################
683
+ # Where to store log file
684
+ AUDIT_LOGS_FILE_PATH = f"{DATA_DIR}/audit.log"
685
+ # Maximum size of a file before rotating into a new log file
686
+ AUDIT_LOG_FILE_ROTATION_SIZE = os.getenv("AUDIT_LOG_FILE_ROTATION_SIZE", "10MB")
687
+
688
+ # Comma separated list of logger names to use for audit logging
689
+ # Default is "uvicorn.access" which is the access log for Uvicorn
690
+ # You can add more logger names to this list if you want to capture more logs
691
+ AUDIT_UVICORN_LOGGER_NAMES = os.getenv(
692
+ "AUDIT_UVICORN_LOGGER_NAMES", "uvicorn.access"
693
+ ).split(",")
694
+
695
+ # METADATA | REQUEST | REQUEST_RESPONSE
696
+ AUDIT_LOG_LEVEL = os.getenv("AUDIT_LOG_LEVEL", "NONE").upper()
697
+ try:
698
+ MAX_BODY_LOG_SIZE = int(os.environ.get("MAX_BODY_LOG_SIZE") or 2048)
699
+ except ValueError:
700
+ MAX_BODY_LOG_SIZE = 2048
701
+
702
+ # Comma separated list for urls to exclude from audit
703
+ AUDIT_EXCLUDED_PATHS = os.getenv("AUDIT_EXCLUDED_PATHS", "/chats,/chat,/folders").split(
704
+ ","
705
+ )
706
+ AUDIT_EXCLUDED_PATHS = [path.strip() for path in AUDIT_EXCLUDED_PATHS]
707
+ AUDIT_EXCLUDED_PATHS = [path.lstrip("/") for path in AUDIT_EXCLUDED_PATHS]
708
+
709
+
710
+ ####################################
711
+ # OPENTELEMETRY
712
+ ####################################
713
+
714
+ ENABLE_OTEL = os.environ.get("ENABLE_OTEL", "False").lower() == "true"
715
+ ENABLE_OTEL_TRACES = os.environ.get("ENABLE_OTEL_TRACES", "False").lower() == "true"
716
+ ENABLE_OTEL_METRICS = os.environ.get("ENABLE_OTEL_METRICS", "False").lower() == "true"
717
+ ENABLE_OTEL_LOGS = os.environ.get("ENABLE_OTEL_LOGS", "False").lower() == "true"
718
+
719
+ OTEL_EXPORTER_OTLP_ENDPOINT = os.environ.get(
720
+ "OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4317"
721
+ )
722
+ OTEL_METRICS_EXPORTER_OTLP_ENDPOINT = os.environ.get(
723
+ "OTEL_METRICS_EXPORTER_OTLP_ENDPOINT", OTEL_EXPORTER_OTLP_ENDPOINT
724
+ )
725
+ OTEL_LOGS_EXPORTER_OTLP_ENDPOINT = os.environ.get(
726
+ "OTEL_LOGS_EXPORTER_OTLP_ENDPOINT", OTEL_EXPORTER_OTLP_ENDPOINT
727
+ )
728
+ OTEL_EXPORTER_OTLP_INSECURE = (
729
+ os.environ.get("OTEL_EXPORTER_OTLP_INSECURE", "False").lower() == "true"
730
+ )
731
+ OTEL_METRICS_EXPORTER_OTLP_INSECURE = (
732
+ os.environ.get(
733
+ "OTEL_METRICS_EXPORTER_OTLP_INSECURE", str(OTEL_EXPORTER_OTLP_INSECURE)
734
+ ).lower()
735
+ == "true"
736
+ )
737
+ OTEL_LOGS_EXPORTER_OTLP_INSECURE = (
738
+ os.environ.get(
739
+ "OTEL_LOGS_EXPORTER_OTLP_INSECURE", str(OTEL_EXPORTER_OTLP_INSECURE)
740
+ ).lower()
741
+ == "true"
742
+ )
743
+ OTEL_SERVICE_NAME = os.environ.get("OTEL_SERVICE_NAME", "open-webui")
744
+ OTEL_RESOURCE_ATTRIBUTES = os.environ.get(
745
+ "OTEL_RESOURCE_ATTRIBUTES", ""
746
+ ) # e.g. key1=val1,key2=val2
747
+ OTEL_TRACES_SAMPLER = os.environ.get(
748
+ "OTEL_TRACES_SAMPLER", "parentbased_always_on"
749
+ ).lower()
750
+ OTEL_BASIC_AUTH_USERNAME = os.environ.get("OTEL_BASIC_AUTH_USERNAME", "")
751
+ OTEL_BASIC_AUTH_PASSWORD = os.environ.get("OTEL_BASIC_AUTH_PASSWORD", "")
752
+
753
+ OTEL_METRICS_BASIC_AUTH_USERNAME = os.environ.get(
754
+ "OTEL_METRICS_BASIC_AUTH_USERNAME", OTEL_BASIC_AUTH_USERNAME
755
+ )
756
+ OTEL_METRICS_BASIC_AUTH_PASSWORD = os.environ.get(
757
+ "OTEL_METRICS_BASIC_AUTH_PASSWORD", OTEL_BASIC_AUTH_PASSWORD
758
+ )
759
+ OTEL_LOGS_BASIC_AUTH_USERNAME = os.environ.get(
760
+ "OTEL_LOGS_BASIC_AUTH_USERNAME", OTEL_BASIC_AUTH_USERNAME
761
+ )
762
+ OTEL_LOGS_BASIC_AUTH_PASSWORD = os.environ.get(
763
+ "OTEL_LOGS_BASIC_AUTH_PASSWORD", OTEL_BASIC_AUTH_PASSWORD
764
+ )
765
+
766
+ OTEL_OTLP_SPAN_EXPORTER = os.environ.get(
767
+ "OTEL_OTLP_SPAN_EXPORTER", "grpc"
768
+ ).lower() # grpc or http
769
+
770
+ OTEL_METRICS_OTLP_SPAN_EXPORTER = os.environ.get(
771
+ "OTEL_METRICS_OTLP_SPAN_EXPORTER", OTEL_OTLP_SPAN_EXPORTER
772
+ ).lower() # grpc or http
773
+
774
+ OTEL_LOGS_OTLP_SPAN_EXPORTER = os.environ.get(
775
+ "OTEL_LOGS_OTLP_SPAN_EXPORTER", OTEL_OTLP_SPAN_EXPORTER
776
+ ).lower() # grpc or http
777
+
778
+ ####################################
779
+ # TOOLS/FUNCTIONS PIP OPTIONS
780
+ ####################################
781
+
782
+ PIP_OPTIONS = os.getenv("PIP_OPTIONS", "").split()
783
+ PIP_PACKAGE_INDEX_OPTIONS = os.getenv("PIP_PACKAGE_INDEX_OPTIONS", "").split()
784
+
785
+
786
+ ####################################
787
+ # PROGRESSIVE WEB APP OPTIONS
788
+ ####################################
789
+
790
+ EXTERNAL_PWA_MANIFEST_URL = os.environ.get("EXTERNAL_PWA_MANIFEST_URL")