一个跑在 OpenWrt 路由器上的零依赖局域网协作服务——共享剪贴板、文件公告板、WebRTC 点对点传文件,以及配套的 LuCI 管理界面。
为什么做这个
跨设备传文件、同步一段文字或链接,是家庭、办公室、宿舍里最高频也最碎片化的需求。但现有的方案总有让人不舒服的地方:
- 要登录第三方账号,隐私和依赖都交出去了;
- 大文件走公网云中转,速度受限、流量有成本,还可能在途中被看;
- 没公网或弱公网环境直接不可用;
- 每台设备都得装客户端,手机、电脑、平板、同事的机器……装一遍就劝退一半人。
而每个家庭/办公室几乎都有一台 7×24 小时开机的东西——路由器。它天然就在局域网的中心,天然常驻,天然被所有设备信任。那为什么不能把路由器本身变成一个"随时可用的本地共享空间"?
这就是 Nook 的出发点:
- 零客户端——浏览器打开即用,不用装任何 App;
- 零公网依赖——不需要外部服务器、云账号、公网链路,断网也能用;
- 本地优先——文件内容不落路由器磁盘,尽量在设备之间点对点直传;
- 路由器即入口——开机常驻,永远在同一个地址等你。
项目形态
Nook 包含两个仓库:
- nook——Go 编写的核心服务,跑在 OpenWrt/ImmortalWrt 上,提供 Web 页面 + WebSocket 信令 + 内存态协调(Hub)。
- luci-app-nook——配套的 LuCI 应用,在 OpenWrt 管理界面 Services → Nook 里查看服务状态、启停服务、配置监听地址。
两个包都是标准 OpenWrt 打包(nFPM 生成 ipk / apk),覆盖 aarch64_cortex-a53 和 x86_64 两种架构,CI 在打 v 开头 tag 时自动构建发布。
能做什么
打开 http://路由器地址:8088 之后:
- 局域网设备自动发现——所有打开页面的设备实时出现在在线列表里,离线自动移除;
- 共享剪贴板——发布一段文本/链接,局域网内所有设备实时同步;自动识别 URL 可点击;每条支持复制,还带系统剪贴板 Paste;
- 文件公告板——把文件"发布"到公告板,其他设备点一下就能领取;支持多文件、单条撤回、一键清空自己的公告;
- 直接发送——在设备列表里点某个设备,直接给它发文本或文件,对方弹窗接受/拒绝;
- 设备命名——自定义设备名,可记忆(刷新后保留),可一键重置回自动生成的默认名(按 UA 生成 + 随机后缀)。
关键设计:文件内容不经过路由器
这是 Nook 最核心的设计决策:默认不把文件存到路由器上。
文件公告板只在路由器内存里保存文件的元信息(文件名、大小、MIME、发布者、发布时间)。真正的文件内容始终留在发布者的浏览器里;其他设备点击领取时,通过 WebRTC DataChannel 在设备之间点对点传输,路由器只做信令中转。
对应的取舍:
- 发布者的页面需要保持打开、设备保持在线;
- 发布者刷新页面或关浏览器,相关公告自动失效;
- 路由器不持久保存文件内容——低端路由器的存储和 CPU 都不会被大文件拖垮。
架构
路由器 (OpenWrt)
┌──────────────────────────────────────┐
│ /usr/bin/nook (单二进制, 前端已嵌入) │
│ - HTTP: / /ws /config │
│ - Hub: 内存态 (peers/clipboard/offers)│
└──────────┬───────────────┬────────────┘
WebSocket (信令/元信息) HTTP (页面)
│ │
┌─────▼─────┐ ┌─────▼─────┐
│ 设备 A │ │ 设备 B │
│ 浏览器 │◄──►│ 浏览器 │
└───────────┘WebRTC└───────────┘
文件内容 P2P 直传, 不经过路由器
技术栈刻意保持极简:
- 后端:Go 标准库 +
gorilla/websocket+google/uuid,全部代码约 600 行(不含前端); - 前端:纯 HTML/CSS/JS,零第三方依赖,通过
embed.FS打进单二进制——部署就是一个文件 + init.d 脚本 + UCI 配置; - 宿主集成:procd 管理、
respawn自动重启、/etc/config/nook标准 UCI 配置。
WebSocket 协议
设备通过 /ws 注册,消息是 JSON,核心类型:
hello/welcome——注册与全量状态下发(自身 ID、在线设备、剪贴板、文件公告);peer-joined/peer-left——上下线广播;clipboard-add/clipboard-added——剪贴板发布与广播;file-board-add/file-board-added/file-board-remove/file-board-removed——公告板增删;file-board-request/file-board-unavailable——领取请求与失败;signal——WebRTC offer/answer/ICE candidate 的定向转发。
文件传输协议(DataChannel 内)
发送方先发 file-offer(文件名/大小/MIME),接收方回 file-accept 或 file-reject,然后按 64 KB 分块流式发送二进制,最后 file-done 收尾。状态机:waiting → accepted → sending → done,或 rejected / error。公告板领取和直接发送共用这套机制。
一些工程细节
- WebSocket 升级时做 Origin 校验,防跨站 WebSocket 劫持;
- 心跳 Ping 30 秒,读超时 60 秒,写超时 10 秒,空闲连接不被误杀;
- 单条消息上限 64 KB;剪贴板最多 50 条、单条 64 KB;公告板最多 100 条;
- 优雅关闭:收到 SIGINT/SIGTERM 后 5 秒内收尾;
- 设备 ID 用 UUID 前 8 位,不可预测;
- 前端断线 3 秒自动重连,重连后重新注册拉全量状态。
配套的 LuCI 界面
luci-app-nook 让 Nook 融入 OpenWrt 原生管理体验:
- 后端是 ucode 写的 rpcd 插件(
luci.nook),暴露get_status和service_action两个方法; - 状态页显示 running(运行中)和 enabled(开机自启)两个状态,一键 start/stop/restart/enable/disable;
- 配置页读写
/etc/config/nook的启用开关和监听地址,改配置自动触发服务重载; - 同时兼容 LuCI 23.05 和 24.10(通知 API 做了降级);
- 权限走标准 ACL,最小授权:读状态 + 写 nook 配置 + 执行 init.d 脚本。
安装与使用
# aarch64 / x86_64 按架构选包
opkg install ./nook_0.1.6-1_aarch64_cortex-a53.ipk
# 或 apk 包管理器(未签名本地包)
apk add --allow-untrusted ./nook_0.1.6-r1_aarch64_cortex-a53.apk
/etc/init.d/nook enable
/etc/init.d/nook start
然后同一局域网内访问 http://192.168.1.1:8088 即可。升级行为也考虑过:首次安装不自动启动;升级时若服务正在运行则自动重启,未运行则不打扰。
设计取舍与边界
Nook 明确不做的事,和做了一样重要:
- 不做公网/跨网传输——只面向同一局域网;
- 不做账号体系——没有登录、没有云同步;
- 不做持久化——剪贴板、在线设备、公告都是内存态,服务重启即清空(已接受的取舍);
- 不做认证加密——当前版本基于"可信局域网"假设;没有端到端加密,局域网内流量可被嗅探。这一点在文档里写得很清楚,后续版本会评估设备配对与传输加密。
小结
Nook 解决的是一个小而真实的问题:在局域网里,设备之间应该能像在同一个房间一样自然地交换东西。它把"路由器"这个每家都有的基础设施变成了协作入口,用浏览器消灭客户端,用 WebRTC 让文件内容绕开路由器,用最克制的技术栈(Go + 原生前端 + 标准 OpenWrt 打包)换来低端路由器上也能跑的轻量体验。
代码在 GitHub,欢迎围观和提 issue:
发表评论