warpgate/warpgate-prd-v3.md
grabbit 3caddc6370 Simplify architecture: read-only cache + one-way SD upload
Replace OverlayFS + sync daemon with two independent subsystems:
- Read-only cache: rclone --read-only + Samba read only = yes
- SD Uploader: staging → SFTP direct upload to NAS (temp file + rename)

Remove: OverlayFS, sync daemon, three-timestamp model, write-back,
conflict detection, dirty file tracking. Net -299 lines.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 14:35:43 +08:00

55 KiB
Raw Blame History

Warpgate — Make your NAS feel local — 产品方案与需求描述v3


一、产品定位

一句话描述:摄影师的随身存储中枢——外拍插卡自动归档,路上缓存加速访问,全程高速组网,云端容灾兜底。

核心价值

  1. 远程访问加速:用户在外通过 Tailscale 等组网工具访问家中 NAS 时受限于公网带宽SMB 协议体验极差(卡顿、超时、缩略图加载慢)。本产品在客户端侧部署一层 SSD 只读缓存对上层应用Lightroom、Finder、Explorer 等)完全透明,首次访问按需拉取并缓存到本地 SSD后续访问直接命中缓存。
  2. 外拍现场备份归档:摄影师外拍结束插入 SD 卡,一键备份到本地 SSD后台自动异步归档回家中 NAS。把「现场备份」和「远程归档」打通成一条自动流水线市面上没有产品做到这一点。
  3. 数据安全兜底:可选的云端异地容灾,为用户的数据提供多层保护。

产品形态

  • 软件方案MVP配置文件 + 一键部署脚本,部署在任意 Linux 主机上Docker 镜像在 v2.5 提供)
  • 硬件一体机(目标形态):定制盒子,内置 SSD + 电池 + SD 卡槽 + WiFi开箱即用

市场机会

  • Gnarbox曾最受欢迎的摄影师外拍备份设备已停产市场空缺明显
  • UnifyDrive UT2$599硬件形态相似但软件体验差、电池仅 1 小时
  • ClouZen TAINER 功能单一,只能备份不能联网同步
  • 没有产品把「现场备份」和「远程归档回 NAS」打通成自动流水线

二、目标用户

用户画像 典型场景 痛点
摄影师 出差/外拍酒店回看、粗修当天照片Lightroom RAW 文件 25-60MBSMB 远程逐张打开极慢,预览生成卡死
视频创作者 远程剪辑,浏览素材库、拖拽代理文件 视频文件更大,顺序播放需持续带宽
设计师 出差访问公司 NAS 上的 PSD/AI 源文件 大文件 + 多图层,打开一个文件几分钟
远程办公族 日常办公文档、项目资料存 NAS 小文件多SMB 目录浏览延迟高,体验卡顿
NAS 重度用户 旅行途中访问个人数据 没有公网 IP 或带宽不足,现有方案都不理想

核心用户优先级摄影师Lightroom + 外拍备份)> 远程办公 > 视频创作者


三、系统架构

3.1 整体架构

graph LR
    subgraph clients ["客户端设备"]
        LR["Lightroom<br/>macOS"]
        Linux["Linux<br/>客户端"]
        iPad["iPad<br/>移动端"]
    end

    subgraph proxy ["Linux Proxy局域网·高速"]
        Samba["Samba Server<br/>(read only)"]
        NFS_S["NFS Server<br/>(read only)"]
        WebDAV_S["WebDAV Server<br/>(read only)"]
        VFS["rclone VFS mount<br/>(只读缓存)"]
        Uploader["SD Uploader<br/>(单向上传到 NAS)"]
        SSD["SSD 缓存 + 元数据 DB"]
        Samba --> VFS
        NFS_S --> VFS
        WebDAV_S --> VFS
        VFS --> SSD
    end

    subgraph remote ["远程(公网 / Tailscale·低速"]
        TS["Tailscale /<br/>WireGuard"]
        NAS["任意品牌 NAS<br/>(SFTP)"]
        TS --- NAS
    end

    LR -- "SMB" --> Samba
    Linux -- "NFS" --> NFS_S
    iPad -- "WebDAV" --> WebDAV_S
    VFS -- "SFTP (读取)" --> TS
    Uploader -- "SFTP (上传)" --> TS

3.2 协议选择说明

协议 原因
客户端 → Proxy SMB 对 Lightroom/macOS/Windows 原生兼容,应用无感
客户端 → Proxy NFS Linux 客户端性能更好,内核级支持
客户端 → Proxy WebDAV 移动端 App 支持广泛
Proxy → NAS SFTP 高延迟链路下比 SMB 稳定得多,任意品牌 NAS 均支持,无需额外套件

3.3 多协议对外服务(设计讨论)

问题:客户端 → Warpgate 之间只支持 SMB 是否足够?

讨论不同客户端设备对协议的偏好不同。macOS + Lightroom 最适合 SMB但 Linux 客户端用 NFS 性能更好(内核级支持,且 Linux 侧还能再叠一层 FS-CacheiPad/移动端 App 则普遍支持 WebDAV。

设计决策:所有对外协议服务共享同一个 rclone FUSE 只读挂载点。缓存层只有一份,不会因为多协议而重复缓存。所有协议均为只读模式,客户端无法通过共享写入文件。

graph LR
    SMB["SMB Server<br/>(read only)"] --> Mount["/mnt/nas-photos<br/>(rclone 只读 FUSE 挂载)"]
    NFS["NFS Server<br/>(read only)"] --> Mount
    WebDAV["WebDAV<br/>(read only)"] --> Mount
    Mount --> SSD["SSD 缓存<br/>(rclone 内部管理)"]

注意事项:所有共享均为只读。文件写入通过 SD 卡导入功能单向上传到 NAS独立于缓存系统


四、核心功能

P0MVP 必须)

4.1 透明多协议只读代理

  • 对外暴露标准 SMB 只读共享,客户端连接方式与直连 NAS 完全一致
  • 支持 SMB2/SMB3 协议(read only = yes
  • 同时支持 NFS 只读导出Linux 客户端)和 WebDAV 只读服务(移动端)
  • 支持 macOSFinder/Lightroom、WindowsExplorer、Linux、移动端客户端
  • 文件读取、目录浏览、文件属性(时间戳/权限)均正常工作
  • 所有协议共享同一个 rclone 只读缓存层,不重复存储
  • 只读设计Lightroom 等应用读取 RAW 文件,编辑参数存在本地 catalog 中,不需要写入 NAS 文件所在目录

4.2 读缓存Read-through Cache

  • 文件首次被访问时,从远程 NAS 拉取并存入本地 SSD 缓存
  • 后续访问同一文件直接从本地 SSD 返回
  • 支持分块读取chunked read大文件不需要整个下载完才能开始读取
  • 支持预读read-ahead顺序读取场景下提前拉取后续数据
  • 目录列表缓存:目录结构缓存一段时间,避免频繁远程查询

4.3 缓存一致性

  • 缓存为只读,不存在本地修改,因此不存在写冲突问题
  • 后台轮询检测远程变更,自动标记缓存失效(详见第六章)
  • 远程文件被修改 → 下次访问时 rclone 自动拉取最新版本
  • 远程文件被删除 → rclone 缓存刷新后文件自动消失
  • 无脏文件、无回写、无冲突检测 —— 架构极简

4.5 远程变更检测

  • 基于 SFTP 的分层轮询机制,自动发现远程数据变化(详见第六章)
  • 不依赖任何 NAS 品牌特有 API纯 SFTP 协议实现
  • 每日全量校对兜底

4.6 缓存空间管理

  • 设置缓存总大小上限
  • 超出上限时按 LRU最久未访问策略自动淘汰
  • 可设置缓存盘最低保留空间,防止磁盘写满
  • 可设置缓存最大保留时间
  • 缓存全部为只读cleanrclone 可自由淘汰任何缓存文件

4.7 一键部署

  • 提供完整的配置文件 + 部署脚本
  • 自动安装依赖rclone、Samba、NFS、fuse
  • 自动配置 systemd 服务,开机自启
  • 自动配置日志轮转
  • 缓存盘文件系统建议 btrfs/ZFSCoW + journal 保护一致性)

P1重要但非 MVP

4.8 SD 卡导入 + 自动归档Ingest

摄影师外拍结束,把 SD 卡插入盒子,一键备份到本地 SSD 暂存,后台独立进程通过 SFTP 直接上传到家中 NAS。与只读缓存完全独立——导入是单向的「SD → NAS」管道不经过缓存系统。

  • 检测到 SD/CFexpress 卡插入后,支持物理按钮一键触发导入或自动导入模式
  • 导入前空间检查:估算 SD 卡总数据量,检查 SSD 暂存区可用空间(扣除 CACHE_MIN_FREE空间不足时拒绝导入并通知用户
  • 复制文件到 SSD 暂存目录(ingest_staging/),导入时计算文件 checksumSHA-256确保数据完整
  • 导入记录写入 import_history 持久表(详见 5.6.4
  • SD Uploader 独立进程通过 SFTP 将暂存文件直接上传到 NAS 目标路径,无网络时排队等待
  • 上传成功后清理暂存文件 + 可选 vfs/forget 通知 rclone 刷新(使新文件在只读缓存中可见)
  • 支持重复文件检测(基于文件名+大小+checksum查询 import_history 持久表(详见 5.6.4),避免重复导入
  • 导入完成通过 LED 指示灯 / 蜂鸣器提示
  • 支持按日期模板自动组织目标路径(如 /{year}/{month}/{date}/),日期来源为 EXIF 拍摄日期,非 EXIF 文件回退到文件 mtime详见 INGEST_DATE_SOURCE 配置)
  • 导入中断保护:导入过程维护状态机(detecting → copying → checksumming → uploading → complete),中断的文件(未完成 checksum 校验)将被清理(详见 5.8

4.9 双卡备份 + 校验

摄影师同时插入两张 SD 卡(或一张 SD 卡 + 一个 USB 移动硬盘),盒子自动做双向 checksum 比对,确保两份备份完全一致。

  • 并行读取两个存储设备的文件列表
  • 逐文件比对:文件名 + 大小一致后计算 SHA-256 比对
  • 不一致的文件标记为异常并通知用户
  • 仅一侧存在的文件单独提示
  • 校验完成输出报告LED 状态 + CLI 可查详情)
  • 校验通过的文件自动进入 4.8 导入归档流程

4.10 配网模式 + Captive Portal 代理Setup AP

盒子是 Headless 设备(无屏幕),而绝大多数酒店/机场 WiFi 需要网页认证Captive Portal。没有这个功能旅途场景直接不可用。

核心流程

① 盒子开机,检测到未配置 WiFi 或无法联网
   → 自动进入「配网模式」WiFi 模块启动临时 APSSID: Warpgate-Setup

② 用户手机连接 Warpgate-Setup 热点
   → 自动弹出配网页面(或手动访问 http://192.168.4.1

③ 配网页面显示周围可用 WiFi 列表,用户选择酒店 WiFi

④ 盒子连接酒店 WiFiWiFi 模块切换为 AP+STA 并发模式)
   → 检测到 Captive Portal 重定向

⑤ 盒子将 Captive Portal 认证页面代理到配网页面
   → 用户在手机上完成酒店 WiFi 的网页认证(输入房号/姓名等)

⑥ 认证通过盒子获得互联网访问Tailscale 自动连接
   → 配网模式关闭,临时 AP 关闭(或保持为管理入口)

硬件要求WiFi 模块必须支持 AP+STA 并发模式(同时作为热点和连接外部 WiFi这是配网模式的前提。大多数支持 AP 模式的 WiFi 芯片均支持此功能。

Fallback 方案(不需要额外开发,文档中列出即可):

  • USB 网络共享:手机 USB 连接盒子共享手机网络tethering绕过酒店 WiFi
  • 手机热点:盒子直连手机 4G/5G 热点
  • 有线以太网:部分酒店有网口,直插通常无需认证
  • MAC 克隆warpgate clone-mac <MAC> 克隆已认证设备的 MAC 地址(高级用户)

4.11 缓存预热Warm-up

  • 命令行手动预热指定目录
  • 按时间范围预热(如"最近 7 天新增的文件"
  • 定时预热任务(如每天凌晨自动拉取最新数据)
  • 预热进度显示

4.12 管理工具CLI

  • warpgate status — 查看服务状态、缓存使用量、SD 上传队列、当前带宽限速
  • warpgate cache-list — 列出缓存中的文件
  • warpgate cache-clean — 清理缓存
  • warpgate warmup — 手动预热
  • warpgate bwlimit — 动态调整带宽限制
  • warpgate ingest — 手动触发 SD 卡导入
  • warpgate verify — 双卡校验
  • warpgate log — 查看实时日志
  • warpgate speed-test — 链路速度测试
  • warpgate setup-wifi — 手动进入配网模式
  • warpgate clone-mac <MAC> — 克隆指定设备的 MAC 地址

4.13 带宽管理

  • 支持上传SD 导入上传)/下载(缓存拉取)分别限速
  • 运行时动态调整限速(不重启服务)
  • SD 上传带宽不影响读取体验
  • 自适应限速Adaptive Throttle:基于吞吐量观测自动降速,避免 SD 上传占满链路
    • 监控上传吞吐量的滑动窗口(如最近 30s 平均值),当吞吐持续下降超过阈值时判定链路拥塞
    • 拥塞时自动降速,释放带宽给读取和其他流量
    • 每隔一段时间小幅探测提速
    • throttle 状态通过 warpgate status 实时可见
    • 用户可通过 BW_ADAPTIVE 配置关闭自适应限速
    • 自适应限速仅控制 SD 上传,不影响缓存读取拉取

4.14 连接容错

  • Tailscale 断连时自动重试
  • 已缓存的文件在离线时仍可正常读取
  • SD 上传队列在恢复连接后自动续传
  • 连接超时参数可配置

P2后续迭代

4.16 WiFi AP 现场共享

盒子内置 WiFi 模块开启持久热点,现场团队设备连上即可通过 SMB/WebDAV 访问缓存目录。与 4.10 配网模式的区别:配网 AP 是临时的(完成配网后关闭),本功能是持久的团队共享热点。

  • 支持 AP 模式,复用已有的 SMB/WebDAV 多协议服务
  • AP 网络与 Tailscale/WAN 网络隔离(安全考虑)
  • AP 模式下仍可同时通过有线/4G 连接 Tailscale 做后台 SD 上传
  • 硬件要求需要两个独立网络接口——WiFi 模块用于 AP 热点,有线/USB 4G 网卡用于 WAN/Tailscale 连接。一体机硬件设计需预留双网卡
  • 典型场景:婚礼现场摄影师导入 SD 卡后,助理 iPad 连上 WiFi 即可浏览选片

4.17 Web 管理界面

  • 缓存状态仪表盘大小、命中率、SD 上传队列、带宽趋势图)
  • 缓存文件浏览器(查看/手动清除/手动预热)
  • 配置修改界面(参数调整无需编辑配置文件)
  • SD 导入历史浏览
  • 实时日志查看器

4.18 NAS 侧 Agent 推送(可选增强)

  • 在 NAS 上运行轻量 AgentDocker 容器),监听文件变化主动推送给 Proxy
  • 实现秒级远程变更感知(替代分钟级轮询)
  • 不依赖品牌 API基于 inotify 通用方案

4.19 多 NAS / 多目录支持

  • 同时连接多个远程 NAS如家里 + 工作室)
  • 每个 NAS 独立共享名,独立缓存策略
  • 每个共享可配置不同的缓存大小和保留时间

4.20 智能缓存策略

  • 根据文件类型自动调整策略:
    • .lrcat / .xmpLightroom catalog/sidecar→ 高缓存优先级
    • .CR3 / .ARW / .NEFRAW 照片)→ 大块预读,长缓存保留
    • .mp4 / .mov(视频)→ 顺序预读优化
    • .psd / .ai(设计文件)→ 完整缓存,避免分块导致的兼容问题
  • 基于访问频率自动调整缓存优先级(热数据不被淘汰)

4.21 Docker 镜像

  • 一行命令启动:docker run -v /mnt/ssd:/cache warpgate
  • docker-compose 配置
  • 支持环境变量或挂载配置文件

4.22 通知机制

  • SD 上传失败告警Webhook / Telegram / 邮件)
  • 缓存空间不足告警
  • NAS 离线告警
  • SD 导入完成 / 上传完成通知(可选)

五、数据一致性模型

5.1 设计目标

采用只读缓存 + 单向上传模型,两个功能完全独立。具体承诺:

  1. 远程 NAS 上的变更会在可控时间内被 Proxy 感知并更新本地缓存(后台轮询)
  2. 已缓存的文件在离线时仍可正常访问
  3. SD 卡导入的文件最终会上传到远程 NASSD Uploader 异步上传 + 断电恢复靠 SSD 持久化暂存文件)
  4. 无写冲突:缓存只读,不存在本地修改与远程变更冲突的可能

5.2 设计讨论与决策过程

问题 A为什么选择只读缓存而非读写缓存

讨论:最初设计了 OverlayFS + sync daemon 的读写缓存方案(本地写入 → 异步回写 NAS但面临大量边界场景

  • 写冲突检测(本地改了文件 + NAS 也被别人改了)
  • mtime 精度不一致导致误判
  • 断电后脏文件恢复
  • TOCTOU 竞态条件
  • 远程删除后脏文件复活

最终决策:只读缓存 + 单向上传。理由:

  • 摄影师核心工作流是「浏览/粗选 RAW」只读+ 「SD 卡导入」(单向),不需要通过缓存写回 NAS
  • Lightroom 编辑参数存在本地 catalog.lrcat不修改 RAW 文件本身
  • 只读缓存消灭了所有写冲突、脏文件、回写等复杂性
  • SD 导入是纯新文件的单向上传,不存在冲突
  • 写回能力可以作为 v2.0 按需加入

MVP 部署建议建议缓存盘使用 btrfs 或 ZFS 文件系统CoW + checksum 保护断电一致性)。部署脚本应检测并警告非 CoW 文件系统。

问题 BSD 导入上传中断怎么办?

讨论SD 卡导入后文件暂存在 SSD 上等待上传到 NAS。上传过程中可能断电、断网、进程崩溃。

决策

  • 暂存文件持久化在 SSD 上,重启后 SD Uploader 扫描暂存目录自动继续上传
  • 上传到 NAS 时使用临时文件名(.warpgate-tmp-<hash>),完成后 rename 为最终文件名,避免 NAS 上出现不完整文件
  • 上传成功后清理暂存文件,可选 vfs/forget 刷新缓存使新文件可见

5.3 一致性模型(只读缓存 + 单向上传)

架构分为两个完全独立的子系统:

只读缓存路径:应用 → Samba/NFS只读 → rclone FUSE 挂载 → SSD 缓存 ← SFTP 从 NAS 按需拉取
SD 上传路径:  SD 卡 → ingest_staging/SSD 暂存) → SD Uploader → SFTP 直传 NAS

只读缓存

  • rclone 以 --read-only --vfs-cache-mode full 挂载Samba/NFS 以只读模式共享
  • 所有缓存文件都是 clean 的与远程一致rclone 自由管理 LRU 淘汰
  • 后台轮询检测远程变更 → vfs/forget 通知 rclone 刷新 → 下次访问拿到最新版本
  • 无脏文件、无回写、无冲突 —— 零数据一致性风险

SD 单向上传

  • SD Uploader 是独立进程,与缓存系统无交互
  • ingest_staging/ 扫描待上传文件 → SFTP 上传到 NAS临时文件名 + rename
  • 上传失败自动重试(指数退避),断电重启后扫描暂存目录继续上传
  • 上传成功后清理暂存文件,可选 vfs/forget 刷新缓存使新文件可见
  • 无冲突SD 导入的都是新文件NAS 上不存在同名文件时直接创建已存在时跳过import_history 去重已在导入阶段处理)

缓存淘汰rclone 由 --vfs-cache-max-size--vfs-cache-max-age 控制 LRU 淘汰,所有缓存文件均可安全淘汰。

5.4 读取时的缓存验证

缓存为只读,所有文件都是 clean 的,验证逻辑非常简单:

远程状态 缓存行为
远程没变 直接用缓存
远程被修改 🔄 轮询发现变化 → vfs/forget → rclone 下次访问时拉新版本
远程被删除 🗑️ 轮询发现变化 → vfs/forget → rclone 刷新后文件消失

注意:读取热路径上不查远程(不产生网络请求),直接返回缓存。远程变更由后台轮询线程定期发现(见第六章)。

5.5 关键场景走查

场景 1Cache 关机几天NAS 上文件被修改

flowchart TD
    D1["Day 1: Cache 缓存 photo.cr3 (clean)"] --> Off["Day 1: Cache 关机"]
    Off --> D3["Day 3: NAS 上 photo.cr3 被修改"]
    D3 --> D5["Day 5: Cache 开机<br/>rclone 启动,轮询线程开始"]
    D5 --> Detect["轮询发现目录 mtime 变化"]
    Detect --> Forget["vfs/forget → rclone 缓存失效"]
    Forget --> Access["用户访问时 rclone 自动拉新版本"]
    Access --> Result["✅ 用户读到 NAS 最新版本"]

场景 2NAS 删了文件

flowchart TD
    D1["Day 1: Cache 缓存 photo.cr3 (clean)"]
    D1 --> D3["Day 3: NAS 上删除 photo.cr3"]
    D3 --> Poll["轮询检测到远程目录变化"]
    Poll --> Forget["vfs/forget → rclone 刷新"]
    Forget --> Gone["rclone 缓存自动消失"]
    Gone --> Result["✅ 本地缓存与远程保持一致"]

场景 3SD 卡导入 + 上传到 NAS最常见 happy path

flowchart TD
    Insert["摄影师插入 SD 卡"] --> Detect["检测到卡,按按钮触发导入"]
    Detect --> Copy["复制到 ingest_staging/<br/>计算 SHA-256"]
    Copy --> Dedup["查 import_history → 无重复"]
    Dedup --> Record["记录 import_history"]
    Record --> Upload["SD Uploader 通过 SFTP<br/>上传到 NAS临时文件 + rename"]
    Upload --> Clean["清理暂存文件<br/>vfs/forget 刷新缓存"]
    Clean --> Result["✅ 文件安全到达 NAS<br/>缓存中可见新文件"]

场景 4SD 导入后离线,后续上传

flowchart TD
    Insert["外拍现场,无网络"] --> Import["SD 卡导入到暂存目录<br/>文件安全存在 SSD 上"]
    Import --> Queue["SD Uploader 发现无网络<br/>上传排队等待"]
    Queue --> Hotel["回到酒店,网络恢复"]
    Hotel --> Resume["SD Uploader 自动续传<br/>逐文件上传到 NAS"]
    Resume --> Result["✅ 无需人工干预"]

5.6 元数据持久化metadata DB

metadata.db 用于 SD 卡导入去重import_history远程变更轮询dir_snapshots。使用 SQLite 存储。只读缓存不需要额外状态数据库——rclone 内部管理所有缓存状态。

5.6.1 设计讨论metadata DB 应该包含什么?

问题metadata DB 需要存 NAS 侧的所有文件元数据吗?

分析NAS 上可能有几十万甚至上百万文件,但用户一次出差实际访问的可能只有几百到几千个。如果全量存储 NAS 文件元数据,会带来几个问题:

  • 初始化成本高——首次使用需要递归扫描整个 NAS 目录树
  • 存储浪费——大量从未访问的文件元数据没有价值
  • 同步负担重——需要持续维护全量数据的一致性

结论metadata DB 只管辅助功能(轮询和导入去重),不存文件级缓存状态。缓存状态完全由 rclone 内部管理。

怎么检测远程文件被删了?

rclone 自动处理,不需要数据库:

假设目录 /2026/02/ 下有 200 张照片,用户只缓存了 3 张。

场景 ANAS 上 IMG_0050.cr3 被删了(从未缓存过) → 跟缓存无关,不处理

场景 BNAS 上 IMG_0001.cr3 被删了(缓存过) → 轮询发现目录变化 → vfs/forget → rclone 刷新 → 缓存文件消失

场景 CNAS 上 IMG_0050.cr3 被修改了(缓存过) → 轮询发现目录变化 → vfs/forget → rclone 刷新 → 下次读取拿到新版本

最终决策

用途 理由
dir_snapshots 轮询目录 mtime 快检 记住上次轮询时目录的 mtime判断是否变化
import_history SD 导入去重 持久记录历史导入,防止重复导入

5.6.2 缓存目录结构

/mnt/ssd/warpgate/
├── rclone-cache/                 # rclone VFS 内部缓存目录rclone 自动管理)
│   └── vfs/
│       └── photos/
│           └── 2026/02/
│               └── IMG_0001.cr3  # 远程拉取缓存
├── metadata.db                   # SQLite 元数据库WAL 模式,详见 5.6.7
└── ingest_staging/               # SD 卡导入暂存目录(导入状态机使用,详见 5.8
    └── <session-id>/            # 每次导入会话独立目录

挂载关系

# rclone 只读 FUSE 挂载(= Samba/NFS 共享的根目录)
rclone mount remote:photos /mnt/nas-photos \
  --read-only \
  --vfs-cache-mode full \
  --vfs-cache-max-size ${CACHE_MAX_SIZE} \
  --vfs-cache-max-age ${CACHE_MAX_AGE} \
  --cache-dir /mnt/ssd/warpgate/rclone-cache \
  --rc

# Samba 配置(只读)
# [nas-photos]
#     path = /mnt/nas-photos
#     read only = yes

重要

  • rclone-cache/ 由 rclone 内部管理,外部进程不得直接操作
  • ingest_staging/ 由 SD 导入进程管理SD Uploader 从此目录读取待上传文件
  • Samba/NFS 直接服务于 rclone FUSE 挂载点 /mnt/nas-photos

5.6.3 表结构定义

表 1dir_snapshots — 目录级轮询快照

用于分层轮询的"目录 mtime 快检"。只记录被缓存文件所在的目录,不是 NAS 全量目录。

CREATE TABLE dir_snapshots (
    dir_path        TEXT PRIMARY KEY,    -- 目录相对路径,如 /2026/02/
    remote_mtime    INTEGER,            -- 上次轮询时远程目录的 mtime
                                        -- NULL 表示 SD 卡导入创建的目录,远程尚不存在
    last_polled     INTEGER NOT NULL,    -- 上次轮询时间
    last_accessed   INTEGER NOT NULL     -- 目录最后被访问时间(决定热/温/冷分级)
);

生命周期:

某目录下的文件首次被缓存    → INSERT
SD 卡导入上传到新目录        → INSERTSD Uploader 上传完成后remote_mtime 为上传后 stat 的值)
轮询时目录 mtime 没变       → UPDATE last_polled
轮询时目录 mtime 变了       → UPDATE remote_mtime触发 vfs/forget 刷新缓存
目录下已无缓存文件          → DELETE可选

表 1bdir_file_list — 目录文件列表快照(后续增强)

MVP 阶段不需要。后续加入后,记录被关心目录下的全部远程文件,支持精确变更类型识别和智能预热。

CREATE TABLE dir_file_list (
    dir_path        TEXT NOT NULL,       -- 所属目录
    file_name       TEXT NOT NULL,       -- 文件名
    remote_mtime    INTEGER NOT NULL,    -- 上次已知的远程 mtime
    remote_size     INTEGER NOT NULL,    -- 上次已知的远程文件大小
    snapshot_time   INTEGER NOT NULL,    -- 快照时间
    PRIMARY KEY (dir_path, file_name)
);

CREATE INDEX idx_dir ON dir_file_list(dir_path);

5.6.4 导入历史表(重复检测用)

问题:重复文件检测不能只看当前暂存目录内容——已上传到 NAS 并从暂存区清理的文件,再次导入同一张 SD 卡时无法检测到重复。

解决方案import_history 持久表,记录所有历史导入记录,永不删除。

CREATE TABLE import_history (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    original_path   TEXT NOT NULL,       -- SD 卡上的原始路径
    target_path     TEXT NOT NULL,       -- 上传到 NAS 的目标路径
    file_size       INTEGER NOT NULL,    -- 文件大小
    checksum        TEXT NOT NULL,       -- SHA-256 校验和
    source_device   TEXT,               -- SD 卡设备标识(如序列号)
    imported_at     INTEGER NOT NULL,    -- 导入时间
    uploaded_at     INTEGER,            -- 上传到 NAS 的时间NULL=未上传)
    state           TEXT NOT NULL        -- imported / uploaded / failed
);

CREATE INDEX idx_checksum ON import_history(checksum);
CREATE INDEX idx_original_path ON import_history(original_path, file_size);

重复检测流程:

导入文件前 → 计算 checksum
  → 查 import_history WHERE checksum = ? AND file_size = ?
  → 命中 → 跳过该文件,标记为"已导入过"
  → 未命中 → 执行导入

5.6.5 MVP 表结构关系与数据规模

erDiagram
    dir_snapshots {
        TEXT dir_path PK "如 /2026/02/"
        INTEGER remote_mtime
        INTEGER last_polled
        INTEGER last_accessed
    }

    import_history {
        INTEGER id PK "独立于缓存生命周期"
        TEXT original_path
        TEXT target_path
        TEXT checksum "持续累积,永久保留"
        TEXT state "imported/uploaded/failed"
    }

两张核心表。只读缓存不需要额外的文件状态数据库——rclone 内部管理所有缓存状态。后续可选增加 dir_file_list精确变更检测

数据规模估算:

记录范围 预估行数 存储开销
dir_snapshots 有缓存文件的目录 ~50 ~5 KB
import_history 所有历史导入记录(持续累积) ~5,000 ~500 KB
总计 < 1 MB

5.6.6 远程变更检测

后台轮询线程通过 dir_snapshots 表实现分层轮询(热/温/冷目录区分访问频率)。发现目录 mtime 变化后:

  1. vfs/forget 通知 rclone 清除该目录的缓存元数据
  2. rclone 下次访问该目录时自动从远程重新读取(删除的文件消失,修改的文件 mtime 更新,新增的文件出现)

不需要逐文件对比数据库记录——rclone 的 vfs/forget + 自动重新拉取机制覆盖所有远程变更场景。

5.6.7 SQLite 并发访问策略

metadata.db 会被多个进程/线程并发访问:轮询线程(读写 dir_snapshots、SD 卡导入进程(读写 import_history、SD Uploader更新 import_history 状态、CLI 管理工具(只读查询)。

要求metadata.db 必须存放在本地文件系统SSD 的 ext4/btrfs/ZFS严禁放在 rclone FUSE 挂载目录中。SQLite WAL 依赖 POSIX 文件锁和共享内存(-shm 文件FUSE/网络文件系统无法正确支持这些语义,会导致数据库损坏。

metadata.db 必须以 WALWrite-Ahead Logging模式运行:

PRAGMA journal_mode=WAL;
PRAGMA busy_timeout=5000;  -- 锁等待超时 5 秒

WAL 模式的优势:

  • 读操作不阻塞写操作,写操作不阻塞读操作
  • 多个进程可以同时读取,只有写入互斥
  • 适合"多读少写"的缓存元数据场景

所有访问 metadata.db 的进程必须使用同一个 WAL 模式配置。部署脚本在初始化数据库时自动设置。

5.7 SD 卡导入与上传

SD 卡导入和上传是独立于缓存系统的单向管道SD 卡 → SSD 暂存 → SFTP 上传 NAS。

实现要点

  1. SD 卡导入进程将文件复制到 ingest_staging/<session-id>/ 暂存目录并计算 checksum
  2. 查 import_history 去重,校验通过后记录导入历史
  3. SD Uploader 独立进程扫描 ingest_staging/ 中已校验完成的文件
  4. 通过 SFTP 上传到 NAS 目标路径(临时文件名 .warpgate-tmp-<hash> → rename 为最终文件名)
  5. 上传成功后更新 import_history 状态 + 清理暂存文件 + 可选 vfs/forget 刷新缓存

性能优势:导入直接写入 SSD 本地文件系统,无 FUSE 开销。导入速度取决于 SD 卡读取速度和 SSD 写入速度,通常可达 100MB/s+。上传带宽独立于缓存读取带宽。

5.8 SD 卡导入状态机(中断保护)

问题SD 卡导入过程中可能发生中断(卡被拔出、电池耗尽、进程崩溃)。部分复制的文件不应进入上传队列。

解决方案:导入过程维护严格的状态机,只有完整校验通过的文件才进入上传队列。

stateDiagram-v2
    [*] --> detecting: SD 卡插入
    detecting --> copying: 列出文件清单
    copying --> checksumming: 复制到 ingest_staging/
    checksumming --> registered: SHA-256 校验通过 +<br/>写入 import_history
    registered --> uploading: SD Uploader 开始上传
    uploading --> complete: SFTP 上传成功 +<br/>清理暂存文件
    complete --> [*]

    note right of detecting: 中断 → 清理 staging 临时文件
    note right of copying: 中断 → 清理 staging 临时文件
    note right of checksumming: 中断 → 清理不完整文件
    note right of registered: 中断 → 文件在暂存区 + DB<br/>重启后 SD Uploader 继续上传
    note right of uploading: 中断 → NAS 上临时文件<br/>重启后重新上传覆盖

中断保护:每个导入会话有唯一 session_iddetecting/copying/checksumming 阶段中断时,ingest_staging/ 中的临时文件在下次启动时自动清理。registered/uploading 阶段中断时文件安全在暂存目录中SD Uploader 重启后扫描暂存目录继续上传。NAS 上的临时文件名(.warpgate-tmp-<hash>)确保不完整上传不会被当作正常文件。

实现要点

  • 暂存目录 ingest_staging/<session-id>/ 按导入会话隔离
  • SD Uploader 启动时扫描 ingest_staging/,清理未完成校验的会话,续传已校验的文件
  • 导入进度持久化到 ingest_sessions 表或简单的 JSON 文件,支持断点续传

六、远程变更检测机制

6.1 设计约束

不依赖任何 NAS 品牌特有 API。产品需要支持群晖、QNAP、威联通、TrueNAS 等任意品牌 NAS因此只能基于标准 SFTP 协议实现。

6.2 SFTP 协议的能力边界(设计讨论)

问题:远程 NAS 上数据更新了Cache 怎么知道?能否实时感知?

讨论SFTP 协议本身没有任何通知/推送/订阅机制。它是无状态的文件传输协议,不支持 inotify、webhook、filesystem watch 等概念。每次想知道远程状态,必须主动发请求查询。

考虑过的方案

方案 原理 实时性 品牌依赖 取舍
SFTP 全量轮询 递归 ls 对比 mtime 分钟级 文件多时开销大
SFTP 分层轮询 先查目录 mtime变了再查文件 分钟级 高效,推荐
群晖 FileStation API 调 DSM Web API 秒级 仅群晖 不通用
NAS 侧 Agent 推送 inotifywait → HTTP 通知 秒级 需要 NAS 装软件

最终决策

  • P0MVPSFTP 分层轮询,零额外依赖
  • P2后续:可选的 NAS 侧 Agent 推送,作为增强项给需要秒级同步的用户

6.3 分层轮询策略

核心优化思路:SFTP 目录本身也有 mtime。当目录下有文件新增/修改/删除时,目录的 mtime 会更新。因此可以先查目录 mtime一个 stat 请求),没变就跳过整个目录下所有文件的检查,大幅减少远程请求量。

flowchart TD
    Start["轮询触发"] --> L3{"第三层:热度分级<br/>决定轮询间隔"}
    L3 -->|"热目录7天内访问<br/>每 30s"| L1
    L3 -->|"温目录7-30天<br/>每 5m"| L1
    L3 -->|"冷目录30天+<br/>每 1h"| L1

    L1["第一层:目录 mtime 快检<br/>SFTP stat 查目录 mtime<br/>(每目录 1 个请求)"]
    L1 -->|"mtime 没变"| Skip["跳过该目录 ✅<br/>所有文件一定没变"]
    L1 -->|"mtime 变了"| L2

    L2["第二层:文件级 mtime 对比<br/>SFTP ls -l 该目录"]
    L2 --> Changed["发现变化 → 标记缓存失效<br/>(等访问时再拉)"]
    L2 --> Del["发现删除 → 清理本地缓存"]
    L2 --> New["远程新增 → 不处理"]

    L4["第四层:每日全量校对(兜底)<br/>凌晨全量递归对比<br/>捕捉遗漏 + 清理过期条目"]

轮询伪代码MVP

# watched_directories = SELECT dir_path FROM dir_snapshots
# 按热度分级决定轮询间隔(热 30s / 温 5m / 冷 1h

for dir in watched_directories:
    if now - dir.last_polled < poll_interval_for(dir):
        continue  # 还没到该目录的轮询时间

    # 第一层:目录 mtime 快检1 个 SFTP stat 请求)
    dir_mtime = sftp_stat(dir.dir_path).mtime
    if dir_mtime == dir.remote_mtime:
        UPDATE dir_snapshots SET last_polled = now WHERE dir_path = dir.dir_path
        continue  # 目录没变,跳过 ✅

    # 第二层:目录变了 → 通知 rclone 刷新缓存
    rclone_rc("vfs/forget", dir=dir.dir_path)   # 清除 rclone 目录缓存
    # rclone 下次访问时自动从远程重新读取:
    #   - 远程删除的文件 → rclone 缓存消失 → 文件不再可见
    #   - 远程修改的文件 → rclone 下次访问时拉新版本
    #   - 远程新增的文件 → rclone 下次访问目录时可见
    log_info(f"Remote directory changed: {dir.dir_path}, rclone cache invalidated")

    # 更新目录快照
    UPDATE dir_snapshots SET remote_mtime = dir_mtime, last_polled = now
    WHERE dir_path = dir.dir_path

6.4 后续增强NAS 侧 Agent 推送P2

对于需要秒级同步的用户,可选在 NAS 上部署轻量 AgentDocker 容器),通过 inotify 监听变化并推送:

flowchart LR
    subgraph NAS ["群晖 NAS"]
        Agent["inotifywait 监听文件变化<br/>(Docker 容器)"]
    end

    subgraph Proxy ["Linux Proxy"]
        HTTP["HTTP 接收端<br/>触发缓存刷新"]
    end

    Agent -- "轻量 HTTP POST<br/>(via Tailscale)" --> HTTP

此方案作为分层轮询的增强,不是替代。即使 Agent 不可用,轮询机制仍然工作。


七、缓存行为详细描述

7.1 读取流程

flowchart TD
    App["应用请求读取文件"] --> Samba["① Samba/NFS只读"]
    Samba --> FUSE["② rclone FUSE 挂载"]
    FUSE -->|"缓存命中"| Return["直接返回缓存<br/>SSD 速度)"]
    FUSE -->|"缓存未命中"| Remote["③ rclone 从远程 NAS 拉取"]
    FUSE -->|"缓存已过期"| Refresh["④ rclone 检查远程 mtime<br/>变了则重新拉取"]

    Remote --> Chunk["⑤ 按 chunk 分块下载"]
    Chunk --> Cache["⑥ 缓存到 SSD"]
    Cache --> ReturnData["⑦ 返回数据给应用"]
    Refresh --> ReturnData

设计要点

  • 读取热路径上不查远程(不产生网络请求),直接返回缓存,保证响应速度
  • 目录缓存通过 rclone --dir-cache-time 控制过期刷新
  • 后台轮询线程通过 vfs/forget 主动刷新已知变化的目录

7.2 SD 导入上传流程

flowchart TD
    SD["① SD 卡插入 → 导入到暂存目录<br/>SHA-256 校验 + import_history 去重)"]
    SD --> Staging["② 文件安全存在 ingest_staging/<br/>SSD 本地目录)"]
    Staging --> Scan["③ SD Uploader 扫描暂存目录"]
    Scan --> Upload["④ SFTP 上传到 NAS<br/>(临时文件名 → rename"]

    Upload -->|"上传成功"| Clean["⑤ 更新 import_history<br/>清理暂存文件<br/>vfs/forget 刷新缓存"]
    Upload -->|"上传失败"| Retry["⑤ 自动重试<br/>(指数退避)"]
    Upload -->|"网络不可用"| Queue["⑤ 排队等待<br/>网络恢复后自动续传"]
    Retry --> Upload
    Queue --> Upload

7.3 缓存淘汰策略

缓存全部由 rclone 管理,所有文件都是 clean 的(与远程一致),可安全淘汰:

  • --vfs-cache-max-size:缓存总大小上限,超出时按 LRU 淘汰
  • --vfs-cache-max-age:缓存最大保留时间
  • rclone 自行管理淘汰,无需外部干预
  • 无脏文件 → 不存在「不能淘汰」的场景 → 缓存空间管理极简

7.3.1 SD 导入暂存空间保护

问题SD 卡导入的暂存文件占用 SSD 空间,等待上传到 NAS。离线时暂存文件会累积。

保护措施

  1. 导入前空间预检:估算 SD 卡总数据量,检查 SSD 可用空间(扣除缓存 + CACHE_MIN_FREE空间不足时拒绝导入并通知用户LED 红灯 + CLI 提示),建议先连网上传或清理
  2. 配置验证(部署时):INGEST_MAX_IMPORT_SIZE + CACHE_MIN_FREE < SSD 总容量 - CACHE_MAX_SIZE,不满足时部署脚本报警告

7.4 离线行为

场景 行为
远程不可达,读取已缓存文件 正常返回,无影响
远程不可达,读取未缓存文件 超时报错(可配置超时时间)
远程不可达SD 卡导入 正常导入到暂存目录,上传排队等待网络恢复
远程不可达,后台轮询 静默跳过,不报错,下次重试
恢复连接后 SD Uploader 自动续传 + 立即触发一轮轮询

八、配置参数清单

连接配置

参数 说明 默认值 建议值
NAS_HOST 远程 NAS 的 Tailscale IP - 100.x.x.x
NAS_USER SFTP 用户名 - -
NAS_PASS / NAS_KEY_FILE 认证信息 - 建议密钥
NAS_REMOTE_PATH NAS 上的目标路径 - /volume1/photos
SFTP_PORT SFTP 端口 22 22
SFTP_CONNECTIONS SFTP 连接复用数 8 4-16

缓存配置

参数 说明 默认值 建议值
CACHE_DIR 缓存存储路径 - SSD 路径,建议 btrfs/ZFS
CACHE_MAX_SIZE 缓存大小上限 200G SSD 容量的 70-80%
CACHE_MAX_AGE 缓存最大保留时间 720h30天 按使用习惯
CACHE_MIN_FREE 缓存盘最低可用空间 10G 10-20G

读取优化

参数 说明 默认值 场景建议
READ_CHUNK_SIZE 分块读取大小 256M RAW 照片: 256M,视频: 512M,文档: 64M
READ_CHUNK_LIMIT chunk 自动增长上限 1G -
READ_AHEAD 预读缓冲区 512M 视频场景可加到 1G
BUFFER_SIZE 内存缓冲区 256M -

带宽配置

参数 说明 默认值 场景建议
UPLOAD_TRANSFERS SD 上传并发线程 4 带宽小就设 2
BW_LIMIT_UP SD 上传限速上限 0(不限) 酒店 WiFi 建议 10-20M
BW_LIMIT_DOWN 缓存拉取下载限速 0(不限) 一般不限
BW_ADAPTIVE 自适应上传限速开关 yes yes=根据吞吐量自动降速,no=纯手动

目录缓存与轮询

参数 说明 默认值 场景建议
DIR_CACHE_TIME 目录列表缓存时间 1h 个人: 1-2h,协作: 5-15m
POLL_HOT_INTERVAL 热目录轮询间隔7天内有访问 30s -
POLL_WARM_INTERVAL 温目录轮询间隔7-30天内访问 5m -
POLL_COLD_INTERVAL 冷目录轮询间隔30天+未访问) 1h -
FULL_SYNC_SCHEDULE 每日全量校对时间 03:00 凌晨低峰期

多协议配置

参数 说明 默认值 建议值
ENABLE_SMB 启用 SMB 共享 yes yes
ENABLE_NFS 启用 NFS 导出 no 有 Linux 客户端时开启
ENABLE_WEBDAV 启用 WebDAV 服务 no 有移动端需求时开启
NFS_ALLOWED_NETWORK NFS 允许访问的网段 192.168.0.0/24 按实际局域网设置
WEBDAV_PORT WebDAV 监听端口 8080 -

SD 卡导入配置

参数 说明 默认值 建议值
INGEST_MAX_IMPORT_SIZE 单次导入预留空间上限 256G 按最大 SD 卡容量设置
INGEST_AUTO 插卡后自动导入 no 需按按钮确认
INGEST_TARGET_PATH 导入到 NAS 的目标路径模板 /{year}/{month}/{date}/ 按个人习惯,变量从 INGEST_DATE_SOURCE 确定
INGEST_DATE_SOURCE 路径模板中日期变量的来源 exif exif=EXIF拍摄日期回退到mtimemtime=文件修改时间,import=导入时间
INGEST_DUPLICATE_CHECK 重复文件检测(基于文件名+大小+checksum yes yes
INGEST_DELETE_AFTER 导入+校验完成后是否删除卡上数据 no no(安全起见)
INGEST_IO_CLASS 导入时的 I/O 调度优先级 best-effort:4 使用 ionice 设置,避免导入阻塞缓存读取

配网模式配置

参数 说明 默认值 建议值
SETUP_AP_SSID 配网热点名称 Warpgate-Setup -
SETUP_AP_PASSWORD 配网热点密码(空=开放) 首次配网建议开放,降低门槛
SETUP_AP_AUTO 无网络时自动进入配网模式 yes yes
SETUP_AP_TIMEOUT 配网完成后临时 AP 保持时间 5m 认证成功后自动关闭
SETUP_PORTAL_LISTEN 配网 Web 服务监听地址 192.168.4.1:80 -

WiFi AP 配置

参数 说明 默认值 建议值
AP_ENABLED 启用 WiFi 热点 no 现场共享时开启
AP_SSID 热点名称 Warpgate -
AP_PASSWORD 热点密码 随机生成 首次配置时设定
AP_ISOLATION AP 网络与 WAN 隔离 yes yes
AP_MAX_CLIENTS 最大连接数 8 -

九、场景预设(模板)

为降低用户配置门槛,提供开箱即用的预设模板。

摄影师模式

重点优化大文件读取性能、RAW 浏览流畅
- CACHE_MAX_SIZE=500G
- READ_CHUNK_SIZE=256M
- READ_AHEAD=512M
- DIR_CACHE_TIME=2h      ← 目录结构不常变
- POLL_HOT_INTERVAL=30s
- UPLOAD_TRANSFERS=4      ← SD 上传并发
- ENABLE_SMB=yes
- ENABLE_NFS=no
- ENABLE_WEBDAV=no

视频剪辑模式

重点优化:顺序读取性能、大文件预读
- CACHE_MAX_SIZE=1T
- READ_CHUNK_SIZE=512M
- READ_AHEAD=1G          ← 大预读保证播放流畅
- DIR_CACHE_TIME=1h
- POLL_HOT_INTERVAL=1m
- UPLOAD_TRANSFERS=2      ← 减少 SD 上传并发,保带宽给播放
- ENABLE_SMB=yes
- ENABLE_NFS=no
- ENABLE_WEBDAV=no

文档办公模式

重点优化:小文件快速响应、频繁感知远程变更
- CACHE_MAX_SIZE=50G
- READ_CHUNK_SIZE=64M
- READ_AHEAD=128M
- DIR_CACHE_TIME=30m     ← 协作场景需要较快看到新文件
- POLL_HOT_INTERVAL=15s  ← 更频繁感知远程变更
- UPLOAD_TRANSFERS=4
- ENABLE_SMB=yes
- ENABLE_NFS=no
- ENABLE_WEBDAV=yes      ← 移动端也能访问

十、部署要求

硬件要求(通用 Linux 主机部署)

组件 最低配置 推荐配置
CPU ARMv8 / x86_64 任意 N100 或同级
内存 1 GB 2-4 GB
缓存盘 任意 SSD NVMe SSD
缓存容量 32 GB 常用数据量的 30%+
网口 100M 千兆2.5G 更好)
断电保护 - 内置电池或外接 UPS

硬件要求(一体机目标形态)

通用要求之外,一体机额外需要:

组件 说明
SD 卡槽 SD / microSD覆盖大多数相机
CFexpress 槽(可选) CFexpress Type-B高端相机用户
USB-A/C 口 至少 2 个用于外接读卡器XQD 等)或移动硬盘
WiFi 模块 支持 AP+STA 并发模式(配网必须),建议 WiFi 6
物理按钮 触发 SD 卡导入 / 确认操作
LED 状态指示 导入进度 / 完成 / 错误 / 上传状态
内置电池 支持断电保护 + 便携使用

缓存盘文件系统建议btrfs 或 ZFS。利用 CoWCopy-on-Write和 journal 机制,即使意外断电也能保证文件系统级别的一致性。

# btrfs 格式化示例
mkfs.btrfs /dev/ssd_partition
mount -o compress=zstd /dev/ssd_partition /mnt/ssd/warpgate

软件要求

组件 版本
OS Ubuntu 22.04+ / Debian 12+ / 任意 Linux
rclone 1.65+(关键参数:--read-only --vfs-cache-mode full --vfs-cache-max-size {CACHE_MAX_SIZE} --cache-dir {CACHE_DIR}/rclone-cache --rc)。--read-only 确保只读挂载。--rc 启用 RC API供轮询线程调用 vfs/forget 刷新目录缓存
Samba 4.x
NFS server nfs-kernel-server如启用 NFS
FUSE 3.x
SQLite 3.x元数据存储
Tailscale / ZeroTier 已配置并可连通 NAS

NAS 侧要求

项目 要求
SFTP 服务 开启(群晖:控制面板 → 文件服务 → FTP → 勾选 SFTP
用户权限 SFTP 用户对目标目录有读写权限
Tailscale 已安装并登录同一网络
品牌 无限制,任何支持 SFTP 的 NAS 均可(群晖/QNAP/威联通/TrueNAS/DIY 等)

十一、风险与局限

风险 等级 说明 缓解措施
首次访问慢 固有 未缓存文件必须走远程 预热功能;分块下载优化
缓存一致性延迟 远程变更在轮询间隔内不可见 分层轮询(热目录 30s后续可选 Agent 推送
Tailscale 断连 远程不可达时新文件无法获取 已缓存文件仍可用SD 上传自动排队;恢复后自动续传
轮询开销 大量文件目录轮询消耗带宽 目录 mtime 快检跳过未变目录;热度分级降低冷目录频率
SD 卡导入数据损坏 卡本身坏块导致导入不完整 导入时计算 SHA-256 校验和;双卡校验比对
SD 卡导入中断 卡被拔出 / 电池耗尽 / 进程崩溃 导入状态机保护5.8);未完成文件清理
SD 上传中断 上传过程中断网/断电 临时文件名保护 NAS 数据完整;重启后自动续传
暂存空间耗尽 离线时 SD 导入暂存累积 导入前空间预检7.3.1
中转服务带宽成本 DERP 中继带宽随用户增长上升 大部分连接走 P2P 直连;按流量分级限速/计费;初期节点少按需扩容
云备份存储成本 用户数据增长导致存储费用上升 接低价对象存储B2/R2按量计费传导成本增量备份减少传输量
酒店 Captive Portal Headless 设备无法完成网页认证,旅途场景不可用 配网 AP + Portal 代理4.10fallbackUSB tethering / 手机热点 / MAC 克隆

十二、后续演进方向

阶段 内容 重点
v1.0 — MVP 配置文件 + 部署脚本 + CLI 管理 SMB 只读共享 + rclone 只读缓存 + 分层轮询 + SD 导入 + SD Uploader 单向上传 NAS
v1.5 — 硬件原型 + P1 功能 SD 卡导入 + 双卡校验 + 配网模式 + Captive Portal 代理 + LED/按钮交互 + 缓存预热 + 带宽管理 + 连接容错 硬件原型开发P1 功能完善
v2.0 — 组网服务 内置 Headscale + 高速 DERP 节点 + WiFi AP 共享 开箱即连 + 现场团队协作
v2.5 — 容灾 + 附加 云端异地备份 + Docker 镜像 + 多协议NFS/WebDAV+ NAS 侧 Agent 推送 数据安全闭环 + 降低部署门槛
v3.0 — 硬件产品 定制硬件SSD + 电池 + SD 槽 + WiFi工业设计开箱即用 产品化,面向非技术用户

十三、付费服务

13.1 Headscale + 高速 DERP 中转

问题Tailscale 官方 DERP 是共享资源,跨运营商/跨国时带宽受限。用户自建 DERP 需要有 VPS + 运维能力,门槛高。

方案

flowchart BT
    subgraph infra ["运营基础设施"]
        HS["Headscale<br/>控制面板<br/>(用户管理)"]
        DERP1["DERP 节点<br/>国内 BGP<br/>(低延迟)"]
        DERP2["DERP 节点<br/>香港/日本<br/>(跨境加速)"]
    end

    Box["盒子<br/>开箱即连<br/>(内置配置)"] --> HS
    Box --> DERP1
    NAS_C["NAS 端<br/>自动连接<br/>(安装脚本)"] --> DERP1
    NAS_C --> DERP2
    Travel["出差设备<br/>自动连接<br/>(通过盒子中继)"] --> DERP2

用户体验

  1. 买盒子 → 开机 → 扫码绑定账号
  2. NAS 端运行一行安装脚本,加入用户的 Tailnet
  3. 完成。盒子带到任何地方,自动通过最优 DERP 节点连回家
  4. 无需了解 Headscale、DERP、WireGuard 等概念

迁移路径(避免 vendor lock-in

  • 盒子底层使用标准 WireGuard 协议,用户可随时切换到自建 Headscale 或官方 Tailscale
  • 提供配置导出工具:一键导出 WireGuard 配置、节点列表、DERP 自定义映射
  • 如用户不再使用我们的 Headscale 服务,盒子仍可正常工作(手动配置 Tailscale/WireGuard

定价思路

套餐 内容 参考价
免费 Headscale 控制面板 + 1 个基础 DERP 节点(限速) ¥0
基础版 + 多节点智能选路 + 不限速 ¥15-30/月
专业版 + 优先带宽 + 跨境加速节点 + SLA 保证 ¥50-100/月

成本控制

  • DERP 中继只在无法打洞直连时使用,大部分 Tailscale 连接是 P2P 直连,中继流量占比通常不高
  • 可按实际中继流量动态限速/计费,避免被少数大流量用户拖垮
  • 初期节点少1-2 个),按用户增长逐步扩容

13.2 异地容灾备份

问题NAS 在家里是单点故障——硬盘坏、被盗、火灾、水灾都可能导致数据永久丢失。

方案

flowchart TD
    SSD["盒子 SSD 缓存<br/>(热数据子集)"]
    SSD -->|"空闲时段<br/>加密增量备份"| Cloud

    subgraph Cloud ["云存储服务"]
        UX["用户视角: 一键开通,按月付费"]
        Backend["后端: B2 / R2 / MinIO<br/>(~$5/TB/月)"]
        Encrypt["数据加密: 用户本地生成密钥<br/>运营方看不到明文"]
        KeyBackup["密钥备份: 首次设置强制引导<br/>(密钥文件 / 助记词 / 密码管理器)"]
        Sync["增量同步: 只传变化部分"]
        Restore["恢复: 新盒子 → 输入密钥 → 自动拉取"]
    end

与现有架构的关系

  • 复用 SD Uploader 的思路:本地文件 → 异步上传到云端
  • 不同点:备份目标是云端对象存储而非 NAS且可以备份 NAS 全量数据(不限于缓存过的文件)
  • 可以做成两级:
    • 热备份:盒子 SSD 上缓存过的文件自动备份(几乎零额外成本)
    • 全量备份:通过以下任一路径从 NAS 全量增量备份到云端:
      • 方案 1推荐:盒子在家局域网时自动执行(高速,零公网带宽消耗)
      • 方案 2:盒子在外通过 Tailscale 远程执行(速度受限于公网带宽,但保证便携场景也能跑备份)
      • 方案 3远期:在 NAS 侧部署独立的备份 agentDocker 容器NAS 直接备份到云端,不依赖盒子在线
    • 便携性说明:盒子的核心场景是带出去用。全量备份不要求盒子必须在家——方案 2 保证在外时也能慢速备份,方案 3 完全解耦盒子和全量备份

定价思路

套餐 内容 参考价
免费 热数据备份(仅缓存过的文件),上限 50GB ¥0
基础版 全量备份500GB ¥15/月
专业版 全量备份5TB ¥50/月
按量 超出部分 ¥10/TB/月

十四、明确不做的方向

方向 原因
缩略图/预览生成、Web 相册 破坏「透明代理」核心定位,产品本质是协议透传不是数据加工
AI 选片 非核心,远期可选
程序员场景Git 缓存、Docker 镜像等) 痛点不够强已有成熟方案Git 天然分布式、Codespaces 等)
公网文件分享链接 法律风险 + 需求不明确
多设备 SaaS 管理面板 需求不明确,过早
Docker 开放运行环境 产品定位发散(注:这里指的是允许用户在盒子上运行任意 Docker 容器,而非 4.21 的"将本产品打包为 Docker 镜像部署"

十五、术语表

术语 说明
Warpgate / 盒子 本产品——部署在用户身边的 SSD 缓存代理设备
NAS 用户家中的网络存储设备Network Attached Storage
VFS rclone 的虚拟文件系统层Virtual File System将远程存储挂载为本地目录
FUSE 用户空间文件系统Filesystem in UserspaceLinux 内核机制,允许 rclone 在不修改内核的情况下提供文件系统挂载
FUSE 挂载点 rclone 通过 FUSE 暴露的只读本地目录(如 /mnt/nas-photosSamba/NFS 直接服务于此目录
SD Uploader 独立上传进程,将 SD 卡导入的暂存文件通过 SFTP 单向上传到 NAS
ingest_staging SD 卡导入暂存目录,文件在此完成校验后等待上传到 NAS
LRU 最近最少使用Least Recently Used缓存淘汰算法
SFTP SSH 文件传输协议,本产品与 NAS 通信的主要协议
Tailscale 基于 WireGuard 的组网工具,用于建立盒子与 NAS 之间的安全隧道
Headscale Tailscale 控制面板的开源自建实现
DERP Tailscale 的中继服务器Designated Encrypted Relay for Packets在无法直连时中转流量
Ingest / 导入 SD 卡文件导入到缓存并自动归档到 NAS 的过程
WAL SQLite 的写前日志模式Write-Ahead Logging允许并发读写
Captive Portal 强制门户认证,酒店/机场等 WiFi 连接后重定向到网页要求登录的机制
AP+STA 并发 WiFi 模块同时作为热点AP和连接外部网络STA的工作模式