OneNote
作为一位不知名软粉,长期以来我都是用 OneNote 记笔记。我上大学的第一部手机是 Windows Phone,后来也陆续买过好几代 Surface。从上大学到找工作,草稿、速记、笔记、作业(新冠期间)、面试等几乎都是用 Surface 写在 OneNote 里,即便后来换了 MacBook,也依然保持着之前的习惯。


我喜欢这种手写的体验,相比键盘输入,总觉得学习能更用心。而且 OneNote 能提供一个无限宽的画布,标注与引申知识点非常方便。然而工作之后,手写的场景越来越少了,像代码片段、截图以及附件这种对手写不太友好的使用场景越来越多。更重要的是,手写难以被检索,当然,OneNote 对中文的检索能力也确实拉跨。

Obsidian + LiveSync
我的目标是找到一款支持离线阅读和修改、能跨设备同步、端到端加密、最好免费的软件。我体验了一些原生的笔记软件或者“邪修”,包括 VSCode、印象笔记、wolai 等,最终打算试试 Obsidian,这款号称 AI 时代的第二大脑。
安装
官网有几乎所有平台的客户端,这点非常赞,如果不需要同步,完全可以做到开箱即用。

LiveSync CouchDB
当然我对 Obsidian 的要求肯定不会止步于基础功能。为了同步到 iPad 以及 Windows 设备,我计划用 LiveSync 自建服务器来完成,这款同步插件的教程非常多,号称可以做到行级的冲突处理以及秒级的同步效率。关于自建服务器,一方面,我认为笔记是非常需要注重隐私保护的内容;另一方面,我有好几台闲置的服务器和域名,这和 LiveSync 自建服务器的要求十分契合。
Obsidian 的插件说明其实是链接到 Github 的 readme 文档,所以点开插件的 Repository 就能直接跳转到插件的代码仓库。LiveSync 的全称是 Self-hosted LiveSync,直接搜索 Sync,第二个就是。

自建服务的安装指引地址为:https://github.com/vrtmrz/obsidian-livesync/blob/main/docs/setup_own_server.md
这里有三种方式:Docker,Docker Compose,直接安装 CouchDB。其实本质都是安装 CouchDB,然后再暴露服务到公网供不同客户端连接。建议是通过 Docker Compose 安装,这样所有的配置信息只需要写到一个文件里就能无限复用,不需要反复敲命令,后续如果要迁移也非常方便。
Step 1. 创建目录以及编写 Docker Compose
# Creating the save data & configuration directories.
mkdir couchdb-data
mkdir couchdb-etc
接着在当前目录写 docker-compose.yml 文件
services:
couchdb:
image: couchdb:latest
container_name: couchdb-for-ols
user: 5984:5984
environment:
- COUCHDB_USER=<INSERT USERNAME HERE> #Please change as you like.
- COUCHDB_PASSWORD=<INSERT PASSWORD HERE> #Please change as you like.
volumes:
- ./couchdb-data:/opt/couchdb/data
- ./couchdb-etc:/opt/couchdb/etc/local.d
ports:
- 5984:5984
restart: unless-stopped
这里的 volumes 用的是相对路径,./couchdb-data:/opt/couchdb/data 的意思是:把当前运行 docker compose up -d 命令所在的目录下的 couchdb-data 文件夹,映射到容器内部的/opt/couchdb/data 。所以只需要确保执行时的终端路径与我们创建的couchdb-data 和 couchdb-etc 在同级即可。
如果你的环境中有 Django 相关库,可以用下面的代码生成一个高强度的密码:
python -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())'
踩坑 1 :COUCHDB_PASSWORD 如果包含特殊符号,不需要用引号。一旦用了引号,Docker Compose 会把引号当成密码的一部分,会在后面的 Step 3 报错,提示用户名或密码不正确。

Step 2. 根据 Docker Compose 构建容器
按照官方指引,我们编写完 Compose 文件先执行docker compose up 来检查容器是否正常。不出意料,新的问题就出现了:

踩坑 2:Docker 的后台服务守护进程是属于 root 用户的,普通用户默认没有权限与 Docker 的核心 API 进行通信,所以这里必须加 sudo 执行,或者将当前用户加入到 docker 用户组中,这样后续就不需要加 sudo 了:sudo usermod -aG docker $USER (之后需要重启 session)
踩坑 3:/opt/couchdb/etc/local.d 对应的是我们创建的./couchdb-etc 文件夹,这个文件夹的所有者和用户组都是我们当前终端中登录的用户(例如我这里是 ubuntu)。而 CouchDB 在 Docker 容器内部启动时,不是以 root 运行也不是以 ubuntu 运行,而是以一个名为 couchdb 的内置用户运行的,这个用户和用户组对应的是我们在 compose 中写的 user: 5984:5984 。因此解决方案是,修改我们创建的这两个目录的所有者以及用户组:sudo chown -R 5984:5984 couchdb-data couchdb-etc 。因为这两个目录的权限是 775,所以作为 Others 的 ubuntu 也能正常访问(但不能写入)。
再次执行 docker compose up,如果出现以下内容,这一步就算成功了:

Step 3. 初始化配置
官方提供了一个 couchdb-init.sh 脚本来做初始化,只需要执行:
curl -s https://raw.githubusercontent.com/vrtmrz/obsidian-livesync/main/utils/couchdb/couchdb-init.sh | bash
结果这里又报错了:

踩坑 4:官方教程的这一步的前提其实是通过环境变量来注入数据的,而我们一直是通过 Docker Compose 文件来处理,所以会报错 Hostname 找不到,可以通过管道传递变量:
curl -s https://raw.githubusercontent.com/vrtmrz/obsidian-livesync/main/utils/couchdb/couchdb-init.sh | hostname=http://<YOUR SERVER IP>:5984 username=<INSERT USERNAME HERE> password=<INSERT PASSWORD HERE> bash
官方教程在后面其实也提供了这一条命令,但是藏在比较靠下的位置,这里的三处按照 compose 文件中的内容填写就好了,如果继续报错说密码不正确,可以回到 Step 1 检查 compose 文件是否包含引号。
注意:如果创建容器时使用的密码带了引号,不能通过直接改 compose 文件来解决,因为这个密码在创建容器时已经生效了,被写到了容器环境中,只能完整重新来过,不过重新创建也很方便:
sudo docker compose down && sudo rm -rf couchdb-data/* couchdb-etc/*
先按照上面的指令删除已创建的容器,接着修改 compose 文件后从 Step 2 重新开始。
当执行初始化脚本后显示如下内容,就说明我们的服务端已经成功一半了:

Cloudflared
经过上一步,我们已经完成了 CouchDB 的部署。现在要做的便是将这个服务暴露到公网,让每个客户端都能正常访问到。因为我的域名托管在 Cloudflare,且 Obsidian 要求必须使用 HTTPS 证书进行连接,而 Cloudflare 会自动管理和部署这张证书,所以使用 Cloudflare 来操作便顺理成章。实际上,Cloudflare 其实是充当了一个具备自动发证能力的全球反向代理。
传统:
浏览器 <---> (公网) <---> 服务器 (Nginx + CouchDB)
Cloudflare:
浏览器 <---> 第一段连接 <---> Cloudflare 全球边缘节点 <---> 第二段连接 <---> 服务器 (Cloudflared + CouchDB)
Step 1. 进入 Zero Trust 控制台
左侧导航栏的 Protect & Connect 下就有入口:

首次进入,需要选择付费计划,可以选择免费档位,绑定信用卡或者 PayPal,并不会扣钱。
Step 2. 创建隧道 Tunnels
选择 Networks > Connectors:

创建隧道,选择 Cloudflared,Tunnel name 可以随便填,之后会给出 Connector 安装的命令。
Step 3. 服务器安装 Connector

选择自己的服务器系统即可。需要注意的是,这里对于 Linux 的发行版没有太细的划分,像 CentOS 只需要选择 RedHat,Ubuntu 就选 Debian 即可。
安装完毕之后,如果一切正常,下方的 Connectors 就会出现你的服务器:

接着就可以点击下一步继续了。
Step 4. 配置外网路由
这是最后一步,也就是告诉隧道:“当别人访问什么域名时,把流量送到哪里去”。

Domain 选择你的域名,接着在 Subdomain 填写你希望让客户端访问的子域名,Cloudflare 会自动在 DNS 记录中新增这一条(这点让我不由感叹,这就是生态的力量!)。
下方的 Service 选择 HTTP 即可,因为这里代表的是 Cloudflared 与 CouchDB 服务在本地的通信方式。所以整个流程可以总结为:
- 外部用户到 Cloudflare 之间是 HTTPS;
- Cloudflare 到我们的服务器之间是 Tunnel 加密隧道;
- 我们服务器内部的 Tunnel 出口到 CouchDB 之间走本地的 localhost:5984(HTTP);
如果一切正常,我们在浏览器输入这个 subdomain,会弹出一个 username + password 的弹窗,对应的账号和密码就是我们在 compose 文件中填写的内容。
客户端配置
回到客户端的插件配置,由于我们是自建服务,要选择手动填写服务器信息:

后续的配置填写都比较简单,一直到后面显示 Connected to couchdb successfully 就大功告成了(真的吗?)
注意:这里可能会要求填一个 Peer-To-Peer 密码,这个密码主要用于加解密上传和下载的流量,不一定要和服务端配置密码相同;相应的,其它客户端也要使用相同的密码。
配置完一台设备之后,可以生成二维码来为其它设备配置:

不太理解这个插件的设计,大量的 emoji,配置项非常不直观,而且这里展示二维码还需要再次点击For your eyes only 选项。
注意:根据文案提示,iOS 设备是 直接用自带相机 扫描二维码之后跳转 Obsidian 客户端进行配置。
实际体验
谁懂每次在 OneNote 上敲 latex 公式,都要先去 Word 中敲完然后拷贝过来啊…… 况且 office 三件套在 mac 上的体验本来就很差:

上面的第一行是我用 latex 敲完的,已经在工具栏选择了 latex,但是无法渲染,而第三行是我用 Word 自带的插入功能,插入大型矩阵手写拼出来的……
现在可以很方便地用 Obsidian 处理:

得益于插件带来的可扩展性,表格和搜索的效果也比 OneNote 好很多很多。虽然是 markdown 文件,图片和附件的插入也比我预想的要方便。
真正令人崩溃的是 LiveSync 的同步体验:

我都已经快写完了,刚刚看了一眼,手机上还没同步……
更不要说还有这个实验性功能(没有关闭选项,是下载社区插件自带的)的告警:

我扒了代码,也明确是 bug。问题的根源是:插件保存任何设置都会触发一个事件发布,但处理器没有检查设备间 P2P 同步(不经过服务器)的选项是否开启,直接无条件尝试打开 P2P 连接,所以所有不使用 P2P 的用户都会受到这个 bug 的影响——只要设置被保存就反复弹通知。可是我自建服务器的目的本来就是为了通过服务器中转同步……
而且,在测试双端同步时,不知道怎么回事还触发了一次数据库被锁定:

这个 unlock 按钮根本点不了,接着再一通乱点导致数据损坏,还好全都是测试数据。
要知道我现在只有两个客户端,不仅没有编辑同一个文件,另一个客户端(iOS)都没启动。更重要的是插件设置里测试 CouchDB 的访问是成功的,我不得不对这个插件的可用性表示强烈的怀疑!
所以总结就是折腾这么久结果发现用不了……
下一步
浪费了太多时间,我已经不打算在 LiveSync 上投入更多的时间了。等我缓一缓再去试试其它同步插件。如果同步的问题能搞定,我应该会接着考虑从 OneNote 迁移笔记过来了。
最后用另一个插件,自建 MinIO S3 存储解决了,这里也有个坑,新版的 MinIO 为了区别于商业版,在 web 页面上移除了很多实用的功能,例如:版本控制等。如果需要使用这些功能可以安装旧版。
可以参考我的 docker-compose.yml 配置: