这篇文章记录一套真正部署并经过公网读写测试的私有云盘:RK3576 负责保存文件,Windows 电脑通过 Headscale/Tailscale 把 SSD、U 盘分别挂载为 Z:、Y:,陌生设备则打开 HTTPS 域名、输入一个访问 Key 后使用网页文件管理器。
项目配置、鉴权程序、systemd 服务和更细的逐步命令已经整理到:
为了避免泄露真实环境,本文统一使用 cloud.example.com、headscale.example.com、203.0.113.10 和 <TOKEN> 等示例值。照着部署时必须替换成自己的信息。
一、我要实现什么
硬件和环境如下:
- RK3576,Debian 12 ARM64,4 GB 内存;
- 128 GB NVMe SSD;
- 64 GB U 盘;
- 一台有公网 IP 的 Linux 服务器;
- Windows 电脑;
- 校园网或手机热点。
目标有两种访问方式:
Windows Z/Y 盘:Windows -> Tailscale/Headscale -> RK -> Samba -> /srv/cloud
公网网页:Browser -> HTTPS/Nginx -> FRP -> RK Nginx -> Key 鉴权 -> FileBrowser Quantum -> SSD/USB两条链路操作的是同一份文件,不需要同步。公网服务器只转发流量,不保存云盘数据。
二、为什么要拆成这些组件
Headscale 和 Tailscale
Tailscale 客户端在设备之间建立基于 WireGuard 的加密网络,能穿过多数 NAT。Headscale 是自托管控制服务器,负责设备注册、密钥协调、地址分配和访问策略,但不承担普通文件流量。
两端网络允许时,文件数据点对点传输;校园网限制较严时,Tailscale 会退回 DERP 中继。控制服务器放在自己的公网服务器上,设备就不必依赖 Tailscale 官方控制平面。
Samba
Samba 在 Linux 上实现 SMB 协议。Windows 原生支持 SMB,所以可以直接把共享目录映射为 Z:,使用体验和本地分区很接近。
我没有把 445 端口暴露到校园网或公网。Samba 只监听 RK 的 127.0.0.1:445,然后用 Tailscale Serve 把 Tailnet 内的 445 转发到这个回环端口。
FRP
陌生设备未必安装 Tailscale,因此还要有公网 Web。RK 在 NAT 后面主动连接公网服务器上的 FRPS,建立反向隧道;公网 Nginx 收到 HTTPS 请求后,把它送入隧道。
FRP 的远端代理端口只绑定公网服务器的 127.0.0.1,外界只能走 Nginx 的 443,不能绕过 TLS 和站点规则。
FileBrowser Quantum 与 Key 网关
FileBrowser 负责文件列表、上传、下载、重命名和删除。它没有直接暴露,而是信任 RK Nginx 注入的 X-Forwarded-User。
前面再放一个很小的 Python 鉴权服务:用户输入 Key 后获得 HMAC 签名 Cookie,Nginx 用 auth_request 检查会话。Key 只以 scrypt 散列保存,支持过期、撤销、轮换和全局会话失效。
三、先检查开发板
cat /etc/os-releaseuname -auname -mfree -hlsblk -o NAME,MODEL,SIZE,TYPE,FSTYPE,MOUNTPOINTS,TRANdf -hTip -br addressip route重点是通过型号、容量和传输类型分清 eMMC、NVMe 和 U 盘。本文假设系统在 mmcblk0,SSD 是 /dev/nvme0n1,U 盘是 /dev/sda。
如果先用手机热点联网:
nmcli device wifi listnmcli device wifi connect '<SSID>' password '<PASSWORD>' ifname wlan0ping -c 3 1.1.1.1校园网若是 802.1X,不能只填账号密码,还要知道 EAP 类型、二阶段认证和 CA 证书策略。我先用普通热点把环境配好,再迁移到校园网,这样排障变量更少。
四、为什么发现设备后还要挂载
lsblk 能看到磁盘,只代表内核发现了块设备。应用只能通过 Linux 目录树访问文件系统,所以还必须把分区挂载到目录。
本方案没有把 SSD 和 U 盘做 RAID、LVM 或联合文件系统,而是分别挂载:
/srv/cloud├── SSD -> NVMe ext4└── USB -> U 盘 ext4这样一块设备拔出或损坏不会连带破坏另一块盘。代价是它们不是一个可自动汇总容量的文件系统。
下列格式化命令会清空目标磁盘。务必先用
lsblk、blkid和findmnt确认设备,绝对不要对系统 eMMC 执行。
wipefs -a /dev/nvme0n1parted -s /dev/nvme0n1 mklabel gpt mkpart primary ext4 0% 100%mkfs.ext4 -L RK_SSD /dev/nvme0n1p1
wipefs -a /dev/sdaparted -s /dev/sda mklabel gpt mkpart primary ext4 0% 100%mkfs.ext4 -L RK_USB /dev/sda1
blkid /dev/nvme0n1p1 /dev/sda1创建服务用户和挂载点:
groupadd --system clouduseradd --system --gid cloud --home-dir /nonexistent \ --shell /usr/sbin/nologin cloudinstall -d -m 0770 -o cloud -g cloud /srv/cloud/SSD /srv/cloud/USB把真实 UUID 写入 /etc/fstab:
UUID=<SSD_UUID> /srv/cloud/SSD ext4 defaults,noatime,nofail,x-systemd.device-timeout=10s 0 2UUID=<USB_UUID> /srv/cloud/USB ext4 defaults,noatime,nofail,x-systemd.device-timeout=10s 0 2systemctl daemon-reloadmount -achown cloud:cloud /srv/cloud/SSD /srv/cloud/USBchmod 0770 /srv/cloud/SSD /srv/cloud/USBfindmnt /srv/cloud/SSD /srv/cloud/USBdf -hT /srv/cloud/SSD /srv/cloud/USB如果挂载失败却继续向空目录写文件,数据会落到 eMMC 上,这是必须防范的坑。
五、部署 Headscale
先把 headscale.example.com 解析到公网服务器。Debian 12 可安装 Headscale 官方 DEB:
HEADSCALE_VERSION=0.29.3wget -O /tmp/headscale.deb \ "https://github.com/juanfont/headscale/releases/download/v${HEADSCALE_VERSION}/headscale_${HEADSCALE_VERSION}_linux_amd64.deb"apt install -y /tmp/headscale.deb nginx certbot配置的核心内容:
server_url: https://headscale.example.comlisten_addr: 127.0.0.1:8080trusted_proxies: - 127.0.0.1/32 - ::1/128
prefixes: v4: 100.64.0.0/10 allocation: sequential
database: type: sqlite sqlite: path: /var/lib/headscale/db.sqlite
dns: magic_dns: true base_domain: tail.example.comHeadscale 只监听回环,公网由 Nginx 终止 TLS。Nginx 反向代理必须传递 Upgrade,因为 Tailscale Control Protocol 会通过 WebSocket POST 工作。
systemctl enable --now headscalecurl -fsS http://127.0.0.1:8080/healthheadscale users create cloud仓库中提供了完整的 Headscale 配置和 Nginx 示例。
六、注册 RK 和 Windows
RK 安装 Tailscale:
curl -fsSL https://tailscale.com/install.sh | shsystemctl enable --now tailscaled在 Headscale 服务器生成单次、短期预认证 Key:
headscale preauthkeys create --user cloud --expiration 1h --reusable=false在 RK 使用一次后即可丢弃明文:
tailscale up \ --login-server=https://headscale.example.com \ --authkey='<PREAUTH_KEY>' \ --hostname=rk3576-cloudtailscale statustailscale ip -4Windows 安装 Tailscale 后,用另一个单次 Key 注册:
& "$env:ProgramFiles\Tailscale\tailscale.exe" up ` --login-server=https://headscale.example.com ` --authkey='<ANOTHER_PREAUTH_KEY>' ` --hostname='windows-client'这里容易误解:预认证 Key 只用于首次把设备登记到 Tailnet。登记完成后设备用自己的节点密钥自动连接,所以每次重启不需要登录账号。
七、Samba 和 Windows Z、Y 盘
apt install -y samba在 /etc/samba/smb.conf 添加:
[global] interfaces = lo bind interfaces only = yes smb ports = 445 map to guest = never
[RKSSD] path = /srv/cloud/SSD browseable = yes read only = no valid users = cloud force user = cloud create mask = 0660 directory mask = 0770
[RKUSB] path = /srv/cloud/USB browseable = yes read only = no valid users = cloud force user = cloud create mask = 0660 force create mode = 0660 directory mask = 0770 force directory mode = 0770smbpasswd -a cloudtestparm -ssystemctl enable --now smbdtailscale serve --bg --tcp=445 tcp://127.0.0.1:445tailscale serve status我还给 smbd 和 FileBrowser 增加了 mountpoint 启动前检查。任意一块盘没有真正挂载时,文件服务会拒绝启动,避免数据误写进 eMMC 上的空目录。
在 Windows 管理员 PowerShell 中:
net use Z: "\\<RK_TAILSCALE_IP>\RKSSD" /user:cloud * /persistent:yesnet use Y: "\\<RK_TAILSCALE_IP>\RKUSB" /user:cloud * /persistent:yes星号让 Windows 交互输入 Samba 密码,避免明文出现在命令历史。此后 Z: 是 SSD,Y: 是 U 盘,容量也会分别正确显示。
八、FileBrowser 为什么要配置两个 source
最初我把 /srv/cloud 配成一个名为 RK Cloud 的 source,网页能看到 SSD 和 USB 文件夹,却无法正确显示容量。
原因是 /srv/cloud 本身位于 eMMC,SSD 和 U 盘只是它下面的两个挂载点。一个 source 对应一个文件系统统计,无法把两个独立挂载的容量自动相加。
最终配置改成:
server: port: 18082 listen: "127.0.0.1" sources: - path: "/srv/cloud/SSD" name: "SSD" config: defaultEnabled: true private: true - path: "/srv/cloud/USB" name: "USB" config: defaultEnabled: true private: true
auth: methods: password: enabled: false proxy: enabled: true header: "X-Forwarded-User"
frontend: disableUsedPercentage: false这样网页左侧会显示 SSD、USB 两个入口和各自的占用条。配置文件和 systemd 服务在仓库的 filebrowser/、systemd/ 中。
安装 ARM64 版本:
wget -O /tmp/filebrowser-quantum \ https://github.com/gtsteffaniak/filebrowser/releases/download/v1.5.5-stable/linux-arm64-filebrowserinstall -m 0755 /tmp/filebrowser-quantum /usr/local/bin/filebrowser-quantumsystemctl enable --now filebrowser-quantum九、单 Key 登录的实现
公网 Web 不开放 FileBrowser 的管理后台和密码登录。自定义网关提供:
rk1.<id>.<random>形式的高熵 Key;- scrypt 散列存储;
- HMAC 签名、
Secure、HttpOnly、SameSite=LaxCookie; - 12 小时会话;
- 10 分钟内连续失败限速;
- Key 过期、撤销、轮换;
- 全局会话重置。
部署仓库中的 gateway/cloud_auth.py 后:
cloudkey initcloudkey add owner --expires nevercloudkey listadd 和 rotate 只显示一次明文,应该立刻保存到密码管理器。管理命令还有:
cloudkey add guest --expires 7dcloudkey rotate ownercloudkey revoke guestcloudkey sessions-resetRK Nginx 先向 cloud-auth 发内部鉴权请求,成功后才把 X-Forwarded-User 注入 FileBrowser。FileBrowser 只监听回环地址,否则任何能直连 18082 的人都能伪造认证头。
十、FRP 和公网 HTTPS
公网服务器 FRPS 的关键配置:
bindAddr = "0.0.0.0"bindPort = 10925proxyBindAddr = "127.0.0.1"
auth.method = "token"auth.token = "<RANDOM_64_HEX_TOKEN>"token 可以现场生成:
openssl rand -hex 32RK 的 FRPC:
serverAddr = "203.0.113.10"serverPort = 10925loginFailExit = falseauth.method = "token"auth.token = "<SAME_TOKEN>"
[[proxies]]name = "rk-cloud-web"type = "tcp"localIP = "127.0.0.1"localPort = 18080remotePort = 18082loginFailExit = false 配合 systemd 的 Restart=always,校园网短暂断开后会自动恢复。
公网 Nginx 只需要把云盘域名代理到本机 127.0.0.1:18082:
location / { proxy_pass http://127.0.0.1:18082; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Proto https; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Real-IP $remote_addr; proxy_request_buffering off; proxy_buffering off; client_max_body_size 0;}先把 cloud.example.com 解析到公网服务器,再用 Certbot 申请证书。仓库的 server/ 目录包含 HTTP、HTTPS、FRPS 和 unavailable 页面示例。
十一、这次实际遇到的几个问题
1. 502 Bad Gateway
公网 Nginx 返回 502,说明域名和 Nginx 已经工作,后端链路断在 FRP、RK Nginx、鉴权服务或 FileBrowser。
# 公网服务器systemctl status frps nginxss -lntp | grep -E ':(10925|18082)\b'
# RKsystemctl status frpc-cloud nginx cloud-auth filebrowser-quantumjournalctl -u frpc-cloud -n 100 --no-pagercurl -I http://127.0.0.1:18080/login有一次 502 的直接原因是 Python 登录页模板用了 str.format(),而 CSS 也包含花括号,结果 CSS 被误当成格式字段并抛出 KeyError。后来改成明确占位符的 .replace(),避免模板语法与 CSS 冲突。
2. 登录页 403
403 来自鉴权服务的来源校验。浏览器翻译或某些跳转可能发送 Origin: null,两层代理如果又没有一致传递 Host,就会被拒绝。
最终逻辑是:普通 Origin 必须和可信 Host 一致;Origin: null 只有在 Host、X-Forwarded-Host 归一化后唯一且一致时才放行。这样兼容浏览器,又不把 CSRF 防护完全关闭。
3. 页面一直显示“恢复连接”
这是公网入口在线但 RK 到服务器的 FRP 隧道中断。校园网切换、Wi-Fi 省电或 DNS 暂时异常都可能触发。systemd 自动重启 FRPC,再加一个周期检查 NetworkManager、Tailscale 和 FRPC 的 timer,可以自动恢复大多数短暂故障。
4. 有些文件无法访问
网页和 Samba 都以 cloud 用户操作。root 手动复制的文件若属于 root:root 且权限太紧,就会出现能看到但打不开或无法修改。
只修复明确目录:
chown -R cloud:cloud /srv/cloud/SSD/目标目录chmod -R u+rwX,g+rwX,o-rwx /srv/cloud/SSD/目标目录不要对系统根目录递归 chown,也不要用 chmod 777。
5. 一个共享无法正确表示两块盘的容量
Samba 共享根目录跨越两个文件系统时,Windows 只能显示共享根对应的一个文件系统统计,不能自然表示 SSD 与 U 盘之和。最终我把 Samba 也拆成 RKSSD、RKUSB 两个共享,分别映射为 Z:、Y:;网页则通过拆分 source 达到同样效果。
十二、验收与重启测试
RK 本地:
systemctl --no-pager status \ tailscaled smbd filebrowser-quantum cloud-auth nginx frpc-cloudfindmnt /srv/cloud/SSD /srv/cloud/USBdf -hT /srv/cloud/SSD /srv/cloud/USBtailscale serve status公网服务器:
systemctl --no-pager status headscale frps nginxcurl -fsS https://headscale.example.com/healthcurl -I https://cloud.example.com/login最后分别在网页 SSD、USB 两个来源执行上传、读取、重命名、删除,再去 Z: 盘确认变化。仓库中的 tests/public_crud.ps1 可以自动完成两块盘的公网 CRUD 测试,并在 finally 中清理测试文件。
完成后必须重启 RK 再测一次。只有挂载、Tailnet、Samba、Tailscale Serve、FileBrowser、鉴权、FRPC 全部能自动恢复,才算真正可用。
十三、安全和备份
这套方案缩小了暴露面,但它不是备份系统:
- 445 只放在 Tailnet,绝不直接开放公网;
- FileBrowser 与鉴权服务只监听回环;
- FRP 远端代理只绑定公网服务器回环;
- 所有公网文件流量经过 HTTPS;
- Key、FRP token、Samba 密码、预认证 Key和 SSH 私钥绝不入库;
- 网页和 SMB 删除的是同一份真实文件,默认没有回收站;
- 重要文件必须另做离线或异地备份,并测试恢复。
结语
这套架构把“自己的设备像本地盘一样用”和“陌生设备临时用网页访问”分开处理:前者用 Tailnet + SMB 获得原生体验,后者用 HTTPS + Key 获得低门槛访问。RK 保存数据,公网服务器只做控制和转发,SSD 与 U 盘保持独立,故障边界也比较清晰。
如果要完全复现,建议以 cloud 仓库的部署指南 为准,逐项替换示例值,并在每一层完成本地检查后再继续下一层。
Some information may be outdated