作为一名长期使用 Obsidian 记录生活和技术的用户,我也同时是静态博客 Hugo 的拥趸。但长久以来,最大的痛点在于如何把这两者优雅地连接起来。

我不希望每次发布文章都要手动复制文件、处理图片路径、调整 Front Matter。我理想中的流是:在 Obsidian 里按下 Ctrl+S,本地的 Hugo 预览就能实时更新,确认无误后一个 Git Push 完成发布。

经过一番折腾,我终于基于 CouchDB 和自己编写的一个同步脚本,打通了这套全链路。

架构概览

我的整套发布流涉及以下几个核心节点:

  1. 写作端:Obsidian (配合 Self-hosted Livesync 插件)
  2. 数据中转:CouchDB (运行在 Docker 中)
  3. 同步核心自研提取脚本 (运行在 Docker 中)
  4. 生成端:本地 Hugo 环境
  5. 图床方案:Lsky Pro + Cloudflare R2

核心流程

  1. Obsidian 写作:我在 Obsidian 中修改笔记。
  2. 实时同步Obsidian Livesync 插件将变更实时推送到我的私有 CouchDB 数据库。
  3. 自动提取:我的同步脚本监听数据库变动,解析数据,还原为标准的 .md 文件写入 Hugo 的 content 目录。
  4. 本地调试:本地运行的 hugo server 检测到文件变动,实时刷新浏览器预览。
  5. 发布:确认无误后,Git Commit & Push,触发 CI/CD 或直接推送到服务器。

为什么不直接用 Obsidian Git?

很多人可能会问:“为什么不直接用 Obsidian Git 插件把 Vault 推送到 GitHub,然后用 GitHub Actions 自动构建 Hugo 呢?”

虽然 Git 方案配置简单,但在实际的高频写作多端同步场景下,我这套“数据库 + 中转脚本”的方案有着无法替代的优势:

1. 移动端的“无感”体验 (The Mobile Experience)

  • Git 方案的痛点:在手机(尤其是 iOS)上使用 Git 体验极差。你需要在写作前 Pull,写作后 Push。如果忘记 Pull 直接写,很容易产生冲突;如果忘记 Push,回到电脑前又看不到最新的修改。这打断了“随时随地记录”的心流。

  • 本方案优势Livesync 是基于数据库的实时(或准实时)同步。我在 iPhone 上写一段话,几秒钟后它就自动出现在了我的电脑和 CouchDB 里,完全不需要手动操作。这种体验更接近 Notion 或 Google Docs,丝般顺滑。

2. “保存”与“发布”的逻辑解耦

  • Git 方案的痛点:如果你用 Git 插件自动备份(比如每 10 分钟),你的 Commit 历史会充斥着 “Auto backup” 这样的垃圾信息。如果你手动 Commit,又很容易忘记。更重要的是,保存笔记发布博客混在了一起,你想保存草稿,却可能意外触发了 CI/CD 自动发布。

  • 本方案优势

    • 写作层:利用 CouchDB 同步,我可以随意修改、产生无数个中间版本,这只是“数据同步”。

    • 发布层:当我觉得文章写好了,脚本已经把它提取到了 Hugo 目录。此时我只需在 Hugo 目录下做一次有意义的 Git Commit & Push

    • 结论:保持了 Git 历史的干净,且发布动作充满了仪式感和掌控感。

3. 精准的隐私隔离 (Selective Publishing)

  • Git 方案的痛点:通常 Git 插件是针对整个 Vault 的。如果你想把博客源码开源,却又想保留私人日记,你需要折腾 .gitignore 或者复杂的 submodule 嵌套,稍有不慎就会把私钥或日记 push 上去。

  • 本方案优势:我的脚本原生支持 TARGET_FOLDER 过滤。我的 Vault 里可以有 DiaryWorkFinance 等无数私密文件夹,脚本只会把 Blog 文件夹下的内容提取出来。物理级别的隔离,让我对隐私安全绝对放心。

4. 避免 Git 冲突地狱

  • Git 方案的痛点:Obsidian 的文件本质是文本,但前台配置(workspace, appearance)经常变动。多端 Git 同步时,经常出现 Merge Conflict,导致文件里出现 <<<< HEAD 这种破坏标记,修复起来很烦人。

  • 本方案优势:Livesync 基于 CouchDB 的文档级冲突解决机制,比 Git 处理纯文本冲突更智能、更温和。即使出现冲突,也是在数据库层面解决,不会破坏本地正在调试的 Hugo 构建环境。

5. 纯净的内容交付

  • Git 方案的痛点:Obsidian 的 Markdown 语法(如双链 [[Link]])和 Hugo 标准 Markdown 并不完全兼容。直接 Push 原文可能导致博客渲染出错。

  • 本方案优势:既然中间有一个 Python 脚本在跑,我拥有了预处理能力。未来我可以在脚本里加入正则替换功能,比如自动把 [[Obsidian链接]] 转换为 [Hugo链接](...),或者自动处理 Front Matter 的格式转换,这是直接 Git Push 做不到的。

6. Self-Hosted 玩家的终极快乐:内网闭环

如果你不是将博客托管在 GitHub Pages,而是运行在自己的服务器(VPS 或 NAS)上,那这个方案简直是为你量身定做的。

  • Git 方案的痛点:如果你自建博客,使用 Git 发布通常意味着:本地 Push -> GitHub 接收 -> 触发 Webhook -> 服务器 Pull -> 构建。或者你需要配置 GitHub Actions Runner,把数据传给微软(GitHub),再传回你的服务器。这不仅慢,而且依赖外部网络。一旦 GitHub 宕机或网络波动,发布就卡住了。

  • 本方案优势:这是一条纯内网的、点对点的高速通道。

    • 拓扑优势:Obsidian 在手边,CouchDB 和 Web Server 在自家服务器里。数据从头到尾没有流经任何第三方平台。

    • 架构极简:脚本直接把 Markdown 吐到 Web 服务器(如 Nginx 容器挂载的目录)或 Hugo 的监听目录下。没有 Webhook,没有 CI/CD 流水线,没有公网传输

    • 极致速度:这种“左手倒右手”的本地文件系统操作,让更新速度只取决于你服务器的磁盘 IO,而不是公网带宽。


关于图片的处理:图床方案

在静态博客中,图片管理一直是个麻烦事。如果把图片和 Markdown 混在一起同步,仓库体积会迅速膨胀,且路径处理极其繁琐。

我的解决方案是完全剥离图片存储

  • 存储后端:使用 Cloudflare R2,免费额度大且速度快。
  • 管理前端:自建 Lsky Pro 图床。
  • 上传插件:在 Obsidian 中安装 Image To Lskypro 插件。

效果:当我在 Obsidian 中粘贴图片时,插件自动将其上传至 R2,并返回标准的 Markdown 图片链接(如 ![] (https://img.mydomain.com/...))。

因此,存入 CouchDB 和同步到本地的 Markdown 文件中,只包含纯文本链接,干净清爽。


缺失的一环:从 CouchDB 到 Markdown

Obsidian Livesync 插件非常强大,实现了多端实时同步。但它为了效率,将数据以 JSON/Blob 的形式存在 CouchDB 里,并不是直接存储为文件系统中的 .md 文件。

为了把数据库里的“碎片”还原成 Hugo 能识别的博客文章,我需要一个“翻译官”。

自研同步脚本 (Obsidian-Sync-Script)

我在 Docker 容器中跑了一个 Python 脚本,充当了这个桥梁。

它主要做了这几件事:

  • ⏱️ 慵懒轮询:脚本会以设定的时间间隔(例如每 20 秒)去询问 CouchDB 是否有新变动。虽然不是毫秒级实时,但作为博客发布流,这种准实时的节奏既节省资源,又能保证写完几口咖啡的时间里内容就已同步。
  • 📂 目录还原:完美保留 Obsidian 中的文件夹层级结构(例如 /Blog/Tech/Docker.md)。
  • 🧹 自动清理:如果在 Obsidian 里删除了文章或移动了文件夹,脚本会自动同步删除或移动本地文件,保持目录整洁。
  • 🛡️ 智能过滤:我设置了 TARGET_FOLDER 变量,只同步 Blog 文件夹下的内容,Obsidian 里的其他私有笔记绝不会泄露到博客目录

设计哲学:为什么选择“慵懒轮询”而非“事件驱动”?

在系统设计中,我们常面临 Push(推/回调)与 Pull(拉/轮询)的抉择。

CouchDB 支持 http_notification,理论上我可以写一个 HTTP Server 来接收变动通知。甚至可以通过防抖(Debounce) 算法来解决“写作时信号过密”的问题。

但经过深思熟虑,我依然坚定地选择了 Pull(轮询) 模式,原因在于架构的极简性

1. 避免“服务端”的运维成本

如果使用回调,我的脚本必须升级为一个Web 服务器,时刻监听端口等待 CouchDB 的调用。这引入了额外的网络拓扑问题:容器端口映射、防火墙配置、以及潜在的安全风险。 而轮询模式下,脚本仅仅是一个客户端。它在容器内部安安静静地运行,只在需要时主动发起请求。“主动出击”永远比“被动监听”更容易维护。

2. 用最低的成本解决“并发冲突”

写作是一个高频持续的动作。使用回调模式,面对短时间内涌入的数十次请求,我需要编写复杂的多线程锁(Lock)或任务队列机制,来防止多个请求同时写入磁盘造成文件损坏。 而慵懒轮询天然就是一种单线程的节流机制While True 循环保证了同一时间只有一个同步任务在执行,上一次没同步完,绝不会开始下一次。这种串行化的特性,用最笨的代码换来了最高的稳定性。


开源与使用

为了方便维护和部署,我将这个脚本封装成了 Docker 镜像。如果你也在使用 Obsidian Livesync + CouchDB 方案,并且想实现类似的自动化导出功能,可以直接使用我的镜像。

Github 项目地址:

👉 Obsidian-CouchDB-Sync-Script

(具体的 Docker Compose 配置和环境变量说明,请移步 GitHub 仓库的 README 查看)


结语

折腾工具的最终目的是为了忘记工具。

现在,无论是用电脑还是手机,只要我在 Obsidian 的 Blog 目录下新建一篇笔记,它就会自动出现在我的 Hugo 目录中。这种无感知的写作体验,让写博客重新变成了一件纯粹快乐的事情。