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修改为所有复杂格式,以此解决字幕的方块问题。