2025年开年,拿回2080Ti和P100后,以最低成本装了一台i3-12100 32G 1t+4t的主机当GPU服务器。恰逢DeepSeek爆火,为了充分利用这台机器,准备进行本地部署,并把之前在树莓派上部署的Immich和Jellyfin迁移到新服务器上,用来硬件解码视频。
1.数据迁移
服务器间的通讯使用ZeroTier进行连通,根据在树莓派上部署服务的经验,ZeroTier更加稳定(毕竟作为系统服务运行)。在新服务器端,直接在WSL2内部安装ZeroTier更加方便,虽然WSL2可以走Mirrored网络模式,但这种方式并不稳定,并且在Windows端同时有ZeroTier、Clash(Tun模式)两个虚拟网卡,很容易造成冲突。
接下来就是同步,这里使用Rsync命令,轻松愉快。以同步电影的脚本为例:
#!/bin/bash
# 定义远程和本地路径
REMOTE_SERVER="pi@192.168.*.*"
REMOTE_PATH="/srv/dev-disk-by-uuid-***/netflix"
LOCAL_PATH="/media/netflix"
# 需要同步的文件夹列表
FOLDERS=("电影" "电视剧" "动漫" "纪录片")
# 循环同步每个文件夹
for FOLDER in "${FOLDERS[@]}"; do
echo "正在同步 $FOLDER..."
rsync -avz --progress --ignore-existing "$REMOTE_SERVER:$REMOTE_PATH/$FOLDER" "$LOCAL_PATH/"
done
echo "同步完成!"
由于树莓派端每日2:30自动重启,因此可以使用cron设定脚本以指定在2:40开始同步:
40 2 * * * /bin/bash /media/netflix/rsync_netflix.sh >> /media/netflix/logfile.log 2>&1
2.DeepSeek本地部署
🆙——2025.3.11更新——🆙
还是没有搞明白自己的需求,近期发现本地部署并不需要做到All in One,能够满足知识库的建立并正确问答就已经可以了。因此,目前使用Ollama+RagFlow进行知识库的搭建,其实联网搜索并非刚需(尤其是联网搜索无论是软件还是搜索引擎适配其实效果都比较一般)。
⏱️——以下是折腾历史——⏱️
在完成数据迁移后,可以开始DeepSeek的部署工作。这里采用的Stack是SearXNG(搜索)+Ollama(模型部署)+Open web UI(前端)。使用Docker compose部署非常方便。
在创建容器前,需要先安装Nvidia驱动和Cuda。WSL2的一大好处是,在Windows端方便地安装驱动后,WSL2端无需进行其它配置即可使用。驱动下载后,在WSL2端需要安装Container的Cuda支持,参考官方文档即可。
需要注意的点是,WSL2如果通过bind来映射模型目录,虽然看上去没有问题,但由于WSL2的实现方法会导致读取速度异常慢。所以,此处模型cache目录应当使用docker volume的方式挂载。此外,目前由于Open WebUI存在某些bug,网页搜索暂时无法使用,可以等待后续更新。以下是Docker compose文件的参考(启用Cuda支持):
services:
searxng:
container_name: searxng
image: searxng/searxng:latest
environment:
- SEARXNG_SETTINGS_PATH=/etc/searxng/settings.yml
ports:
- "8080:8080"
volumes:
- ./searxng:/etc/searxng:rw
env_file:
- .env
restart: unless-stopped
cap_drop:
- ALL
cap_add:
- CHOWN
- SETGID
- SETUID
- DAC_OVERRIDE
logging:
driver: "json-file"
options:
max-size: "1m"
max-file: "1"
ollama:
image: ollama/ollama
container_name: ollama
ports:
- "11434:11434"
volumes:
- ollama_volume:/root/.ollama
#command: run deepseek-r1:32b
restart: always
environment:
#- OLLAMA_RUN_PARALLEL=4
#- OLLAMA_MAX_LOADED_MODELS=2
- CUDA_VISIBLE_DEVICES=0,1
- OLLAMA_KEEP_ALIVE=-1
- OLLAMA_SCHED_SPREAD=1
deploy:
resources:
reservations:
devices:
- driver: nvidia
capabilities: [gpu]
open-webui:
image: ghcr.io/open-webui/open-webui:main
container_name: open-webui
ports:
- "3000:8080"
environment:
- OLLAMA_BASE_URL=http://ollama:11434
- USER_AGENT='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.1> - ENABLE_WEB_SEARCH=True
volumes:
- /home/zhn/open-webui:/app/backend/data
restart: always
volumes:
ollama_volume:
如要下载模型,直接在Ollama容器参见支持的模型列表并执行命令即可:
ollama pull deepseek-r1:32b
3.Immich的同步
🆙——2025.3.11更新——🆙
果然不要重新造轮子。目前,使用syncthing+zerotier已经能够很方便地满足双向同步需求,且操作非常简单。🤣🤣🤣🤣
⏱️——以下是折腾历史——⏱️
在树莓派运行Immich有些困难,因为它包含人脸识别、视觉大模型等需要GPU的功能。此外,受限于家用宽带的上行带宽,Immich难以直接播放4K的视频,需要转码后客户端方可流畅播放。当进行视频转码,树莓派孱弱的CPU将会被占满,以至于其他服务正常运行也会受到干扰。因此,将服务迁移至GPU服务器是必要的。
至于数据,对于我来说,我希望对数据做异地容灾,即本机、树莓派服务器、GPU服务器各保存一份数据。此前,树莓派上部署的Nextcloud很好地满足了我的需求,手机端拍摄的照片会直接传输至树莓派服务器。但加入了GPU服务器后,需要考虑如下两种同步方案:
- 不进行冗余备份,直接mount nfs来远程挂载Nextcloud文件夹;
- 实现GPU服务器和树莓派服务器的双向同步。
第一种方案最容易想到,但并不实用。因为需要等待ZeroTier启动后再启动Docker来挂载,虽然看上去容易实现但实际操作中遇到了很多意想不到的问题,也耗费了大量时间,这显然是不划算的。于是,我最终转向第二种方案,使用Unison和inotify进行双向同步。
3.1 安装软件
sudo apt update
sudo apt install unison inotify-tools fswatch -y
接下来需要GPU服务器端在/home/用户/.unison下配置:
# 远端目录
root = ssh://pi@192.168.*.*//srv/dev-disk-by-uuid-*/nextcloud/data/IndigoFloyd/files/
# 本地目录
root = /media/sync/
# 仅同步 '照片' 和 '视频' 目录
path = 照片
path = 视频
# 自动跳过无意义的文件
ignore = Name {.*.swp, *.tmp, Thumbs.db}
# 解决冲突时默认使用新文件
prefer = newer
# 进行快速同步
fastcheck = true
# 遇到错误自动继续
retry = 10
3.2 编写脚本
#!/bin/bash
# 这是GPU服务器端的脚本
# 监听 /media/sync/ 目录,发生更改时执行 unison
inotifywait -m -r -e modify,create,delete,move /media/sync/ |
while read path action file; do
echo "本机检测到变动: $file ($action),正在同步..."
unison nextcloud_sync -auto -batch
done
#!/bin/bash
# 这是树莓派端脚本
# 远端 Nextcloud "照片" 和 "视频" 文件夹路径
PHOTO_DIR="/srv/dev-disk-by-uuid-*/nextcloud/data/IndigoFloyd/files/照片"
VIDEO_DIR="/srv/dev-disk-by-uuid-*/nextcloud/data/IndigoFloyd/files/视频"
# 分别监听 "照片" 和 "视频" 文件夹,发生创建、修改、删除时触发同步
fswatch -r --event Created --event Updated --event Removed "$PHOTO_DIR" "$VIDEO_DIR" | while read file; do
echo "远端检测到变动: $file,正在同步到本机..."
# 远端执行 SSH 连接到本机,触发 `unison` 运行
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null zhn@192.168.*.* "unison nextcloud_sync -auto -batch"
done
3.3 解决文件锁
由于有时会发生意外状况,导致同步意外中断。这就会导致文件锁没有及时解除,发现这个问题也耗费了很长时间。
报错如下:
Fatal error: Warning: the archives are locked.
If no other instance of unison is running, the locks should be removed.
The file /home/zhn/.unison/lkfa28d0847335b7837711995ab5602456 on host DESKTOP-TRGVT7V should be deleted
Please delete lock files as appropriate and try again.
Unison不会自动删除这些锁,这不利于“甩手掌柜”式的自动化管理。因此,需要写一个脚本用于检测unison日志中是否出现should be deleted的锁,并及时删除。这里的解决方法是使用cron每3分钟运行如下脚本:
#!/bin/bash # 定义日志文件路径 LOG_FILE="/home/zhn/.unison/unison.log" LOG_DIR="/home/zhn/.unison/" # 远程主机信息 REMOTE_HOST="192.168.*.*" REMOTE_USER="pi" # 检查日志文件是否更新 if [ -f "$LOG_FILE" ]; then # 获取当前时间,并格式化为 YYYY-MM-DD_HH-MM-SS CURRENT_TIME=$(date "+%Y-%m-%d_%H-%M-%S") echo "$CURRENT_TIME$ script starts" # 查找日志中的“should be deleted”条目 grep "should be deleted" "$LOG_FILE" | while read line; do # 提取文件路径 file_to_delete=$(echo "$line" | sed -n 's/.*file \(.*\) on.*/\1/p') echo "$file_to_delete" # 判断文件路径并删除 if [[ "$file_to_delete" =~ ^/home/pi ]]; then # 远程删除文件 echo "$(date) 文件在远程主机,删除文件:$file_to_delete" ssh "$REMOTE_USER"@"$REMOTE_HOST" "rm -f $file_to_delete" elif [[ "$file_to_delete" =~ ^/home/zhn ]]; then # 本地删除文件 echo "$(date) 文件在本地,删除文件:$file_to_delete" rm -f "$file_to_delete" else echo "$(date) 文件路径不符合预期,跳过删除:$file_to_delete" fi done echo "重置日志中" mv "$LOG_FILE" "${LOG_DIR}unison.log.$CURRENT_TIME" fi
这样,两端就完成了双向同步,在同步目录下touch一个文件,可以发现两端都出现了。
4.Jellyfin的优化
这一部分相对简单。尝到了Docker compose方便的甜头,我对Jellyfin也使用该法部署,配置文件如下:
services:
jellyfin:
image: lscr.io/linuxserver/jellyfin:latest
privileged: true
container_name: jellyfin
ports:
- 8096:8096
#network_mode: "host"
volumes:
- /home/zhn/jellyfin/config:/config
- /home/zhn/jellyfin/cache:/cache
#- /mnt/netflix:/media/netflix
- type: bind
source: /media/netflix
target: /media
read_only: true
- type: bind
source: /mnt/c/Windows/fonts
target: /usr/share/fonts/custom
read_only: true
runtime: nvidia
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
restart: 'always'
environment:
- PUID=1000
- PGID=1000
- TZ=Asia/Shanghai
- NVIDIA_DRIVER_CAPABILITIES=all
- NVIDIA_VISIBLE_DEVICES=all
- JELLYFIN_PublishedServerUrl=https://tv.indigofloyd.space
- DOCKER_MODS=linuxserver/mods:universal-package-install
- INSTALL_PACKAGES=fonts-noto-cjk-extra
extra_hosts:
- 'host.docker.internal:host-gateway'
这里选择linuxserver维护的镜像而非官方镜像,因为方便字体的安装。除此以外,就是Nvidia的GPU支持,启用方法同其它容器一致。在一切搞定后,需要在如图处由Auto修改为所有复杂格式,以此解决字幕的方块问题。