A. 最终效果

图像加载似乎还有点问题,知道的大佬请帮忙看看: http://192.168.3.4:8080/static/favicon.png
B. 设置镜像
bash
npm install -g nrm
nrm ls # 查看可用镜像
nrm use taobao # 切换到淘宝源
C. 官方教程
C.1 配置前端
- 克隆仓库
bash
git clone https://github.com/open-webui/open-webui.git
cd open-webui
- 创建
.env
文件:
bash
cp -RPp .env.example .env
- 安装依赖项:
bash
npm install
- 启动前端:
bash
npm run dev
- 前端访问
bash
http://localhost:5173
C.2 配置后端
- 导航到后端
bash
cd backend
- 利用Conda创建环境
bash
conda create --name open-webui python=3.11
conda activate open-webui
- 安装依赖项
bash
pip install -r requirements.txt -U
- 启动后端
bash
sh dev.sh
- API文档:
bash
http://localhost:8080/docs
E. 离线迁移
E.1 离线准备
- 严格按照上面执行后会有如下的项目文件夹(会增加一个
node_modules
文件夹)
E.2 离线迁移
- 此时,可用任何方式,迁移上述项目文件夹到断网的服务器
F. 项目修改
F.1 运行报错
- 在断网服务器上,如果直接执行C.1中的
npm run dev
,会报错如下:
bash
[lgk@localhost open-webui]$ npm run dev
> [email protected] dev
> npm run pyodide:fetch && vite dev --host
> [email protected] pyodide:fetch
> node scripts/prepare-pyodide.js
Setting up pyodide + micropip
Pyodide packages are already installed. Skipping download.
Copying Pyodide files into static directory
node:internal/modules/esm/resolve:275
throw new ERR_MODULE_NOT_FOUND(
^
Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/home/lgk/Downloads/new-open-webui/open-webui/node_modules/dist/node/cli.js' imported from /home/lgk/Downloads/new-open-webui/open-webui/node_modules/.bin/vite
at finalizeResolution (node:internal/modules/esm/resolve:275:11)
at moduleResolve (node:internal/modules/esm/resolve:932:10)
at defaultResolve (node:internal/modules/esm/resolve:1056:11)
at ModuleLoader.defaultResolve (node:internal/modules/esm/loader:654:12)
at #cachedDefaultResolve (node:internal/modules/esm/loader:603:25)
at ModuleLoader.resolve (node:internal/modules/esm/loader:586:38)
at ModuleLoader.getModuleJobForImport (node:internal/modules/esm/loader:242:38)
at onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:546:36)
at TracingChannel.tracePromise (node:diagnostics_channel:344:14)
at ModuleLoader.import (node:internal/modules/esm/loader:545:21) {
code: 'ERR_MODULE_NOT_FOUND',
url: 'file:///home/lgk/Downloads/new-open-webui/open-webui/node_modules/dist/node/cli.js'
}
Node.js v22.13.1
F.2 解决方法

-
修改1(按照你自己的路径配置):
- 路径:
node_modules/vite/bin/vite.js
- 替换:
return import('/home/lgk/Projects/new-open-webui/open-webui/node_modules/vite/dist/node/cli.js')
- 路径:
-
修改2(按照你自己的路径配置):
- 路径:
node_modules/.bin/vite
- 替换:
return import('/home/lgk/Projects/new-open-webui/open-webui/node_modules/vite/dist/node/cli.js')
- 路径:
F.3 修改文件
- 解决每次
npm run dev
都要在线Installing package: seaborn
等相关工具包的问题 - 绝对路径:
/home/lgk/Projects/new-open-webui/open-webui/scripts/prepare-pyodide.js
- 相对路径:
scripts/prepare-pyodide.js
js
const packages = [
'micropip',
'packaging',
'requests',
'beautifulsoup4',
'numpy',
'pandas',
'matplotlib',
'scikit-learn',
'scipy',
'regex',
'sympy',
'tiktoken',
'seaborn',
'pytz'
];
import { loadPyodide } from 'pyodide';
import { setGlobalDispatcher, ProxyAgent } from 'undici';
import { writeFile, readFile, copyFile, readdir, rmdir, access } from 'fs/promises';
import { constants } from 'fs';
import path from 'path';
/**
* 初始化网络代理(如果设置了代理)
*/
function initNetworkProxyFromEnv() {
const allProxy = process.env.all_proxy || process.env.ALL_PROXY;
const httpsProxy = process.env.https_proxy || process.env.HTTPS_PROXY;
const httpProxy = process.env.http_proxy || process.env.HTTP_PROXY;
const preferedProxy = httpsProxy || allProxy || httpProxy;
if (!preferedProxy || !preferedProxy.startsWith('http')) return;
let preferedProxyURL;
try {
preferedProxyURL = new URL(preferedProxy).toString();
} catch {
console.warn(`Invalid network proxy URL: "${preferedProxy}"`);
return;
}
const dispatcher = new ProxyAgent({ uri: preferedProxyURL });
setGlobalDispatcher(dispatcher);
console.log(`Initialized network proxy "${preferedProxy}" from env`);
}
/**
* 检查文件或目录是否存在
*/
async function fileExists(filePath) {
try {
await access(filePath, constants.F_OK);
return true;
} catch {
return false;
}
}
/**
* 检查 Pyodide 依赖是否已经安装
*/
async function isPackageCached(pkg) {
const packageFiles = await readdir('static/pyodide');
return packageFiles.some(file => file.startsWith(`${pkg}-`));
}
/**
* 下载并缓存 Pyodide 依赖
*/
async function downloadPackages() {
console.log('Setting up pyodide + micropip');
const lockFilePath = 'static/pyodide/pyodide-lock.json';
// **第一步:如果 lock 文件存在,且所有依赖已安装,则跳过安装**
if (await fileExists(lockFilePath)) {
console.log('Pyodide packages are already installed. Skipping download.');
return;
}
let pyodide;
try {
pyodide = await loadPyodide({
packageCacheDir: 'static/pyodide'
});
} catch (err) {
console.error('Failed to load Pyodide:', err);
return;
}
// **第二步:检查 Pyodide 版本是否匹配**
try {
const packageJson = JSON.parse(await readFile('package.json'));
const pyodideVersion = packageJson.dependencies.pyodide.replace('^', '');
const pyodidePackageJsonPath = 'static/pyodide/package.json';
if (await fileExists(pyodidePackageJsonPath)) {
const pyodidePackageJson = JSON.parse(await readFile(pyodidePackageJsonPath));
const pyodidePackageVersion = pyodidePackageJson.version.replace('^', '');
if (pyodideVersion !== pyodidePackageVersion) {
console.log('Pyodide version mismatch, clearing outdated files...');
await rmdir('static/pyodide', { recursive: true });
}
}
} catch (e) {
console.log('Pyodide package not found, proceeding with download.');
}
// **第三步:安装 Pyodide 依赖**
try {
console.log('Loading micropip package');
await pyodide.loadPackage('micropip');
const micropip = pyodide.pyimport('micropip');
console.log('Downloading Pyodide packages:', packages);
for (const pkg of packages) {
// **检查是否已经缓存**
if (await isPackageCached(pkg)) {
console.log(`Package ${pkg} is already cached, skipping.`);
continue;
}
console.log(`Installing package: ${pkg}`);
await micropip.install(pkg);
}
console.log('Pyodide packages downloaded, freezing into lock file');
const lockFile = await micropip.freeze();
await writeFile(lockFilePath, lockFile);
} catch (err) {
console.error('Failed to install Pyodide packages:', err);
}
}
/**
* 复制 Pyodide 相关文件
*/
async function copyPyodide() {
console.log('Copying Pyodide files into static directory');
// **检查 `static/pyodide/` 是否存在**
if (!(await fileExists('static/pyodide'))) {
console.log('static/pyodide/ does not exist, skipping copy.');
return;
}
// **复制 Pyodide 相关文件**
for await (const entry of await readdir('node_modules/pyodide')) {
await copyFile(`node_modules/pyodide/${entry}`, `static/pyodide/${entry}`);
}
}
// **执行脚本**
initNetworkProxyFromEnv();
await downloadPackages();
await copyPyodide();
G. 系统配置
- 后续会遇到这个问题
bash
ENOSPC: System limit for number of file watchers reached
- 查看系统设置
bash
cat /proc/sys/fs/inotify/max_user_watches
cat /proc/sys/fs/inotify/max_user_instances
- 更新系统设置(临时)
bash
sudo sysctl fs.inotify.max_user_watches=524288
sudo sysctl fs.inotify.max_user_instances=1024
- 更新系统设置(永久)
bash
echo "fs.inotify.max_user_watches=524288" | sudo tee -a /etc/sysctl.conf
echo "fs.inotify.max_user_instances=1024" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
H. 端口放行(临时)
bash
sudo firewall-cmd --add-port=5173/tcp
sudo firewall-cmd --add-port=8080/tcp
sudo firewall-cmd --list-ports
I. 项目启动
- Terminal 1(项目文件夹所在路径):
bash
npm run dev
- Terminal 2(项目文件夹所在路径/backend):
bash
conda activate <Your_Open-WebUI_ENV_IN_C.2>
sh dev.sh

J. 参考资料
J.1 代理配置
bash
export http_proxy=http://192.168.3.69:7890
export https_proxy=http://192.168.3.69:7890
npm install
npm config set proxy http://192.168.3.69:7890
npm config set https-proxy http://192.168.3.69:7890
J.2 NodeJS安装
-
下载
- 下载地址1(最新版):Node.js --- Download Node.js®
- 下载地址2(历史版): Index of /download/release/v22.13.1/
-
安装
bash
tar -xvf node-v22.13.1-linux-x64.tar.xz -C ~/Softwares
- 配置(此处修改为你自己解压后的NodeJS路径)
bash
echo 'export PATH=/home/lgk/Softwares/node-v22.13.1-linux-x64/node-v22.13.1-linux-x64:$PATH' >> ~/.bashrc
- 激活
bash
source ~/.bashrc