🧭 家庭服务器全家桶折腾笔记:OpenWrt + OpenList + qBittorrent

💡 这套系统不是为了跑分,而是为了解决三个具体问题:网盘太分散、下载不可控、公网暴露太多。最终用三台机器拼出一条从「找资源」到「躺在沙发上看片」的完整链路。

0. 为什么折腾

事情的起因很普通:网盘会员到期,资源散在阿里、夸克、百度、蓝奏四个地方;NAS 上那块 2T 盘大部分时间只是躺着;而每次想看点东西,都要经历「找资源 → 下载 → 解压 → 改名 → 找字幕」的完整折磨。

于是给自己定了三个目标:

  • 一个入口:所有网盘在一个页面里浏览、搜索、转存
  • 自动下载:磁力/种子丢进去,剩下的交给系统
  • 安全第一:管理后台只在内网,公网只留一条 Cloudflare Tunnel

同时也给自己画了三条红线:

  1. 不把路由器当全能机,路由只做路由
  2. 不让下载目录和媒体库重复占空间
  3. 不为了「看起来高级」而堆服务

1. 最终架构

先看全貌:

家庭网络与自建图床拓扑

ProcessOn 原始拓扑图(重绘清晰版)ProcessOn 原始拓扑图重绘清晰版

保留 ProcessOn 原图的浅色版式和信息结构,修复了文字溢出、节点过窄和箭头穿越的问题。原始 PNG 备份:processon-topology-original.png

说明:ProcessOn 原图的信息很全,但节点较窄、中文有溢出,所以正文主图用重绘版;原图保留在这里作对照。

整套系统分成五层:

层级角色关键组件
公网与入口宽带、光猫、主路由Internet / 光猫 / 192.168.31.1
旁路由与局域网分流、内网主机OpenClash .50 / Mac .101 / fnOS .51 / Debian .55
服务层聚合、下载、媒体、建站OpenList、qBittorrent、MoviePilot、Jellyfin、1Panel
存储与备份数据盘、对象存储、异地备份2T 数据盘、Garage、Cloudflare R2、GitHub Images
管理面远程维护、健康监控Tailscale、Uptime Kuma、两个自制看板

1.1 机器分工

机器地址配置角色
iStoreOS 软路由192.168.31.502C / 1G旁路由、OpenClash、规则分流
fnOS NAS192.168.31.512C / 5G + 2TOpenList、qBittorrent、MoviePilot、Jellyfin
Debian 建站机192.168.31.551C / 3G1Panel、OpenResty、Lsky、Garage、cloudflared
主路由192.168.31.1家用路由DHCP、默认网关、局域网交换

这套分工和之前的 J4125 All-in-One 思路不太一样。All-in-One 很酷,但一台机器挂了,网络、存储、建站一起挂。现在把故障域拆开:

  • 路由挂了,至少 NAS 和建站机还在
  • NAS 挂了,博客和图片站还能活
  • 建站机挂了,下载和媒体库不受影响

代价是多了几根网线和几个电源适配器,但换来的是排障时不用「一锅端」。

1.2 四条设计原则

原则一:主路由仍然是默认网关。 OpenClash 跑在旁路由上,只接管需要分流的客户端,不把全家人的网络绑在一台虚拟机上。

原则二:管理面不暴露公网。 1Panel、qBittorrent WebUI、OpenList 管理页、Jellyfin 全部只绑内网地址。需要远程时走 Tailscale,不开公网端口。

原则三:公网入口只走 Cloudflare Tunnel。 路由器不做任何端口映射,家庭公网 IP 不暴露。

原则四:下载和媒体库放在同一文件系统。 MoviePilot 用硬链接入库,不重复占空间。

2. 网络底座:iStoreOS + OpenClash

软路由是整套系统的地基。我用的是 iStoreOS 24.10.8,底层是 OpenWrt 24.10 分支,管理地址 192.168.31.50。

2.1 为什么选 iStoreOS

之前用过原版 OpenWrt 和爱快,最后留下 iStoreOS,原因很实际:

  • 中文生态完整:应用商店里直接能装 OpenClash、PassWall、Docker 等插件,不用到处找 ipk
  • LAN 默认是 DHCP 客户端:不会像原版 OpenWrt 那样首启广播 192.168.1.x 的野 DHCP,怼上家里现有网段就是事故
  • 底层仍是 OpenWrt:出问题能按 OpenWrt 的思路查,不会被厂商 UI 锁死
  • 比爱快更适合折腾:爱快的流控和多拨很强,但插件生态和 OpenWrt 差距明显

2.2 安装 OpenClash

在 iStore 应用中心搜索 OpenClash 一键安装即可,依赖和内核会自动带上。也可以用命令行:

1
2
opkg update
opkg install luci-app-openclash

装完在 LuCI 的「服务 → OpenClash」进入配置。

2.3 订阅、分流与 DNS

我的订阅是 Clash 格式,核心配置只有四步:

  1. 配置订阅:填入订阅链接,更新配置
  2. 运行模式:选 Fake-IP(增强)模式,兼容性和速度比较均衡
  3. 规则设置:启用大陆白名单分流,国内直连、国外走代理、局域网不走代理
  4. 节点选择:在控制面板里测延迟,选一个稳定的节点

DNS 是最容易踩坑的地方。OpenClash 接管 DNS 之后,要注意:

  • dnsmasq 的上游要指向 OpenClash 的监听端口,否则会出现「能上 Google 但国内站点变慢」
  • 检查 DNS 泄露,避免请求绕过代理直接打到本地运营商
  • 局域网设备的 DNS 要么统一走主路由,要么明确指向旁路由,不要一半一半

验证分流是否正常:

1
2
3
4
5
6
7
8
# 国内站点应该直连
curl -I https://www.baidu.com

# 国外站点应该走代理
curl -I https://www.google.com

# 查看当前出口 IP
curl -s https://ipinfo.io/ip

2.4 旁路由模式与回退

旁路由有两种常见玩法:

模式做法风险
全局旁路由主路由 DHCP 网关直接指向 .50旁路由一挂,全家断网
按需旁路由只给需要的客户端手动设网关 .50配置分散,但故障影响小

我用的是第二种。Mac 和需要看流媒体的设备走旁路由,手机、电视、智能家居仍然走主路由。

回退也很简单:关掉 OpenClash,把客户端网关改回 192.168.31.1 即可。不要在没想好回退方案之前动主路由的 DHCP。

2.5 安全提醒

iStoreOS 默认的 ttyd 终端(7681 端口)在局域网内是裸奔的。配置完成后建议到「系统 → 管理权」里关掉,或者至少加认证。这个端口一旦被扫到,等于把路由器 root 权限送人。

还有一个反直觉的坑:LuCI 网页密码和 SSH 密码不一定是同一个。iStoreOS 首次安装后,网页可能用默认密码能进,但 SSH 会提示 Authentication failed。正确做法是先在网页「系统 → 管理权 → 路由器密码」里设置密码,SSH 才会生效。

3. 核心主角:fnOS 上的 OpenList

OpenList 是 AList 的社区延续版。我这次装的是 v4.2.9,跑在 fnOS 的 Docker 里,端口 5244。

3.1 为什么选 OpenList

  • 多网盘统一挂载:阿里、夸克、百度、蓝奏云在一个文件树里
  • WebDAV / API:第三方播放器、下载器、脚本都能接
  • 离线下载:磁力、种子、直链可以交给网盘侧执行
  • 社区活跃:AList 原项目维护节奏放缓后,OpenList 接过了社区维护

数据流大概是这样:

OpenList 数据流与离线下载

3.2 Docker 部署

1
2
3
4
5
6
7
8
9
10
11
12
13
14
services:
openlist:
image: openlistteam/openlist:latest
container_name: openlist
restart: unless-stopped
ports:
- "5244:5244"
volumes:
- ./data:/opt/openlist/data
- /vol00/downloads:/downloads
environment:
- PUID=0
- PGID=0
- UMASK=022

启动并查看日志:

1
2
docker compose up -d
docker logs -f openlist

首次进入需要管理员密码,可以用容器内命令生成:

1
docker exec -it openlist ./openlist admin random

3.3 挂载四类网盘

进入后台的「存储 → 添加」,按驱动类型填授权信息。我的挂载表:

网盘驱动用途注意事项
阿里云盘Aliyundrive Open主存储、离线下载目录token 有有效期,过期单独刷新
夸克网盘Quark影视资源、转存高频请求可能触发限流
百度网盘Baidu Netdisk老资料、备份大文件下载速度受会员影响
蓝奏云Lanzou小文件、工具包适合放安装包和脚本

挂载完成后,首页就是一张聚合列表,所有网盘在同一个页面里统一浏览、搜索和转存。

OpenList 聚合网盘首页

3.4 离线下载是怎么跑的

OpenList 的离线下载不是在本机跑 BT,而是把任务提交给网盘侧执行。流程大致是:

  1. 在 Web 或 API 提交磁力/种子/直链
  2. OpenList 创建任务,写入离线下载队列
  3. 网盘侧开始下载,OpenList 轮询状态
  4. 下载完成后,文件出现在对应网盘的离线目录
  5. 你再通过聚合文件树浏览、转存或播放

这样做的好处是:家里的上行带宽和 NAS 性能都不是瓶颈,坏处是依赖网盘侧的可用性和限流策略。

3.5 界面美化

OpenList 后台的「全局 → 自定义头部 / 自定义内容」可以注入 CSS 和 JS。我的方案是:

  • 换一张暗色系背景图,正文区域加毛玻璃
  • 侧边栏加「媒体库」快捷入口,直链到 Jellyfin
  • 顶栏搜索用 Cmd + K 唤起,这是 OpenList 前端自带的能力,只要 search_index 不为 none 就会出现
  • 底部保留「由 OpenList 驱动 | 管理」,低调一点

核心 CSS 大概是这样:

1
2
3
4
5
6
7
8
9
body {
background: url("/images/bg.webp") center/cover fixed no-repeat;
}

.main-content {
backdrop-filter: blur(14px);
background: rgba(18, 18, 24, 0.72);
border-radius: 16px;
}

美化的底线是可读性。背景再好看,正文看不清就是负优化。

4. 两个自制看板

OpenList 自带的离线下载入口藏得比较深,也没有一个统一的地方看各服务健康状态。于是我写了两个单页看板,分别挂在 8788 和 8789。

4.1 一键下载(8788)

原理不复杂:把磁力、种子或直链提交到 OpenList 的离线下载接口,由网盘侧完成下载再转存。

  • 前端一个输入框 + 目标云盘下拉框 + 访问令牌
  • 令牌存在浏览器本地,不落服务器
  • 提交后记录历史任务,画一条「最近提交趋势」折线图
  • 失败任务保留错误信息,方便判断是磁力失效还是网盘限流

4.2 状态总览(8789)

这个页面聚合了所有关键服务的健康数据:

  • 服务健康数 / 云盘在线数(OpenList 挂载状态探测)
  • qBittorrent、MoviePilot、OpenList 各自的可达性
  • 用 ECharts 画服务可用性趋势、健康占比环形图、云盘驱动分布柱状图
  • 30 秒自动刷新

状态总览看板

两个页面都是纯静态 HTML + 原生 JS,部署时丢到一个 nginx 目录,或者直接起个 python -m http.server 容器就行,没有任何后端依赖。数据全部来自 OpenList API 和本地端口探测。

看板里的健康探测一定要带超时,别让一个不在线的服务把整页拖死。看板本身也要加认证,不要因为在内网就裸奔。

5. 下载与媒体流水线

BT/PT 下载交给 qBittorrent,整理和刮削交给 MoviePilot,播放交给 Jellyfin。整条链路是:

下载与媒体流水线

5.1 qBittorrent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
services:
qbittorrent:
image: linuxserver/qbittorrent:latest
container_name: qbittorrent
restart: unless-stopped
environment:
- PUID=1000
- PGID=1000
- TZ=Asia/Shanghai
- WEBUI_PORT=8080
ports:
- "8080:8080"
- "6881:6881"
- "6881:6881/udp"
volumes:
- ./config:/config
- /vol00/downloads:/downloads
mem_limit: 1g

几个关键点:

  • WebUI 只绑内网,不做任何端口映射或穿透
  • 下载目录直接落在 2T 数据盘上
  • 内存限制 1G,防止 qB 在做种高峰吃光 NAS 内存
  • 首次登录后到「设置 → 下载」里确认默认保存路径是 /downloads

5.2 MoviePilot

MoviePilot v3 在这套流水线里扮演「调度大脑」:

  • 下载器接 qBittorrent,自动选择默认下载器
  • 媒体服务器接 Jellyfin
  • 订阅、整理、硬链接一条龙
  • 下载完成后自动刮削、重命名、入库

5.3 Jellyfin

Jellyfin 里建好「电影」和「电视剧」两个媒体库,目录分别指向 /media/Movies 和 /media/TV。

2C5G 的机器不建议开实时转码。能直连播放的客户端尽量直连,电视端优先选支持 HEVC 硬解的设备;实在需要转码,再考虑加显卡或换更强的机器。

5.4 硬链接:省空间的关键

下载目录和媒体库必须在同一个文件系统里,硬链接才有意义。如果两个目录跨了挂载点,硬链接会失败,MoviePilot 只能复制文件,2T 盘很快就会被同一部电影占两份。

检查方法:

1
2
df -h /vol00/downloads /media
stat -c '%d %n' /vol00/downloads /media

两个路径的 %d 设备号一致,才说明在同一个文件系统。

6. 建站与公网入口

最后是 .55 那台 1C3G 的小主机,负责对外建站和图片服务。

公网入口与安全边界

6.1 安装 1Panel

1
2
3
curl -sSL https://resource.fit2cloud.com/1panel/package/quick_start.sh -o /tmp/qs.sh
printf '\n\n\n\n\n\n' | bash /tmp/qs.sh
1pctl user-info

安装完成后把面板端口固定为 10086,并开启安全入口。面板只监听局域网,不暴露公网。 安全入口忘了可以用 1pctl user-info 查回来。

6.2 建一个测试站点

在 1Panel 里装 OpenResty,创建一个静态站点,比如 test.local,然后用 Host 头验证:

1
curl -H "Host: test.local" http://192.168.31.55/

返回 200 就说明反向代理链路打通了。

6.3 Cloudflare Tunnel

对外出口规划走 Cloudflare Tunnel,路由器不用开任何端口映射:

1
2
3
4
cloudflared tunnel login
cloudflared tunnel create home-lab
cloudflared tunnel route dns home-lab blog.example.com
cloudflared tunnel run home-lab

配置文件大概是这样:

1
2
3
4
5
6
7
8
9
tunnel: <TUNNEL_UUID>
credentials-file: /root/.cloudflared/<TUNNEL_UUID>.json

ingress:
- hostname: blog.example.com
service: http://127.0.0.1:80
- hostname: img.example.com
service: http://127.0.0.1:3900
- service: http_status:404

把 tunnel 配置成 systemd 服务后,只有 Cloudflare 的出站连接能进来,家庭 IP 完全不暴露。

6.4 为什么不用 frp / 端口映射

方案优点缺点
端口映射简单直接家庭 IP 暴露,攻击面大
frp需要公网 VPS,灵活要维护 VPS,仍有公网入口
Cloudflare Tunnel不开端口、自带 TLS/WAF域名 NS 必须托管在 Cloudflare

我选 Tunnel 的核心理由是不需要公网 IP,也不需要开放任何入站端口。对家庭网络来说,少一个入口就少一类风险。

7. 稳定性与备份

服务跑起来只是开始,能不能在出问题时恢复才是关键。

7.1 健康监控

Uptime Kuma 跑在 192.168.31.51:3001,目前监控:

  • 博客主页
  • 图床域名
  • OpenList
  • qBittorrent WebUI
  • Jellyfin

检测间隔 60 秒,失败重试 2 次。状态总览里目前是 9/9 服务正常、6/6 云盘在线。

7.2 备份策略

数据位置频率恢复方式
博客源码GitHub每次提交clone + npm ci + hexo generate
博客构建产物GitHub / 本地每次部署重新构建
图片GitHub Images + Garage每次上传 / 5 分钟同步从仓库或对象存储拉回
数据库每日备份到 R2每天 03:30还原后重启容器
配置文件本地 + R2变更时按文档恢复

备份不做恢复演练等于没备份。我至少每季度会挑一个服务,从备份里完整重建一次,确认文档和脚本都还能用。

8. 踩坑清单

#坑现象解法
1ttyd 裸奔7681 端口无需认证就能进终端配置完关掉,或加认证
2OpenClash DNS 泄露国外能上,国内变慢或解析异常检查 dnsmasq 上游,统一 DNS 出口
3LuCI 密码 ≠ SSH 密码网页能进,SSH 提示 Authentication failed先在网页设置路由器密码
4OpenList token 过期网盘显示离线,文件打不开后台单独刷新 token,不用重挂
5Docker 目录权限容器启动但无法写入确认 PUID/PGID 与宿主机目录属主一致
6qBittorrent 吃内存做种高峰 NAS 变卡mem_limit: 1g,限制连接数
72T 盘没挂上下载跑到系统盘,空间瞬间告急启动前 df -h 确认挂载点
8硬链接失败媒体库和下载目录各占一份确认在同一文件系统,检查 stat -c %d
9Jellyfin 转码卡顿2C5G 实时转码直接满载优先直连,关闭不必要的转码
10Tunnel 配了不生效域名打不开确认域名 NS 已托管在 Cloudflare
111Panel 安全入口丢失面板地址进不去1pctl user-info 查回
12看板无认证内网任何人可提交下载任务加 Basic Auth 或只走 Tailscale

9. 成本、功耗与取舍

这套系统用的都是现有硬件,主要成本是硬盘、电费和域名:

  • 2T 数据盘:一次性投入
  • 三台机器常年开机:功耗比一台台式机低,但也不是零
  • Cloudflare R2 / Tunnel:个人用量基本在免费额度内
  • 域名:每年固定支出

如果重新来一次,我会做几件事:

  1. 给 NAS 配一个 UPS,避免断电把文件系统搞坏
  2. 用 VLAN 把 IoT 设备和 NAS 管理面隔开
  3. 把配置文件和 compose 全部纳入 Git,换机器时直接拉起来
  4. 备份加一份离线冷备,不要只依赖云

但有一件事不会变:不会为了折腾而折腾。如果只是想在手机上看 NAS 里的文件,一个 SMB 共享就够了,没必要上这么一套。

10. 总结

现在这套系统的日常是这样的:

  • 想存资源 → 丢磁力到一键下载看板,网盘离线完成后自动转存
  • 想看电影 → MoviePilot 订阅自动下载、整理、刮削,Jellyfin 直接播
  • 想建站 → 1Panel + Cloudflare Tunnel,公网出口不暴露家庭 IP
  • 想维护 → Tailscale 进内网,Uptime Kuma 看健康状态

折腾的意义大概就在这里:把散落的硬件和服务,拼成一个真正为自己工作的系统。

后续计划把 Cloudflare Tunnel 正式接入、图片 CDN 优化、Uptime Kuma 告警也补全,到时候再写一篇续集。


本文所有命令都在自己的环境里实际跑过,IP 和端口按家里网段记录。折腾快乐,数据无价——每次改配置之前,先想一遍:重启之后,它还能回来吗?