Linux 下对 DeepSeek 本地部署:文件自动检测与知识库同步,含 OpenWebUI、Docker 及 Ollama 部署教程

Lijiong

在当今数字化时代,知识管理与高效检索成为提升个人和团队生产力的关键。如何将文件夹中的文件自动检测并同步到知识库中,同时利用强大的向量化模型进行高效检索,是许多技术爱好者和开发者关注的焦点。本文将带你深入了解如何基于 Linux,通过 OpenWebUI 和 Ollama,结合 Docker 的强大容器化能力,实现一个本地化的、自动化的知识库同步与检索系统。

接下来,我将详细展开每一步的部署过程,从安装 Docker 和 Ollama(为了防止破坏 Linux 环境),到配置 OpenWebUI 的知识库,再到实现文件夹的自动化同步。无论你是技术新手还是资深开发者,本文都将为你提供清晰的指引,帮助你快速搭建一个高效、安全且易于管理的本地知识库系统。

首先介绍部署的环境:

使用Ubuntu24.10作为本次教程的平台:

Ubantu设置界面

安装 Ollama 和 Docker:

进入二者的官网:

Ollama

Docker

安装 Ollama 时如果出现找不到命令,跟随 Terminal 的提示进行库的安装:

Ollama官网

安装完成后回到它的官网点击左上角的 Models,选择需要的 LLM 模型并且复制下载命令,这里我选择的是 deepseek-r1-7b版本:

Ollama 模型选择界面,DeepSeek-r1-7B模型

打开终端并且输入命令行:

下载完成后可以直接在终端进行对话(如果需要的话,下一次在终端启动可以继续使用上面那个相同的命令激活。)

此时还需要在 Ollama 安装 Ebedding 模型(经过和默认模型以及国内先进的模型对比我选择了以下模型):

Ollama 的embedding模型选择界面,nomic-embed-text模型

该模型的拉取和上文 Deepseek 的拉取一致。

拉取完成后可用在终端敲入以下代码来查看已经下载的模型:

1
2

ollama list

Docker 的安装,下面是用 Docker Desktop 实现的,为了防止容器内 OpenWebUI 无法访问宿主机的 Ollama ,安装在它的官网有详细的说明文档,跟着官网的步骤一步一步安装上去:

但是不可否认的是Docker Desktop有很多奇奇怪怪的问题,在这个安装的末尾我会介绍怎么修改Ollama服务来让Docker Engine能够正常访问模型,而不用安装Docker Desktop。

第一步安装apt储存库,直接复制文档中的命令然后在终端中运行即可:

1
2
3
4
5
6
7
8
9
10
11
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
# Add the repository to Apt sources:
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update

第二步,下载最新的 deb 包

第三部,终端中切换到下载目录,输入以下代码:

1
2
sudo apt-get update
sudo apt-get install ./docker-desktop-amd64.deb

Linux 上的安装没有 Windows 上很多奇奇怪怪的问题,安装完成后可以直接打开了:

Docker 安装完成后有部分型号电脑会要求重启,重启后 Docker 应该会自启动,如果电脑无法进行科学上网,进入 Setting–Docker engine,将以下代码替换进入框内。

1
2
3
{
"registry-mirrors": ["https://docker.mirrors.ustc.edu.cn"]
}

Openweb UI 的配置(以下分为两种情况,一种为科学上网可用,一种为无法连接境外互联网可用):

1.无法连接境外互联网:继续上文的步骤,在终端中输入以下命令来拉取清华的镜像源:

1
docker pull ghcr.io/open-webui/open-webui:main

在镜像源拉取成功后输入以下命令:

不需要使用 NVIDIA Cuda 加速:

1
docker run -d -p 3000:8080 --add-host=host.docker.internal:host-gateway -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:main

需要使用 NVIDIA Cuda 加速(确保 NVIDIA 显卡驱动更新到最新):

1
docker run -d -p 3000:8080 --gpus all --add-host=host.docker.internal:host-gateway -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:cuda

2.科学上网可用(不需要更改 Docker engine 设置):访问 OpenWeb UI 官网:

复制官网提供的 Docker 命令:

不需要使用 NVIDIA Cuda 加速:

1
docker run -d -p 3000:8080 --add-host=host.docker.internal:host-gateway -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:main

需要使用 NVIDIA Cuda 加速(确保 NVIDIA 显卡驱动更新到最新):

1
docker run -d -p 3000:8080 --gpus all --add-host=host.docker.internal:host-gateway -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:cuda

使用 NVIDIA Cuda 加速在 Linux 上很容易概率性出现问题,尝试自己安装适合的 NVIDIA 驱动,这里可以详见我另一篇Blog,如何正确在 Linux 上解决 Cuda 加速问题:点这里

程序拉取完成后进入 Docker 应该可以看到如下画面:

点击 Ports 下的端口号,将会跳转浏览器并且启动 OpenWeb UI 的界面。初次登陆会让你注册用户,第一个注册的用户将会成为权限最大的管理员,账号密码等信息都是存在本地的不用担心信息泄露。

如果此时链接不上 Ollama 内的模型,可能是 Ollama 的本地 API 出现问题,一般这个问题会出现在使用国内镜像源拉取的 OpenWeb UI 上,进入设置-管理员设置-外部链接,将 Ollama API 切换成以下代码:

1
http://host.docker.internal:11434

好,现在来介绍如何直接用 Docker Engine 部署 OpenWebUI:

首先安装 Docker Engine:

先设置 Docker 的apt储存库:

1
2
3
4
5
6
7
8
9
10
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update

安装 Docker 包:

1
sudo apt-get install docker-ce docker-ce-cli containerd docker-buildx-plugin docker-compose-plugin

通过运行镜像来验证安装是否成功hello-world:

1
sudo docker run hello-world

接下来编辑 Ollama 服务,以确保 OpenWebUI 能够正常调用模型

1
sudo systemctl edit ollama.service

进入文本编辑器,输入代码并且保存:

1
2
[Service]
Environment="OLLAMA_HOST=0.0.0.0"

输入以下三行代码,重启 Docker 和 Ollama

1
2
3
sudo systemctl daemon-reload
sudo systemctl restart ollama
sudo systemctl restart docker

输入以下代码查看容器运行情况,当它显示为 healthy 时,直接在浏览器访问 localhost:3000

1
sudo systemctl restart ollama

现在应该可以和你的模型进行正常的对话了,记住将 LLM 模型(此处为 Deepseek)设为默认,对 Ebedding 模型说话会报错!

调整 Web UI:接下来配置知识库所需的文件的处理细节:依旧是在设置-管理员设置-文档,将语义向量模型引擎切换成 Ollama 并且将语义向量模型设置为 nomic-embed-text:latest 其他设置可以按照图里面的需求调整,如果你有其他需求也可以自行调整,调整完毕需要点击保存:

知识库:工作空间 - 知识库 - 创建知识库,根据你的需求来创建知识库(可以选择对全部用户开放还是创建私人知识库,最高级管理员可以看见所有用户创建的知识库不管是 Public/Private)。

现在需要获取自己 Web UI 的 API key,进入设置 - 账户 - API 密钥 - 启用 API 密钥,复制 API 密钥,不要用 JWT 令牌:

获取知识库的 ID,每一个知识库都有自己独立的 ID:

进入一个知识库,在导航栏上最后一串数字字母混合物就是这个知识库的 ID:

自动监视并且同步目标文件夹内的文件进入指定知识库:

以下是重点!!!

OpenWeb UI 只能支持 .docx .csv .pdf .txt 文件老版本的 .doc .xlsx .xls 文件需要转化后才能上传,如果需要这个自动监控并且转换的脚本可以去我的 GitHub 上拿:链接在这里

在 Linux 下运行 Python,先检查是否安装了 Python,如果没有,运行以下命令:

1
2
sudo apt update
sudo apt install python3

安装相关的库:

1
2
sudo apt install python3-watchdog
sudo apt install python3-requests

创建一个 .py 文件,将脚本放进去,使用终端以此命令运行这个脚本。

1
python3 /home/ll/test/try.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
import os
import time
import hashlib
import requests
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
# API 和全局配置
API_BASE_URL = 'http://localhost:3000/api/v1/'
TOKEN = "sk-7ff02c364c57478ab3dcc062eaaf4fe6" # 替换为你的授权令牌
# 文件夹与知识库的映射关系,前面是文件夹路径,后面是知识库ID,每一个知识库都有自己的ID
FOLDER_KNOWLEDGE_MAP = {
r"/home/ll/test/file": "8d885deb-f653-4b44-9f0e-21ea4fe19584"
}
# 文件存储状态(保存已上传文件的信息,避免重复上传)
uploaded_files = {}
# 指定哈希文件的保存路径
PERSISTENCE_FILE = r"/home/ll/test/hashes.txt" # 替换为你的哈希文件保存路径
# 日志函数
def log(message, level="INFO"):
print(f"[{level}] {message}")
# 确保哈希文件存在
def ensure_persistence_file():
if not os.path.exists(PERSISTENCE_FILE):
log(f"Hash file does not exist. Creating a new one at {PERSISTENCE_FILE}")
open(PERSISTENCE_FILE, "w").close()
# 加载已上传文件的哈希值
def load_uploaded_files():
log(f"Loading uploaded files from {PERSISTENCE_FILE}...")
try:
with open(PERSISTENCE_FILE, "r") as f:
for line in f:
line = line.strip()
if line:
try:
file_path, checksum = line.split("|")
uploaded_files[file_path] = checksum
log(f"Loaded {file_path} with checksum {checksum}", level="DEBUG")
except ValueError:
log(f"Skipping invalid line in hash file: {line}", level="WARNING")
except FileNotFoundError:
log(f"Hash file {PERSISTENCE_FILE} not found. Starting with an empty hash list.", level="WARNING")
# 保存已上传文件的哈希值
def save_uploaded_files():
log(f"Saving uploaded files to {PERSISTENCE_FILE}...")
try:
with open(PERSISTENCE_FILE, "w") as f:
for file_path, checksum in uploaded_files.items():
f.write(f"{file_path}|{checksum}\n")
log("Successfully saved uploaded files.")
except Exception as e:
log(f"Failed to save uploaded files: {e}", level="ERROR")
# 上传文件到知识库
def upload_file(file_path, knowledge_id):
headers = {
'Authorization': f'Bearer {TOKEN}',
'Accept': 'application/json'
}
log(f"Uploading {file_path} to knowledge {knowledge_id}")
try:
with open(file_path, 'rb') as file:
checksum = hashlib.sha256(file.read()).hexdigest()
if file_path in uploaded_files and uploaded_files[file_path] == checksum:
log(f"File {file_path} has already been uploaded. Skipping.", level="DEBUG")
return
with open(file_path, 'rb') as file:
response = requests.post(
f'{API_BASE_URL}files/',
headers=headers,
files={'file': file},
timeout=200
)
if response.status_code == 200:
file_id = response.json().get('id')
add_file_to_knowledge(file_id, knowledge_id)
uploaded_files[file_path] = checksum
log(f"Successfully uploaded {file_path}")
else:
log(f"Error uploading {file_path}: {response.status_code} - {response.text}", level="ERROR")
except requests.exceptions.RequestException as e:
log(f"Failed to upload {file_path}: {e}", level="ERROR")
# 将文件添加到知识库
def add_file_to_knowledge(file_id, knowledge_id):
headers = {
'Authorization': f'Bearer {TOKEN}',
'Content-Type': 'application/json'
}
data = {'file_id': file_id}
response = requests.post(
f'{API_BASE_URL}knowledge/{knowledge_id}/file/add',
headers=headers,
json=data
)
if response.status_code != 200:
log(f"Error adding file {file_id} to knowledge {knowledge_id}: {response.status_code} - {response.text}", level="ERROR")
# 文件系统事件处理器
class SyncHandler(FileSystemEventHandler):
def __init__(self, source_folder, knowledge_id):
self.source_folder = source_folder
self.knowledge_id = knowledge_id
def on_created(self, event):
if not event.is_directory:
file_path = event.src_path
file_name = os.path.basename(file_path)
if (os.path.exists(file_path) and
os.path.getsize(file_path) > 0 and
not file_name.startswith('~$') and
not file_name.endswith('.tmp')):
upload_file(file_path, self.knowledge_id)
log(f"Detected new file: {file_path}")
def on_modified(self, event):
if not event.is_directory:
file_path = event.src_path
file_name = os.path.basename(file_path)
if (os.path.exists(file_path) and
os.path.getsize(file_path) > 0 and
not file_name.startswith('~$') and
not file_name.endswith('.tmp')):
upload_file(file_path, self.knowledge_id)
log(f"Detected modified file: {file_path}")
def on_deleted(self, event):
if not event.is_directory:
file_path = event.src_path
if file_path in uploaded_files:
del uploaded_files[file_path]
log(f"Deleted file {file_path} removed from uploaded_files list.")
# 程序启动时进行增量同步
def initial_sync(folder, knowledge_id):
log(f"Performing initial sync for folder {folder} with knowledge {knowledge_id}")
for root, _, files in os.walk(folder):
for file in files:
file_path = os.path.join(root, file)
if (os.path.exists(file_path) and
os.path.getsize(file_path) > 0 and
not file.startswith('~$') and
not file.endswith('.tmp')):
upload_file(file_path, knowledge_id)
# 主函数
def main():
log("Starting file synchronization...")
ensure_persistence_file()
load_uploaded_files()
observers = []
for folder, knowledge_id in FOLDER_KNOWLEDGE_MAP.items():
# Perform initial sync to handle any changes that occurred while the program was not running
initial_sync(folder, knowledge_id)
log(f"Setting up observer for folder {folder} with knowledge {knowledge_id}")
observer = Observer()
handler = SyncHandler(folder, knowledge_id)
observer.schedule(handler, folder, recursive=True)
observer.start()
observers.append(observer)
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
log("Stopping synchronization...")
save_uploaded_files()
for observer in observers:
observer.stop()
for observer in observers:
observer.join()
if __name__ == "__main__":
main()

文章到这里就差不多结束了,后续还有新的发现和功能实现我将会持续更新

(刚接触 Linux 没多久,如有问题请告知我,谢谢!!!)

经过几次小迭代,转化功能和同步功能已经被我整合到一起去了,并且封装成exe文件,压缩包还是在之前Github的链接上,请按照以下步骤来进行同步和上传功能的配置:
1:下载程序包,解压完成后应该会看见两个exe文件,首先点击名为”填写配置文件”的exe文件,填写必要的信息:知识库接口、API Token、你期望配置文件的存放位置、你需要的知识库映射数量、填写完成后点击下一步,填写文件夹和知识库ID的对应关系(知识库ID的位置我在文章中写了的,如果找不到可以访问lijiong.online进行查找)然后点击保存,回到主页面再点一次保存,最后×掉这个窗口,路径配置就完成了。

2.将另一个名为Sync-OpenWebUI的exe文件添加到Windows任务计划程序中,设置为开机延时2min启动(等待Open web UI启动以防出现bug),重启电脑查看任务管理器中这个程序进程是否正常,如果正常文件应该正常被转化和上传到知识库中。转化后的文件存在你设置的配置文件储存地址。中文文件夹被重命名为拼音简写。

需要代码讲解的可以移步我的另一篇基于Windows平台的Blog:

这里

也欢迎关注我的知乎账号:点击此处跳转

转载记得标明出处噢!

  • Title: Linux 下对 DeepSeek 本地部署:文件自动检测与知识库同步,含 OpenWebUI、Docker 及 Ollama 部署教程
  • Author: Lijiong
  • Created at : 2025-04-01 16:43:34
  • Updated at : 2025-04-13 22:02:25
  • Link: https://lijiong.online/2025/04/01/1/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments