这个站的文章发布,现在只需要在本地跑一条命令:

./publish.sh my-note.md

这篇文章记录这个脚本的工作原理,以及它和 Obsidian 的配合方式。

为什么不用 git,也不用 CMS

静态博客的常见发布方式有几种:git push 触发 CI、网页版 CMS、或者 Hugo 自带的 hugo deploy。我的场景有点特殊:写作在本地 Obsidian 里完成,服务器是自己的一台 VPS,中间不想引入第三方平台。那么最朴素的方案反而是最合适——scp 把文件传上去,ssh 触发服务器上现成的部署脚本。不需要守护进程,不需要 webhook,不需要在服务器上装任何新东西。

脚本做了什么

publish.sh 按顺序做七件事,任何一步失败都会中止,线上内容保持原样:

  1. 定位正文。 接受一个 .md 文件或一个目录。如果是目录,约定正文叫 index.md——这是 Hugo 的 page bundle 结构,文章和它的图片放在同一个文件夹里,作为一个整体发布。
  2. 生成 slug。 URL 取自文件名:剥掉 2026-08-22- 这样的日期前缀,转小写,非法字符转成连字符。Obsidian Hugo Publish.md 变成obsidian-hugo-publish。纯中文文件名会报错退出——这是有意的,URL 里的拼音或机器翻译都不如一个自己起的英文 slug 体面。
  3. 校验 front matter。 检查 title 存在;检查正文里没有 Obsidian 的 wiki 图片语法 ![[...]](Hugo 不认识它)。
  4. 校验图片。 扫描正文里所有 ![](...) 引用,确认每一个本地图片文件都真实存在。少了图片就中止——线上出现裂图比不发更糟。
  5. 补齐 date。 所有校验通过后,才往 front matter 里补 date。顺序很重要:先校验后改文件,失败的文章不会被留下修改痕迹。
  6. 组装并上传。 把正文和被引用的图片拷进一个临时目录,组成 content/notes/<slug>/ 的结构,scp 整包传上服务器。
  7. 触发部署,回写状态。 ssh 到服务器执行 deploy.sh(构建 → 同步到 nginx 目录 → reload),最后把 Obsidian 笔记里的 status: 改成published

和 Obsidian 的配合

在每篇笔记的 Properties 里维护一个 status 字段(idea / draft / published),配合 Dataview 插件拉一张表就是内容流水线看板:哪些是想法,哪些在写,哪些已发布。脚本发布成功后会自动把这个字段改成 published,看板不需要手动维护。

想用得更顺手,可以装 Shell Commands 插件,把下面这条命令绑定到快捷键:

/path/to/publish.sh "{{file_path:absolute}}"

{{file_path:absolute}} 是当前笔记的绝对路径。绑定后,写完文章按一下快捷键,终端里的整个流程就变成了一个动作。

结语

这个脚本没有什么高明之处,它只是把"校验 → 上传 → 部署"这条链路上所有我曾经手滑过的环节固定了下来:忘了补日期、图片路径写错、文件名是中文导致 URL 崩掉。自动化最大的价值往往不是省时间,而是让同一个人不会犯第二次同样的错。