早上好,我是まさきん。
我把 Obsidian 的全部笔记,做成了每天备份到 Google Drive 的形式。本以为只是用 Mac 自带的 rsync 复制过去而已,结果因为日文的长文件名失败了一次。估计会有人卡在同一个地方,所以把步骤和规避方法记下来。
※本文为发布时的信息。最新条件请以官方网站为准。
- Obsidian 的备份,只要用 rsync 把库(vault)复制到 Google Drive 就能搭起来
- macOS 自带的 rsync(openrsync),遇到日文长文件名时因为建不出临时文件而失败
- 加上
--inplace之后就不再创建临时文件,在我的环境里顺利通过了 - 用 launchd 每天执行2次。首次是约2万个文件、数GB的规模,退出码为0
- 机密信息和缓存排除在对象之外,云端那一侧的确认定为每周1次
为什么又多加了一个备份地点
Obsidian 的全部笔记被称为“库(vault)”。我的库,此前一直备份在 GitHub 上。不过,备份地点只有一个,总觉得有点不踏实。
于是,我选了 Google Drive 作为另一个存放处。在电脑版 Google 云端硬盘的“我的云端硬盘”下面准备一个文件夹,然后用 rsync 把增量复制过去。
前些天,我写了退订 Obsidian Sync 的事。笔记的正本已经搬到了 Notion,但库的文件本身还留在手边。既然停了同步,备份这件事我就想自己掌握在手里。
库只是一个普通的文件夹
Obsidian 官方帮助里说,笔记是以 Markdown 格式的文本文件保存的。库就是本地文件系统上的一个文件夹,也包含子文件夹。
设置放在库根目录下的 .obsidian 文件夹里。按说明,快捷键和主题等都放在这里。
也就是说,备份不需要什么特别的工具。把文件夹复制到别的地方,那就是备份了。rsync 之所以能用,多亏了这样的构造。
同步和备份是两回事
Obsidian 官方帮助把同步和备份分开来说明。同步让笔记保持最新。但它提示说,同步并不能防止数据丢失。
| 项目 | 同步 | 备份 |
|---|---|---|
| 目的 | 让所有设备保持同一状态 | 在别的地方留一份副本 |
| 方向 | 基本是双向 | 从源到备份处的单向 |
| 删除时 | 删除的结果也会传到别处 | 视设置而定,会留在备份处 |
| 损坏时 | 损坏的状态也可能传过去 | 有可能留下传过去之前的版本 |
| 这次的角色 | 不使用 | 用 rsync 每天复制2次 |
这次做的是右边的备份。我不会用 Obsidian 去打开 Google Drive 上的文件夹,它只是作为复制的目的地放在那里。
官方帮助里还有一条注意事项:不要用多个服务同步同一个库,以免数据冲突或损坏。不直接编辑备份处,我认为也和这条注意事项相吻合。
整体构成
整套机制只有3个部件。
- 库的文件夹(例:
~/Notes) - Google Drive“我的云端硬盘”下的备份文件夹
- 调用 rsync 的 shell 脚本,以及定时运行它的 launchd
rsync 负责复制到本地的文件夹。从那里再往上的上传,是电脑版 Google 云端硬盘的工作。脚本不会调用云端的 API。
镜像和流式传输的区别
电脑版 Google 云端硬盘对“我的云端硬盘”有2种处理方式。我把官方帮助的说明整理成了表。
| 项目 | 流式传输 | 镜像 |
|---|---|---|
| 文件的保存位置 | 主要在云端 | 云端和本地两边 |
| 离线使用 | 仅限指定为离线的文件 | 随时可用 |
| 电脑的容量 | 几乎不占用 | 占用一份完整副本的量 |
| 在 macOS 上的位置 | ~/Library/CloudStorage 下(不可更改) | 在设置里选择的文件夹 |
macOS 上的流式传输,会显示在 Finder 侧边栏的“位置”里。默认位置是 ~/Library/CloudStorage,据说不能更改。
从作为 rsync 复制目的地的角度来考虑,大概可以这样整理:镜像方式下,复制的结果也会完整地留在手边,相应地,库和备份会双重占用容量。流式传输可以节省容量,但文件的实体主要在云端那一侧。
哪个才是正解,我想要看磁盘的剩余空间。如果是数GB的规模,镜像的负担也许不大。另外,Obsidian 官方帮助里有关于把库放在 Google Drive 上的说明,内容是要设置成可以离线使用。这讲的是同步的用途,不过思路似乎可以参考。
rsync 的基本命令
骨架大概就是这样。路径用的是一般化的例子。
#!/bin/bash
SRC="$HOME/Notes/"
DEST="$HOME/GoogleDrive/vault-backup/"
rsync -a --inplace \
--exclude '.venv/' \
--exclude 'node_modules/' \
--exclude '.cache/' \
--exclude 'tmp/' \
--exclude '.env' \
--exclude '*.token' \
"$SRC" "$DEST"
rc=$?
echo "$(date '+%F %T') rc=$rc" >> "$HOME/Library/Logs/vaultbackup.log"
exit $rc
DEST 要换成自己“我的云端硬盘”下的实际路径。如果是流式传输,就在 ~/Library/CloudStorage 下面。
用到的选项不多。
| 选项 | 含义(man 页面的说明) | 这次的用法 |
|---|---|---|
-a | 一并指定递归、保留时间等 | 加上 |
--inplace | 不创建临时文件,直接写入目的地 | 加上(后文的规避方法) |
--exclude | 排除匹配模式的文件 | 排除机密信息和缓存 |
-n | 不实际改动目的地的试运行 | 首次执行之前用来确认 |
--delete | 把源里没有的文件从目的地删除 | 加不加取决于方针 |
加上 --delete 之后,在源里删掉的文件也会从备份处消失,行为就接近同步了。如果想把误删的笔记留下来,我想也可以选择不加。
源路径末尾的斜杠也需要留意。加上的话,复制的是“文件夹里的内容”。不加的话,会连同文件夹一起再深一层地放进去。
哪些东西排除在备份之外
库里面往往会混进笔记以外的东西。我把下面4类排除在了对象之外。
| 种类 | 例 | 排除的理由 |
|---|---|---|
| 虚拟环境 | .venv/ 和 node_modules/ | 可以重建。文件数量多 |
| 缓存 | .cache/ 等 | 可以重建。内容变化频繁 |
| 临时存放处 | tmp/ 等 | 没有必要保留 |
| 机密信息 | .env 和令牌文件 | 不想扩大放到云端的范围 |
我认为判断的轴有2条:能重建的东西排除,泄露了会麻烦的东西也排除。就这2条。
机密信息这种东西,备份地点越多,存放的地方也越多。所以我从一开始就不把它放进对象里。机密信息的找法,在gitleaks 显示“no leaks”却仍漏掉2处的事里也写过。
排除的设定,可以用加了 -n 的试运行来确认。再加上 -v 的话,会把预定要复制的文件列出来。看一看里面有没有出现机密文件的名字,会比较安心。
卡住的地方:因为日文长文件名而失败
第一次执行,中途就停了。原因是文件名很长的日文文件。
报错里出现的是 mkstempat 这个函数名,内容是创建临时文件失败。原因是临时文件的名字超过了255字节。
机制是这样的。rsync 通常会在目的地创建一个临时文件,传输结束后再把它改成本来的名字。samba.org 的 rsync man 页面里,也写了这一默认行为。
临时文件的名字会比原来的名字更长。所以,即使原来的名字刚好在长度范围内,临时文件的名字也可能超过上限。
文件本身明明可以存在,却偏偏在复制的时候失败。这是因为原文件名虽在上限之内,临时文件的名字却超过了上限。习惯用较长的日文给笔记起标题的人,也许很容易踩到。
255字节相当于多少个日文字符
在 macOS 的头文件里,NAME_MAX 被定义为255。注释写的是“文件名的最大字节数”。
下面是计算。日文的大部分字符,在 UTF-8 下是1个字符3个字节。255除以3是85。也就是说,如果是纯日文的名字,大约85个字符是个参考值。混有扩展名或半角字符的话,数字会稍有变化。
另一方面,事情要更复杂一点。在 Apple 的开发者论坛上,Apple 的支持人员还给出了另一种说明:APFS 本身的上限是255个字符,和 NAME_MAX 对不上。那里还写着,上限会因卷的格式而变。
所以,我把“约85个字符”当作偏安全一侧的参考值来对待。实际生效的上限,也许会因复制目的地的文件夹建立在哪种机制之上而变化。
规避方法是 —inplace
对策只是加一个选项而已:--inplace。
openrsync 的 man 页面上说明,它不创建临时文件,而是直接操作目的地的文件。既然不创建临时文件,名字变长的问题也就不会发生。在我的环境里,这样就一路通到了最后。
不过,也有要付出的代价。samba.org 的 man 页面列举的注意事项大致是这些。
- 传输过程中,目的地的文件处于不完整的状态
- 中途停下的话,那种不完整的状态可能会留下来
- 不要用于更新别人正打开着的文件
这次的目的地,是一个谁也不会打开的备份文件夹。就算停了,下次执行时也会重新复制。所以我判断,在这个用途下这是可以接受的代价。
也有把文件名本身改短的办法。话虽如此,去改约2万个文件的名字并不现实。
macOS 的 rsync 是 openrsync
说到底,Mac 的 rsync 内里已经和以前不一样了。在近年的 macOS 上,自带的 rsync 换成了一个叫 openrsync 的实现。
Apple 公开了 rsync 的源代码。那个仓库里包含 openrsync 目录。man 页面的名字也是“openrsync”。其中说明,它与 samba.org 版的协议27〜29兼容。
切换的时期,据说是在 macOS Sequoia 15.4 前后。不过,在 Apple 的官方发行说明里没能确认到。所以这里就停留在“据说”。
实务上要注意的只有一点:在网上找到的 rsync 例子,不一定能原样运行。要用的选项,最好在自己机器上查一下 man rsync 来确认。
用 launchd 每天运行2次
定时执行交给了 launchd。在 Apple 面向开发者的文档里,cron 虽然还能用,但已不被推荐,文档引导大家使用 launchd。
时间用 StartCalendarInterval 这个键来指定。plist 的骨架如下。任务名和时间只是示例。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.example.vaultbackup</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>/Users/yourname/bin/vaultbackup.sh</string>
</array>
<key>StartCalendarInterval</key>
<array>
<dict>
<key>Hour</key><integer>3</integer>
<key>Minute</key><integer>30</integer>
</dict>
<dict>
<key>Hour</key><integer>13</integer>
<key>Minute</key><integer>30</integer>
</dict>
</array>
<key>StandardOutPath</key>
<string>/tmp/vaultbackup.out.log</string>
<key>StandardErrorPath</key>
<string>/tmp/vaultbackup.err.log</string>
</dict>
</plist>
按 launchd.plist 的 man 页面,表示时间的字典可以并列多个。每天2次的话,就写2个字典。没写的项目按“全部”处理。
我定在了凌晨和午后这2次。凌晨那次负责收前一天的部分,午后那次负责收上午的工作。
睡眠期间的处理和 cron 不同。Apple 的文档里说,如果在预定时间处于睡眠状态,会在唤醒时执行。
习惯了 cron 写法的人,也请一并看看cron 的日期与星期陷阱的文章。我认为定时执行这种东西,写完之后的验算很重要。
首次和定时执行的结果
首次是约2万个文件、数GB的规模。加上 --inplace 之后的首次执行正常结束,退出码为0。
由 launchd 定时触发的执行,也同样以0结束。手动运行的时候,和定时运行的时候,两边我都确认了,因为运行的环境稍有不同。
不过,这只意味着“成功复制到了本地的备份文件夹”。上传到云端是另一回事。首次上传很花时间。在写这篇文章的时间点,我还没能确认它已经完成。所以,这部分的结果不写。
云端那一侧的确认每周1次
即使 rsync 的退出码是0,也不代表已经送到了云端。所以,我另外安排了一项“有没有送到”的确认。频率只有每周1次。
要看的是2点。
- 新旧:云端那一侧的文件是否是最近的日期
- 内容:实际打开文件能否读出来
如果只看日期,损坏的文件看上去也是新的。做到打开来读这一步,才称得上是“能恢复的备份”吧。
不每天做,是因为坚持不下去。我认为这里按周来做就足够了。
失败的通知不放在脚本里
失败时的通知,不从脚本里发送。我设计成交给已有的监控机制来处理。
脚本的职责只有2项:复制,以及把退出码留在日志里。就这2项。
要是连通知也塞进去,通知对象的设置和机密信息就会混进脚本里。把监控集中在一处,即使任务增多,要修改的地方也只有一个。
放弃 Time Machine 的理由
既然是 Mac,Time Machine 也是候选之一。Obsidian 官方帮助也把它作为操作系统自带的工具列了出来。
不过,我的 Mac 上还没有设置保存位置,照现在这样是用不了的。按 Apple 的支持页面,它需要外置存储,容量以 Mac 的2倍以上为参考。
这次我没有准备到那一步,就放弃了。Time Machine 是保护整台 Mac 的工具。和这次“只把库转移到别的地方”的目的相比,角色也许稍有不同。
常见的失误与陷阱
把搭建过程中觉得容易绊倒的地方列出来。
- 因为日文长文件名,建不出临时文件而失败
- 想把网上的 rsync 例子原样用在 openrsync 上
- 忘了加源路径末尾的斜杠,结果深了一层
- 没有排除
.env和令牌,把它们传到了云端 - 看到退出码是0,就以为也送到了云端
- 用 Obsidian 打开备份处的文件夹并做了编辑
我实际踩到的是第一条。第五条,我打算靠每周1次的确认来防范。
开始之前要确认的事
- 库的文件夹的位置,以及大致的文件数量和容量
- Google Drive 的剩余容量,以及电脑磁盘的剩余空间
- “我的云端硬盘”用的是流式传输还是镜像
- 库里有没有
.env或令牌文件 - 有没有日文超过80个字符那样的长文件名
- 用
-n试运行,复制的对象是否符合预期
常见问题
Q. Obsidian 的备份只放 Google Drive 够吗?
只有一处的话,那里用不了的时候就麻烦了。Obsidian 官方帮助也说明,云端既是物理存储的替代,也是它的补充。我做成了和 GitHub 并用的两处。
Q. 可以直接打开放在 Google Drive 上的库吗?
Obsidian 官方帮助里说,通过 Google Drive 进行同步并不受官方支持。这次的构成也是以不打开备份处为前提。我认为只在原来的库里编辑才安全。
Q. --inplace 可以一直加着吗?
要看用途。man 页面里有一条注意事项:传输过程中目的地会处于不完整的状态。如果是谁也不会打开的备份处,可以说影响很小。
Q. 安装 Homebrew 的 rsync 这个方法怎么样?
也有另外安装 samba.org 版 rsync 的方法。不过我没有试过。这次是用加上 --inplace 的方式来应对的。
Q. 每天2次会不会太多?
rsync 只复制差异部分。没有变更的话,几乎什么都不做就结束了。增加次数负担也很小,所以也许可以按“丢了也无妨的时间跨度”来决定。
Q. Mac 处于睡眠状态时会怎样?
Apple 的文档里说,睡眠期间错过时间的任务会在唤醒时执行。关机状态下如何处理,需要另行确认。
Q. 在源里删了笔记,备份处也会消失吗?
没有加 --delete 的话,会留在备份处。加了的话,源里没有的文件也会从备份处消失。选哪一种,取决于你对备份的期待是什么。
Q. 上传有没有完成,在哪里看?
可以在电脑版 Google 云端硬盘的应用里确认同步的状况。首次上传是否完成,我还没能确认。打算在每周1次的确认里,一直看到能打开来读为止。
参考的官方信息
- Obsidian 如何存储数据(Obsidian Help)
- 备份你的 Obsidian 文件(Obsidian Help)
- 在多台设备间同步笔记(Obsidian Help)
- 使用电脑版云端硬盘(Google 云端硬盘帮助)
- 文件的流式传输与镜像(Google 云端硬盘帮助)
- 在 macOS 上使用电脑版云端硬盘(Google 云端硬盘帮助)
- rsync(1) man 页面(samba.org)
- rsync 的源代码(Apple OSS Distributions)
- 定时任务的调度(Apple Developer)
- 如何查询 APFS 的文件名长度上限?(Apple Developer Forums)
- 用 Time Machine 备份 Mac(Apple 支持)
总结
Obsidian 的库只是一个普通的文件夹。所以,只靠 rsync 和 launchd 就搭起了每天的备份。
卡住的地方是日文的长文件名。加一个 --inplace 就能通过,但要付出代价这一点还是应该知道。
是否送到了云端,接下来才要确认。不妨先从 -n 的试运行开始,用手边的库试一试吧。
把备份这样的“每月的机制”整理好之后,每个月的通信费也许也能用同样的眼光重新审视。
本文包含联盟营销广告。如果您通过本站链接申请相关商品或服务,我们可能会从合作企业获得报酬。此外,运营者是乐天集团的员工,也可能通过员工推荐计划获得报酬。文章内容与评价均基于运营者的真实体验与调查完成,与是否存在广告无关,但请您在阅读前了解上述关系。详情请参阅免责声明与联盟营销说明。 免责声明与联盟营销说明
本文包含机器翻译内容。正式条件请以乐天移动官方网站的多语言页面为准。
如果您对乐天移动的通信区域·信号状况有疑问,可以通过官方的 电波改善调查申请表 进行咨询。