四天,40 次提交,约 7000 行 Go——我和 AI 结对,给吃灰的树莓派 2B 写了一个插件化的同步备份服务。现在它每 5 分钟看一眼我的仓库,有更新就拉下来存个快照,而这一切只吃不到 10MB 内存。

起因:吃灰的树莓派和备份焦虑
去年写过一篇树莓派2B系统安装以及远程ssh配置,装完系统、配好 SSH 之后,这块 2015 年的老板子就一直躺着吃灰。1GB 内存、32 位 ARM、百兆网口——跑桌面不行,跑 Docker 勉强,指望它干重活是不现实的。
但它有两个被低估的优点:开着几乎不费电,而且永远在线。
另一方面,我的数据其实都悬在几个单点上:博客数据躺在 Typecho 里,代码在 GitHub,笔记在坚果云。任何一家的故障、封号、误删,都可能让我措手不及。买 NAS 太重,订阅网盘太贵,那不如让这台吃灰的树莓派干点正事:在家里放一个盒子,自动把我关心的东西拉取下来,保留历史版本,需要时从局域网取回。
想法很朴素:监听我的 Git 仓库(包括私有仓库),检测到更新就单向拉到本地;以后还可能接 WebDAV、S3 这类源;历史版本可以随时回看和恢复。9 月 7 日晚上,我把这个想法丢给了 AI,开始头脑风暴。
先想清楚再动手:三个核心设计
这次我强迫自己先设计、后编码。来回讨论了一晚上,最关键的三个决策先定了下来。
决策一:只做单向,先写清楚不做什么
备份工具最容易做歪的地方,就是顺手把"双向同步"和"冲突合并"也做了。想明白之后我干脆把它写进了设计文档的"非目标"一节:树莓派就是一台只读的备份终端,远端永远是真源,本地只负责拉取和保存。不做双向同步,不做公网暴露,不做加密存储(硬链接快照和加密天生不兼容),第一版也不做实时推送监听。
把"不做什么"放在第一节,之后所有架构讨论都省事了很多——剩下的问题变成一个纯粹的工程题:怎么在 1GB 内存里把单向拉取做到极致。
决策二:硬链接快照,历史版本几乎白送
这是整个项目里我最得意的设计。每次同步生成一个完整版本目录,但不是复制一份——未变更的文件跨版本共享同一个 inode,不管保留多少个版本,磁盘占用约等于一份全量。实测里未变更文件的 nlink 数随版本增长,六个版本滚动后空间纹丝不动。
配套的纪律也全部写死:所有状态文件用 fsync + rename 原子写,掉电安全;新版本先落在 staging 区,校验通过才转正;current 是一个指向最新版本的符号链接,切换用 rename 原子完成,任何时刻断电都不会出现半成品镜像。保留策略双保险:keepLast 按版本数量滚动,keepDays 按天数清理。
这套设计后来挨过最狠的一顿打:连续 kill -9 内核再重启,镜像依然完整,中断的 staging 会被下次同步自动清掉。
决策三:插件协议,任何语言两百行接入
存储源的适配没有走"内核里 if-else"的老路,而是做成了外部进程协议:插件是独立二进制,内核按需拉起、用完即退,通过 stdin/stdout 上的行分隔 JSON-RPC 通信。插件只需要实现两个方法:
snapshot:返回远端当前的文件清单(路径 + 指纹)fetchFile:按路径下载单个文件(流式)
内核只认"清单 A 对比清单 B 的 diff"——Git 的 commit hash、WebDAV 的 ETag、本地目录的 sha256,最后都归一化成同一种东西,插件逻辑薄到只剩一层皮。官方插件 plugin-git 和 plugin-local 用 Go 写,但按这个协议,用 Python、Node 甚至 Shell 都能写一个新源。CLI 的交互式向导也是由插件自报的配置 schema 驱动的:新增一种源类型,向导自动跟着变。
整体架构一张图就能说清:

和 AI 结对:从头脑风暴到 v0.1.0 的四天
开发工具是跑在我终端里的编码智能体 pi。整个项目我印象里只有两次"人类创意时刻":第一次的头脑风暴,和最后的"画个 logo"。其余时间我们遵循同一条工作流:头脑风暴拍板所有架构决策,写定稿设计文档和分阶段 TODO,然后一个阶段一个阶段推进,每阶段跑完验收才放行下一个。
9 月 7 日晚:头脑风暴加设计定稿。AI 提出的核心抽象——所有源归一为"文件清单快照"——被原封不动保留到了最后。当晚我还纠结过要不要把整个服务合并进 Next.js,睡了一觉想清楚了:这是个常驻服务,不是网站,洗洗睡。
9 月 8 日:阶段 0-2。脚手架搭好 monorepo,内核引擎加 plugin-local 插件端到端打通,git 和 webdav 插件跟上。
9 月 9 日:强度最大的一天,28 次提交。内核 IPC 和 CLI 十三个命令、React 面板,然后是漫长的 UI 打磨:深色模式切不动、手机端视口忽大忽小、长文件名撑爆表格、同步记录不换行。最典型的是下载——React 单页应用里 <a href> 下载走的是浏览器原生导航,请求头里的 Bearer 令牌根本带不上去。最后定了 Cookie 加 Bearer 双通道:浏览器下载靠 HttpOnly Cookie,CLI 调 API 靠 Bearer,缺一即 401。
9 月 10 日凌晨:阶段 0-4 全面验收(12 条面板清单、13 条 CLI 命令逐个过),画 logo、写 LICENSE 和 README,v0.1.0 发布。

这期间还有两次中途转向,都是做减法:
- WebDAV 插件做完后被舍弃。代码归档进 archive/,不打包、不部署、不恢复。真实场景里我的源就是 Git 仓库,为用不上的源长期养一套测试不值。
- TypeScript 外层全部推翻重写成 Go。最初的设计是"Go 内核 + TS 外层",做到一半我把它推翻了:CLI、面板 server、部署脚本全部换成 Go 和 shell。导火索是我想让树莓派上不装 Node 运行时——面板 server 若是 TS,设备上就得常驻一个 Node 进程。转向的代价是两天重写,收益是目标机零运行时依赖,前端只在开发机构建,设备只托管静态文件。
部署之夜:纸面方案和现实的差距
设计文档写得再漂亮,第一次上真机照样被教育。部署到树莓派的当晚,模板接连炸出 6 个 bug,挑三个最典型的:
socket 路径分裂。内核把相对路径的 unix socket 解析到 etc/ 下面,面板却按绝对路径去 /opt/edge-sync/var 找——两份配置单独看都"对",拼在一起 IPC 就是连不上。修复:模板里显式钉死同一个绝对路径。
内核以 root 跑,git 认证全灭。systemd unit 默认 root 运行,而 root 用户里没有部署用户的 SSH 密钥和端口绕行配置,git 任务全部认证失败。修复:unit 加 User=,服务以部署用户身份运行。
非 root 用户绑不了 80 端口。User= 和特权端口互斥,经典 Linux 问题。修复:unit 里加 AmbientCapabilities=CAP_NET_BIND_SERVICE,非 root 进程也能绑 80。
网络这一段更有意思。树莓派挂在手机热点下,运营商把 GitHub SSH 的 22 端口整个封了,表现就是 ssh -T git@github.com 永远超时。绕行方案是走 443 的 ssh.github.com,写进设备的 ~/.ssh/config 一劳永逸。慢网络下首次 clone 还会撞上默认超时,任务级把 snapshotTimeout 放宽到 5 分钟之后,私有镜像仓库的全量拉取 45 秒完成;之后的轮询靠 hash 短路,仓库没变就零下载。
私有仓库的钥匙也顺手解决了:把树莓派的公钥加成 GitHub 仓库的只读 deploy key,每个任务配独立 keyFile,进程天然隔离。
最后的稳定性数字:kill -9 内核,systemd 几秒内自愈,面板无感;edge-syncd 常驻内存 4.2-9.5MB,面板 6.4-7.2MB,加起来不到 20MB。
收尾:系统调优与成绩单
服务稳了,顺手给树莓派做了轮保养:停掉 ModemManager、triggerhappy 这类 2B 用不上的服务,journald 日志限了 50MB 防止 SD 卡被撑爆,开启自动安全更新,再挂上 bcm2835 硬件看门狗——系统真 hang 死时自动重启。做完这些,空闲温度 33.6°C,全套服务安静地跑着。
最终成绩单:
| 项 | 数据 |
|---|---|
| 开发周期 | 4 天(09-07 至 09-10) |
| 提交 | 40 次 |
| 代码量 | Go 约 7000 行(41 个文件)+ 前端约 1700 行 |
| 二进制 | 5 个静态二进制,内核 9.5MB / 面板 7.2MB(armv7 实测),目标机零运行时依赖 |
| 实机内存 | 内核 4.2-9.5MB,面板 6.4-7.2MB |
| 质量 | go test 12 包全绿,面板 12 条验收清单,CLI 13 命令实机过 |
项目已开源(MIT 协议):GitHub - Young143l/edge-sync。树莓派、随身 WiFi 这类 ARM 小设备理论上都能跑,README 里有一份完整的部署手册和排障表。
结语:AI 结对开发的体感
从让 AI 管理你的博客,到给博客写 CLI,再到这次让 AI 跟我一起从零造一个服务,这条线越走越深。四天写完一个完整的项目,复盘下来,快的原因不是"AI 写代码快",而是几个工作流上的选择:
- 先文档后代码。设计文档定稿前一行代码不写,之后每一步都是在执行既定方案,方向性返工几乎为零。
- 分阶段验收。每个阶段有明确的交付物和验收清单,问题当阶段消化,绝不滚雪球。
- 人管决策,AI 管执行。这四天里我做的最有价值的事是三次说不:不做双向同步、舍弃 WebDAV、外层全面 Go 化。往哪走、不往哪走,短期内还是得人来拍板。
现在这个服务正守着我的博客仓库:5 分钟一轮询,仓库没变就零下载,有变更就多落一个快照。接下来可能会补 webhook 实时触发,试试 S3 源的插件,或者把 512MB 的随身 WiFi 也发展成备份节点。
如果你也有一台吃灰的 ARM 小设备,不妨让它下岗再就业。
没有评论