告别万元订阅与格式锁定:深度拆解 Keelhaven —— 专为 Mac 打造的开源隐私备份神器(Swift 6 原生菜单栏 + Restic 工业级加密引擎 + S3/NAS 全链路实操)
在数字游民与开发者的日常工作中,数据丢失的恐惧始终如影随形:
- 一次误执行
rm -rf; - 一次 Git Rebase 事故覆盖了未提交的代码草稿;
- 移动硬盘遭遇文件系统损坏,或者固态硬盘遭遇毫无征兆的主控暴毙;
- 外出办公时 MacBook 意外遗失或进水损坏……
很多人以为开通了 iCloud Drive 就能高枕无忧,但这其实是一个致命的认知误区:同步(Sync)绝对不等于备份(Backup)。当一个文件在本地被勒索病毒加密、被误删或被脏数据污染时,iCloud 会在几毫秒内将这个破坏性的改动忠实地同步到你所有的 Apple 设备上。
那么,macOS 自带的 Time Machine(时间机器) 呢?
- 时间机器是本地整机灾难恢复的标杆,但它高度依赖插在工位桌上的那块专用外置硬盘;
- 当你尝试通过局域网 SMB / NAS 跑时间机器时,庞大脆弱的
sparsebundle镜像经常因为网络波动损坏崩溃; - 面对低成本的对象存储(如 AWS S3、Cloudflare R2、Backblaze B2、Wasabi),时间机器完全无法原生支持端到端客户端加密上传。
市面上的商业级工具(如 Arq Backup),虽然支持云端存储,但不仅闭源且售价高昂(单机买断或每年 $60 订阅),更重要的是其采用了专有格式,一旦停止维护,数据提取极其被动。
在开源界,restic 早已是公认的工业级备份王者:基于 Go 语言、支持 AES-256-CTR + Poly1305 强加密、基于 Rabin 指纹的变长内容分块去重(CDC)、支持几乎所有存储后端。然而,restic 是一个纯 CLI 命令行工具 —— 编写 launchd plist 配置繁琐、笔记本合盖休眠后任务经常漏跑、密码管理与运行状态完全缺乏可视化。
开源社区迎来了一款真正补齐 Mac 备份最后一块拼图的作品 —— Keelhaven(由开发者 shenxianpeng 开源)。
它用现代 Swift 6 + 原生 SwiftUI,为久经考验的 restic 引擎穿上了一件极其克制优雅的“菜单栏外衣”:住进 macOS 菜单栏、无 Dock 栏干扰、密钥全进系统钥匙串配合 Touch ID、支持睡眠唤醒补跑与断网锁自愈、全面兼容外置硬盘/S3/SFTP,并且 100% 保持标准 restic 开放格式,零锁定、零遥测、零云端账号!
本文将以架构设计与源码实现的第一视角,深度剖析这款专为 Mac 打造的隐私优先备份神器。
🧭 一眼看穿:macOS 备份方案全景横向对比
在深入技术架构之前,我们先通过客观的维度对比,理清 Keelhaven 在 Mac 备份生态中的精准生态位:
| 评估维度 | Keelhaven (本项目) | Apple Time Machine (时间机器) | 商业软件 (Arq Backup 7) | 开源跨平台 (Kopia Desktop) |
|---|---|---|---|---|
| 开源与许可 | 100% 开源 (GPLv3) | 闭源 (macOS 系统级) | 闭源商业软件 | 开源 (Apache-2.0) |
| 费用模式 | 完全免费,无订阅、无套路 | 免费 (系统自带) | $49.99 授权费 或 $60/年订阅 | 免费 |
| 底层备份引擎 | restic 0.19+ (全球工业级基石) | APFS 快照 / FSEvents | 专有备份引擎 | Kopia 原生引擎 |
| 供应商锁定风险 | 零锁定 (任何电脑用 restic 命令行可秒级恢复) | 强绑定 macOS 生态 | 存在专有格式与版本兼容包袱 | 需安装 Kopia CLI |
| 端到端加密 | AES-256 + Poly1305 (离开 Mac 前完成加密) | 仅支持整盘 FileVault 加密 | 支持端到端加密 | 支持端到端加密 |
| 远端存储兼容性 | 全兼容 (本地/S3/B2/R2/Wasabi/SFTP/NAS) | 局域网 SMB 脆弱,不支持对象存储 | 支持多种云端对象存储 | 支持多种云端对象存储 |
| macOS 原生度 | 极高 (SwiftUI 菜单栏常驻,仅 20MB 内存) | 深度系统集成 | 独立大型主窗口应用 | 臃肿的 Electron 套壳 (150MB+ 内存) |
| 钥匙串与生物识别 | 系统 Keychain + Touch ID 密码提取 | 系统钥匙串 | 专有密码管理 | 独立应用主密码 |
| 遥测与隐私 | 零统计、零遥测、零外部服务器 | 遵循 Apple 隐私协议 | 包含许可校验与商业网络请求 | 社区标准构建 |
[!NOTE]
黄金 3-2-1 备份法则的最佳搭档:
Keelhaven 并非用于取代时间机器,而是天作之合:
- Time Machine 负责工位桌面上那块移动硬盘的整机全盘快照,专治系统崩溃与新机迁移;
- Keelhaven 负责你最核心的个人资产(代码库、Obsidian 笔记、财务票据、精修照片、密钥配置),通过客户端强加密实时异步推送到异地的 S3 云存储或家庭 NAS,抵御火灾、盗窃与物理硬件损毁!
一、 系统架构:严格分层的双模块哲学
在软件工程中,许多工具往往因为把业务逻辑、CLI 进程调用和 UI 渲染混在一起,导致后期难以维护且极易崩溃。
Keelhaven 在设计之初就在 docs/ARCHITECTURE.md 中确立了坚不可摧的双层硬隔离架构:
flowchart TD
subgraph AppTarget["Keelhaven (SwiftUI 应用主工程)"]
UI["MenuBar 菜单栏视图 + 引导向导 (Wizard)"]
AppState["@MainActor @Observable AppState (全局状态中枢)"]
Services["系统服务薄包装: SchedulerService / NotificationService / BiometricAuth"]
UI --> AppState
AppState --> Services
end
subgraph CoreTarget["KeelhavenCore (纯 SwiftPM 核心引擎包 - 零 UI 依赖)"]
Models["领域模型: BackupPlan / Destination / Schedule / RetentionPolicy"]
Keychain["凭据隔离: KeychainStoring / KeychainStore"]
SchedMath["纯函数调度数学: SchedulePolicy / CheckPolicy / PrunePolicy"]
ResticRunner["异步进程执行器: ResticRunner (Actor)"]
Parsers["NDJSON 流式解析器: ResticJSON (基于 restic 真实 Fixture)"]
ResticRunner --> Parsers
end
subgraph ExternalEngine["底层随包内置二进制"]
ResticBin["restic Universal Binary (arm64 + x86_64, ≥ 0.19)"]
end
AppState ==>|依赖与调用| CoreTarget
ResticRunner ==>|fork minimal clean process| ResticBin
1. 架构核心优势
- KeelhavenCore 纯逻辑自治:完全剥离了 AppKit / SwiftUI 视图层,涵盖了所有数据模型、进程管道、Keychain 存储协议与调度算法。其所有代码在 GitHub Actions CI 中强制执行 llvm-cov 100% 行级测试覆盖率门禁(Line Coverage Gate)!
- 免除一切环境依赖(No-Homebrew Needed):传统开源 GUI 经常要求用户先跑
brew install xxx。Keelhaven 在构建期通过Scripts/fetch-restic.sh校验 SHA256 签名,直接将通用的arm64 + x86_64restic 原生二进制封装到.app包内的Contents/MacOS/restic中,下载打开即用; - 单例排队执行(Serialized Backups):全应用生命周期内严格保证同一时间仅有一个 restic 进程在运行。多计划备份、定时体检与空间回收严格串行排队,从根源上杜绝了对同一个存储仓库的并发加锁冲突。
二、 核心技术与硬核工程实现深度剖析
1. 绝不泄露秘密:零命令行落盘的 Keychain 凭据体系
在 Linux 或 macOS 终端中,许多开发者习惯通过 restic backup -p password.txt 或命令行参数传递凭据。这是一个巨大的安全隐患 —— 系统上的任何未授权进程都可以通过 ps aux | grep restic 直接截获明文密码!
Keelhaven 在 RepoCredentials.swift 与 KeychainStore.swift 中构建了坚不可摧的凭据管道:
sequenceDiagram
autonumber
participant App as AppState (主线程)
participant KC as macOS 系统 Keychain (钥匙串)
participant Runner as ResticRunner (Swift Actor)
participant Child as restic 子进程
App->>KC: 检索该计划 UUID 对应的加密凭据
KC-->>App: 返回内存中的私有密码与 S3 密钥
App->>Runner: 构造 RepoCredentials 注入 Actor
Note over Runner: 构建极简白名单环境字典 (Minimal Clean Environment)<br/>只保留 PATH、TMPDIR 等基础系统变量
Runner->>Runner: 注入 RESTIC_PASSWORD, AWS_ACCESS_KEY_ID...
Runner->>Child: Process.run() 仅通过进程私有内存环境注入
Note over Child: 运行期间 argv 只有 ["backup", "--json"]<br/>ps aux 绝无明文泄露,磁盘零明文残留!
- Touch ID 生物识别提取:在
BiometricAuthService中,用户如果想查看或拷贝自己当初设置的仓库密码,必须通过 Mac 原生 Touch ID 指纹鉴权,防止他人借用电脑时窃取密码; - 凭据生命周期与计划解绑销毁:当用户删除某个备份计划时,系统会原子化地擦除 Keychain 中的对应条目。
2. 毫秒级异步流式解析:NDJSON + Swift AsyncThrowingStream
restic 在执行备份时,提供了一个强大的 --json 标志,能够实时以换行分隔的 JSON 流(Newline Delimited JSON, NDJSON)输出备份进度:
{"message_type":"status","percent_done":0.42,"total_files":1520,"bytes_done":104857600}
在 ResticRunner.swift 中,Keelhaven 没有采用低效的定时轮询或全量缓冲,而是充分利用 Swift 现代并发特性,将 POSIX Pipe 的输入流接入 bytes.lines 异步迭代器:
// ResticRunner.swift 核心异步流式管道实现节选
public nonisolated func backupStream(
_ command: ResticCommand,
destination: Destination,
credentials: RepoCredentials
) -> AsyncThrowingStream<BackupProgressEvent, Error> {
AsyncThrowingStream { continuation in
let process = Process()
process.executableURL = self.binaryURL
process.arguments = command.arguments
process.environment = credentials.environment(for: destination)
process.standardInput = FileHandle.nullDevice
let stdoutPipe = Pipe()
let stderrPipe = Pipe()
process.standardOutput = stdoutPipe
process.standardError = stderrPipe
let exitCodes = AsyncStream<Int32> { exitContinuation in
process.terminationHandler = { finished in
exitContinuation.yield(finished.terminationStatus)
exitContinuation.finish()
}
}
let worker = Task {
do {
try process.run()
} catch {
continuation.finish(throwing: ResticError.binaryNotFound)
return
}
// 监听 stdout 的行切片异步流
var lines = stdoutPipe.fileHandleForReading.bytes.lines.makeAsyncIterator()
while let line = (try? await lines.next()) ?? nil {
if let event = ResticJSON.decodeProgressEvent(fromLine: line) {
continuation.yield(event) // 实时通知 UI 驱动菜单栏进度条
}
}
// 获取退出码并根据错误模型分类
var exitCode: Int32 = -1
for await code in exitCodes { exitCode = code }
if exitCode == 0 {
continuation.finish()
} else {
let stderrData = try? stderrPipe.fileHandleForReading.readToEnd()
let stderrText = String(decoding: stderrData ?? Data(), as: UTF8.self)
continuation.finish(throwing: ResticError.classify(exitCode: exitCode, stderr: stderrText))
}
}
// 优雅取消:用户点击停止时发送 SIGINT,触发 restic 干净解锁并退出
continuation.onTermination = { termination in
guard case .cancelled = termination else { return }
if process.isRunning {
process.interrupt() // 发送 SIGINT 触发 restic 自身释放锁
}
worker.cancel()
}
}
}
[!TIP]
优雅取消的黑科技:普通软件在强制终止进程时往往发送SIGKILL,这会导致 restic 的仓库锁(Lock)残留在远程存储桶中。Keelhaven 在监听到任务取消时调用process.interrupt()(即发送SIGINT),restic 捕获该信号后会先清理远程仓库锁,再干净自毁退出!
3. 休眠唤醒与纯函数调度数学(SchedulePolicy)
许多运行在后台的备份工具,最让人头疼的是 MacBook 合盖休眠后,原本定在下午 3 点的计划就彻底“漏跑”了,直到第二天同一时间才会再次触发。
Keelhaven 将调度逻辑剥离为纯函数(Pure Functions)SchedulePolicy:
public enum SchedulePolicy {
/// 判定某个计划在给定的时间点是否已经“逾期未跑”
public static func isDue(
_ plan: BackupPlan,
now: Date,
calendar: Calendar = .current
) -> Bool {
// 从未运行过的计划立即执行
guard let lastRunDate = plan.lastRun?.date else {
return true
}
// 计算上一次运行之后理应触发的下一次时间节点
return nextRun(for: plan.schedule, after: lastRunDate, calendar: calendar) <= now
}
}
在 AppState.swift 中,调度器不仅每 60 秒轮询一次,还会监听 macOS 原生工作区通知:
NSWorkspace.shared.notificationCenter.addObserver(
forName: NSWorkspace.didWakeNotification,
object: nil,
queue: .main
) { _ in
AppState.shared.runDuePlans()
}
当你合上电脑外出开会、两小时后重新翻开屏幕唤醒 Mac 时,Keelhaven 在唤醒的一瞬间便会检测到错过的窗口,立即静默自动补齐备份!
4. 尾随式生命周期:体检(Check)与空间回收(Prune)的工程智慧
restic 的两个高级命令:
restic check:全量遍历索引树,校验仓库完整性;restic forget --prune:按策略丢弃老快照,并重新打包(Repack)数据块,回收存储空间。
很多初级开发者会简单地把这两个操作也做成独立的定时器,但这在真实网络环境下是灾难性的:--prune 重打包会消耗大量网络上行下行流量与 API 调用次数;且如果单独触发,很容易与常规备份争抢仓库排他锁。
Keelhaven 采用了极其聪明的 “尾随机制(Tail-Riding Lifecycle)”:
flowchart LR
Start([定时器触发]) --> RunBackup["① 执行增量备份 (restic backup)"]
RunBackup --> BackupSuccess{"备份成功?"}
BackupSuccess -- 否 --> AlertFail["弹窗报警并在行内显示失败原因"]
BackupSuccess -- 是 --> CheckDue{"CheckPolicy.isDue?<br/>(默认每周最多1次)"}
CheckDue -- 是 --> RunCheck["② 尾随运行完整性检查 (restic check)"]
CheckDue -- 否 --> PruneDue
RunCheck --> PruneDue{"PrunePolicy.isDue?<br/>(保留策略开启且满7天)"}
PruneDue -- 是 --> RunPrune["③ 尾随回收空间 (restic forget --prune)"]
PruneDue -- 否 --> Finish([任务安静结束])
RunPrune --> Finish
- 目标确定可达:只有在主备份 100% 成功后,才判定当前远端存储可连通;
- 消除排他锁争抢:同一个串行队列下紧接着执行
check或prune,完全避免锁竞争; - 智能频次限流:空间回收极为消耗带宽,因此即便你设置的是“每小时备份”,
PrunePolicy也强制将其拦截在每周最多执行一次,在空间回收与云端 API 账单之间达成了完美平衡。
5. 孤立锁自愈机制(Orphan Lock Recovery)
当网络突然断开、或者电脑电量耗尽意外关机时,restic 在远端仓库中留下的独占锁文件可能无法被清除,导致后续所有备份持续报错 repository is already locked (code 11)。
传统方式下,用户必须打开终端自己敲 restic unlock。而在 Keelhaven 中:
AppState精准识别 restic 的退出码11,在 UI 状态行中呈现独特的.failedLocked状态;- 不展示冷冰冰的报错红字,而是动态呈现一个 “解除锁定并重试 (Unlock)” 按钮;
- 内部调用安全模式的
restic unlock(不带--remove-all),只清除属主已失效的僵尸锁,绝不误伤其他 Mac 正在进行的合法备份!
三、 实战上手:从零搭建双重容灾备份体系
现在,我们以实操视角,带你一步步配置一套符合专业标准的备份方案。
1. 极速安装
方式 A:Homebrew Cask 一键安装(推荐)
brew install --cask shenxianpeng/tap/keelhaven
方式 B:终端极简脚本安装
curl -fsSL https://keelhaven.app/install.sh | bash
[!TIP]
关于初次打开的 macOS 门禁提示:
由于 Keelhaven 为纯免费开源软件,尚未向 Apple 支付昂贵的商业开发者年费进行公证,首次双击打开若提示“无法验证开发者”:
- macOS 15 (Sequoia):打开 系统设置 › 隐私与安全性,拉到底部点 仍要打开;
- 开发者极客一条命令:终端执行
xattr -d com.apple.quarantine /Applications/Keelhaven.app即可永久移除系统隔离标记。
2. 场景实操一:备份至随身外置移动固态硬盘(SSD)
- 点击 macOS 顶部菜单栏的 Keelhaven 图标,点击 “新建计划 (New Plan)”;
- 选择源目录:通过原生文件选择器挑选你的重要文件夹(如
~/Documents、~/Projects、~/Pictures); - 选择存储目的地:点击 “本地或外部驱动器 (Local Directory)”,选择移动硬盘挂载路径(如
/Volumes/ExtremeSSD/KeelhavenBackup); - 设置独立加密密码:输入并牢记密码,Keelhaven 会自动将其加密注入 macOS Keychain;
- 设定频率:选择每天或每周定时触发。
3. 场景实操二:备份至任意兼容 S3 的对象存储(Cloudflare R2 / Backblaze B2 / AWS)
以 Cloudflare R2(每月提供 10GB 免费存储且零出网流量费)为例:
- 在 Cloudflare 控制台创建 R2 Bucket(例如
my-mac-backup),生成Access Key ID与Secret Access Key,复制 S3 Endpoint 地址:
https://<account_id>.r2.cloudflarestorage.com - 在 Keelhaven 新建计划中选择 “S3 兼容存储 (S3 Bucket)”:
- Endpoint:
https://<account_id>.r2.cloudflarestorage.com - Bucket:
my-mac-backup - Region:
auto - Access Key / Secret Key:填入凭据(保存在系统钥匙串中)
- Endpoint:
- 设置仓库加密主密码,点击创建。Keelhaven 将自动调用
restic init初始化远端存储桶,并开启毫秒级增量流式备份!
4. 极致无锁定证明:脱离 Keelhaven 恢复数据的黑客演练
假设你的电脑丢失了,手头只有一台普通的 Linux 服务器或另一台新 Mac,没有任何 Keelhaven 客户端,你如何取回你的数据?
这正是 Keelhaven 最令人安心的地方 —— 没有任何私有协议封装:
# 1. 任意机器安装原生 restic 命令行
brew install restic # 或 apt install restic
# 2. 导出你在 Keychain 里保存的密码与 S3 凭据
export RESTIC_REPOSITORY="s3:https://<account_id>.r2.cloudflarestorage.com/my-mac-backup"
export RESTIC_PASSWORD="YourSecretPassword"
export AWS_ACCESS_KEY_ID="your-r2-key"
export AWS_SECRET_ACCESS_KEY="your-r2-secret"
# 3. 查看所有历史快照
restic snapshots
# 输出示例:
# ID Time Host Tags Paths
# ----------------------------------------------------------------------
# 4f8a12b0 2026-09-05 20:30:00 MacBook keelhaven /Users/me/Projects
# 9d3c5e7a 2026-09-06 18:00:00 MacBook keelhaven /Users/me/Projects
# 4. 一键将最新快照恢复至本地当前目录
restic restore latest --target ./RestoredData
数据完整如初!你的资产掌控权永远紧握在自己手中,不受任何商业公司兴衰的裹挟。
四、 总结与极客思考
在软件行业充斥着“为了上云而上云”、“为了收费而发明私有格式”的今天,Keelhaven 的出现如同一缕清流:
- 小而美的 Unix 哲学:它没有重造一套脆弱的备份算法轮子,而是站在巨人肩膀上,将世界上最顶级的加密引擎(restic)与 Apple 生态中最优雅的人机交互(SwiftUI 菜单栏常驻、Keychain、Touch ID)进行了严丝合缝的拼接;
- 绝对的数字自主权:拒绝账号体系,拒绝中转云端,备份目的地完全由用户自主掌控;
- 教科书级的现代 Swift 工程典范:100% 测试覆盖率的纯逻辑核心包、基于
AsyncSequence的进程流式迭代、纯函数无副作用的调度判定。
如果你正在寻找一款能够守护你多年心血资产、静默无感、在关键时刻绝不掉链子的 macOS 异地备份方案,Keelhaven 绝对是你 Mac 菜单栏中那个最值得信赖的“避风港(Haven)”。
项目仓库:GitHub - shenxianpeng/keelhaven
官方网站:keelhaven.app
开源协议:GPL-3.0 License (restic 引擎为 BSD-2-Clause)
运行要求:macOS 14.0+ (Apple Silicon / Intel Universal)