搭建个人博客并不难,难的是让它真正融入日常写作,而不是上线后逐渐荒废。
我从17年的时候就开始折腾个人博客站点,过去几年我在多个平台上写过东西:公众号、掘金、Notion 公开页、Ghost、Hugo、甚至 Jekyll。 最早是通过WordPress和宝塔搭建的一个非常简陋的博客. 每换一次平台,写作的动力都会消耗一部分——先要习惯新的编辑器,再要迁移历史内容,再要适应它的分享和搜索逻辑。写博客本身没问题,问题在于博客不应该是一件独立的事情。它应该是我日常知识管理的自然出口。
这篇文章记录我最终选择的方案,以及每个选择背后的理由。整套系统由 Obsidian、两个 GitHub 仓库、Astro 和 GitHub Pages 组成,可以一句话概括:
在 Obsidian 里写作,git push 之后自动上线。
为什么要建立这套工作流
我给自己列了几个硬性要求,任何一条被打破,方案都不成立:
第一,写作环境必须是 Obsidian。我所有的技术阅读笔记、项目文档、想法草稿都在同一个 Vault 里。让博客成为其中一部分,而不是”另开一个博客工具”,这是效率的分水岭。多年经验告诉我:任何需要切换写作工具的博客,都会在三个月内被放弃。
第二,知识库必须保持私有。个人知识库里除了公开的博客的,还有大量草稿、失败的实验、客户资料、书籍摘录。这些内容不是我的博客的内容,不应该出现在公开互联网上。所以博客的源代码仓库和知识库仓库必须彻底分离。
第三,发布必须”零心理成本”。每次发文都要打开 CMS、拷贝粘贴、调整格式,几十次之后热情就会耗尽。我希望”写完一篇文章”和”文章上线”之间只有一次 git push,不需要人工干预。
第四,基础设施必须可以完全托管。我的目标是快速启动,不想要维护复杂的前后端(我曾经写过一套前后端的博客,但是用下来发现使用维护成本较大)。GitHub Pages 是唯一同时提供免费托管 + 自动 HTTPS + 自动构建 + 可以关联自定义域名的方案。
第五,拒绝千篇一律。默认主题就意味着”和几万个博客长得一样”。这套系统必须让我拥有 CSS 层面的完整控制权。
Astro 静态站点 + GitHub Pages + 自定义 CNAME + Obsidian 内容管理,是我在权衡这五条之后收敛出来的方案。
整体架构
链路如下:
Obsidian (本地写作)
↓ git push
Obsidian-Knowloedge (私有仓库,含全部知识库,包含非博客的个人的内容)
↓ GitHub Actions: 触发 dispatch
eniac-blog (公开站点仓库)
↓ GitHub Actions: checkout → sync → build
GitHub Pages (blog.3n1ac.com)
关键节点有 4 个:
- Obsidian Vault 是唯一的内容真源。所有博客文章位于
2-Area/博客写作/Posts/<slug>/index.md。 - Obsidian-Knowloedge 仓库是私有的,承担”完整知识库云备份”和”触发博客构建”两个角色。
- eniac-blog 仓库是公开的,承担 Astro 项目源码 + 一个同步脚本 + GitHub Actions 工作流。它不存储任何文章内容——文章在构建时从 Obsidian 仓库实时拉取。
- GitHub Pages 部署最终产物,配合自定义域名
blog.3n1ac.com。
所以完成链路流程如下:
- 我在本地的 Obsidian 完成写作。
- 通过 git push 推送到 GitHub 上的私密仓库 Obsidian-Knowloedge 的 main 分支。
- Obsidian-Knowloedge 的 workflow 检测到 push, 并且改动的文件命中 paths 过滤器 (2-Area/博客写作/Posts/**),启动一个 runner, 通过 curl 调 GitHub 的 repository_dispatch API, 向公开仓库 EniacBlog 发出一个 obsidian-update 事件。 (repository_dispatch 是 GitHub Actions 提供的跨仓库触发通道。)
- EniacBlog 的 workflow 声明监听 obsidian-update 类型的 dispatch,收到事件后启动一个新的 runner。
- 在同一个 runner 里,先 checkout EniacBlog 自身代码到工作目录, 再用 OBSIDIAN_TOKEN checkout Obsidian-Knowloedge 到 ./obsidian/ 子目录。两个仓库并排放在这个 runner 里。
- runner 里执行 npm ci 安装依赖。
- 运行 npm run sync-blog(即 scripts/sync-blog.js)。 脚本做两件事: (a) 只挑 publish: true 的文章,从 ./obsidian/2-Area/博客写作/Posts/ 复制到 ./src/content/posts/; (b) 把 Obsidian 方言
! [ [ 图. png ] ] 翻译成标准 Markdown ! [ alt ](./assets/xxx.png),并把源图复制过去。 - 运行 npm run build,Astro 把 src/ 编译成 dist/(纯静态 HTML), 紧接着 pagefind 扫描 dist 生成搜索索引。
- .actions/upload-pages-artifact 把 dist 打包上传, actions/deploy-pages 触发 Pages 发布。dist 里的 CNAME 文件让 Pages 使用自定义域名 blog.3n1ac.com。
- runner 销毁,私有仓库那份临时 checkout 一起消失, 公开仓库的 git 历史里从未出现过任何私有内容。
这种分离带来一个天然优势:博客仓库可以随便公开、随便让别人看代码,而不会泄露一个字的私有笔记。 概括来讲
为什么选择双仓库
单仓库方案(把博客源码和文章内容放一起)是主流做法,Hugo、Jekyll、Astro 官方教程里都这么演示。我一开始也考虑过。但真实场景下,这个选择很快会变成负担:
- 内容和代码耦合。想改一下 CSS,得 clone 一份含全部文章的仓库。想让别人 PR 一个功能,得给他看你所有的写作历史。
- 草稿和已发布内容互相污染。所有
.md文件都在同一目录树,你必须靠命名约定或 gitignore 手动区分,一旦忘记publish: false,草稿就上线了。 - 完整知识库无法参与。我在 Obsidian 里写博客草稿时,会随手引用 PARA 里的其他笔记(
[[某个概念]])。这些内部链接如果和博客仓库共用,要么全部公开,要么全部丢失。
最重要的是,一旦某个文件在某个 commit 里出现过,它就永远留在这个仓库的历史里**。你可以 .gitignore 未来的文件,但无法反向清理已提交的内容。哪怕后来用 filter-branch 或 BFG 抹掉,历史指纹依然会因为 hash 变化留下痕迹。
想真正做到私有内容永不进入公开仓库,只有一条路:私有内容从来不被公开仓库追踪。
如何通过双仓库保证文件安全
双仓库不是简单地”分成两份”。真正让它成立的是三个约束叠加起来的效果。
约束一:公开仓的 .gitignore 明确排除文章文件。
# eniac-blog/.gitignore
src/content/posts/*
!src/content/posts/.gitkeep
约束二:构建时才把内容”临时借用”过来。
CI 里的关键一步:
- name: Checkout Obsidian
uses: actions/checkout@v4
with:
repository: EniacTNB/Obsidian-Knowloedge
path: obsidian
token: ${{ secrets.OBSIDIAN_TOKEN }}
persist-credentials: false
私有仓被 checkout 到 CI runner 的临时目录 ./obsidian/。这份 checkout 不属于 eniac-blog 的 git 追踪范围——它只是构建过程中的一份工作副本,在 runner 销毁时一起消失。
接下来 npm run sync-blog 只把符合 publish: true 白名单的文章复制到 src/content/posts/,然后 Astro 把这些 markdown 编译成 HTML。最终提交给 GitHub Pages 的产物是纯静态的 HTML/CSS/JS——里面既没有原始 markdown,也没有草稿的任何痕迹,更没有那份完整的 Obsidian Vault。
约束三:Token 权限对称、最小化,不构成回路。
OBSIDIAN_TOKEN:只能读 Obsidian 私有仓 → 存在 eniac-blog 的 Secret 里,用于 checkoutBLOG_TOKEN:只能写 eniac-blog 公开仓 → 存在 Obsidian 私有仓的 Secret 里,用于 dispatch
这两条 token 的权限方向永远不能反向——从公开仓这一侧,无法向私有仓写任何东西。就算 eniac-blog 仓库被入侵、被恶意 PR,攻击者最多能读 Obsidian 里已经”过了白名单”的公开发布文章,不可能反向污染知识库、也不可能读到未发布的私有内容。
为什么这套机制能真正做到隔离
三个约束叠加后,系统具备了一个关键属性:信息单向流动。
私有知识库 (完整 Vault)
↓ 白名单过滤 (publish: true)
↓ 一次性 CI runner (构建完销毁,无持久化)
公开博客 (静态 HTML)
从上到下每一步都是不可逆的窄化:私有仓 → 只发布几篇 → 只编译成静态产物。信息永远不会反向回流,也永远不会”顺便带出”没被显式发布的内容。
双仓库的隔离靠的是仓库边界本身:不需要每次 commit 都提醒自己,不需要靠脑子记住哪个文件不能公开,不需要看 diff 的时候特别警觉。这些都由架构保证。架构比纪律更可靠——这是我在很多其他系统上反复验证过的判断。
后续计划
有几件事我还没做,但列在待办清单里:
评论系统。目前没有评论。倾向用 utterances 或 giscus——把评论存到 GitHub Issues/Discussions 里,零后端、零骚扰、和整个系统的托管哲学一致。
订阅通道。RSS 已经就绪,下一步准备加一个基于 Buttondown 或 ConvertKit 的邮件订阅,让不用 RSS 的读者也能被动收到更新。
系列文章。当前 tag 是扁平的,想加一层”系列”元数据,把有先后顺序的文章串联起来(例如”从零搭建博客”整个系列)。技术实现只是 frontmatter 加一个 series 字段 + 一个 Astro 页面聚合。
阅读统计。想知道哪些文章被真正读完,而不只是打开。倾向用 Plausible 或自建轻量方案,不打算引入 GA,不接受隐私妥协。
更多项目展示。/projects/ 页目前只有两个占位。等有几个真实项目(个人工具、开源库、AI 实验)沉淀出来,会一次性补齐。
移动端深度打磨。目前移动端能用,但没有针对性优化。想做:更好的封面比例适配、更紧凑的导航、代码块横滑体验、图片全屏预览。
这套工作流不是”最完美的博客方案”,但它是最适合我当前节奏的方案。它把博客从一个需要单独维护的东西,变成了知识管理系统的一个副产品。写作依然是那件核心的事,其他一切都自动化了。
如果你也在犹豫要不要重建自己的博客——不妨从”你现在的写作工具是什么”开始想,而不是从”哪个博客框架最流行”开始。让工具适配你的思维方式,而不是相反。
工具会过时,框架会重构,但思考方式,决定我们能走多远。
