这个站的文章发布,现在只需要在本地跑一条命令:
./publish.sh my-note.md
这篇文章记录这个脚本的工作原理,以及它和 Obsidian 的配合方式。
为什么不用 git,也不用 CMS
静态博客的常见发布方式有几种:git push 触发 CI、网页版 CMS、或者 Hugo 自带的 hugo deploy。我的场景有点特殊:写作在本地 Obsidian 里完成,服务器是自己的一台 VPS,中间不想引入第三方平台。那么最朴素的方案反而是最合适——scp 把文件传上去,ssh 触发服务器上现成的部署脚本。不需要守护进程,不需要 webhook,不需要在服务器上装任何新东西。
脚本做了什么
publish.sh 按顺序做七件事,任何一步失败都会中止,线上内容保持原样:
- 定位正文。 接受一个
.md文件或一个目录。如果是目录,约定正文叫index.md——这是 Hugo 的 page bundle 结构,文章和它的图片放在同一个文件夹里,作为一个整体发布。 - 生成 slug。 URL 取自文件名:剥掉
2026-08-22-这样的日期前缀,转小写,非法字符转成连字符。Obsidian Hugo Publish.md变成obsidian-hugo-publish。纯中文文件名会报错退出——这是有意的,URL 里的拼音或机器翻译都不如一个自己起的英文 slug 体面。 - 校验 front matter。 检查
title存在;检查正文里没有 Obsidian 的 wiki 图片语法![[...]](Hugo 不认识它)。 - 校验图片。 扫描正文里所有
引用,确认每一个本地图片文件都真实存在。少了图片就中止——线上出现裂图比不发更糟。 - 补齐 date。 所有校验通过后,才往 front matter 里补
date。顺序很重要:先校验后改文件,失败的文章不会被留下修改痕迹。 - 组装并上传。 把正文和被引用的图片拷进一个临时目录,组成
content/notes/<slug>/的结构,scp整包传上服务器。 - 触发部署,回写状态。
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 崩掉。自动化最大的价值往往不是省时间,而是让同一个人不会犯第二次同样的错。