ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

caveman:用纯文本和Git打造极简个人知识库

caveman:用纯文本和Git打造极简个人知识库 1. caveman是个什么项目从笔记工具失控到“数字洞穴壁画”说实话caveman这个项目一开始是我给自己开的一个玩笑。2025年我实在受不了自己的数字生活了四千多条笔记分布在六个工具里——微信收藏、系统备忘录、Notion、飞书文档、知乎收藏、两个本地文件目录彼此之间老死不相往来。有一天我想找一条关于“发酵面团湿度”的记录翻了整整一个小时最后发现它老老实实躺在微信收藏里旁边还挂着两条过期的购物链接。那一刻我做了个决定从今天起过“穴居人”的日子。所谓穴居人不是不写代码、不碰电脑而是回到最原始、最笨、但最可靠的技术栈重新接管自己的内容。这个项目被我命名为caveman核心是一套极简个人知识库加发布管线所有内容都落成纯文本Markdown文件用Git管理版本用一个自己写的标准库脚本构建出静态页面再用rsync推到我的小服务器上。说出来可能有点寒酸但正是这套组合拳治好了我的信息焦虑。这个项目适合谁我觉得最适合两类人。第一类跟我一样被各种云笔记平台绑定过、导出格式折腾到怀疑人生的普通写作者第二类是喜欢折腾但已经厌倦了“为了维护系统而维护系统”的技术爱好者。如果你只是想要一个能用的笔记软件那继续用现成的就好没问题。但如果你也受够了数据躺在别人手里、想自己掌握一切又不想学一堆新框架那caveman这套思路值得参考。1.1 数据失控是真实痛点不是矫情以前我从来没认真盘点过自己的内容资产。直到某个周六下午我把所有工具的导出文件全部解压到同一个目录里才发现情况有多混乱Notion导出的HTML嵌套了七八层div旧博客的数据库备份打不开因为PHP版本不匹配iPhone备忘录的格式虽然能导出来但无法按时间批量操作微信收藏甚至没有官方批量导出。更难受的是检索。有数据库的时候检索很快但每个平台只有它自己的搜索跨平台找东西基本靠记忆。很多内容在收藏的那一刻就等于被丢弃了因为它们和别的信息隔离开来永远不会被再次发现。caveman要解决的最核心问题不是“存储”而是“让内容流动起来”——把我写的、收藏的、摘录的、灵光一现的所有文字统一放到一个我能完全控制、能全文检索、能批量操作的地方。我当时的标准就三条第一数据必须是普通文本任何设备、任何年代都能打开第二文件名要有时间信息这样不用查数据库也知道先后顺序第三不需要复杂软件就能完成整理和发布。这三个标准基本把市面上的知识库产品全排除干净了。1.2 我试过的路大家都在船上船却在漏写caveman之前我试过很多方案。Notion确实好用功能全面、数据库视图灵活但我每次导出都觉得像被绑架——导出的Markdown里塞满了它的特有关键词标题层级也会变乱。而且Notion必须联网有一次我在高铁上想翻一篇技术笔记断网状态下页面根本打不开只能盯着那个旋转的加载图标发呆。后来试过开源双链笔记软件装上插件、配好同步折腾了整整一个周末。说实话双链图谱第一眼确实惊艳看得见每篇笔记之间的关系网。但问题在于图谱好看的前提是你得手动维护链接或者依赖全局匹配。我写了三十篇笔记之后发现为了“让图谱好看”这件事本身我花掉的时间比写内容还多。这完全本末倒置。还有一次本地数据库文件损坏虽然最后靠备份恢复了但对它的信任已经没了。我也想过自建DocuWiki或者BookStack这类带界面的知识库系统装完之后看着那一堆PHP依赖、配置文件、数据库表结构我脑子里出现了一个问题三个月后我还愿意给这个环境打补丁吗答案是不愿意。于是掉头往反方向走——越轻越好最好是连数据库都用不上的方案。1.3 caveman的三条铁律所以caveman从第一天起就给自己定死了三条规矩单一数据源所有内容只存在于content目录下的Markdown文件中不上别的数据库不复制到别的工具。纯文本优先任何编辑器都能打开任何系统都能读取格式是Markdown五年后、十年后依然可读。弱依赖构建脚本只用Python标准库和Shell命令不装Node、不装数据库、不装HTTP服务框架发布只靠Git和rsync。我把这套东西称为“数字洞穴壁画”。古人在岩壁上画下图案过几千年我们还能看懂现在的人把记忆存在私有格式里过五年可能就得靠厂商心情才能导出。我要的不是花哨是活得久。caveman这个名字就是想提醒自己技术永远在变但文字本身不需要被技术绑架。2. 为什么偏偏选“最原始”的技术栈选型背后的真实逻辑很多人看到caveman的第一反应是你放着现成的Hugo、Next.js不用偏偏自己写脚本是不是闲得慌说实话一开始我也怀疑过自己但用了三个月之后我确信这套“原始栈”的选择是有道理的。这一节我把每个组件为什么这么选一次说清楚。层级方案为什么是它内容格式Markdown纯文本可读性最强、迁移零成本、任何设备都能编辑版本管理Git天然支持历史回溯、冲突管理、多设备同步逻辑构建工具Python标准库Makefile没有版本依赖不会半年不维护就跑不起来发布方式rsync SSH一条命令同步整个目录不需要额外服务本地检索ripgrep fzf秒级全文搜索不需要装搜索引擎2.1 纯文本是抵抗工具绑架的唯一办法我见过太多人把文章写进Word写进各种私有格式的编辑器里最后导出时遇见一堆乱码。Markdown的好处在于它本质上是带少量符号的普通文本就算你完全不会渲染直接用记事本打开也能看懂标题、链接、列表。这意味着我永远不会被单个工具锁死今天可以用VS Code写明天用Vim写后天在手机上随便找一个文本编辑器写文件仍然是同一个文件。纯文本还有一个隐藏优势可以批量改。以前我如果想统一把全文里的“gallery”改成“相册”在Notion里可能要点半天现在只要一条sed命令两秒钟扫完所有文件。有一次我想把某段时间写的两百篇笔记里的个人称呼统一修改就这么干的干净利落而且改完能用git diff看得清清楚楚哪一行变了、怎么变的一清二楚。2.2 Git不只是备份是给你一套后悔药把Git选为caveman的核心不是因为它新潮恰恰因为它足够成熟。Git仓库本身就是完整的版本历史我可以知道每篇笔记是什么时候创建的、哪句话是哪次修改加进去的、为什么删掉了一段。有些事情文字记录早就忘了但git log会如实告诉你。多设备同步这个问题Git的处理方式也简单直接一台机器上写好commit之后push到远程仓库另一台机器上pull下来。中间如果两台设备都改了同一个文件Git会报告冲突。说实话这个“冲突”机制一开始让人觉得麻烦但后来我发现它反而是保护伞——它强迫我面对同一个问题“到底哪个版本才是对的”这比网盘那种“静默覆盖”要安全得多。当然Git不等于备份远程仓库要自己搭或者选择信任第三方的Git托管服务。我的做法是在自己的服务器上建了一个裸仓库这样数据始终在我手里。这一点很关键尤其当你真的把caveman当长期项目跑的时候远端仓库就是唯一的锚。2.3 自写构建脚本不是硬核是杀鸡不用牛刀现成的静态站点生成器很强大Hugo渲染快、主题多Next.js社区也很繁荣。但它们每一样都自带依赖生态升级要跟着走主题要看作者更新插件和版本的兼容性时不时出问题。对于一个月发几篇文章的个人知识库来说这种复杂度完全是多余的。caveman的构建脚本只做四件事扫描content目录下的所有Markdown文件解析每篇文件头部的元信息按时间排序生成文章列表页把正文渲染成静态HTML。这些逻辑用Python标准库加几十行代码完全够用不引入任何第三方包。写完之后我甚至有点后悔——当初拖了两周才动工真正写脚本只花了一个下午。很多东西的复杂度是被想象放大的。2.4 发布走rsync而不是一键部署平台一键部署平台确实方便代码推上去自动构建自动上线。但对个人内容库来说我更需要的是“可控”。rsync的思路简单粗暴本地构建完静态文件通过SSH同步到服务器上的Web目录。没有构建服务器没有容器没有配置中心。服务器上只需要有rsync和SSHD就能跑。这套方案还有一个很大的好处芯片级简单出问题太好排查了。如果发布失败无外乎三种可能——SSH连不上、目录权限不对、源文件没构建好。不像容器编排那种层层嵌套的环境出问题你都不知道先看哪个日志。3. 目录骨架、文件规范和构建脚本caveman到底怎么搭聊完选型该说点能直接抄作业的了。我尽量把caveman的目录设计、文件规范和核心脚本讲得具体一点你可以照着搭也可以根据自己习惯改。记住一个原则结构是为内容服务的别为了结构本身搞太复杂。3.1 目录结构一次说清这是我的caveman仓库的实际目录布局kb/ ├── content/ │ ├── posts/ │ │ ├── 2025-06-01-bread-humidity.md │ │ └── 2025-06-05-git-notes.md │ └── notes/ │ └── inbox-20250607.md ├── templates/ │ ├── post.html │ └── list.html ├── scripts/ │ ├── build.py │ └── index.py ├── public/ ├── Makefile └── README.mdcontent目录是全部家当。posts放正式成文的内容notes放碎片想法和临时摘录其中inbox文件是我在手机上随手记的入口每周整理一次。templates里是两个极简HTML模板。public是构建输出目录平时不进Git。Makefile把构建流程串起来让发布变成一条命令。3.2 一篇笔记的完整生命周期每篇Markdown文件的开头必须有一段frontmatter用三条短横线包起来的元信息区长这样--- title: 发酵面团湿度笔记 date: 2025-06-01 tags: [cooking, bread] status: publish ---正文就是普通Markdown。status字段有三种状态draft表示还没写完构建时会跳过publish表示要上线archive表示归档不再出现在首页列表但保留在站点里。这个三态设计非常重要它让“写完再发布”和“先记下来再说”两种场景都有了出口。我的习惯流程是手机上随手记一条inbox文件比如“2025-06-07 查一下面包整形手法”周末整理时把内容结构化改成正式文件名补全tags和status。写废了的草稿也不会被丢状态设成draft就好它只是不显示出现在网站上但内容还在Git历史里。3.3 构建脚本的核心逻辑构建脚本不需要很复杂核心就这两个函数。第一个是解析frontmatterimport pathlib, re def parse_frontmatter(text): m re.match(r^---\n(.*?)\n---\n(.*), text, re.S) if not m: return {}, text meta {} for line in m.group(1).splitlines(): if : in line: k, v line.split(:, 1) meta[k.strip()] v.strip().strip() return meta, m.group(2)第二个是生成文章列表页def build_index(posts): posts.sort(keylambda x: x[date], reverseTrue) items \n.join( flia href{p[slug]}{p[title]}/aem{p[date]}/em/li for p in posts ) return ful{items}/ul实际代码比这个长一点但核心逻辑就是这么朴素。构建时扫描posts目录逐个解析过滤status最后渲染到templates里输出到public。没有增量构建没有缓存三百篇页面全部重新生成也就一两秒我觉得完全不需要优化到毫秒级。3.4 发布钩子与回滚发布走Git裸仓库加post-receive钩子。服务器上建一个裸仓库然后写钩子#!/bin/bash GIT_WORK_TREE/opt/kb GIT_DIR/srv/git/kb.git git checkout -f cd /opt/kb make publish本地写完内容后执行git push一键触发服务器检出并发布。注意钩子脚本要给可执行权限否则push成功但不会触发任何动作我当时就栽在这个细节上。“回滚”同样简单想回到昨天的版本本地直接git revert或者reset到对应commit再push服务器上的钩子会把旧版本重新检出来并发布。我回滚过一次全程没超过三十秒。4. 让内容能被找到:标签、索引与搜索的取舍有了内容、有了发布真正难的是怎么把旧内容翻出来。这一节记录我在检索侧的实践和取舍。这里想明白之后caveman才算真正能用起来。4.1 标签体系的两级设计我试过很多种标签方案最终沉淀下来的规则很简单只有两级标签。第一级是主题域比如cooking、tech、reading、life第二级是具体小标签比如bread、git、koji。搜索时先按主题域收窄再看具体小标签定位。不建议搞超过两级的原因是维护成本递增很快。标签一旦超过三级整理时你要反复思考这个标签应该挂在哪个父级下面这种思考本身就是在消耗写作精力。而两级刚好一套放之四海皆准的大分类加上一套自由生长的小标签既不冗余又足够精准。4.2 自动索引页不用手维护访客点开站点应该能看到按时间线排列的文章列表同时每个标签也应当有自己的页面。caveman的构建脚本每次都会自动扫描所有文件的tags动态生成标签索引页和统一文章列表不需要我手动维护。这也意味着当你给某篇文章改了标签重新构建后索引立即跟着变不用去改第二处地方。有一点要提醒如果改了文件名也就是slug变了以前发布过的站外链接会失效。我的办法是在旧文件名位置放一个redirect文件做法虽然原始但足够解决问题。站内内容之间的引用链则靠构建时全局扫描旧链接并提示输出警告保证自己不会带着一屁股死链上线。4.3 搜索这条命令我一天用十几次caveman没有内置搜索框因为终端里的检索已经足够好用。我在本地用ripgrep配合fzf做一个模糊搜索终端命令rg -li search_keyword content/ | fzf --preview head -20 {}输入关键词秒级列出所有包含它的文件再即时预览前20行选中后直接打开。这套组合虽然看起来像是“命令行原教旨主义”但实际体验比很多笔记软件内置的搜索都快。对于几千条纯文本笔记ripgrep的性能完全不需要担心零点几秒就能出结果。4.4 为什么我最终放弃了双链现在知识管理圈不提双链就像少了点什么。但caveman刻意不做双链这是我的真实取舍。双链的价值在于自动发现笔记之间隐藏的关联但代价是你要么花时间手动维护链接要么接受全局匹配带来的大量噪声。我自己的实践是只在需要的文章底部写一个“相关条目”小段落手动放两三个链接既不追求图谱也不追求自动关联。与其维护一套复杂的链接机制不如每次写文章时顺带想一下“这篇内容跟以前的哪篇相关”。这个动作不需要额外工具但效果却最直接——半年后你看到“相关条目”四五个字就能顺着找到整个知识簇。5. 三个月实测维护成本、翻车记录和补救办法方案说得再好听也得经过真实使用检验。到写这篇文章为止caveman已经稳定跑了三个多月我把这段时间的几个关键数字和翻车现场都留个档给后来者参考。指标数值内容总量523篇其中正式文章47篇其余为笔记和摘录日均新增约1.8条全文搜索命中率近三个月检索约50次48次一次命中日常维护时间大约每周15分钟主要是整理inbox和修标签非预期翻车次数3次全部为操作失误没有一次是系统崩溃5.1 翻车记录文件名里的空格害我找了一下午第一次翻车完全是格式规范问题。我在手机上有条笔记文件名是“发酵 测试.md”中间带了一个空格。结果build.py在生成链接时没有做URL编码导致页面里的链接断了一截样式也乱了。更麻烦的是我在本地grep的时候明明能看到内容但构建出来后怎么都打不开。查了半天才定位到是空格问题。从那天起我定了一条硬规矩文件名一律用短横线连接不出现空格和中文中文标题放frontmatter里。这条规范看着简单但能帮你避开一大类线上问题。5.2 翻车记录rsync的--delete差点清空线上目录这是让我最后怕的一次。当时我调整了构建脚本的输出目录本地已经构建好了但public目录下还残留着旧版页面。我为了让服务器和本地“绝对一致”在rsync发布命令里加了--delete参数。结果因为本地构建脚本出了问题public目录是空的命令执行后线上目录也被清空了。事后我总结了两个防呆手段。第一发布前脚本里必须检查构建产物里的index.html存在才允许继续执行第二不再直接rsync到线上目录而是先同步到一个临时目录同步完成后再用mv做原子替换。这样即使同步出问题线上也不会瞬间变成空白页。5.3 手机端操作与Git冲突处理移动端的体验是这套方案里最弱的一环。我目前的做法是用Git客户端在手机上提交新增文件但因为手机端环境不稳定偶尔会出现和电脑编辑同一份文件的情况就会产生Git冲突。这事的解决办法很简单手机上只做新增inbox和草稿绝不在手机上修改已有正式文件。新文件之间很少冲突旧文件保持一方权威。如果确实需要临时改一个旧文件我宁愿在inbox里记一条备忘等回到电脑前再统一修改。这个看起来笨拙的决策帮我把移动端出错的概率降到了几乎没有。5.4 每周维护其实只有十分钟很多人以为自建方案维护成本高但caveman的真实维护时间很少。每周一次整理inbox把临时记录归入相应主题和标签偶尔扫一眼tag列表合并重复标签如果发布地址变了改一下配置。全部加起来一次不会超过十五分钟。相比以前在Notion里整理数据库视图、调整分组结构的时间已经算是极省了。6. 下一步caveman v2的几个想法和三个入坑建议项目跑顺之后我开始琢磨下一版还能做点什么。当然caveman的精神就是不做重所以未来的改动一定是往“更省事”方向改而不是往“更强大”方向堆功能。v2我最想做的三件事一是给构建脚本加一个增量构建开关只在文件有变化时重建相关页面省掉每次全量重跑的一两秒二是在移动端做一个更顺畅的添加入口目前是用Git客户端提交文件流程上还能简化三是完善内容生命周期提醒让长期不动的draft文件自动进入我的视野提醒我哪些想法已经过期、哪些值得重新整理。如果你也想搭一个类似的东西我给三条发自内心的建议。第一条头一个月只用一个inbox文件什么都往里丢别急着分目录、建标签先跑通“记录”这个动作。第二条写一个再也不能更简单的脚本别一上来就学我的完整结构因为你的整理习惯一定跟我不同。第三条git远程仓库越早建越好最好从第一篇笔记就纳入版本管理——千万别写了两个月才想起备份那种把内容重写一遍的滋味我不希望你体验。好了这就是caveman到目前为止的全部家底。我依旧每天用这个极简系统记录、整理、发布也依旧隔三差五靠git log回忆某个想法是什么时候冒出来的。工具链虽然原始但它给了我一种踏实感这些文字在我手里不在别人的服务器上也不依赖某个厂商的格式继续存活。如果你也正在被越来越重的工具折磨试试往回走一步回到最朴素的“穴居人”生活方式说不定会和我一样找回记录的轻松感。
返回列表