mirror of
https://github.com/open-webui/open-webui.git
synced 2026-10-21 19:03:02 -05:00
The Docker image now runs Python 3.12 on Debian 13 (trixie) instead of Python 3.11 on Debian 12, and the pip install docs recommend 3.12. Image size stays about the same: the slim image is 1 MB smaller and the standard image is about 50 MB larger, mostly from the newer ffmpeg and pandoc that Debian 13 ships. Admins should know that custom functions, tools and anything installed with pip inside the container now run on Python 3.12, so a package that only has builds for 3.11 needs a newer version. Installing with pip still works on both Python 3.11 and 3.12.
139 lines
4.6 KiB
Python
139 lines
4.6 KiB
Python
import base64
|
|
import os
|
|
import secrets
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import Annotated
|
|
|
|
import typer
|
|
import uvicorn
|
|
|
|
app = typer.Typer()
|
|
mfa_app = typer.Typer(help='Manage multi-factor authentication.')
|
|
app.add_typer(mfa_app, name='mfa')
|
|
|
|
KEY_FILE = Path.cwd() / '.webui_secret_key'
|
|
DEFAULT_SECRET_KEY_LENGTH = 24
|
|
|
|
|
|
def version_callback(value: bool) -> None:
|
|
if value:
|
|
from open_webui.env import VERSION
|
|
|
|
# LICENSE covers this Open WebUI CLI identifier.
|
|
# Do not alter, remove, obscure, or replace it except as LICENSE permits:
|
|
# https://docs.openwebui.com/license.
|
|
typer.echo(f'Open WebUI version: {VERSION}')
|
|
raise typer.Exit()
|
|
|
|
|
|
@app.command()
|
|
def main(
|
|
version: Annotated[bool | None, typer.Option('--version', callback=version_callback)] = None,
|
|
):
|
|
pass
|
|
|
|
|
|
@app.command()
|
|
def serve(
|
|
host: str = '0.0.0.0',
|
|
port: int = 8080,
|
|
):
|
|
os.environ['FROM_INIT_PY'] = 'true'
|
|
if os.getenv('WEBUI_SECRET_KEY') is None:
|
|
typer.echo('Loading WEBUI_SECRET_KEY from file, not provided as an environment variable.')
|
|
if not KEY_FILE.exists():
|
|
key_length = int(os.getenv('WEBUI_SECRET_KEY_LENGTH', DEFAULT_SECRET_KEY_LENGTH))
|
|
if key_length < 1:
|
|
raise ValueError('WEBUI_SECRET_KEY_LENGTH must be a positive integer')
|
|
typer.echo(f'Generating a new secret key and saving it to {KEY_FILE}')
|
|
KEY_FILE.write_bytes(base64.b64encode(secrets.token_bytes(key_length)))
|
|
typer.echo(f'Loading WEBUI_SECRET_KEY from {KEY_FILE}')
|
|
os.environ['WEBUI_SECRET_KEY'] = KEY_FILE.read_text()
|
|
|
|
if os.getenv('USE_CUDA_DOCKER', 'false') == 'true':
|
|
typer.echo('CUDA is enabled, appending LD_LIBRARY_PATH to include torch/cudnn & cublas libraries.')
|
|
LD_LIBRARY_PATH = os.getenv('LD_LIBRARY_PATH', '').split(':')
|
|
os.environ['LD_LIBRARY_PATH'] = ':'.join(
|
|
LD_LIBRARY_PATH
|
|
+ [
|
|
'/usr/local/lib/python3.12/site-packages/torch/lib',
|
|
'/usr/local/lib/python3.12/site-packages/nvidia/cudnn/lib',
|
|
]
|
|
)
|
|
try:
|
|
import torch
|
|
|
|
assert torch.cuda.is_available(), 'CUDA not available'
|
|
typer.echo('CUDA seems to be working')
|
|
except Exception as e:
|
|
typer.echo(
|
|
'Error when testing CUDA but USE_CUDA_DOCKER is true. '
|
|
'Resetting USE_CUDA_DOCKER to false and removing '
|
|
f'LD_LIBRARY_PATH modifications: {e}'
|
|
)
|
|
os.environ['USE_CUDA_DOCKER'] = 'false'
|
|
os.environ['LD_LIBRARY_PATH'] = ':'.join(LD_LIBRARY_PATH)
|
|
|
|
import open_webui.main # noqa: F401
|
|
from open_webui.env import UVICORN_WORKERS, UVICORN_WS_PER_MESSAGE_DEFLATE
|
|
|
|
# On Windows, uvicorn's default loop factory hardcodes ProactorEventLoop,
|
|
# which is incompatible with psycopg v3 async. Setting loop='none' lets
|
|
# asyncio.run() respect the WindowsSelectorEventLoopPolicy set in db.py.
|
|
loop = 'none' if sys.platform == 'win32' else 'auto'
|
|
|
|
uvicorn.run(
|
|
'open_webui.main:app',
|
|
host=host,
|
|
port=port,
|
|
forwarded_allow_ips=os.getenv('FORWARDED_ALLOW_IPS', '*'),
|
|
workers=UVICORN_WORKERS,
|
|
ws_per_message_deflate=UVICORN_WS_PER_MESSAGE_DEFLATE,
|
|
loop=loop,
|
|
)
|
|
|
|
|
|
@app.command()
|
|
def dev(
|
|
host: str = '0.0.0.0',
|
|
port: int = 8080,
|
|
reload: bool = True,
|
|
):
|
|
from open_webui.env import UVICORN_WS_PER_MESSAGE_DEFLATE
|
|
|
|
uvicorn.run(
|
|
'open_webui.main:app',
|
|
host=host,
|
|
port=port,
|
|
reload=reload,
|
|
forwarded_allow_ips=os.getenv('FORWARDED_ALLOW_IPS', '*'),
|
|
ws_per_message_deflate=UVICORN_WS_PER_MESSAGE_DEFLATE,
|
|
)
|
|
|
|
|
|
@mfa_app.command()
|
|
def reset(email: str, reason: Annotated[str, typer.Option('--reason')]):
|
|
"""Issue a one-time recovery ticket after the operator verifies the user's identity."""
|
|
import asyncio
|
|
|
|
if not os.getenv('WEBUI_SECRET_KEY'):
|
|
if not KEY_FILE.exists():
|
|
raise typer.BadParameter(
|
|
'Provide the existing WEBUI_SECRET_KEY or run in the directory containing .webui_secret_key.'
|
|
)
|
|
os.environ['WEBUI_SECRET_KEY'] = KEY_FILE.read_text()
|
|
from open_webui.utils.mfa import reset_mfa
|
|
|
|
try:
|
|
ticket = asyncio.run(reset_mfa(email, reason))
|
|
except Exception as error:
|
|
typer.echo(f'Reset failed: {error}', err=True)
|
|
raise typer.Exit(1) from None
|
|
typer.echo('Recovery token (expires in 30 minutes; deliver securely to the verified user):')
|
|
typer.echo(ticket)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
app()
|