revert useless dockfile change
|
|
@ -1,10 +1,7 @@
|
||||||
{
|
{
|
||||||
"permissions": {
|
"permissions": {
|
||||||
"allow": [
|
"allow": [
|
||||||
"Bash(tree:*)",
|
"Bash(tree:*)"
|
||||||
"Bash(node:*)",
|
|
||||||
"Bash(npm --version:*)",
|
|
||||||
"Bash(test:*)"
|
|
||||||
],
|
],
|
||||||
"deny": [],
|
"deny": [],
|
||||||
"ask": []
|
"ask": []
|
||||||
|
|
|
||||||
137
.github/workflows/docker-image.yml
vendored
|
|
@ -1,137 +0,0 @@
|
||||||
name: Docker Image CI
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ main ]
|
|
||||||
pull_request:
|
|
||||||
branches: [ main ]
|
|
||||||
workflow_dispatch: # 允许手动触发
|
|
||||||
|
|
||||||
env:
|
|
||||||
IMAGE_NAME: open-webui-next
|
|
||||||
OUTPUT_DIR: /tmp/docker-images
|
|
||||||
KEEP_VERSIONS: 2
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
packages: write
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: 检出代码
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: 设置环境变量
|
|
||||||
run: |
|
|
||||||
echo "IMAGE_TAG=${{ github.run_number }}" >> $GITHUB_ENV
|
|
||||||
echo "BUILD_NUMBER=${{ github.run_number }}" >> $GITHUB_ENV
|
|
||||||
echo "✓ 代码检出完成"
|
|
||||||
ls -la
|
|
||||||
|
|
||||||
- name: 验证 Dockerfile
|
|
||||||
run: |
|
|
||||||
if [ ! -f "Dockerfile" ]; then
|
|
||||||
echo "❌ 找不到 Dockerfile"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "✓ Dockerfile 存在"
|
|
||||||
head -10 Dockerfile
|
|
||||||
|
|
||||||
- name: 设置 Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
|
|
||||||
- name: 构建 Docker 镜像
|
|
||||||
uses: docker/build-push-action@v5
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
load: true
|
|
||||||
tags: |
|
|
||||||
${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }}
|
|
||||||
${{ env.IMAGE_NAME }}:latest
|
|
||||||
build-args: |
|
|
||||||
USE_SLIM=true
|
|
||||||
cache-from: type=gha
|
|
||||||
cache-to: type=gha,mode=max
|
|
||||||
|
|
||||||
- name: 验证镜像构建
|
|
||||||
run: |
|
|
||||||
echo "✓ 镜像构建完成"
|
|
||||||
docker images | grep ${{ env.IMAGE_NAME }}
|
|
||||||
|
|
||||||
- name: 创建输出目录
|
|
||||||
run: |
|
|
||||||
mkdir -p ${{ env.OUTPUT_DIR }}
|
|
||||||
echo "输出目录: ${{ env.OUTPUT_DIR }}"
|
|
||||||
|
|
||||||
- name: 导出镜像
|
|
||||||
run: |
|
|
||||||
echo "导出镜像..."
|
|
||||||
docker save ${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }} | gzip > ${{ env.OUTPUT_DIR }}/${{ env.IMAGE_NAME }}-${{ env.IMAGE_TAG }}.tar.gz
|
|
||||||
docker save ${{ env.IMAGE_NAME }}:latest | gzip > ${{ env.OUTPUT_DIR }}/${{ env.IMAGE_NAME }}-latest.tar.gz
|
|
||||||
echo "✓ 镜像导出完成"
|
|
||||||
ls -lh ${{ env.OUTPUT_DIR }}/${{ env.IMAGE_NAME }}*.tar.gz
|
|
||||||
|
|
||||||
- name: 上传镜像制品
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: docker-images-${{ env.IMAGE_TAG }}
|
|
||||||
path: |
|
|
||||||
${{ env.OUTPUT_DIR }}/${{ env.IMAGE_NAME }}-${{ env.IMAGE_TAG }}.tar.gz
|
|
||||||
${{ env.OUTPUT_DIR }}/${{ env.IMAGE_NAME }}-latest.tar.gz
|
|
||||||
retention-days: 30
|
|
||||||
|
|
||||||
- name: 清理本地资源
|
|
||||||
if: always()
|
|
||||||
run: |
|
|
||||||
echo "========================================="
|
|
||||||
echo "当前构建: ${{ env.BUILD_NUMBER }}"
|
|
||||||
VERSION_TO_DELETE=$((BUILD_NUMBER - 2))
|
|
||||||
echo "准备清理版本: ${VERSION_TO_DELETE}"
|
|
||||||
echo "========================================="
|
|
||||||
|
|
||||||
# 清理旧版本的 Docker 镜像
|
|
||||||
echo "清理 Docker 镜像..."
|
|
||||||
docker rmi ${{ env.IMAGE_NAME }}:${VERSION_TO_DELETE} 2>/dev/null || echo "镜像 ${VERSION_TO_DELETE} 不存在或已清理"
|
|
||||||
|
|
||||||
# 清理旧版本的 tar.gz 文件
|
|
||||||
echo "清理导出文件..."
|
|
||||||
rm -f ${{ env.OUTPUT_DIR }}/${{ env.IMAGE_NAME }}-${VERSION_TO_DELETE}.tar.gz
|
|
||||||
|
|
||||||
# 清理未使用的 Docker 资源
|
|
||||||
echo "清理未使用的 Docker 资源..."
|
|
||||||
docker image prune -f
|
|
||||||
|
|
||||||
echo "✓ 清理完成"
|
|
||||||
echo ""
|
|
||||||
echo "剩余镜像:"
|
|
||||||
docker images | grep ${{ env.IMAGE_NAME }} || echo "无相关镜像"
|
|
||||||
echo ""
|
|
||||||
echo "剩余文件:"
|
|
||||||
ls -lh ${{ env.OUTPUT_DIR }}/${{ env.IMAGE_NAME }}*.tar.gz 2>/dev/null || echo "无相关文件"
|
|
||||||
|
|
||||||
- name: 显示磁盘使用情况
|
|
||||||
if: always()
|
|
||||||
run: |
|
|
||||||
echo "最终磁盘使用情况:"
|
|
||||||
df -h ${{ env.OUTPUT_DIR }}
|
|
||||||
|
|
||||||
- name: 构建摘要
|
|
||||||
if: success()
|
|
||||||
run: |
|
|
||||||
echo "=========================================" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "✅ 构建成功!" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "**镜像信息:**" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "- 镜像名称: \`${{ env.IMAGE_NAME }}\`" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "- 镜像标签: \`${{ env.IMAGE_TAG }}\`, \`latest\`" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "- 构建编号: \`${{ github.run_number }}\`" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "**导出文件:**" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
|
|
||||||
ls -lh ${{ env.OUTPUT_DIR }}/${{ env.IMAGE_NAME }}*.tar.gz >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "=========================================" >> $GITHUB_STEP_SUMMARY
|
|
||||||
234
Dockerfile
|
|
@ -1,92 +1,45 @@
|
||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
# Initialize device type args
|
# Initialize device type args
|
||||||
|
# use build args in the docker build command with --build-arg="BUILDARG=true"
|
||||||
ARG USE_CUDA=false
|
ARG USE_CUDA=false
|
||||||
ARG USE_OLLAMA=false
|
ARG USE_OLLAMA=false
|
||||||
ARG USE_SLIM=false
|
ARG USE_SLIM=false
|
||||||
ARG USE_PERMISSION_HARDENING=false
|
ARG USE_PERMISSION_HARDENING=false
|
||||||
|
# Tested with cu117 for CUDA 11 and cu121 for CUDA 12 (default)
|
||||||
ARG USE_CUDA_VER=cu128
|
ARG USE_CUDA_VER=cu128
|
||||||
|
# any sentence transformer model; models to use can be found at https://huggingface.co/models?library=sentence-transformers
|
||||||
|
# Leaderboard: https://huggingface.co/spaces/mteb/leaderboard
|
||||||
|
# for better performance and multilangauge support use "intfloat/multilingual-e5-large" (~2.5GB) or "intfloat/multilingual-e5-base" (~1.5GB)
|
||||||
|
# IMPORTANT: If you change the embedding model (sentence-transformers/all-MiniLM-L6-v2) and vice versa, you aren't able to use RAG Chat with your previous documents loaded in the WebUI! You need to re-embed them.
|
||||||
ARG USE_EMBEDDING_MODEL=sentence-transformers/all-MiniLM-L6-v2
|
ARG USE_EMBEDDING_MODEL=sentence-transformers/all-MiniLM-L6-v2
|
||||||
ARG USE_RERANKING_MODEL=""
|
ARG USE_RERANKING_MODEL=""
|
||||||
|
|
||||||
|
# Tiktoken encoding name; models to use can be found at https://huggingface.co/models?library=tiktoken
|
||||||
ARG USE_TIKTOKEN_ENCODING_NAME="cl100k_base"
|
ARG USE_TIKTOKEN_ENCODING_NAME="cl100k_base"
|
||||||
|
|
||||||
ARG BUILD_HASH=dev-build
|
ARG BUILD_HASH=dev-build
|
||||||
|
# Override at your own risk - non-root configurations are untested
|
||||||
ARG UID=0
|
ARG UID=0
|
||||||
ARG GID=0
|
ARG GID=0
|
||||||
|
|
||||||
######## WebUI frontend ########
|
######## WebUI frontend ########
|
||||||
FROM --platform=$BUILDPLATFORM node:20-alpine3.20 AS build
|
FROM --platform=$BUILDPLATFORM node:22-alpine3.20 AS build
|
||||||
ARG BUILD_HASH
|
ARG BUILD_HASH
|
||||||
|
|
||||||
# ========== 配置 Alpine 镜像源 ==========
|
# Set Node.js options (heap limit Allocation failed - JavaScript heap out of memory)
|
||||||
RUN echo "https://mirrors.aliyun.com/alpine/v3.20/main" > /etc/apk/repositories && \
|
# ENV NODE_OPTIONS="--max-old-space-size=4096"
|
||||||
echo "https://mirrors.aliyun.com/alpine/v3.20/community" >> /etc/apk/repositories && \
|
|
||||||
apk update
|
|
||||||
|
|
||||||
# ========== 增加 Node.js 堆内存限制 ==========
|
|
||||||
ENV NODE_OPTIONS="--max-old-space-size=4096"
|
|
||||||
|
|
||||||
# ========== 配置 npm 镜像源 ==========
|
|
||||||
RUN npm config set registry https://registry.npmmirror.com && \
|
|
||||||
npm config set fetch-timeout 120000 && \
|
|
||||||
npm config set fetch-retries 5 && \
|
|
||||||
npm config set fetch-retry-mintimeout 20000 && \
|
|
||||||
npm config set fetch-retry-maxtimeout 120000 && \
|
|
||||||
npm config set maxsockets 5
|
|
||||||
|
|
||||||
# ========== 配置二进制包镜像 ==========
|
|
||||||
ENV ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ \
|
|
||||||
SASS_BINARY_SITE=https://npmmirror.com/mirrors/node-sass/ \
|
|
||||||
PHANTOMJS_CDNURL=https://nppmirror.com/mirrors/phantomjs/ \
|
|
||||||
CHROMEDRIVER_CDNURL=https://npmmirror.com/mirrors/chromedriver/ \
|
|
||||||
OPERADRIVER_CDNURL=https://npmmirror.com/mirrors/operadriver/ \
|
|
||||||
PYTHON_MIRROR=https://npmmirror.com/mirrors/python/
|
|
||||||
|
|
||||||
# ========== 配置代理(可选)==========
|
|
||||||
ARG HTTP_PROXY
|
|
||||||
ARG HTTPS_PROXY
|
|
||||||
ENV HTTP_PROXY=${HTTP_PROXY}
|
|
||||||
ENV HTTPS_PROXY=${HTTPS_PROXY}
|
|
||||||
ENV NO_PROXY=localhost,127.0.0.1,mirrors.aliyun.com,registry.nppmirror.com,nppmirror.com
|
|
||||||
|
|
||||||
# ========== 安装必要工具 ==========
|
|
||||||
RUN apk add --no-cache git python3 make g++ && \
|
|
||||||
if [ -n "$HTTP_PROXY" ]; then \
|
|
||||||
git config --global http.proxy ${HTTP_PROXY} && \
|
|
||||||
git config --global https.proxy ${HTTPS_PROXY} && \
|
|
||||||
git config --global http.sslVerify false; \
|
|
||||||
fi
|
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# ========== 安装依赖(不使用 --ignore-scripts)==========
|
# to store git revision in build
|
||||||
|
RUN apk add --no-cache git
|
||||||
|
|
||||||
COPY package.json package-lock.json ./
|
COPY package.json package-lock.json ./
|
||||||
|
RUN npm ci --force
|
||||||
|
|
||||||
RUN echo "==================================" && \
|
|
||||||
echo "Starting npm install" && \
|
|
||||||
echo "Time: $(date)" && \
|
|
||||||
echo "==================================" && \
|
|
||||||
npm cache clean --force && \
|
|
||||||
npm install --legacy-peer-deps --no-audit --no-fund || \
|
|
||||||
(echo "First attempt failed, retrying..." && \
|
|
||||||
rm -rf node_modules package-lock.json && \
|
|
||||||
npm install --legacy-peer-deps --no-audit --no-fund) && \
|
|
||||||
echo "==================================" && \
|
|
||||||
echo "npm install completed" && \
|
|
||||||
echo "Time: $(date)" && \
|
|
||||||
echo "=================================="
|
|
||||||
|
|
||||||
# ========== 构建前端 ==========
|
|
||||||
COPY . .
|
COPY . .
|
||||||
ENV APP_BUILD_HASH=${BUILD_HASH}
|
ENV APP_BUILD_HASH=${BUILD_HASH}
|
||||||
|
RUN npm run build
|
||||||
RUN echo "==================================" && \
|
|
||||||
echo "Starting frontend build" && \
|
|
||||||
echo "Time: $(date)" && \
|
|
||||||
echo "==================================" && \
|
|
||||||
npm run build && \
|
|
||||||
echo "==================================" && \
|
|
||||||
echo "Build completed" && \
|
|
||||||
echo "Time: $(date)" && \
|
|
||||||
echo "=================================="
|
|
||||||
|
|
||||||
######## WebUI backend ########
|
######## WebUI backend ########
|
||||||
FROM python:3.11-slim-bookworm AS base
|
FROM python:3.11-slim-bookworm AS base
|
||||||
|
|
@ -105,6 +58,7 @@ ARG GID
|
||||||
## Basis ##
|
## Basis ##
|
||||||
ENV ENV=prod \
|
ENV ENV=prod \
|
||||||
PORT=8080 \
|
PORT=8080 \
|
||||||
|
# pass build args to the build
|
||||||
USE_OLLAMA_DOCKER=${USE_OLLAMA} \
|
USE_OLLAMA_DOCKER=${USE_OLLAMA} \
|
||||||
USE_CUDA_DOCKER=${USE_CUDA} \
|
USE_CUDA_DOCKER=${USE_CUDA} \
|
||||||
USE_SLIM_DOCKER=${USE_SLIM} \
|
USE_SLIM_DOCKER=${USE_SLIM} \
|
||||||
|
|
@ -124,23 +78,31 @@ ENV OPENAI_API_KEY="" \
|
||||||
ANONYMIZED_TELEMETRY=false
|
ANONYMIZED_TELEMETRY=false
|
||||||
|
|
||||||
#### Other models #########################################################
|
#### Other models #########################################################
|
||||||
|
## whisper TTS model settings ##
|
||||||
ENV WHISPER_MODEL="base" \
|
ENV WHISPER_MODEL="base" \
|
||||||
WHISPER_MODEL_DIR="/app/backend/data/cache/whisper/models" \
|
WHISPER_MODEL_DIR="/app/backend/data/cache/whisper/models"
|
||||||
RAG_EMBEDDING_MODEL="$USE_EMBEDDING_MODEL_DOCKER" \
|
|
||||||
RAG_RERANKING_MODEL="$USE_RERANKING_MODEL_DOCKER" \
|
|
||||||
SENTENCE_TRANSFORMERS_HOME="/app/backend/data/cache/embedding/models" \
|
|
||||||
TIKTOKEN_ENCODING_NAME="cl100k_base" \
|
|
||||||
TIKTOKEN_CACHE_DIR="/app/backend/data/cache/tiktoken" \
|
|
||||||
HF_HOME="/app/backend/data/cache/embedding/models"
|
|
||||||
|
|
||||||
# ========== 配置 Hugging Face 镜像 ==========
|
## RAG Embedding model settings ##
|
||||||
ENV HF_ENDPOINT=https://hf-mirror.com
|
ENV RAG_EMBEDDING_MODEL="$USE_EMBEDDING_MODEL_DOCKER" \
|
||||||
|
RAG_RERANKING_MODEL="$USE_RERANKING_MODEL_DOCKER" \
|
||||||
|
SENTENCE_TRANSFORMERS_HOME="/app/backend/data/cache/embedding/models"
|
||||||
|
|
||||||
|
## Tiktoken model settings ##
|
||||||
|
ENV TIKTOKEN_ENCODING_NAME="cl100k_base" \
|
||||||
|
TIKTOKEN_CACHE_DIR="/app/backend/data/cache/tiktoken"
|
||||||
|
|
||||||
|
## Hugging Face download cache ##
|
||||||
|
ENV HF_HOME="/app/backend/data/cache/embedding/models"
|
||||||
|
|
||||||
|
## Torch Extensions ##
|
||||||
|
# ENV TORCH_EXTENSIONS_DIR="/.cache/torch_extensions"
|
||||||
|
|
||||||
|
#### Other models ##########################################################
|
||||||
|
|
||||||
WORKDIR /app/backend
|
WORKDIR /app/backend
|
||||||
|
|
||||||
ENV HOME=/root
|
ENV HOME=/root
|
||||||
|
# Create user and group if not root
|
||||||
# ========== 创建用户和组 ==========
|
|
||||||
RUN if [ $UID -ne 0 ]; then \
|
RUN if [ $UID -ne 0 ]; then \
|
||||||
if [ $GID -ne 0 ]; then \
|
if [ $GID -ne 0 ]; then \
|
||||||
addgroup --gid $GID app; \
|
addgroup --gid $GID app; \
|
||||||
|
|
@ -148,15 +110,13 @@ RUN if [ $UID -ne 0 ]; then \
|
||||||
adduser --uid $UID --gid $GID --home $HOME --disabled-password --no-create-home app; \
|
adduser --uid $UID --gid $GID --home $HOME --disabled-password --no-create-home app; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
RUN mkdir -p $HOME/.cache/chroma && \
|
RUN mkdir -p $HOME/.cache/chroma
|
||||||
echo -n 00000000-0000-0000-0000-000000000000 > $HOME/.cache/chroma/telemetry_user_id && \
|
RUN echo -n 00000000-0000-0000-0000-000000000000 > $HOME/.cache/chroma/telemetry_user_id
|
||||||
chown -R $UID:$GID /app $HOME
|
|
||||||
|
|
||||||
# ========== 配置 Debian 镜像源 ==========
|
# Make sure the user has access to the app and root directory
|
||||||
RUN sed -i 's@deb.debian.org@mirrors.aliyun.com@g' /etc/apt/sources.list.d/debian.sources && \
|
RUN chown -R $UID:$GID /app $HOME
|
||||||
sed -i 's@security.debian.org@mirrors.aliyun.com@g' /etc/apt/sources.list.d/debian.sources
|
|
||||||
|
|
||||||
# ========== 安装系统依赖 ==========
|
# Install common system dependencies
|
||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
apt-get install -y --no-install-recommends \
|
apt-get install -y --no-install-recommends \
|
||||||
git build-essential pandoc gcc netcat-openbsd curl jq \
|
git build-essential pandoc gcc netcat-openbsd curl jq \
|
||||||
|
|
@ -164,100 +124,56 @@ RUN apt-get update && \
|
||||||
ffmpeg libsm6 libxext6 \
|
ffmpeg libsm6 libxext6 \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# ========== 配置 pip 镜像源 ==========
|
# install python dependencies
|
||||||
RUN pip3 config set global.index-url https://mirrors.aliyun.com/pypi/simple/ && \
|
|
||||||
pip3 config set install.trusted-host mirrors.aliyun.com && \
|
|
||||||
pip3 config set global.timeout 600
|
|
||||||
|
|
||||||
# ========== 配置 uv 使用镜像源 ==========
|
|
||||||
ENV UV_INDEX_URL=https://mirrors.aliyun.com/pypi/simple/ \
|
|
||||||
UV_EXTRA_INDEX_URL="" \
|
|
||||||
UV_NO_CACHE=0
|
|
||||||
|
|
||||||
# ========== 安装 Python 依赖 ==========
|
|
||||||
COPY --chown=$UID:$GID ./backend/requirements.txt ./requirements.txt
|
COPY --chown=$UID:$GID ./backend/requirements.txt ./requirements.txt
|
||||||
|
|
||||||
RUN echo "==================================" && \
|
RUN pip3 install --no-cache-dir uv && \
|
||||||
echo "Installing Python dependencies" && \
|
|
||||||
echo "Time: $(date)" && \
|
|
||||||
echo "==================================" && \
|
|
||||||
pip3 install uv && \
|
|
||||||
if [ "$USE_CUDA" = "true" ]; then \
|
if [ "$USE_CUDA" = "true" ]; then \
|
||||||
echo "Installing PyTorch with CUDA support..." && \
|
# If you use CUDA the whisper and embedding model will be downloaded on first use
|
||||||
pip3 install torch torchvision torchaudio \
|
pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/$USE_CUDA_DOCKER_VER --no-cache-dir && \
|
||||||
--index-url https://mirrors.aliyun.com/pypi/simple/ \
|
uv pip install --system -r requirements.txt --no-cache-dir && \
|
||||||
--trusted-host mirrors.aliyun.com || \
|
python -c "import os; from sentence_transformers import SentenceTransformer; SentenceTransformer(os.environ['RAG_EMBEDDING_MODEL'], device='cpu')" && \
|
||||||
(echo "Aliyun failed, trying Tsinghua mirror..." && \
|
python -c "import os; from faster_whisper import WhisperModel; WhisperModel(os.environ['WHISPER_MODEL'], device='cpu', compute_type='int8', download_root=os.environ['WHISPER_MODEL_DIR'])"; \
|
||||||
pip3 install torch torchvision torchaudio \
|
python -c "import os; import tiktoken; tiktoken.get_encoding(os.environ['TIKTOKEN_ENCODING_NAME'])"; \
|
||||||
--index-url https://pypi.tuna.tsinghua.edu.cn/simple/ \
|
|
||||||
--trusted-host pypi.tuna.tsinghua.edu.cn) || \
|
|
||||||
(echo "Mirrors failed, trying official PyTorch repo..." && \
|
|
||||||
pip3 install torch torchvision torchaudio \
|
|
||||||
--index-url https://download.pytorch.org/whl/$USE_CUDA_DOCKER_VER) && \
|
|
||||||
echo "Installing other requirements with uv..." && \
|
|
||||||
uv pip install --system -r requirements.txt \
|
|
||||||
--index-url https://mirrors.aliyun.com/pypi/simple/ && \
|
|
||||||
if [ "$USE_SLIM" != "true" ]; then \
|
|
||||||
echo "Downloading models..." && \
|
|
||||||
python -c "import os; from sentence_transformers import SentenceTransformer; SentenceTransformer(os.environ['RAG_EMBEDDING_MODEL'], device='cpu')" && \
|
|
||||||
python -c "import os; from faster_whisper import WhisperModel; WhisperModel(os.environ['WHISPER_MODEL'], device='cpu', compute_type='int8', download_root=os.environ['WHISPER_MODEL_DIR'])" && \
|
|
||||||
python -c "import os; import tiktoken; tiktoken.get_encoding(os.environ['TIKTOKEN_ENCODING_NAME'])"; \
|
|
||||||
fi; \
|
|
||||||
else \
|
else \
|
||||||
echo "Installing PyTorch CPU version..." && \
|
pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu --no-cache-dir && \
|
||||||
pip3 install torch torchvision torchaudio \
|
uv pip install --system -r requirements.txt --no-cache-dir && \
|
||||||
--index-url https://mirrors.aliyun.com/pypi/simple/ \
|
if [ "$USE_SLIM" != "true" ]; then \
|
||||||
--trusted-host mirrors.aliyun.com || \
|
python -c "import os; from sentence_transformers import SentenceTransformer; SentenceTransformer(os.environ['RAG_EMBEDDING_MODEL'], device='cpu')" && \
|
||||||
(echo "Aliyun failed, trying Tsinghua mirror..." && \
|
python -c "import os; from faster_whisper import WhisperModel; WhisperModel(os.environ['WHISPER_MODEL'], device='cpu', compute_type='int8', download_root=os.environ['WHISPER_MODEL_DIR'])"; \
|
||||||
pip3 install torch torchvision torchaudio \
|
python -c "import os; import tiktoken; tiktoken.get_encoding(os.environ['TIKTOKEN_ENCODING_NAME'])"; \
|
||||||
--index-url https://pypi.tuna.tsinghua.edu.cn/simple/ \
|
fi; \
|
||||||
--trusted-host pypi.tuna.tsinghua.edu.cn) || \
|
fi; \
|
||||||
(echo "Tsinghua failed, trying USTC mirror..." && \
|
|
||||||
pip3 install torch torchvision torchaudio \
|
|
||||||
--index-url https://mirrors.ustc.edu.cn/pypi/web/simple/ \
|
|
||||||
--trusted-host mirrors.ustc.edu.cn) || \
|
|
||||||
(echo "All mirrors failed, trying official PyTorch CPU repo..." && \
|
|
||||||
pip3 install torch torchvision torchaudio \
|
|
||||||
--index-url https://download.pytorch.org/whl/cpu) && \
|
|
||||||
echo "Installing other requirements with uv..." && \
|
|
||||||
uv pip install --system -r requirements.txt \
|
|
||||||
--index-url https://mirrors.aliyun.com/pypi/simple/ && \
|
|
||||||
if [ "$USE_SLIM" != "true" ]; then \
|
|
||||||
echo "Downloading models..." && \
|
|
||||||
python -c "import os; from sentence_transformers import SentenceTransformer; SentenceTransformer(os.environ['RAG_EMBEDDING_MODEL'], device='cpu')" && \
|
|
||||||
python -c "import os; from faster_whisper import WhisperModel; WhisperModel(os.environ['WHISPER_MODEL'], device='cpu', compute_type='int8', download_root=os.environ['WHISPER_MODEL_DIR'])" && \
|
|
||||||
python -c "import os; import tiktoken; tiktoken.get_encoding(os.environ['TIKTOKEN_ENCODING_NAME'])"; \
|
|
||||||
fi; \
|
|
||||||
fi && \
|
|
||||||
mkdir -p /app/backend/data && chown -R $UID:$GID /app/backend/data/ && \
|
mkdir -p /app/backend/data && chown -R $UID:$GID /app/backend/data/ && \
|
||||||
echo "==================================" && \
|
rm -rf /var/lib/apt/lists/*;
|
||||||
echo "Python dependencies installed" && \
|
|
||||||
echo "Time: $(date)" && \
|
|
||||||
echo "=================================="
|
|
||||||
|
|
||||||
# ========== 安装 Ollama ==========
|
# Install Ollama if requested
|
||||||
RUN if [ "$USE_OLLAMA" = "true" ]; then \
|
RUN if [ "$USE_OLLAMA" = "true" ]; then \
|
||||||
date +%s > /tmp/ollama_build_hash && \
|
date +%s > /tmp/ollama_build_hash && \
|
||||||
echo "Installing Ollama..." && \
|
echo "Cache broken at timestamp: `cat /tmp/ollama_build_hash`" && \
|
||||||
export HF_ENDPOINT=https://hf-mirror.com && \
|
curl -fsSL https://ollama.com/install.sh | sh && \
|
||||||
curl -fsSL https://ollama.com/install.sh | sh || \
|
rm -rf /var/lib/apt/lists/*; \
|
||||||
(echo "Ollama installation failed, trying with proxy..." && \
|
|
||||||
export http_proxy=http://host.docker.internal:7897 && \
|
|
||||||
export https_proxy=http://host.docker.internal:7897 && \
|
|
||||||
curl -fsSL https://ollama.com/install.sh | sh); \
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ========== 复制构建文件 ==========
|
# copy embedding weight from build
|
||||||
|
# RUN mkdir -p /root/.cache/chroma/onnx_models/all-MiniLM-L6-v2
|
||||||
|
# COPY --from=build /app/onnx /root/.cache/chroma/onnx_models/all-MiniLM-L6-v2/onnx
|
||||||
|
|
||||||
|
# copy built frontend files
|
||||||
COPY --chown=$UID:$GID --from=build /app/build /app/build
|
COPY --chown=$UID:$GID --from=build /app/build /app/build
|
||||||
COPY --chown=$UID:$GID --from=build /app/CHANGELOG.md /app/CHANGELOG.md
|
COPY --chown=$UID:$GID --from=build /app/CHANGELOG.md /app/CHANGELOG.md
|
||||||
COPY --chown=$UID:$GID --from=build /app/package.json /app/package.json
|
COPY --chown=$UID:$GID --from=build /app/package.json /app/package.json
|
||||||
|
|
||||||
|
# copy backend files
|
||||||
COPY --chown=$UID:$GID ./backend .
|
COPY --chown=$UID:$GID ./backend .
|
||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|
||||||
HEALTHCHECK CMD curl --silent --fail http://localhost:${PORT:-8080}/health | jq -ne 'input.status == true' || exit 1
|
HEALTHCHECK CMD curl --silent --fail http://localhost:${PORT:-8080}/health | jq -ne 'input.status == true' || exit 1
|
||||||
|
|
||||||
# ========== 权限加固 ==========
|
# Minimal, atomic permission hardening for OpenShift (arbitrary UID):
|
||||||
|
# - Group 0 owns /app and /root
|
||||||
|
# - Directories are group-writable and have SGID so new files inherit GID 0
|
||||||
RUN if [ "$USE_PERMISSION_HARDENING" = "true" ]; then \
|
RUN if [ "$USE_PERMISSION_HARDENING" = "true" ]; then \
|
||||||
set -eux; \
|
set -eux; \
|
||||||
chgrp -R 0 /app /root || true; \
|
chgrp -R 0 /app /root || true; \
|
||||||
|
|
@ -272,4 +188,4 @@ ARG BUILD_HASH
|
||||||
ENV WEBUI_BUILD_VERSION=${BUILD_HASH}
|
ENV WEBUI_BUILD_VERSION=${BUILD_HASH}
|
||||||
ENV DOCKER=true
|
ENV DOCKER=true
|
||||||
|
|
||||||
CMD [ "bash", "start.sh"]
|
CMD [ "bash", "start.sh"]
|
||||||
|
|
|
||||||
168
Jenkinsfile
vendored
|
|
@ -1,168 +0,0 @@
|
||||||
pipeline {
|
|
||||||
agent any
|
|
||||||
|
|
||||||
environment {
|
|
||||||
REPO_URL = 'git@github.com:ai-friend-coming/open-webui-next.git'
|
|
||||||
IMAGE_NAME = 'open-webui-custom'
|
|
||||||
IMAGE_TAG = "${BUILD_NUMBER}"
|
|
||||||
OUTPUT_DIR = '/var/docker-images'
|
|
||||||
DOCKER_FILE_PATH = 'Dockerfile'
|
|
||||||
}
|
|
||||||
|
|
||||||
options {
|
|
||||||
buildDiscarder(logRotator(numToKeepStr: '10'))
|
|
||||||
timeout(time: 1, unit: 'HOURS')
|
|
||||||
timestamps()
|
|
||||||
}
|
|
||||||
|
|
||||||
stages {
|
|
||||||
stage('准备工作') {
|
|
||||||
steps {
|
|
||||||
script {
|
|
||||||
echo "========================================="
|
|
||||||
echo "开始构建 Build #${BUILD_NUMBER}"
|
|
||||||
echo "仓库: ${REPO_URL}"
|
|
||||||
echo "镜像: ${IMAGE_NAME}:${IMAGE_TAG}"
|
|
||||||
echo "========================================="
|
|
||||||
|
|
||||||
// 检查Docker是否可用
|
|
||||||
sh 'docker --version'
|
|
||||||
sh 'docker info'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stage('检出代码') {
|
|
||||||
steps {
|
|
||||||
script {
|
|
||||||
echo "从 ${REPO_URL} 检出代码..."
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用更简单的checkout方式
|
|
||||||
checkout([
|
|
||||||
$class: 'GitSCM',
|
|
||||||
branches: [[name: '*/main']],
|
|
||||||
userRemoteConfigs: [[
|
|
||||||
url: "${REPO_URL}",
|
|
||||||
credentialsId: 'github-ssh' // 改成你实际创建的凭据ID
|
|
||||||
]]
|
|
||||||
])
|
|
||||||
|
|
||||||
script {
|
|
||||||
echo "代码检出完成"
|
|
||||||
sh 'ls -la'
|
|
||||||
sh 'git log --oneline -1 || echo "无法获取git日志"'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stage('验证 Dockerfile') {
|
|
||||||
steps {
|
|
||||||
script {
|
|
||||||
echo "检查 Dockerfile..."
|
|
||||||
sh """
|
|
||||||
if [ ! -f "${DOCKER_FILE_PATH}" ]; then
|
|
||||||
echo "错误: 找不到 Dockerfile: ${DOCKER_FILE_PATH}"
|
|
||||||
echo "当前目录内容:"
|
|
||||||
ls -la
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "Dockerfile 存在"
|
|
||||||
echo "--- Dockerfile 内容 (前20行) ---"
|
|
||||||
head -20 "${DOCKER_FILE_PATH}"
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stage('创建输出目录') {
|
|
||||||
steps {
|
|
||||||
script {
|
|
||||||
echo "创建输出目录: ${OUTPUT_DIR}"
|
|
||||||
sh """
|
|
||||||
sudo mkdir -p ${OUTPUT_DIR}
|
|
||||||
sudo chmod 777 ${OUTPUT_DIR}
|
|
||||||
ls -ld ${OUTPUT_DIR}
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stage('构建 Docker 镜像') {
|
|
||||||
steps {
|
|
||||||
script {
|
|
||||||
echo "开始构建镜像: ${IMAGE_NAME}:${IMAGE_TAG}"
|
|
||||||
sh """
|
|
||||||
docker build \
|
|
||||||
-t ${IMAGE_NAME}:${IMAGE_TAG} \
|
|
||||||
-t ${IMAGE_NAME}:latest \
|
|
||||||
-f ${DOCKER_FILE_PATH} \
|
|
||||||
.
|
|
||||||
|
|
||||||
echo "镜像构建完成"
|
|
||||||
docker images | grep ${IMAGE_NAME} || echo "未找到镜像"
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stage('导出镜像') {
|
|
||||||
steps {
|
|
||||||
script {
|
|
||||||
echo "导出镜像到 ${OUTPUT_DIR}"
|
|
||||||
sh """
|
|
||||||
echo "导出 ${IMAGE_NAME}:${IMAGE_TAG}..."
|
|
||||||
docker save ${IMAGE_NAME}:${IMAGE_TAG} | gzip > ${OUTPUT_DIR}/${IMAGE_NAME}-${IMAGE_TAG}.tar.gz
|
|
||||||
|
|
||||||
echo "导出 ${IMAGE_NAME}:latest..."
|
|
||||||
docker save ${IMAGE_NAME}:latest | gzip > ${OUTPUT_DIR}/${IMAGE_NAME}-latest.tar.gz
|
|
||||||
|
|
||||||
echo "导出完成"
|
|
||||||
ls -lh ${OUTPUT_DIR}/${IMAGE_NAME}*.tar.gz
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stage('清理旧镜像') {
|
|
||||||
steps {
|
|
||||||
script {
|
|
||||||
echo "清理本地镜像..."
|
|
||||||
sh """
|
|
||||||
docker rmi ${IMAGE_NAME}:${IMAGE_TAG} 2>/dev/null || echo "镜像已删除或不存在"
|
|
||||||
|
|
||||||
# 保留latest标签,方便下次使用
|
|
||||||
# docker rmi ${IMAGE_NAME}:latest 2>/dev/null || true
|
|
||||||
|
|
||||||
# 清理悬空镜像
|
|
||||||
docker image prune -f --filter "dangling=true" || true
|
|
||||||
|
|
||||||
echo "清理完成"
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
post {
|
|
||||||
success {
|
|
||||||
script {
|
|
||||||
echo "========================================="
|
|
||||||
echo "✅ 构建成功!"
|
|
||||||
echo "镜像文件位置: ${OUTPUT_DIR}"
|
|
||||||
sh "ls -lh ${OUTPUT_DIR}/${IMAGE_NAME}*.tar.gz || true"
|
|
||||||
echo "========================================="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
failure {
|
|
||||||
echo "❌ 构建失败,请检查上方日志"
|
|
||||||
}
|
|
||||||
always {
|
|
||||||
script {
|
|
||||||
echo "流水线执行完成"
|
|
||||||
// 可选:清理工作空间(注释掉以便调试)
|
|
||||||
// cleanWs()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
291
LOCAL_SETUP.md
|
|
@ -1,291 +0,0 @@
|
||||||
# Open WebUI 本地开发环境搭建指南
|
|
||||||
|
|
||||||
本指南提供 Open WebUI 项目的本地开发环境配置和运行步骤。
|
|
||||||
|
|
||||||
## 系统要求
|
|
||||||
|
|
||||||
- **Node.js**: v20.19.5 或更高版本
|
|
||||||
- **Python**: 3.12.x (推荐使用 pyenv 管理)
|
|
||||||
- **npm**: 10.8.2 或更高版本
|
|
||||||
- **操作系统**: macOS / Linux / Windows
|
|
||||||
|
|
||||||
## 技术栈
|
|
||||||
|
|
||||||
### 前端
|
|
||||||
- **框架**: SvelteKit 4 + TypeScript
|
|
||||||
- **构建工具**: Vite 5
|
|
||||||
- **样式**: Tailwind CSS 4
|
|
||||||
|
|
||||||
### 后端
|
|
||||||
- **语言**: Python 3.12
|
|
||||||
- **框架**: FastAPI
|
|
||||||
- **数据库**: SQLite (开发环境) / PostgreSQL (生产环境)
|
|
||||||
- **ORM**: SQLAlchemy + Peewee
|
|
||||||
|
|
||||||
## 环境准备
|
|
||||||
|
|
||||||
### 1. 安装 pyenv (如果系统 Python 版本不是 3.12)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# macOS (使用 Homebrew)
|
|
||||||
brew install pyenv
|
|
||||||
|
|
||||||
# 配置 shell 环境变量 (添加到 ~/.zshrc 或 ~/.bash_profile)
|
|
||||||
export PATH="$HOME/.pyenv/bin:$PATH"
|
|
||||||
eval "$(pyenv init -)"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 安装 Python 3.12
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 使用 pyenv 安装 Python 3.12
|
|
||||||
pyenv install 3.12
|
|
||||||
|
|
||||||
# 验证安装
|
|
||||||
pyenv versions
|
|
||||||
```
|
|
||||||
|
|
||||||
## 安装依赖
|
|
||||||
|
|
||||||
### 前端依赖
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 在项目根目录执行
|
|
||||||
npm install --legacy-peer-deps
|
|
||||||
```
|
|
||||||
|
|
||||||
**注意**: 需要使用 `--legacy-peer-deps` 标志来解决 @tiptap 包的版本冲突问题。
|
|
||||||
|
|
||||||
### 后端依赖
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 进入后端目录
|
|
||||||
cd backend
|
|
||||||
|
|
||||||
# 创建 Python 3.12 虚拟环境
|
|
||||||
/Users/你的用户名/.pyenv/versions/3.12.12/bin/python3 -m venv venv
|
|
||||||
|
|
||||||
# 或者,如果系统 Python 已是 3.12
|
|
||||||
python3 -m venv venv
|
|
||||||
|
|
||||||
# 激活虚拟环境
|
|
||||||
source venv/bin/activate # macOS/Linux
|
|
||||||
# 或
|
|
||||||
venv\Scripts\activate # Windows
|
|
||||||
|
|
||||||
# 升级 pip
|
|
||||||
pip install --upgrade pip
|
|
||||||
|
|
||||||
# 安装依赖
|
|
||||||
pip install -r requirements.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
## 运行开发服务器
|
|
||||||
|
|
||||||
### 启动后端服务 (端口 8080)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 在 backend 目录下,激活虚拟环境后
|
|
||||||
cd backend
|
|
||||||
source venv/bin/activate
|
|
||||||
python -m uvicorn open_webui.main:app --reload --port 8080 --host 0.0.0.0
|
|
||||||
```
|
|
||||||
|
|
||||||
后端服务将运行在: **http://localhost:8080**
|
|
||||||
|
|
||||||
后端启动时会自动:
|
|
||||||
- 运行数据库迁移 (Alembic)
|
|
||||||
- 初始化 SQLite 数据库
|
|
||||||
- 配置向量数据库 (ChromaDB)
|
|
||||||
|
|
||||||
### 启动前端服务 (端口 5050)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 在项目根目录
|
|
||||||
npm run dev:5050
|
|
||||||
```
|
|
||||||
|
|
||||||
前端服务将运行在: **http://localhost:5050**
|
|
||||||
|
|
||||||
首次启动时会自动:
|
|
||||||
- 下载 Pyodide 包 (浏览器内 Python 运行时)
|
|
||||||
- 预加载常用 Python 包 (numpy, pandas, matplotlib 等)
|
|
||||||
|
|
||||||
## 访问应用
|
|
||||||
|
|
||||||
打开浏览器访问: **http://localhost:5050**
|
|
||||||
|
|
||||||
前端会通过 Vite 代理将 API 请求转发到后端 (8080 端口)。
|
|
||||||
|
|
||||||
## 开发工作流
|
|
||||||
|
|
||||||
### 目录结构
|
|
||||||
|
|
||||||
```
|
|
||||||
open-webui-next/
|
|
||||||
├── src/ # 前端源码
|
|
||||||
│ ├── routes/ # SvelteKit 路由
|
|
||||||
│ ├── lib/
|
|
||||||
│ │ ├── apis/ # API 客户端
|
|
||||||
│ │ ├── components/ # Svelte 组件
|
|
||||||
│ │ ├── stores/ # 全局状态管理
|
|
||||||
│ │ └── i18n/ # 国际化
|
|
||||||
├── backend/ # 后端源码
|
|
||||||
│ ├── open_webui/
|
|
||||||
│ │ ├── main.py # FastAPI 入口
|
|
||||||
│ │ ├── routers/ # API 路由
|
|
||||||
│ │ ├── models/ # 数据模型
|
|
||||||
│ │ ├── utils/ # 工具函数
|
|
||||||
│ │ └── migrations/ # 数据库迁移
|
|
||||||
│ ├── requirements.txt
|
|
||||||
│ └── venv/ # Python 虚拟环境
|
|
||||||
└── package.json
|
|
||||||
```
|
|
||||||
|
|
||||||
### 常用开发命令
|
|
||||||
|
|
||||||
#### 前端
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run dev # 启动开发服务器 (默认端口 5173)
|
|
||||||
npm run dev:5050 # 启动开发服务器 (端口 5050)
|
|
||||||
npm run build # 构建生产版本
|
|
||||||
npm run lint # 代码检查
|
|
||||||
npm run format # 代码格式化
|
|
||||||
npm run test:frontend # 运行单元测试
|
|
||||||
npm run i18n:parse # 解析并更新翻译文件
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 后端
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 在 backend 目录下,激活虚拟环境后
|
|
||||||
|
|
||||||
# 启动开发服务器 (自动重载)
|
|
||||||
python -m uvicorn open_webui.main:app --reload --port 8080
|
|
||||||
|
|
||||||
# 代码格式化
|
|
||||||
black .
|
|
||||||
|
|
||||||
# 数据库迁移
|
|
||||||
cd backend
|
|
||||||
alembic revision --autogenerate -m "描述" # 生成迁移脚本
|
|
||||||
alembic upgrade head # 执行迁移
|
|
||||||
```
|
|
||||||
|
|
||||||
### 热重载
|
|
||||||
|
|
||||||
- **前端**: Vite 自动检测文件变化并热重载
|
|
||||||
- **后端**: uvicorn 的 `--reload` 参数自动检测 Python 代码变化并重启
|
|
||||||
|
|
||||||
## 常见问题
|
|
||||||
|
|
||||||
### 1. npm install 失败
|
|
||||||
|
|
||||||
**问题**: 依赖版本冲突
|
|
||||||
|
|
||||||
**解决方案**:
|
|
||||||
```bash
|
|
||||||
npm install --legacy-peer-deps
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 后端 Python 版本不兼容
|
|
||||||
|
|
||||||
**问题**: `unstructured` 包不支持 Python 3.13+
|
|
||||||
|
|
||||||
**解决方案**: 使用 Python 3.12:
|
|
||||||
```bash
|
|
||||||
pyenv install 3.12
|
|
||||||
cd backend
|
|
||||||
rm -rf venv
|
|
||||||
/Users/你的用户名/.pyenv/versions/3.12.12/bin/python3 -m venv venv
|
|
||||||
source venv/bin/activate
|
|
||||||
pip install -r requirements.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 端口被占用
|
|
||||||
|
|
||||||
**问题**: 8080 或 5050 端口已被使用
|
|
||||||
|
|
||||||
**解决方案**:
|
|
||||||
```bash
|
|
||||||
# 查找占用端口的进程
|
|
||||||
lsof -i :8080
|
|
||||||
lsof -i :5050
|
|
||||||
|
|
||||||
# 终止进程
|
|
||||||
kill -9 <PID>
|
|
||||||
|
|
||||||
# 或者使用不同端口
|
|
||||||
# 前端:
|
|
||||||
npm run dev -- --port 3000
|
|
||||||
|
|
||||||
# 后端:
|
|
||||||
python -m uvicorn open_webui.main:app --reload --port 8000
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 前端 Pyodide 下载慢
|
|
||||||
|
|
||||||
**问题**: 首次启动下载 Pyodide 包较慢
|
|
||||||
|
|
||||||
**解决方案**: 耐心等待,包会缓存在 `node_modules` 中,后续启动会很快。
|
|
||||||
|
|
||||||
### 5. 数据库迁移错误
|
|
||||||
|
|
||||||
**问题**: Alembic 迁移失败
|
|
||||||
|
|
||||||
**解决方案**:
|
|
||||||
```bash
|
|
||||||
# 删除数据库重新初始化 (仅开发环境)
|
|
||||||
rm backend/data/webui.db
|
|
||||||
python -m uvicorn open_webui.main:app --reload --port 8080
|
|
||||||
```
|
|
||||||
|
|
||||||
## 环境变量配置
|
|
||||||
|
|
||||||
后端可通过环境变量配置,创建 `backend/.env` 文件:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 数据库
|
|
||||||
DATABASE_URL=sqlite:///data/webui.db
|
|
||||||
|
|
||||||
# 向量数据库
|
|
||||||
VECTOR_DB=chroma
|
|
||||||
EMBEDDING_MODEL=sentence-transformers/all-MiniLM-L6-v2
|
|
||||||
|
|
||||||
# CORS (开发环境)
|
|
||||||
CORS_ALLOW_ORIGIN=*
|
|
||||||
|
|
||||||
# 日志级别
|
|
||||||
LOG_LEVEL=INFO
|
|
||||||
```
|
|
||||||
|
|
||||||
## 生产部署
|
|
||||||
|
|
||||||
生产环境使用 Docker 部署,详见项目根目录的 `Dockerfile` 和 `docker-compose.yaml`。
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 构建镜像
|
|
||||||
docker build -t open-webui .
|
|
||||||
|
|
||||||
# 运行容器
|
|
||||||
docker run -d -p 8080:8080 -v open-webui:/app/backend/data open-webui
|
|
||||||
```
|
|
||||||
|
|
||||||
## 更多资源
|
|
||||||
|
|
||||||
- **项目文档**: [CLAUDE.md](./CLAUDE.md)
|
|
||||||
- **API 文档**: http://localhost:8080/docs (启动后端后访问)
|
|
||||||
- **官方仓库**: https://github.com/open-webui/open-webui
|
|
||||||
|
|
||||||
## 贡献指南
|
|
||||||
|
|
||||||
1. Fork 项目
|
|
||||||
2. 创建功能分支 (`git checkout -b feature/AmazingFeature`)
|
|
||||||
3. 提交更改 (`git commit -m 'Add some AmazingFeature'`)
|
|
||||||
4. 推送到分支 (`git push origin feature/AmazingFeature`)
|
|
||||||
5. 创建 Pull Request
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**最后更新**: 2025-11-08
|
|
||||||
|
|
@ -1,88 +1,36 @@
|
||||||
# 本地开发故障排除
|
# Open WebUI Troubleshooting Guide
|
||||||
|
|
||||||
## 问题: "Open WebUI 需要后端服务" 错误
|
## Understanding the Open WebUI Architecture
|
||||||
|
|
||||||
### 🔧 快速解决方案
|
The Open WebUI system is designed to streamline interactions between the client (your browser) and the Ollama API. At the heart of this design is a backend reverse proxy, enhancing security and resolving CORS issues.
|
||||||
|
|
||||||
**请在浏览器中进行硬刷新:**
|
- **How it Works**: The Open WebUI is designed to interact with the Ollama API through a specific route. When a request is made from the WebUI to Ollama, it is not directly sent to the Ollama API. Initially, the request is sent to the Open WebUI backend via `/ollama` route. From there, the backend is responsible for forwarding the request to the Ollama API. This forwarding is accomplished by using the route specified in the `OLLAMA_BASE_URL` environment variable. Therefore, a request made to `/ollama` in the WebUI is effectively the same as making a request to `OLLAMA_BASE_URL` in the backend. For instance, a request to `/ollama/api/tags` in the WebUI is equivalent to `OLLAMA_BASE_URL/api/tags` in the backend.
|
||||||
|
|
||||||
- **macOS**: `Cmd + Shift + R`
|
- **Security Benefits**: This design prevents direct exposure of the Ollama API to the frontend, safeguarding against potential CORS (Cross-Origin Resource Sharing) issues and unauthorized access. Requiring authentication to access the Ollama API further enhances this security layer.
|
||||||
- **Windows/Linux**: `Ctrl + Shift + R`
|
|
||||||
|
|
||||||
然后检查是否解决问题。
|
## Open WebUI: Server Connection Error
|
||||||
|
|
||||||
### 📋 详细排查步骤
|
If you're experiencing connection issues, it’s often due to the WebUI docker container not being able to reach the Ollama server at 127.0.0.1:11434 (host.docker.internal:11434) inside the container . Use the `--network=host` flag in your docker command to resolve this. Note that the port changes from 3000 to 8080, resulting in the link: `http://localhost:8080`.
|
||||||
|
|
||||||
#### 1. 打开浏览器开发者工具
|
**Example Docker Command**:
|
||||||
|
|
||||||
- **macOS**: `Cmd + Option + I`
|
|
||||||
- **Windows/Linux**: `F12`
|
|
||||||
|
|
||||||
#### 2. 检查 Console (控制台)
|
|
||||||
|
|
||||||
查找是否有错误消息,特别是:
|
|
||||||
- 红色的错误信息
|
|
||||||
- 网络请求失败
|
|
||||||
- CORS 相关错误
|
|
||||||
|
|
||||||
#### 3. 检查 Network (网络) 标签
|
|
||||||
|
|
||||||
1. 切换到 Network 标签
|
|
||||||
2. 刷新页面
|
|
||||||
3. 查找对 `http://localhost:8080/api/config` 的请求
|
|
||||||
4. 如果找到,点击查看:
|
|
||||||
- **Status** 应该是 `200`
|
|
||||||
- **Response** 应该包含 JSON 配置
|
|
||||||
|
|
||||||
#### 4. 清除本地存储
|
|
||||||
|
|
||||||
1. 在开发者工具中,转到 **Application** 标签
|
|
||||||
2. 左侧找到 **Local Storage**
|
|
||||||
3. 展开并点击 `http://localhost:5050`
|
|
||||||
4. 点击右键 → **Clear**
|
|
||||||
5. 刷新页面
|
|
||||||
|
|
||||||
### ✅ 验证服务状态
|
|
||||||
|
|
||||||
在终端运行:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 测试后端 API
|
docker run -d --network=host -v open-webui:/app/backend/data -e OLLAMA_BASE_URL=http://127.0.0.1:11434 --name open-webui --restart always ghcr.io/open-webui/open-webui:main
|
||||||
curl http://localhost:8080/api/config
|
|
||||||
|
|
||||||
# 检查端口占用
|
|
||||||
lsof -i :8080 -i :5050 | grep LISTEN
|
|
||||||
```
|
```
|
||||||
|
|
||||||
如果 curl 命令返回 JSON 配置,说明后端正常运行。
|
### Error on Slow Responses for Ollama
|
||||||
|
|
||||||
### 🔄 重启服务 (如果需要)
|
Open WebUI has a default timeout of 5 minutes for Ollama to finish generating the response. If needed, this can be adjusted via the environment variable AIOHTTP_CLIENT_TIMEOUT, which sets the timeout in seconds.
|
||||||
|
|
||||||
如果上述方法无效,停止当前服务 (`Ctrl + C`) 并重新启动:
|
### General Connection Errors
|
||||||
|
|
||||||
**后端:**
|
**Ensure Ollama Version is Up-to-Date**: Always start by checking that you have the latest version of Ollama. Visit [Ollama's official site](https://ollama.com/) for the latest updates.
|
||||||
```bash
|
|
||||||
cd backend
|
|
||||||
source venv/bin/activate
|
|
||||||
python -m uvicorn open_webui.main:app --reload --port 8080 --host 0.0.0.0
|
|
||||||
```
|
|
||||||
|
|
||||||
**前端:**
|
**Troubleshooting Steps**:
|
||||||
```bash
|
|
||||||
npm run dev:5050
|
|
||||||
```
|
|
||||||
|
|
||||||
### 🌐 尝试不同端口
|
1. **Verify Ollama URL Format**:
|
||||||
|
- When running the Web UI container, ensure the `OLLAMA_BASE_URL` is correctly set. (e.g., `http://192.168.1.1:11434` for different host setups).
|
||||||
|
- In the Open WebUI, navigate to "Settings" > "General".
|
||||||
|
- Confirm that the Ollama Server URL is correctly set to `[OLLAMA URL]` (e.g., `http://localhost:11434`).
|
||||||
|
|
||||||
如果端口冲突,可以使用不同端口:
|
By following these enhanced troubleshooting steps, connection issues should be effectively resolved. For further assistance or queries, feel free to reach out to us on our community Discord.
|
||||||
|
|
||||||
**前端:**
|
|
||||||
```bash
|
|
||||||
npm run dev -- --port 3000
|
|
||||||
```
|
|
||||||
|
|
||||||
然后访问 `http://localhost:3000`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**还有问题?** 查看 `/Users/sylar/my_ws/open-webui-next/LOCAL_SETUP.md` 获取完整设置指南。
|
|
||||||
|
|
|
||||||
BIN
backend/open_webui/static/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
0
backend/open_webui/static/custom.css
Normal file
BIN
backend/open_webui/static/favicon-96x96.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
backend/open_webui/static/favicon-dark.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
backend/open_webui/static/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
backend/open_webui/static/favicon.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
3
backend/open_webui/static/favicon.svg
Normal file
|
After Width: | Height: | Size: 14 KiB |
0
backend/open_webui/static/loader.js
Normal file
BIN
backend/open_webui/static/logo.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
21
backend/open_webui/static/site.webmanifest
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"name": "Open WebUI",
|
||||||
|
"short_name": "WebUI",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/static/web-app-manifest-192x192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "maskable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/static/web-app-manifest-512x512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "maskable"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"theme_color": "#ffffff",
|
||||||
|
"background_color": "#ffffff",
|
||||||
|
"display": "standalone"
|
||||||
|
}
|
||||||
BIN
backend/open_webui/static/splash-dark.png
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
backend/open_webui/static/splash.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
1
backend/open_webui/static/user-import.csv
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Name,Email,Password,Role
|
||||||
|
BIN
backend/open_webui/static/user.png
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
BIN
backend/open_webui/static/web-app-manifest-192x192.png
Normal file
|
After Width: | Height: | Size: 8.2 KiB |
BIN
backend/open_webui/static/web-app-manifest-512x512.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
646
package-lock.json
generated
|
|
@ -141,7 +141,6 @@
|
||||||
"vega-lite": "^6.4.1",
|
"vega-lite": "^6.4.1",
|
||||||
"vite-plugin-static-copy": "^2.2.0",
|
"vite-plugin-static-copy": "^2.2.0",
|
||||||
"y-prosemirror": "^1.3.7",
|
"y-prosemirror": "^1.3.7",
|
||||||
"y-protocols": "^1.0.6",
|
|
||||||
"yaml": "^2.7.1",
|
"yaml": "^2.7.1",
|
||||||
"yjs": "^13.6.27"
|
"yjs": "^13.6.27"
|
||||||
},
|
},
|
||||||
|
|
|
||||||