windows版本的 ollama :https://blog.youkuaiyun.com/YXWik/article/details/143871588
环境:centos7
文中各个脚本
1.docker安装 或者 需要重新安装: install_docker.sh
2.docker已安装只需要安装 ollama+ deepseek-r1:7b + Open WebUI : install_ollama.sh
3.空环境需要安装docker + ollama+ deepseek-r1:7b + Open WebUI :
install_docker.sh
install_ollama.sh
(这两个脚本放同一目录下运行install_ollama.sh
即可)
4. Python版安装Open WebUI(此脚本半成品
,需自行修复,与centos7这个环境兼容太差了):install_open_webui.sh
脚本
ollama
ollama是一个简明易用的本地大模型运行框架,只需一条命令即可在本地跑大模型。开源项目,专注于开发和部署先进的大型语言模型(LLM)
官网: https://ollama.com/ 下载地址:https://ollama.com/download/linux
不着急执行,继续阅读文章,后面一键安装脚本
curl -fsSL https://ollama.com/install.sh | sh
ollama --version
Ollama
的官方二进制文件是使用较新的 glibc
和 GCC
编译的,它要求:
glibc
>= 2.27
libstdc++
>= 4.8.5(最好 GCC 9+)
系统 glibc
和 libstdc++
版本太旧,需要更新
系统又安装一些其他程序无法升级,只升级这两个依赖又害怕搞出兼容性的问题
更新半天费劲巴拉的放弃了,采用docker
安装得了
以下是一个完整的 Bash 脚本,用于在 CentOS 7 系统上:
-
检测是否安装 Docker
未安装:自动安装并启动 Docker
-
检查 Docker 是否运行
未运行:启动 Docker 服务
-
检查是否已有 Ollama 容器
有:尝试启动它(如果未运行)
无:拉取并运行 Ollama 容器
注: 此脚本会更改服务器的下载源/etc/yum.repos.d
配置 会更改docker
的 daemon.json
配置
脚本中 /home/ollama
挂载到容器内的 /root/.ollama
,是用于持久化模型数据,如有需要自行更改路径
ollama的脚本处理完后我就想着deepseek模型
(想下载其他模型的也只需要更换脚本下载模型处的 deepseek-r1:7b 即可)和Open WebUI
也放到脚本中自动安装启动,这之后就整理个一键安装脚本得了,以下脚本为一键安装脚本(经过多次优化已验证无误,安装open webui巨慢,不想等或者不需要的可以装完模型就结束)
install_ollama.sh
#!/bin/bash
set -e
# 日志函数
log_info() {
echo -e "\033[1;32m[INFO] $1\033[0m"
}
log_error() {
echo -e "\033[1;31m[ERROR] $1\033[0m"
}
# 检查是否为 root
if [ "$EUID" -ne 0 ]; then
log_error "请以 root 权限运行此脚本"
exit 1
fi
# 安装依赖
if ! command -v curl &> /dev/null; then
log_info "📥 安装 curl..."
yum install -y curl || apt-get update && apt-get install -y curl
fi
# 设置 Docker 镜像加速器
log_info "⚙️ 配置 Docker 镜像加速器..."
cat > /etc/docker/daemon.json << 'EOF'
{
"registry-mirrors": [
"https://docker.m.daocloud.io",
"https://dockerproxy.com",
"https://docker.mirrors.ustc.edu.cn",
"https://docker.nju.edu.cn"
]
}
EOF
# 检查 Docker 是否已安装
if ! command -v docker &> /dev/null; then
log_info "🔍 Docker 未安装,正在调用 install_docker.sh 安装..."
if [ -f "./install_docker.sh" ]; then
chmod +x ./install_docker.sh
./install_docker.sh
else
log_error "❌ 未找到 install_docker.sh 脚本,正在安装采用其他方式安装 Docker"
curl -fsSL https://get.docker.com | sh
systemctl enable docker --now
exit 1
fi
else
log_info "✅ Docker 已安装"
fi
# 确保 Docker 正在运行
if ! systemctl is-active --quiet docker; then
log_info "🔄 Docker 未运行,正在启动..."
systemctl start docker
fi
# 创建 Ollama 数据目录
DATA_DIR="/home/ollama"
if [ ! -d "$DATA_DIR" ]; then
log_info "📁 创建 Ollama 数据目录:$DATA_DIR"
mkdir -p "$DATA_DIR"
fi
# 检查是否已存在 ollama 容器
if docker inspect ollama > /dev/null 2>&1; then
log_info "🔄 检测到已存在的 ollama 容器,正在停止并删除..."
docker stop ollama > /dev/null
docker rm ollama > /dev/null
fi
# 启动 Ollama 容器
log_info "📦 启动 Ollama 容器..."
docker run -d \
--name ollama \
-v "$DATA_DIR":/root/.ollama \
-p 11434:11434 \
-e OLLAMA_HOST=0.0.0.0 \
--add-host=host.docker.internal:host-gateway \
--restart always \
ollama/ollama
# 等待服务就绪
log_info "⏳ 等待 Ollama 服务启动..."
sleep 5
# 检查 API 是否可用
if curl -s http://localhost:11434/api/tags > /dev/null; then
log_info "✅ Ollama 服务启动成功"
else
log_error "❌ Ollama 服务启动失败,请检查日志:docker logs ollama"
exit 1
fi
# 下载模型(可修改为你需要的模型)
MODEL_NAME="deepseek-r1:7b"
log_info "🧠 正在下载模型:$MODEL_NAME"
docker exec -it ollama ollama run $MODEL_NAME
# 部署 Open WebUI(可选)
log_info "🌐 正在部署 Open WebUI..."
docker run -d \
--name open-webui \
-p 3000:8080 \
--network=host \
-e OLLAMA_API_URL=http://192.168.0.180:11434 \
--add-host=host.docker.internal:host-gateway \
-v open-webui:/app/backend/data \
--restart always \
ghcr.io/open-webui/open-webui:main
# 提示完成
log_info "🎉 所有任务已完成!"
log_info "👉 Ollama 地址:http://<你的服务器IP>:11434"
log_info "👉 Open WebUI 地址:http://<你的服务器IP>:3000"
log_info "🧠 模型已下载完成:$MODEL_NAME"
log_info "📁 模型保存路径:$DATA_DIR/models"
新建 install_ollama.sh 文件 将脚本内容保存,然后给文件赋予权限
还需要一个安装docker的脚本 install_docker.sh
,依旧是需要赋权,这个脚本单独出来是为了避免服务器已经有docker,运行此脚本会进行重装,只有服务器无docker才需要运行,如果服务器有docker但是无法安装ollama的,也可以单独运行此脚本进行重装或者将脚本中的 Docker 源配置一下
install_docker.sh
#!/bin/bash
# 删除所有旧的 Docker 源文件
echo "正在删除旧的 Docker 源文件..."
rm -f /etc/yum.repos.d/docker-*.repo
# 清理 yum 缓存
yum clean all
# 创建新的阿里云 Docker 源文件
echo "正在添加阿里云 Docker 源..."
cat > /etc/yum.repos.d/docker-ce.repo << 'EOF'
[docker-ce-stable]
name=Docker CE Stable - $basearch
baseurl=https://mirrors.aliyun.com/docker-ce/linux/centos/7/$basearch/stable
enabled=1
gpgcheck=1
gpgkey=https://mirrors.aliyun.com/docker-ce/linux/centos/gpg
EOF
# 更新 yum 缓存
yum makecache
# 安装必要软件包
echo "正在安装依赖..."
yum install -y yum-utils device-mapper-persistent-data lvm2
# 安装 Docker CE
echo "正在安装 Docker CE..."
yum install -y docker-ce docker-ce-cli containerd.io
# 启动 Docker 服务并设置开机自启
echo "启动 Docker 服务..."
systemctl start docker
systemctl enable docker
# 验证 Docker 是否安装成功
docker --version
systemctl status docker
echo "Docker 安装完成!"
两个脚本准备好久可以进行一键安装了
启动安装ollama脚本
./install_ollama.sh
如果自己有docker但是运行脚本报错如下:
删除所有 Docker 源文件,重新安装docker 或者更改docker源(脚本中有)
运行 install_docker.sh
docker处理完成后重新运行安装ollama的脚本
./install_ollama.sh
如果报错 :docker: Error response from daemon: Get "https://registry-1.docker.io/v2/": dial tcp 103.42.176.244:443: connect: connection refused.
仍然无法访问 Docker Hub
解决方案:重启docker,因为我们的脚本中配置了docker下载源,但是需要重启才可以生效
systemctl daemon-reload
systemctl restart docker
再次运行 ./install_ollama.sh
这里 ctrl+d
退出输入(这里就可以进行对话聊天了,deepseek已经部署好了)
如果不需要Open WebUI
的这里就已经私有化deepseek
完成了
安装Open WebUI
将OLLAMA_BASE_URL
更改为Ollama服务器的URL在进行启动:
docker run -d -p 3000:8080 -e OLLAMA_BASE_URL=http://192.168.0.180:11434 -v open-webui:/app/backend/data --name open-webui --restart always ghcr.nju.edu.cn/open-webui/open-webui:main
要使用Nvidia GPU支持运行Open WebUI,请使用以下命令:
docker run -d -p 3000:8080 --gpus all --add-host=host.docker.internal:host-gateway --network=host -v open-webui:/app/backend/data --name open-webui --restart always ghcr.nju.edu.cn/open-webui/open-webui:cuda
这个也太慢太慢了,五个小时了都没有装完,这个网速被限制了吗,还是外网下载的呢,还是就这么慢,官方给的这 docker下载地址太磨叽了,我之前在windows上用Python装过:https://blog.youkuaiyun.com/YXWik/article/details/143871588,也可以换这种方式进行安装
不想等了,终止了,搞一个基于python
进行安装Open WebUI
的脚本(这个脚本更难搞,搞了好几天,但是最终还是采用docker
安装好的)
install_open_webui.sh
记得给文件授权
最终还是需要升级gcc版本
脚本:Python 3.11 + Open WebUI+自动检测和修复 _ssl模块 (脚本内含centos7的gcc版本等依赖的升级,生产环境慎重使用
)
脚本中安装的openssl是3.4.1版本
脚本会替换CentOS 7镜像源,生产环境需要注意
脚本中将启动的端口该为了9000
,需要变更的自行更改下
gcc镜像地址:http://gnu.mirror.constant.com/gcc/
如果 ollama不在本机装的,需要更改脚本中OLLAMA_API_URL的值
install_open_webui.sh (这个脚本是未完成版,修复脚本中途采用docker安装成功了,如果无法使用docker的可以在此脚本基础上进行修复调整)
#!/bin/bash
set -e
# 日志函数
log_info() {
echo -e "\033[1;32m[INFO] $1\033[0m" >&2
}
log_error() {
echo -e "\033[1;31m[ERROR] $1\033[0m" >&2
}
log_warn() {
echo -e "\033[1;33m[WARN] $1\033[0m" >&2
}
# 带重试的命令执行函数
retry_command() {
local max_retries=$1
shift
local command=("$@")
local retry_delay=5
for ((retry=1; retry<=max_retries; retry++)); do
if "${command[@]}"; then
return 0
fi
if [ $retry -lt $max_retries ]; then
log_info "命令执行失败,$retry_delay 秒后重试(第 $retry/$max_retries 次)..."
sleep $retry_delay
retry_delay=$((retry_delay + 5))
fi
done
log_error "命令执行失败,已达到最大重试次数"
return 1
}
# 带重试的pip安装函数
pip_install_with_retry() {
local full_package=$1
local max_retries=3
local retry_delay=10
local extra_args=${@:2}
# 提取包名(去掉版本号部分)
local package=$(echo "$full_package" | sed -E 's/[=<>~].*//')
for ((retry=1; retry<=max_retries; retry++)); do
log_info "尝试安装 $full_package(第 $retry/$max_retries 次)"
if $PIP_BIN install $extra_args $full_package; then
log_info "$full_package 安装成功"
return 0
fi
if [ $retry -lt $max_retries ]; then
log_info "安装失败,$retry_delay 秒后重试..."
sleep $retry_delay
retry_delay=$((retry_delay + 5))
fi
done
log_error "$full_package 安装失败,已达到最大重试次数"
return 1
}
# 版本比较函数(判断版本1是否>=版本2)
version_ge() {
[ "$(printf '%s\n' "$1" "$2" | sort -V | head -n1)" = "$2" ]
}
# 检测系统类型和包管理器
detect_system() {
log_info "🔍 检测系统类型..."
if [ -f /etc/os-release ]; then
. /etc/os-release
OS=$NAME
VERSION=$VERSION_ID
if [[ "$OS" == "CentOS Linux" ]]; then
OS="CentOS"
fi
elif type lsb_release >/dev/null 2>&1; then
OS=$(lsb_release -si)
VERSION=$(lsb_release -sr)
elif [ -f /etc/lsb-release ]; then
. /etc/lsb-release
OS=$DISTRIB_ID
VERSION=$DISTRIB_RELEASE
elif [ -f /etc/debian_version ]; then
OS=Debian
VERSION=$(cat /etc/debian_version)
elif [ -f /etc/redhat-release ]; then
OS=$(cat /etc/redhat-release | cut -d' ' -f1)
VERSION=$(cat /etc/redhat-release | grep -oE '[0-9]+\.[0-9]+' | head -1)
else
OS=$(uname -s)
VERSION=$(uname -r)
fi
# 检测包管理器
if command -v yum >/dev/null 2>&1; then
PACKAGE_MANAGER="yum"
elif command -v dnf >/dev/null 2>&1; then
PACKAGE_MANAGER="dnf"
elif command -v apt-get >/dev/null 2>&1; then
PACKAGE_MANAGER="apt-get"
elif command -v zypper >/dev/null 2>&1; then
PACKAGE_MANAGER="zypper"
else
PACKAGE_MANAGER="unknown"
fi
log_info "检测到系统: $OS $VERSION,包管理器: $PACKAGE_MANAGER"
}
# 检测OpenSSL版本
check_openssl_version() {
log_info "🔍 检测OpenSSL版本(要求>=3.0.0)..."
# 检查OpenSSL是否安装
if ! command -v openssl &>/dev/null; then
log_info "未安装OpenSSL,需要安装3.0.0及以上版本"
return 1
fi
# 获取完整版本号(如3.1.4)
local openssl_version=$(openssl version | awk '{print $2}')
if [ -z "$openssl_version" ]; then
log_warn "无法解析OpenSSL版本,视为不兼容"
return 1
fi
# 提取主版本号
local major_version=$(echo "$openssl_version" | cut -d'.' -f1)
# 提取次版本号
local minor_version=$(echo "$openssl_version" | cut -d'.' -f2)
log_info "当前OpenSSL版本: $openssl_version(主版本: $major_version,次版本: $minor_version)"
# 检查是否>=3.0.0
if [ "$major_version" -ge 3 ] && [ "$minor_version" -ge 0 ]; then
log_info "✅ OpenSSL版本兼容(>=3.0.0)"
return 0
else
log_warn "❌ OpenSSL版本过低($openssl_version < 3.0.0),需要升级"
return 1
fi
}
# 校验FFmpeg版本是否兼容(必须>=3.1)
check_ffmpeg_compatibility() {
log_info "🔍 校验FFmpeg版本兼容性(要求>=3.1)..."
# 检查FFmpeg是否安装
if ! command -v ffmpeg &>/dev/null; then
log_info "未安装FFmpeg,需要安装兼容版本"
return 1
fi
# 获取主版本号(如5.1.3 -> 5)
local ffmpeg_version=$(/usr/bin/ffmpeg -version | head -n1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | cut -d'.' -f1)
if [ -z "$ffmpeg_version" ]; then
log_warn "无法解析FFmpeg版本,视为不兼容"
return 1
fi
log_info "当前FFmpeg主版本号: $ffmpeg_version"
# 检查是否>=3
if [ "$ffmpeg_version" -ge 3 ]; then
log_info "✅ FFmpeg版本兼容(>=3.1)"
return 0
else
log_warn "❌ FFmpeg版本过低($ffmpeg_version < 3),需要升级"
return 1
fi
}
# 检测CMake版本(新增函数)
check_cmake_version() {
local required_version="3.25"
log_info "🔍 检测CMake版本(要求>=${required_version})..."
# 检查CMake是否安装
if ! command -v cmake &>/dev/null; then
log_info "未安装CMake,需要安装${required_version}及以上版本"
return 1
fi
# 获取完整版本号
local cmake_version=$(cmake --version | head -n1 | awk '{print $3}')
if [ -z "$cmake_version" ]; then
log_warn "无法解析CMake版本,视为不兼容"
return 1
fi
log_info "当前CMake版本: $cmake_version"
# 检查是否>=required_version
if version_ge "$cmake_version" "$required_version"; then
log_info "✅ CMake版本兼容(>=${required_version})"
return 0
else
log_warn "❌ CMake版本过低($cmake_version < ${required_version}),需要升级"
return 1
fi
}
# 卸载旧版FFmpeg(清理环境)
uninstall_old_ffmpeg() {
log_info "🧹 卸载旧版FFmpeg..."
# 根据包管理器卸载
if [ "$PACKAGE_MANAGER" = "yum" ] || [ "$PACKAGE_MANAGER" = "dnf" ]; then
$PACKAGE_MANAGER remove -y ffmpeg ffmpeg-devel >/dev/null 2>&1 || true
elif [ "$PACKAGE_MANAGER" = "apt-get" ]; then
apt-get remove -y ffmpeg libavcodec-dev libavformat-dev >/dev/null 2>&1 || true
elif [ "$PACKAGE_MANAGER" = "zypper" ]; then
zypper remove -y ffmpeg ffmpeg-devel >/dev/null 2>&1 || true
fi
# 清理残留文件(关键步骤)
rm -rf /usr/bin/ffmpeg /usr/lib64/libav* /usr/include/libav* \
/usr/local/bin/ffmpeg /usr/local/lib/libav* /usr/local/include/libav*
# 刷新动态链接库缓存
ldconfig 2>/dev/null
log_info "旧版FFmpeg清理完成"
}
# 升级CMake(新增函数)
upgrade_cmake() {
local required_version="3.25"
local install_version="3.26.4"
log_info "🔧 开始安装CMake ${install_version}(要求>=${required_version})..."
# 卸载旧版本
if [ "$PACKAGE_MANAGER" = "yum" ] || [ "$PACKAGE_MANAGER" = "dnf" ]; then
$PACKAGE_MANAGER remove -y cmake >/dev/null 2>&1 || true
elif [ "$PACKAGE_MANAGER" = "apt-get" ]; then
apt-get remove -y cmake >/dev/null 2>&1 || true
elif [ "$PACKAGE_MANAGER" = "zypper" ]; then
zypper remove -y cmake >/dev/null 2>&1 || true
fi
# 安装编译依赖
log_info "安装CMake编译依赖..."
if [ "$PACKAGE_MANAGER" = "yum" ] || [ "$PACKAGE_MANAGER" = "dnf" ]; then
$PACKAGE_MANAGER install -y gcc-c++ make openssl-devel
elif [ "$PACKAGE_MANAGER" = "apt-get" ]; then
apt-get install -y g++ make libssl-dev
elif [ "$PACKAGE_MANAGER" = "zypper" ]; then
zypper install -y gcc-c++ make libopenssl-devel
fi
# 检测系统架构
local arch=$(uname -m)
case $arch in
x86_64) local cmake_arch="x86_64" ;;
aarch64) local cmake_arch="aarch64" ;;
*)
log_error "不支持的架构: $arch"
exit 1
;;
esac
# 下载并安装新版本
local temp_dir=$(mktemp -d)
cd "$temp_dir" || {
log_error "无法进入临时目录"
exit 1
}
local cmake_tar="cmake-3.26.4.tar.gz"
local cmake_url="https://cmake.org/files/v3.26/cmake-3.26.4.tar.gz"
local backup_url="https://github.com/Kitware/CMake/releases/download/v3.26.4/cmake-3.26.4.tar.gz"
log_info "下载CMake ${install_version}..."
if ! retry_command 2 wget --no-check-certificate "$cmake_url"; then
log_warn "主链接下载失败,尝试备用链接..."
if ! retry_command 2 wget --no-check-certificate "$backup_url"; then
log_error "所有链接下载CMake失败"
cd .. && rm -rf "$temp_dir"
exit 1
fi
fi
# 验证文件是否存在
if [ ! -f "$cmake_tar" ]; then
log_error "CMake安装包下载失败,文件不存在"
cd .. && rm -rf "$temp_dir"
exit 1
fi
# 解压源码包
log_info "解压CMake安装包..."
if ! tar xzf "$cmake_tar"; then
log_error "CMake安装包解压失败"
cd .. && rm -rf "$temp_dir"
exit 1
fi
# 进入源码目录
cd "cmake-3.26.4" || {
log_error "找不到CMake源码目录"
cd .. && rm -rf "$temp_dir"
exit 1
}
# 配置编译选项
log_info "配置CMake编译选项..."
./bootstrap --prefix=/opt/cmake || {
log_error "CMake配置失败"
cd ../.. && rm -rf "$temp_dir"
exit 1
}
# 编译(根据CPU核心数调整线程数)
local cpu_cores=$(nproc)
local make_jobs=$(( cpu_cores > 4 ? 4 : cpu_cores ))
log_info "使用 $make_jobs 线程编译CMake..."
make -j$make_jobs || {
log_error "CMake编译失败"
cd ../.. && rm -rf "$temp_dir"
exit 1
}
# 安装
log_info "安装CMake..."
make install || {
log_error "CMake安装失败"
cd ../.. && rm -rf "$temp_dir"
exit 1
}
# 配置链接
rm -f /usr/bin/cmake /usr/local/bin/cmake
ln -sf /opt/cmake/bin/cmake /usr/bin/cmake
ln -sf /opt/cmake/bin/cmake /usr/local/bin/cmake
# 清理临时文件
cd ../.. && rm -rf "$temp_dir"
# 验证安装
if ! command -v cmake &> /dev/null; then
log_error "CMake安装失败,无法找到可执行文件"
exit 1
fi
# 再次检查版本
if check_cmake_version; then
log_info "✅ CMake升级成功"
return 0
else
log_error "CMake升级后仍不满足版本要求"
exit 1
fi
}
# FFmpeg安装函数(修复OpenSSL识别问题)
install_compatible_ffmpeg() {
log_info "🔧 安装兼容版本FFmpeg 5.1.3(与av包兼容)..."
# 检测OpenSSL是lib还是lib64目录
if [ -d "$OPENSSL_DIR/lib64" ]; then
OPENSSL_LIB_DIR="$OPENSSL_DIR/lib64"
else
OPENSSL_LIB_DIR="$OPENSSL_DIR/lib"
fi
# 预检查OpenSSL是否被正确识别
log_info "预检查OpenSSL配置..."
if ! pkg-config --exists openssl; then
log_error "pkg-config仍然无法检测到OpenSSL"
log_error "请检查$OPENSSL_LIB_DIR/pkgconfig/openssl.pc是否存在"
exit 1
fi
local openssl_version=$(pkg-config --modversion openssl)
log_info "pkg-config报告的OpenSSL版本: $openssl_version"
# 检查主版本是否>=3
local major_version=$(echo "$openssl_version" | cut -d'.' -f1)
if [ "$major_version" -lt 3 ]; then
log_error "pkg-config报告的OpenSSL版本仍然低于3.0.0"
exit 1
fi
local ffmpeg_version="5.1.3"
local ffmpeg_tar="ffmpeg-${ffmpeg_version}.tar.bz2"
local install_prefix="/usr/local/ffmpeg"
# 安装编译依赖
log_info "安装FFmpeg编译依赖..."
if [ "$PACKAGE_MANAGER" = "yum" ] || [ "$PACKAGE_MANAGER" = "dnf" ]; then
$PACKAGE_MANAGER install -y yasm nasm make cmake zlib-devel wget
# 彻底卸载系统默认的openssl相关包
$PACKAGE_MANAGER remove -y openssl openssl-devel
elif [ "$PACKAGE_MANAGER" = "apt-get" ]; then
apt-get install -y yasm nasm make cmake zlib1g-dev wget
apt-get remove -y openssl libssl-dev
elif [ "$PACKAGE_MANAGER" = "zypper" ]; then
zypper install -y yasm nasm make cmake zlib-devel wget
zypper remove -y openssl openssl-devel
fi
# 创建临时编译目录
local build_dir="/tmp/ffmpeg-build"
rm -rf "$build_dir"
mkdir -p "$build_dir"
cd "$build_dir"
# 下载源码(多镜像重试)
local mirrors=(
"https://ffmpeg.org/releases/${ffmpeg_tar}"
"https://mirror.klaus-uwe.me/ffmpeg/releases/${ffmpeg_tar}"
"https://ftp.vim.org/ftp/ffmpeg/releases/${ffmpeg_tar}"
)
local download_success=0
for mirror in "${mirrors[@]}"; do
log_info "尝试从镜像下载FFmpeg ${ffmpeg_version}:$mirror"
if retry_command 2 wget --no-check-certificate "$mirror"; then
log_info "✅ 从镜像下载成功"
download_success=1
break
fi
done
if [ $download_success -eq 0 ]; then
log_error "所有镜像下载FFmpeg失败"
exit 1
fi
# 解压源码
log_info "解压FFmpeg源码..."
if ! tar xjf "$ffmpeg_tar"; then
log_error "源码解压失败,文件可能损坏"
exit 1
fi
cd "ffmpeg-${ffmpeg_version}"
# 清理之前的配置缓存
make distclean 2>/dev/null || true
# 配置编译选项
log_info "配置FFmpeg编译选项,强制使用OpenSSL $OPENSSL_DIR..."
# 显式设置所有必要的环境变量,使用正确的lib目录
export PKG_CONFIG_PATH="$OPENSSL_LIB_DIR/pkgconfig"
export CFLAGS="-I$OPENSSL_DIR/include"
export CPPFLAGS="-I$OPENSSL_DIR/include"
export LDFLAGS="-L$OPENSSL_LIB_DIR -Wl,-rpath,$OPENSSL_LIB_DIR"
export LIBS="-ldl"
./configure \
--prefix="$install_prefix" \
--enable-shared \
--enable-gpl \
--enable-version3 \
--enable-openssl \
--disable-static \
--enable-pic \
--extra-cflags="-I$OPENSSL_DIR/include" \
--extra-ldflags="-L$OPENSSL_LIB_DIR -Wl,-rpath,$OPENSSL_LIB_DIR" \
--extra-libs="-ldl"
# 编译(根据CPU核心数调整线程数)
local cpu_cores=$(nproc)
local make_jobs=$(( cpu_cores > 4 ? 4 : cpu_cores ))
log_info "使用 $make_jobs 线程编译FFmpeg(耗时较长,请耐心等待)..."
if ! make -j$make_jobs; then
log_error "多线程编译失败,尝试单线程编译..."
if ! make -j1; then
log_error "FFmpeg编译彻底失败"
exit 1
fi
fi
# 安装
log_info "安装FFmpeg..."
make install
# 配置系统识别新FFmpeg
log_info "配置FFmpeg环境变量..."
# 1. 添加动态链接库路径
echo "$install_prefix/lib" > /etc/ld.so.conf.d/ffmpeg.conf
ldconfig # 刷新缓存
# 2. 创建软链接到系统路径(确保能被找到)
ln -sf "$install_prefix/bin/ffmpeg" /usr/bin/ffmpeg
ln -sf "$install_prefix/bin/ffprobe" /usr/bin/ffprobe
# 3. 配置pkg-config路径(av包编译需要)
ln -sf "$install_prefix/lib/pkgconfig"/* /usr/lib64/pkgconfig/ 2>/dev/null
ln -sf "$install_prefix/lib/pkgconfig"/* /usr/lib/pkgconfig/ 2>/dev/null
# 验证安装结果
local installed_version=$(ffmpeg -version | head -n1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
if [ "$installed_version" = "$ffmpeg_version" ]; then
log_info "✅ FFmpeg $ffmpeg_version 安装成功"
else
log_error "FFmpeg安装版本不符(实际: $installed_version,预期: $ffmpeg_version)"
exit 1
fi
}
# 安装并验证FFmpeg(整合校验、卸载旧版、安装新版)
setup_compatible_ffmpeg() {
log_info "🎥 开始FFmpeg环境配置..."
# 先校验是否兼容
if check_ffmpeg_compatibility; then
return 0
fi
# 不兼容则卸载旧版
uninstall_old_ffmpeg
# 安装兼容版本
install_compatible_ffmpeg
# 再次验证
check_ffmpeg_compatibility || {
log_error "FFmpeg配置后仍不兼容,无法继续安装"
exit 1
}
}
# 搜索可能的GCC路径(优先高版本)
search_gcc_paths() {
log_info "🔍 搜索可能的GCC路径..."
local possible_paths=(
"/usr/local/gcc-"*"/bin/gcc"
"/opt/rh/devtoolset-*/root/usr/bin/gcc"
"/usr/bin/gcc-"*
"/usr/local/bin/gcc"
"/usr/bin/gcc"
"/usr/lib/gcc/"*"/"*"/bin/gcc"
"$HOME/.local/bin/gcc"
)
for path in "${possible_paths[@]}"; do
if [[ $path == *"*"* ]]; then
for matched_path in $(echo $path 2>/dev/null | sort -Vr); do
if [ -x "$matched_path" ]; then
log_info "发现可执行的GCC: $matched_path"
echo "$matched_path"
return 0
fi
done
else
if [ -x "$path" ]; then
log_info "发现可执行的GCC: $path"
echo "$path"
return 0
fi
fi
done
if command -v gcc >/dev/null 2>&1; then
local gcc_path=$(command -v gcc)
log_info "通过环境变量发现GCC: $gcc_path"
echo "$gcc_path"
return 0
fi
log_info "未发现GCC可执行文件"
echo ""
return 1
}
# 解析GCC版本号
parse_gcc_version() {
local gcc_path=$1
if [ -z "$gcc_path" ] || [ ! -x "$gcc_path" ]; then
log_error "无效的GCC路径: $gcc_path"
echo "0"
return 1
fi
log_info "解析GCC版本: $gcc_path"
local version_output=$("$gcc_path" --version 2>/dev/null | head -n1)
local version=$(echo "$version_output" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -n1 | cut -d'.' -f1)
if [ -z "$version" ] || ! [[ "$version" =~ ^[0-9]+$ ]]; then
log_error "无法解析GCC版本,输出: $version_output"
echo "0"
return 1
fi
log_info "解析到GCC主版本号: $version"
echo "$version"
return 0
}
# 检查GCC版本并升级
check_and_upgrade_gcc() {
local required_version=${1:-9}
log_info "🔍 检查GCC版本(要求: $required_version 及以上)..."
export PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:$PATH
log_info "当前环境变量PATH: $PATH"
local gcc_path=$(search_gcc_paths)
local gcc_version=0
if [ -n "$gcc_path" ]; then
gcc_version=$(parse_gcc_version "$gcc_path")
if ! [[ "$gcc_version" =~ ^[0-9]+$ ]]; then
log_error "获取的GCC版本不是有效整数: $gcc_version"
gcc_version=0
fi
fi
if [ "$gcc_version" -ge "$required_version" ]; then
log_info "✅ GCC版本满足要求 ($gcc_version >= $required_version)"
echo "$gcc_version" > /tmp/gcc_version.txt
return 0
elif [ "$gcc_version" -gt 0 ]; then
log_info "❌ GCC版本过低 ($gcc_version < $required_version),需要升级"
else
log_info "❌ 未安装有效GCC,将进行安装"
fi
install_gcc "$required_version"
return $?
}
# 安装高版本GCC
install_gcc() {
local required_version=$1
log_info "🔧 开始安装GCC $required_version 或更高版本..."
case $OS in
"CentOS"|"Red Hat"|"Oracle Linux")
if install_gcc_rhel "$required_version"; then
return 0
fi
;;
"Ubuntu"|"Debian")
if install_gcc_debian "$required_version"; then
return 0
fi
;;
"SUSE Linux"|"openSUSE")
if install_gcc_suse "$required_version"; then
return 0
fi
;;
*)
log_warn "未识别的系统: $OS,尝试通用安装方法"
;;
esac
if install_gcc_from_source "$required_version"; then
return 0
else
log_error "所有GCC安装方式均失败"
return 1
fi
}
# RHEL系系统安装GCC
install_gcc_rhel() {
local required_version=$1
log_info "尝试在RHEL系系统安装GCC $required_version..."
if [ "$PACKAGE_MANAGER" = "yum" ]; then
yum remove -y gcc gcc-c++ >/dev/null 2>&1 || true
else
dnf remove -y gcc gcc-c++ >/dev/null 2>&1 || true
fi
if [ "$required_version" -le 10 ]; then
log_info "尝试通过Software Collections安装GCC $required_version..."
if ! $PACKAGE_MANAGER install -y centos-release-scl-rh 2>/dev/null; then
log_info "尝试备选SCL仓库..."
if ! $PACKAGE_MANAGER install -y centos-release-scl 2>/dev/null; then
log_warn "SCL仓库安装失败,尝试其他方法"
else
local scl_package="devtoolset-$required_version-gcc"
if ! $PACKAGE_MANAGER install -y "$scl_package" "$scl_package-c++" "devtoolset-$required_version-binutils"; then
log_warn "安装devtoolset-$required_version失败,尝试更高版本"
else
source /opt/rh/devtoolset-$required_version/enable
ln -sf /opt/rh/devtoolset-$required_version/root/usr/bin/gcc /usr/local/bin/gcc
ln -sf /opt/rh/devtoolset-$required_version/root/usr/bin/g++ /usr/local/bin/g++
log_info "✅ 成功安装GCC $required_version (SCL方式)"
echo "$required_version" > /tmp/gcc_version.txt
return 0
fi
fi
fi
fi
log_info "尝试通过默认仓库安装GCC..."
if $PACKAGE_MANAGER install -y gcc gcc-c++; then
local new_version=$(parse_gcc_version "$(command -v gcc)")
if [ -n "$new_version" ] && [ "$new_version" -ge "$required_version" ]; then
log_info "✅ 成功安装GCC $new_version (默认仓库)"
echo "$new_version" > /tmp/gcc_version.txt
return 0
else
log_warn "默认仓库安装的GCC版本不足,需要从源码编译"
fi
else
log_warn "默认仓库安装GCC失败"
fi
return 1
}
# Debian/Ubuntu系系统安装GCC
install_gcc_debian() {
local required_version=$1
log_info "尝试在Debian/Ubuntu系系统安装GCC $required_version..."
retry_command 3 apt-get update -y
apt-get remove -y gcc gcc-c++ >/dev/null 2>&1 || true
local gcc_package="gcc-$required_version"
local gpp_package="g++-$required_version"
if apt-get install -y "$gcc_package" "$gpp_package"; then
update-alternatives --install /usr/bin/gcc gcc /usr/bin/"$gcc_package" 100
update-alternatives --install /usr/bin/g++ g++ /usr/bin/"$gpp_package" 100
local new_version=$(parse_gcc_version "$(command -v gcc)")
if [ -n "$new_version" ] && [ "$new_version" -ge "$required_version" ]; then
log_info "✅ 成功安装GCC $new_version (apt方式)"
echo "$new_version" > /tmp/gcc_version.txt
return 0
fi
else
log_warn "安装GCC $required_version 失败,尝试安装默认版本"
if apt-get install -y gcc g++; then
local new_version=$(parse_gcc_version "$(command -v gcc)")
if [ -n "$new_version" ] && [ "$new_version" -ge "$required_version" ]; then
log_info "✅ 成功安装GCC $new_version (apt方式)"
echo "$new_version" > /tmp/gcc_version.txt
return 0
else
log_warn "默认仓库安装的GCC版本不足,需要从源码编译"
fi
else
log_warn "apt安装GCC失败"
fi
fi
return 1
}
# SUSE系系统安装GCC
install_gcc_suse() {
local required_version=$1
log_info "尝试在SUSE系系统安装GCC $required_version..."
if zypper install -y gcc-$required_version gcc-c++-$required_version; then
local new_version=$(parse_gcc_version "$(command -v gcc)")
if [ -n "$new_version" ] && [ "$new_version" -ge "$required_version" ]; then
log_info "✅ 成功安装GCC $new_version (zypper方式)"
echo "$new_version" > /tmp/gcc_version.txt
return 0
fi
else
log_warn "安装GCC $required_version 失败,尝试安装默认版本"
if zypper install -y gcc gcc-c++; then
local new_version=$(parse_gcc_version "$(command -v gcc)")
if [ -n "$new_version" ] && [ "$new_version" -ge "$required_version" ]; then
log_info "✅ 成功安装GCC $new_version (zypper方式)"
echo "$new_version" > /tmp/gcc_version.txt
return 0
else
log_warn "默认仓库安装的GCC版本不足,需要从源码编译"
fi
else
log_warn "zypper安装GCC失败"
fi
fi
return 1
}
# 从源码编译安装GCC
install_gcc_from_source() {
local required_version=$1
local install_version="9.4.0"
if [ "$required_version" -gt 9 ]; then
install_version="10.5.0"
fi
if [ "$required_version" -gt 10 ]; then
install_version="11.4.0"
fi
if [ "$required_version" -gt 11 ]; then
install_version="12.3.0"
fi
log_info "尝试从源码编译安装GCC ${install_version}..."
log_info "安装GCC编译依赖..."
case $OS in
"CentOS"|"Red Hat"|"Oracle Linux")
$PACKAGE_MANAGER install -y gmp-devel mpfr-devel libmpc-devel wget make lbzip2 bzip2 patch
;;
"Ubuntu"|"Debian")
apt-get install -y libgmp-dev libmpfr-dev libmpc-dev wget make lbzip2 bzip2 patch
;;
"SUSE Linux"|"openSUSE")
zypper install -y gmp-devel mpfr-devel libmpc-devel wget make lbzip2 bzip2 patch
;;
*)
log_warn "未识别系统,尝试安装常见依赖"
if command -v apt-get >/dev/null 2>&1; then
apt-get install -y libgmp-dev libmpfr-dev libmpc-dev wget make lbzip2 bzip2 patch
elif command -v yum >/dev/null 2>&1; then
yum install -y gmp-devel mpfr-devel libmpc-devel wget make lbzip2 bzip2 patch
fi
;;
esac
local gcc_workdir="/tmp/gcc-build"
local gcc_tar="gcc-${install_version}.tar.gz"
local gcc_major_version=$(echo "$install_version" | cut -d'.' -f1)
rm -rf "$gcc_workdir"
mkdir -p "$gcc_workdir"
cd "$gcc_workdir"
# 下载GCC源码
local mirrors=(
"http://gnu.mirror.constant.com/gcc/gcc-${install_version}/${gcc_tar}"
"https://mirror.lzu.edu.cn/gnu/gcc/gcc-${install_version}/${gcc_tar}"
"https://mirrors.ustc.edu.cn/gnu/gcc/gcc-${install_version}/${gcc_tar}"
"https://ftp.gnu.org/gnu/gcc/gcc-${install_version}/${gcc_tar}"
)
local download_success=0
for mirror in "${mirrors[@]}"; do
log_info "尝试从镜像下载GCC ${install_version}:$mirror"
if retry_command 2 wget --no-check-certificate "$mirror"; then
log_info "✅ 从镜像下载成功"
download_success=1
break
fi
done
if [ $download_success -eq 0 ]; then
log_error "所有镜像下载失败"
return 1
fi
log_info "解压GCC源码..."
tar xzf "$gcc_tar" || {
log_error "源码解压失败,文件可能损坏"
return 1
}
cd "gcc-${install_version}"
log_info "准备GCC依赖项..."
if ! ./contrib/download_prerequisites; then
log_error "自动下载依赖失败,尝试手动下载..."
wget ftp://gcc.gnu.org/pub/gcc/infrastructure/gmp-6.1.0.tar.bz2 && tar xjf gmp-6.1.0.tar.bz2 && ln -sf gmp-6.1.0 gmp
wget ftp://gcc.gnu.org/pub/gcc/infrastructure/mpfr-3.1.4.tar.bz2 && tar xjf mpfr-3.1.4.tar.bz2 && ln -sf mpfr-3.1.4 mpfr
wget ftp://gcc.gnu.org/pub/gcc/infrastructure/mpc-1.0.3.tar.gz && tar xzf mpc-1.0.3.tar.gz && ln -sf mpc-1.0.3 mpc
wget ftp://gcc.gnu.org/pub/gcc/infrastructure/isl-0.18.tar.bz2 && tar xjf isl-0.18.tar.bz2 && ln -sf isl-0.18 isl
fi
mkdir -p build
cd build
log_info "配置GCC编译选项..."
../configure --prefix=/usr/local/gcc-${install_version} \
--enable-bootstrap \
--enable-shared \
--enable-threads=posix \
--enable-checking=release \
--with-system-zlib \
--enable-__cxa_atexit \
--disable-libunwind-exceptions \
--enable-gnu-unique-object \
--enable-linker-build-id \
--with-gcc-major-version-only \
--enable-libstdcxx-dual-abi \
--enable-languages=c,c++ \
--disable-multilib
log_info "开始编译GCC(这可能需要1-2小时)..."
local cpu_cores=$(nproc)
local make_jobs=$(( cpu_cores > 4 ? 4 : cpu_cores ))
log_info "使用 $make_jobs 线程进行编译"
if ! make -j$make_jobs; then
log_error "多线程编译失败,尝试单线程编译..."
make -j1
fi
log_info "安装GCC..."
make install || {
log_error "GCC安装失败"
return 1
}
log_info "配置系统使用新GCC..."
ln -sf /usr/local/gcc-${install_version}/bin/gcc /usr/local/bin/gcc
ln -sf /usr/local/gcc-${install_version}/bin/g++ /usr/local/bin/g++
ln -sf /usr/local/gcc-${install_version}/bin/gcc /usr/local/bin/cc
ln -sf /usr/local/gcc-${install_version}/bin/g++ /usr/local/bin/c++
echo "/usr/local/gcc-${install_version}/lib64" > /etc/ld.so.conf.d/gcc-${install_version}.conf
ldconfig 2>/dev/null
log_info "✅ GCC源码编译安装成功,版本: $gcc_major_version"
echo "$gcc_major_version" > /tmp/gcc_version.txt
return 0
}
# 检查是否为 root
if [ "$EUID" -ne 0 ]; then
log_error "请以 root 权限运行此脚本"
exit 1
fi
# 设置变量
WORK_DIR="/root/open-webui"
VENV_DIR="${WORK_DIR}/venv" # 虚拟环境目录
PYTHON_DIR="/usr/local/python3.11"
SYSTEM_PYTHON_BIN="${PYTHON_DIR}/bin/python3.11"
PYTHON_BIN="${VENV_DIR}/bin/python" # 使用虚拟环境的python
PIP_BIN="${VENV_DIR}/bin/pip" # 使用虚拟环境的pip
OPENSSL_DIR="/usr/local/openssl-3.1.4"
OPENSSL_TGZ="openssl-3.1.4.tar.gz"
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
LOCAL_OPENSSL_TGZ="${SCRIPT_DIR}/${OPENSSL_TGZ}"
WEBUI_PORT=9000
PIP_MIRROR="https://pypi.tuna.tsinghua.edu.cn/simple"
PIP_TIMEOUT=120
# 禁用失效仓库
disable_broken_repos() {
log_info "🚫 禁用失效的仓库..."
if command -v yum-config-manager >/dev/null 2>&1; then
yum-config-manager --disable centos-sclo-rh || true
yum-config-manager --disable centos-sclo-sclo || true
yum-config-manager --disable docker-ce-stable || true
yum-config-manager --disable yarn || true
yum-config-manager --save --setopt=*.skip_if_unavailable=true || true
fi
}
# 替换镜像源为 CentOS Vault
setup_centos_vault() {
if [[ "$OS" == "CentOS" && "$VERSION" == "7"* ]]; then
log_info "🔧 替换CentOS 7镜像源为 Vault..."
cd /etc/yum.repos.d
mv CentOS-Base.repo CentOS-Base.repo.bak 2>/dev/null || true
cat > CentOS-Base.repo << 'EOL'
[base]
name=CentOS-7.9.2009 - Base
baseurl=https://vault.centos.org/7.9.2009/os/x86_64/
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7
enabled=1
[updates]
name=CentOS-7.9.2009 - Updates
baseurl=https://vault.centos.org/7.9.2009/updates/x86_64/
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7
enabled=1
[extras]
name=CentOS-7.9.2009 - Extras
baseurl=https://vault.centos.org/7.9.2009/extras/x86_64/
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7
enabled=1
[centosplus]
name=CentOS-7.9.2009 - Plus
baseurl=https://vault.centos.org/7.9.2009/centosplus/x86_64/
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7
enabled=0
EOL
yum clean all
yum makecache
fi
}
# 安装系统依赖(新增Perl模块)
install_system_dependencies() {
log_info "🧰 安装系统依赖..."
case $OS in
"CentOS"|"Red Hat"|"Oracle Linux")
$PACKAGE_MANAGER install -y epel-release gcc make perl perl-IPC-Cmd perl-Error zlib-devel bzip2-devel \
readline-devel sqlite-devel libffi-devel xz-devel wget curl
;;
"Ubuntu"|"Debian")
apt-get install -y build-essential libssl-dev zlib1g-dev libbz2-dev \
libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev libncursesw5-dev \
xz-utils tk-dev libffi-dev liblzma-dev perl libipc-cmd-perl
;;
"SUSE Linux"|"openSUSE")
zypper install -y gcc make zlib-devel bzip2-devel readline-devel sqlite3-devel \
libffi-devel xz-devel wget curl perl perl-IPC-Cmd
;;
*)
log_warn "未识别系统,尝试安装常见依赖"
;;
esac
}
# 创建Python虚拟环境
create_venv() {
log_info "🔧 创建Python虚拟环境..."
# 如果虚拟环境已存在,先删除
if [ -d "$VENV_DIR" ]; then
log_info "虚拟环境已存在,重新创建..."
rm -rf "$VENV_DIR"
fi
# 使用系统Python创建虚拟环境
if ! $SYSTEM_PYTHON_BIN -m venv "$VENV_DIR"; then
log_error "创建虚拟环境失败,尝试安装venv模块..."
# 安装venv所需包
if [ "$PACKAGE_MANAGER" = "yum" ] || [ "$PACKAGE_MANAGER" = "dnf" ]; then
$PACKAGE_MANAGER install -y python3-venv
elif [ "$PACKAGE_MANAGER" = "apt-get" ]; then
apt-get install -y python3-venv
fi
# 再次尝试创建
if ! $SYSTEM_PYTHON_BIN -m venv "$VENV_DIR"; then
log_error "创建虚拟环境失败,无法继续安装"
exit 1
fi
fi
log_info "✅ 虚拟环境创建成功: $VENV_DIR"
}
# 准备 OpenSSL 安装包
prepare_openssl_package() {
cd "$WORK_DIR"
local expected_md5="653ad58812c751b887e8ec37e02bba70"
if [ -f "$LOCAL_OPENSSL_TGZ" ]; then
log_info "📂 发现脚本同级目录存在OpenSSL安装包:$LOCAL_OPENSSL_TGZ"
local actual_md5=$(md5sum "$LOCAL_OPENSSL_TGZ" | awk '{print $1}')
log_info "本地包MD5: $actual_md5,预期MD5: $expected_md5"
if [ "$actual_md5" = "$expected_md5" ]; then
log_info "✅ MD5校验通过,直接使用本地包"
cp "$LOCAL_OPENSSL_TGZ" "$WORK_DIR/"
return 0
else
log_warn "❌ MD5校验不匹配,忽略本地包"
rm -f "$WORK_DIR/$OPENSSL_TGZ"
fi
else
log_info "ℹ️ 脚本同级目录未找到OpenSSL安装包"
fi
if [ -f "$WORK_DIR/$OPENSSL_TGZ" ]; then
log_info "📂 发现工作目录存在OpenSSL安装包"
local actual_md5=$(md5sum "$WORK_DIR/$OPENSSL_TGZ" | awk '{print $1}')
if [ "$actual_md5" = "$expected_md5" ]; then
log_info "✅ MD5校验通过,使用工作目录包"
return 0
else
log_warn "❌ 工作目录包损坏,重新下载"
rm -f "$WORK_DIR/$OPENSSL_TGZ"
fi
fi
log_info "📥 开始在线下载OpenSSL安装包..."
download_openssl
}
# 下载 OpenSSL
download_openssl() {
cd "$WORK_DIR"
local openssl_url="https://github.com/openssl/openssl/releases/download/openssl-3.1.4/openssl-3.1.4.tar.gz"
if [ -f "$OPENSSL_TGZ" ]; then
rm -f "$OPENSSL_TGZ"
fi
MAX_RETRY=5
RETRY_DELAY=10
for i in $(seq 1 $MAX_RETRY); do
log_info "尝试从官方地址下载(第 $i/$MAX_RETRY 次): $openssl_url"
if wget --no-check-certificate -q --show-progress "$openssl_url"; then
log_info "✅ 下载成功"
return 0
else
log_error "❌ 第 $i 次下载失败,$RETRY_DELAY 秒后重试..."
sleep $RETRY_DELAY
RETRY_DELAY=$((RETRY_DELAY + 5))
fi
done
log_error "❌ 达到最大重试次数,下载失败"
log_error "请手动下载 openssl-3.1.4.tar.gz 并放在脚本同级目录后重试"
exit 1
}
# OpenSSL安装函数(修复lib64目录识别问题)
install_openssl() {
log_info "🔐 开始安装 OpenSSL 3.1.4..."
if [ -d "$OPENSSL_DIR" ]; then
rm -rf "$OPENSSL_DIR"
fi
cd "$WORK_DIR"
prepare_openssl_package
# 清理之前可能存在的解压目录
rm -rf openssl-3.1.4
tar xzf "$OPENSSL_TGZ" || {
log_error "❌ 安装包解压失败"
exit 1
}
cd openssl-3.1.4
# 确保Perl模块可用
log_info "检查Perl模块IPC::Cmd是否可用..."
if ! perl -e "use IPC::Cmd;"; then
log_error "Perl模块IPC::Cmd仍然缺失,尝试手动安装..."
# 针对不同系统尝试手动安装缺失的Perl模块
if [ "$PACKAGE_MANAGER" = "yum" ] || [ "$PACKAGE_MANAGER" = "dnf" ]; then
$PACKAGE_MANAGER install -y perl-IPC-Cmd
elif [ "$PACKAGE_MANAGER" = "apt-get" ]; then
apt-get install -y libipc-cmd-perl
elif [ "$PACKAGE_MANAGER" = "zypper" ]; then
zypper install -y perl-IPC-Cmd
else
# 尝试CPAN安装作为最后的手段
log_info "尝试通过CPAN安装IPC::Cmd..."
perl -MCPAN -e 'install IPC::Cmd'
fi
# 再次检查
if ! perl -e "use IPC::Cmd;"; then
log_error "❌ 无法安装Perl模块IPC::Cmd,无法继续"
exit 1
fi
fi
./Configure linux-x86_64 --prefix="$OPENSSL_DIR" --openssldir="$OPENSSL_DIR/etc" shared zlib
make -j$(nproc)
make install_sw
log_info "🔗 配置 OpenSSL 环境变量和pkg-config..."
# 检测是lib还是lib64目录
if [ -d "$OPENSSL_DIR/lib64" ]; then
OPENSSL_LIB_DIR="$OPENSSL_DIR/lib64"
log_info "检测到OpenSSL使用lib64目录结构"
else
OPENSSL_LIB_DIR="$OPENSSL_DIR/lib"
log_info "检测到OpenSSL使用lib目录结构"
fi
# 配置pkg-config以优先使用我们安装的OpenSSL
if [ -d /usr/lib64/pkgconfig ]; then
# 备份并移除系统默认的openssl.pc
mv /usr/lib64/pkgconfig/openssl.pc /usr/lib64/pkgconfig/openssl.pc.bak 2>/dev/null || true
# 创建指向我们安装的openssl.pc的软链接
ln -sf "$OPENSSL_LIB_DIR/pkgconfig/openssl.pc" /usr/lib64/pkgconfig/openssl.pc
fi
if [ -d /usr/lib/pkgconfig ]; then
# 备份并移除系统默认的openssl.pc
mv /usr/lib/pkgconfig/openssl.pc /usr/lib/pkgconfig/openssl.pc.bak 2>/dev/null || true
# 创建指向我们安装的openssl.pc的软链接
ln -sf "$OPENSSL_LIB_DIR/pkgconfig/openssl.pc" /usr/lib/pkgconfig/openssl.pc
fi
# 配置环境变量,使用正确的lib目录
export PKG_CONFIG_PATH="$OPENSSL_LIB_DIR/pkgconfig:$PKG_CONFIG_PATH"
export LD_LIBRARY_PATH="$OPENSSL_LIB_DIR:$LD_LIBRARY_PATH"
export PATH="$OPENSSL_DIR/bin:$PATH"
echo "$OPENSSL_LIB_DIR" > /etc/ld.so.conf.d/openssl.conf
ldconfig
# 替换系统默认的openssl
rm -f /usr/bin/openssl
ln -sf "$OPENSSL_DIR/bin/openssl" /usr/bin/openssl
# 验证OpenSSL版本和pkg-config配置
log_info "当前系统默认OpenSSL版本: $(openssl version | awk '{print $2}')"
# 检查openssl.pc是否存在
if [ -f "$OPENSSL_LIB_DIR/pkgconfig/openssl.pc" ]; then
log_info "openssl.pc文件存在: $OPENSSL_LIB_DIR/pkgconfig/openssl.pc"
log_info "pkg-config检测到的OpenSSL版本: $(pkg-config --modversion openssl 2>/dev/null || echo "未检测到")"
else
log_error "openssl.pc文件不存在于预期位置: $OPENSSL_LIB_DIR/pkgconfig/openssl.pc"
log_error "OpenSSL安装可能不完整"
exit 1
fi
}
# OpenSSL安装主函数
setup_compatible_openssl() {
log_info "🔒 开始OpenSSL环境配置..."
# 先校验是否兼容
if check_openssl_version; then
return 0
fi
# 不兼容或未安装则安装新版
install_openssl
# 再次验证
check_openssl_version || {
log_error "OpenSSL配置后仍不兼容,无法继续安装"
exit 1
}
}
# 检查 Python SSL 支持
check_python_ssl() {
log_info "🔍 检查 Python SSL 支持..."
if $PYTHON_BIN -c "import ssl; print('SSL 支持正常:', ssl.OPENSSL_VERSION)" 2>/dev/null; then
log_info "✅ Python SSL 支持正常"
return 0
else
log_error "❌ Python SSL 支持异常"
return 1
fi
}
# 安装 Python 3.11
install_python() {
if [ -x "$SYSTEM_PYTHON_BIN" ]; then
log_info "✅ Python 3.11 已安装"
# 创建虚拟环境
create_venv
# 检查虚拟环境中的Python SSL支持
if check_python_ssl; then
return 0
else
log_error "虚拟环境中Python SSL支持异常,尝试重新安装Python"
fi
fi
log_info "📥 安装 Python 3.11..."
cd "$WORK_DIR"
PYTHON_TGZ="Python-3.11.0.tgz"
local expected_py_size=26006345
if [ ! -f "$PYTHON_TGZ" ]; then
log_info "未发现本地Python源码包,开始下载..."
if ! retry_command 3 curl -LO --retry 3 --connect-timeout 10 https://www.python.org/ftp/python/3.11.0/Python-3.11.0.tgz; then
log_error "❌ Python 源码包下载失败"
exit 1
fi
else
local actual_py_size=$(stat -c%s "$PYTHON_TGZ" 2>/dev/null || echo 0)
if [ "$actual_py_size" -ne "$expected_py_size" ]; then
log_info "本地Python源码包不完整,重新下载..."
rm -f "$PYTHON_TGZ"
if ! retry_command 3 curl -LO --retry 3 --connect-timeout 10 https://www.python.org/ftp/python/3.11.0/Python-3.11.0.tgz; then
log_error "❌ Python 源码包下载失败"
exit 1
fi
else
log_info "本地Python源码包完整,直接使用"
fi
fi
if [ ! -d "Python-3.11.0" ]; then
log_info "解压Python源码..."
tar xzf "$PYTHON_TGZ"
else
log_info "Python源码已解压,跳过解压步骤"
fi
chown -R root:root "Python-3.11.0"
cd Python-3.11.0
# 检测OpenSSL库目录
if [ -d "$OPENSSL_DIR/lib64" ]; then
OPENSSL_LIB_DIR="$OPENSSL_DIR/lib64"
else
OPENSSL_LIB_DIR="$OPENSSL_DIR/lib"
fi
CPPFLAGS="-I$OPENSSL_DIR/include" \
LDFLAGS="-L$OPENSSL_LIB_DIR -Wl,-rpath,$OPENSSL_LIB_DIR -Wl,--enable-new-dtags" \
./configure --prefix="$PYTHON_DIR" \
--enable-optimizations \
--with-ssl="$OPENSSL_DIR" \
--enable-ipv6 \
--with-ensurepip=install
make -j$(nproc)
make install
log_info "🔗 创建 Python 软链接..."
ln -sf "$PYTHON_DIR/bin/python3.11" /usr/local/bin/python3
ln -sf "$PYTHON_DIR/bin/pip3.11" /usr/local/bin/pip3
chown -R root:root "$PYTHON_DIR"
# 创建虚拟环境
create_venv
check_python_ssl || {
log_error "❌ Python SSL 支持修复失败"
exit 1
}
}
# 检查 Python 版本
check_python_version() {
log_info "🔍 验证 Python 版本..."
if ! command -v "$PYTHON_BIN" &>/dev/null; then
log_error "未找到 Python3"
exit 1
fi
PYTHON_VERSION=$($PYTHON_BIN -c 'import sys; print(".".join(map(str, sys.version_info[:2])))')
log_info "当前 Python 版本: $PYTHON_VERSION"
if [ "$(echo "$PYTHON_VERSION >= 3.11" | bc)" -ne 1 ]; then
log_error "需要 Python 3.11 或更高版本"
exit 1
fi
}
# 配置pip镜像源
configure_pip_mirror() {
log_info "🔧 配置 pip 镜像源为 $PIP_MIRROR..."
mkdir -p /root/.pip
cat > /root/.pip/pip.conf << EOF
[global]
index-url = $PIP_MIRROR
trusted-host = $(echo $PIP_MIRROR | awk -F/ '{print $3}')
timeout = $PIP_TIMEOUT
EOF
# 同时在虚拟环境中配置pip
mkdir -p "${VENV_DIR}/pip"
cat > "${VENV_DIR}/pip/pip.conf" << EOF
[global]
index-url = $PIP_MIRROR
trusted-host = $(echo $PIP_MIRROR | awk -F/ '{print $3}')
timeout = $PIP_TIMEOUT
EOF
}
# 在install_open_webui函数前新增安装Apache Arrow的函数
install_apache_arrow() {
log_info "📦 安装Apache Arrow及Dataset组件(pyarrow需要)..."
case $OS in
"CentOS"|"Red Hat"|"Oracle Linux")
# 安装EPEL仓库(忽略已安装提示)
yum install -y epel-release || true
# 安装Arrow仓库配置(忽略已安装提示)
curl -fsSL https://apache.jfrog.io/artifactory/arrow/centos/$(rpm -E %rhel)/apache-arrow-release-latest.rpm -o apache-arrow-release.rpm
yum install -y ./apache-arrow-release.rpm || true
rm -f apache-arrow-release.rpm
# 强制刷新仓库缓存
yum clean all
yum makecache fast
# 关键:安装所有必需的开发包,包括Dataset组件
log_info "安装Arrow核心库和Dataset组件..."
yum install -y arrow-devel arrow-glib-devel arrow-dataset-devel
;;
# 保持其他系统的安装逻辑不变
"Ubuntu"|"Debian")
# ... 原有代码 ...
;;
"SUSE Linux"|"openSUSE")
# ... 原有代码 ...
;;
esac
# 验证ArrowDataset是否安装成功
if pkg-config --exists arrow-dataset; then
log_info "✅ Apache Arrow及Dataset组件安装成功"
return 0
else
log_error "❌ Apache Arrow Dataset组件安装失败"
# 尝试手动安装作为最后的手段
log_info "尝试手动编译安装Apache Arrow..."
install_arrow_from_source
return $?
fi
}
# 新增:从源码安装Apache Arrow的函数(当包管理器安装失败时)
install_arrow_from_source() {
local arrow_version="14.0.1"
log_info "从源码安装Apache Arrow $arrow_version..."
# 安装编译依赖
yum install -y cmake gcc-c++ git python3-devel boost-devel rapidjson-devel
# 创建临时目录
local temp_dir=$(mktemp -d)
cd "$temp_dir" || {
log_error "无法进入临时目录"
return 1
}
# 克隆源码
git clone --branch apache-arrow-$arrow_version https://github.com/apache/arrow.git
cd arrow/cpp || {
log_error "找不到Arrow源码目录"
return 1
}
# 创建编译目录
mkdir build && cd build
# 配置编译选项
cmake .. \
-DCMAKE_INSTALL_PREFIX=/usr/local \
-DARROW_DATASET=ON \
-DARROW_PYTHON=ON \
-DARROW_WITH_BZ2=ON \
-DARROW_WITH_ZLIB=ON \
-DARROW_WITH_ZSTD=ON \
-DCMAKE_BUILD_TYPE=Release
# 编译并安装
make -j$(nproc)
make install
# 刷新动态链接库
ldconfig
# 清理临时文件
cd ../../../../ && rm -rf "$temp_dir"
# 验证安装
if pkg-config --exists arrow-dataset; then
log_info "✅ 源码安装Apache Arrow成功"
return 0
else
log_error "❌ 源码安装Apache Arrow失败"
return 1
fi
}
# 安装 Open WebUI(强制使用新FFmpeg)
install_open_webui() {
configure_pip_mirror
log_info "📦 通过 pip 安装 Open WebUI..."
pip_install_with_retry "pip" "--upgrade"
log_info "📦 安装兼容版本的numpy..."
pip_install_with_retry "numpy==1.26.4" "--no-cache-dir"
# 安装Apache Arrow系统依赖
install_apache_arrow
# 安装av包
log_info "📦 安装av包(强制关联新FFmpeg)..."
export PKG_CONFIG_PATH="/usr/local/ffmpeg/lib/pkgconfig:$PKG_CONFIG_PATH"
export CFLAGS="-I/usr/local/ffmpeg/include"
export LDFLAGS="-L/usr/local/ffmpeg/lib -Wl,-rpath=/usr/local/ffmpeg/lib"
pip_install_with_retry "av==10.0.0" "--no-cache-dir"
# 使用预编译的pyarrow wheel包
log_info "📦 安装pyarrow(优先使用预编译包)..."
export CMAKE_PREFIX_PATH="/usr/local:/usr"
# 尝试直接安装预编译wheel
if ! pip_install_with_retry "pyarrow" "--no-cache-dir"; then
log_warn "预编译包安装失败,尝试指定较低版本..."
# 若失败,尝试已知兼容的版本
pip_install_with_retry "pyarrow==14.0.1" "--no-cache-dir"
fi
# 安装open-webui
pip_install_with_retry "open-webui" "--no-cache-dir"
}
# 配置系统服务(修复端口配置错误)
configure_service() {
log_info "⚙️ 配置 systemd 服务(端口: $WEBUI_PORT)..."
OPEN_WEBUI_BIN="${VENV_DIR}/bin/open-webui"
GCC_VERSION=$(cat /tmp/gcc_version.txt 2>/dev/null || echo "9")
# 检测OpenSSL库目录
if [ -d "$OPENSSL_DIR/lib64" ]; then
OPENSSL_LIB_DIR="$OPENSSL_DIR/lib64"
else
OPENSSL_LIB_DIR="$OPENSSL_DIR/lib"
fi
cat > /etc/systemd/system/open-webui.service << EOF
[Unit]
Description=Open WebUI Service
After=network.target
[Service]
User=root
Environment="PATH=/usr/local/bin:/usr/bin:${PYTHON_DIR}/bin:${VENV_DIR}/bin"
Environment="LD_LIBRARY_PATH=${OPENSSL_LIB_DIR}:/usr/local/gcc-${GCC_VERSION}.4.0/lib64:/usr/local/ffmpeg/lib"
Environment="PKG_CONFIG_PATH=/usr/local/ffmpeg/lib/pkgconfig:${OPENSSL_LIB_DIR}/pkgconfig"
ExecStart=${OPEN_WEBUI_BIN} serve --port $WEBUI_PORT
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
log_info "🚀 启动 Open WebUI 服务(端口: $WEBUI_PORT)..."
systemctl daemon-reload
systemctl enable open-webui --now
if systemctl is-active --quiet open-webui; then
log_info "✅ Open WebUI 服务已启动"
else
log_error "❌ Open WebUI 服务启动失败"
log_error "查看日志: journalctl -u open-webui -n 50"
exit 1
fi
}
# 显示安装信息
show_info() {
log_info "🎉 安装完成!"
log_info "🌐 访问地址: http://<你的服务器IP>:$WEBUI_PORT"
log_info "📄 服务状态: systemctl status open-webui"
log_info "📜 日志查看: journalctl -u open-webui -f"
log_info "🔄 重启服务: systemctl restart open-webui"
log_info "📈 升级服务: ${PIP_BIN} install --upgrade open-webui"
log_info "🔍 虚拟环境位置: ${VENV_DIR}"
}
# 主函数(新增依赖检测步骤)
main() {
log_info "🔧 开始安装 Open WebUI(将使用 $WEBUI_PORT 端口)..."
detect_system
mkdir -p "$WORK_DIR"
cd "$WORK_DIR"
# 1. 检查并升级GCC
check_and_upgrade_gcc 9 || {
log_error "GCC版本检查和升级失败,无法继续安装"
exit 1
}
# 2. 检查并升级CMake(新增步骤)
log_info "开始CMake环境配置..."
if ! check_cmake_version; then
upgrade_cmake || {
log_error "CMake版本检查和升级失败,无法继续安装"
exit 1
}
fi
# 3. 检查并安装OpenSSL
setup_compatible_openssl || {
log_error "OpenSSL版本检查和安装失败,无法继续安装"
exit 1
}
# 4. 其他系统配置
disable_broken_repos
setup_centos_vault
install_system_dependencies
# 5. 配置FFmpeg
setup_compatible_ffmpeg
# 6. 安装并配置Python
install_python
check_python_version
# 7. 安装Open WebUI及依赖
install_open_webui
# 8. 配置服务并启动
configure_service
show_info
}
main
安装
./install_open_webui.sh
如果openssl-3.1.4.tar.gz 文件下载特别慢可以将 openssl-3.1.4.tar.gz 文件手动下载上传到脚本同级目录,脚本已支持离线安装
安装中发现磁盘空间不够满了(清除了一遍发现空间还是有点少),中途进行了一次Linux磁盘的扩容:https://blog.youkuaiyun.com/YXWik/article/details/149566023
验证openssl的MD5
md5sum openssl-3.1.4.tar.gz
如果MD5值与脚本中配置的不一致,需要手动将查出来的md值写到脚本
因为我的服务器环境什么都没有,所以脚本更适用于什么都没有的空环境,虽然已经适配了各个软件存在的情况,但是没有经过充分的验证,会存在一些意想不到的问题
以下问题是出在安装包时的一个特殊要求:它强制要求在虚拟环境中安装,而当前脚本是在系统全局环境中运行的。要解决这个问题,我们需要修改安装逻辑,为 Open WebUI 创建一个专用的 Python 虚拟环境。问题已在脚本中处理,在此记录一下,安装Open WebUI需要给它虚拟环境
FFmpeg
启用 GPL 许可功能
时,要求 OpenSSL
版本≥3.0.0
,但当前安装的是 OpenSSL 1.1.1w
(低于 3.0.0),导致兼容性冲突
,我这里将 FFmpeg
的 GPL
许可功能禁用
掉了,结果禁用了出现一大堆问题,没办法,只能升级openssl的版本
在脚本运行安装的途中,我决定再次使用docker安装尝试一下
docker run -d -p 3000:8080 -e OLLAMA_BASE_URL=http://host.docker.internal:11434 -v open-webui:/app/backend/data --name open-webui --restart always ghcr.nju.edu.cn/open-webui/open-webui:main
结果很意外,很快很快就成功了,上次可能网络不佳(折腾好几天)
查看日志,等如下图启动成功就可以访问了(需要等一段时间才能启动成功)
docker logs -f open-webui
如果访问不到开放下端口
iptables -I INPUT -p tcp --dport 3000 -j ACCEPT
自行注册一下账号
启动成功了,但是我发现我的open-webui 容器中是访问不到ollama的
但是ollama在浏览器这种外部环境是可以访问的
这种情况是因为ollama proxy 网络代理问题
卸载重装ollama
docker stop ollama
docker rm ollama
docker run -d --name ollama -v /home/ollama:/root/.ollama -p 11434:11434 -e OLLAMA_HOST=0.0.0.0 --add-host=host.docker.internal:host-gateway --restart always ollama/ollama
# 验证
docker exec -it ollama env | grep OLLAMA_HOST
docker exec -it ollama ollama run deepseek-r1:7b
然后再重装 open-webui (我这里重装是因为我之前的 open-webui 安装时指定的OLLAMA_BASE_URL 是http://localhost:11434 ,要更改为 ollama的IP+端口)
docker stop open-webui
docker rm open-webui
docker run -d -p 3000:8080 -e OLLAMA_BASE_URL=http://192.168.0.180:11434 -v open-webui:/app/backend/data --name open-webui --restart always ghcr.nju.edu.cn/open-webui/open-webui:main
# 验证
docker exec -it open-webui curl http://192.168.0.180:11434/api/tags
这里就正常完成啦