Git bare repo + post-receive hook + cron 自动规范化流水线
问题
多台机器往同一个 wiki 写笔记时(比如公司 Kali 上的 Hermes、家里 Windows 上的 Obsidian、服务器上的 Hermes),如何让服务器上的 AI Agent 自动检测到有人 push 了新内容,并自动拉取、规范化、推送回去?
核心思路
不依赖第三方平台(GitHub / GitLab),在自己的服务器上自建一条事件驱动 + 定时轮询的混合流水线。
┌──────────────────┐ ┌──────────────────┐
│ Windows Obsidian │ │ Kali Hermes │
│ (Git 插件 push) │ │ (CLI push) │
└────────┬─────────┘ └────────┬─────────┘
│ │
│ git push raw 笔记 │ git push raw 笔记
▼ ▼
┌─────────────────────────────────────────────────┐
│ /srv/git/work-wiki.git (bare repo) │
│ │
│ ┌─────────────────────────────────────────┐ │
│ │ hooks/post-receive │ │
│ │ while read oldrev newrev refname; do │ │
│ │ echo "$(date -Iseconds) $refname │ │
│ │ $newrev" >> /tmp/pushed.log │ │
│ │ done │ │
│ └──────────────┬──────────────────────────┘ │
│ │ 每次 push 触发 │
│ │ 往 /tmp/ 写入一行 │
└──────────────────┼──────────────────────────────┘
│
│ cron 每 5 分钟检查
▼
┌─────────────────────────────────────────────────┐
│ Server Hermes Agent (我) │
│ │
│ ① tail -1 /tmp/pushed.log │
│ ② diff 上次处理标记 → 有新 push? │
│ ③ yes: cd ~/work-wiki && git pull │
│ ④ 扫描 raw/ 目录下新增/修改的文件 │
│ ⑤ 规范化: raw 笔记 → entities/ | concepts/... │
│ ⑥ 更新 index.md + log.md │
│ ⑦ git commit -m "normalize: ..." + git push │
│ ⑧ 更新处理标记文件 │
│ ⑨ no: 什么都不做,结束 │
└─────────────────────────────────────────────────┘
逐层拆解
第一层:Git Bare Repo(裸仓库)
是什么: 普通 Git 仓库 = .git/(版本数据库)+ 工作区(你编辑的文件)。Bare repo 只有 .git/ 的内容,没有工作区,不能直接改代码。它的唯一用途是接收 git push、提供 git pull。GitHub 上你看到的每个仓库背后就是一个 bare repo。
为什么要用它: 它是多机协作的"中央中转站"。Windows Obsidian push → bare repo;Kali Hermes push → bare repo;Server Hermes pull → 处理 → push 回去。所有人都通过它同步,不会冲突。
物理位置: /srv/git/work-wiki.git。它是一个目录,目录名以 .git 结尾是 Git 约定,表示这是一个 bare repo。
/srv/git/work-wiki.git/
├── HEAD
├── config # repo 配置
├── objects/ # commit、tree、blob 对象 (版本数据本体)
├── refs/ # 分支指针 (master, HEAD...)
├── hooks/ # 钩子脚本 ← 这里就是我们要用的
└── ...
第二层:Post-Receive Hook
是什么: Git 的钩子(hook)机制——在特定事件发生时自动执行的脚本。post-receive 是其中一个:当裸仓库接收到一次 push 后,Git 自动执行这个脚本。
为什么用它: 它能让你在"有人 push 了"这件事发生时,留下一个可检测的信号。没有它,你只能靠定时 git fetch + diff 轮询,浪费资源且无法精确知道 push 的时间点。
脚本内容:
#!/bin/bash
# /srv/git/work-wiki.git/hooks/post-receive
while read oldrev newrev refname; do
echo "$(date -Iseconds) $refname $newrev" >> /tmp/work-wiki-pushed.log
done
Git 执行 post-receive 时,通过 stdin 传入三列:<old-commit> <new-commit> <ref-name>(比如 abc123... def456... refs/heads/master)。脚本读出来,拼上当前时间戳,追加写入 /tmp/work-wiki-pushed.log。
一次 push 后的日志长这样:
2026-05-30T18:39:06+08:00 refs/heads/master ee10419f2f7...
注意: Hook 文件必须可执行(chmod +x),必须放在 bare repo 的 hooks/ 目录下(clone 出来的工作副本里的 hook 不会触发)。sample 后缀的文件不会被运行——Git 只执行去掉 .sample 的同名文件。
第三层:Cron 定时任务
是什么: Linux 的定时任务调度器。*/5 * * * * 表示每 5 分钟执行一次。
为什么用它而不是持续监听: 有两个方案可选:
- 高频轮询(每 5 分钟):适合需要近乎实时的场景,但 token 消耗大
- 低频批处理(每天一次):与每日 lint 合并,节省 token。日常交互中由 Agent 主动检查
当前部署采用方案 2:每天 09:00 一次 cron 统一做规范化 + lint。用户要求读/写 wiki 时 Agent 先 git pull + 检测 push 标记,有新增 raw 内容立即规范化再操作。
检测逻辑:
tail -1 /tmp/pushed.log ← 最新 push 记录
vs
cat /tmp/last-processed ← 上次处理到的记录
相同 → 没新东西 → 退出
不同 → 有新 push → 开始处理 → 处理后更新 last-processed
用的不是轮询时间而是日志行内容来检测——这意味着即便 cron 正好在你 push 之间跑了一次,"我处理过了吗"的判断也是精确的。
第四层:Hermes Agent 的规范化流程
检测到新 push 后,Agent 执行:
① cd ~/work-wiki && git pull origin master
② git diff HEAD@{1}..HEAD --name-only ← 找出变更文件
③ 定位 raw/ 目录下新增/修改的文件
④ 加载 work-wiki-normalize 技能
⑤ 对每个新 raw 笔记:
- 读取内容
- 识别实体(芯片、协议、项目) → 创建/更新 entities/ 页面
- 识别概念(技术原理、Bug 记录) → 创建/更新 concepts/ 页面
- 写 YAML frontmatter(title, created, tags...)
- 添加 wikilink 交叉引用(最少 2 个)
⑥ 更新 index.md(添加新页面链接 + 更新时间 + 页面计数)
⑦ 更新 log.md(追加操作记录)
⑧ git add -A && git commit -m "normalize: ..." && git push origin master
⑨ 更新 /tmp/last-processed
冲突处理: 如果在步骤⑦ push 时发现远程有新提交(non-fast-forward),说明在你处理的这几秒内又有人 push 了。策略是 git pull --rebase 后再 push,确保不丢数据。
为什么用两个文件(pushed.log + last-processed)而不是一个
这是常见的"生产者-消费者"模式:
- pushed.log:post-receive hook 只追加(append-only)。Hook 执行时不需要知道谁来处理、处理了没有,它只负责记录事件。
- last-processed:Agent 只覆盖(overwrite)。处理完就更新为最新行。
两个角色完全解耦——Hook 是生产者,Agent 是消费者。哪怕 Agent 挂了几天,pushed.log 里积压了 100 行,恢复后 Agent 能一次性追上。
怎么部署到 study-wiki
同样的流水线可以复用到 study-wiki:
# 步骤 1:创建 post-receive hook
cat > /srv/git/study-wiki.git/hooks/post-receive << 'EOF'
#!/bin/bash
while read oldrev newrev refname; do
echo "$(date -Iseconds) $refname $newrev" >> /tmp/study-wiki-pushed.log
done
EOF
chmod +x /srv/git/study-wiki.git/hooks/post-receive
# 步骤 2:初始化状态文件
echo "init" > /tmp/study-wiki-last-processed
# 步骤 3:创建 cron job(通过 ops cron 命令)
# 加载 work-wiki-normalize 技能(或 study-wiki-normalize),
# 工作目录设为 ~/study-wiki
与 GitHub Actions 的对比
| 维度 | GitHub Actions | 自托管 bare repo + cron |
|---|---|---|
| 触发方式 | push 事件 → runner | push 事件 → hook → cron 轮询 |
| 延迟 | ~10-30 秒 | 最多 5 分钟 |
| 成本 | 免费额度有限 | 零额外成本 |
| 隐私 | 代码在 GitHub 服务器上 | 代码全在自己服务器上 |
| 依赖 | 依赖 GitHub | 自给自足 |
| 维护 | 零运维 | 需要自己搭 |
选择自托管的核心理由:数据不离开自己的服务器。GitHub 会在训练模型中使用了用户的代码,自托管完全规避了这个风险。
Pitfalls
1. git pull 时遇到 merge conflict
如果 Server Agent 在本地改了文件,同时别人 push 了同一个文件到 bare repo,pull 时会冲突。预防:Agent 先 git stash 再 pull,再 git stash pop。如果 pop 冲突,保留 stash,用远程版本。
2. Hook 不发 notify 不意味着没人 push
Hook 只写文件,不通知任何人。如果 pushed.log 没被 cron 检测到就会"丢失"。但只要 cron 运行且状态文件不被破坏,就不会丢。
3. 多个 Agent 同时跑
cron 可能在上一次还没处理完时就触发下一次。用文件锁(flock)防止并发:
exec 200>/tmp/work-wiki.lock
flock -n 200 || exit 0 # 拿不到锁就退出
当前部署中,cron 一次处理一把而且速战速决(几十秒内完成),5 分钟间隔基本不会重叠。
4. Hook 权限
Bare repo 的 owner 必须是执行 cron 的用户(即 ops),否则 hook 写入 /tmp/ 可能权限不足。当前 server 上所有 bare repo 归 ops 所有,不是问题。
5. 只处理 raw/ 目录
entities/、concepts/ 下的修改是已有规范内容的更新(比如纠正了一个技术细节),不应该被再次规范化。cron 只扫 raw/ 的变更。
第五层:每日 Lint 健康检查
除了每 5 分钟跑一次的"新内容规范化",还有一个每天跑一次的 lint job,对整个 wiki 做全面体检。
触发方式
两种触发:
- 定时触发: cron 每天定时(如 09:00)自动跑 lint
- 手动触发: 用户在微信/终端说"检查 wiki""lint""wiki 健康检查"时立即跑
检查项目(12 项)
基于 llm-wiki 技能的 lint 流程:
| # | 检查项 | 说明 |
|---|---|---|
| 1 | 矛盾检测 | 共享标签/实体的页面,对比事实断言,找出互相矛盾的说法。标 contested: true |
| 2 | 孤儿页面 | 扫描全站 wikilinks,找出入链为零的页面——没有页面指向它,即"孤岛" |
| 3 | 死链 | 链接 目标页面不存在。可能是拼写错误或该页被删除后遗留 |
| 4 | 过时信息 | 页面 updated 日期与最近相关源文件日期差值 > 90 天,标记需审查 |
| 5 | index 同步 | filesystem 上的 .md vs index.md 条目,双向对比。有没有漏登记或多登记的 |
| 6 | frontmatter 校验 | 必填字段 (title, created, type, tags) 是否齐全;tags 是否在 SCHEMA.md 标签体系内 |
| 7 | 置信度审查 | 标记 confidence: low 的页面,以及单源但未标 confidence: medium 的页面 |
| 8 | 源漂移 | raw/ 目录下有 sha256 标记的源文件,重算 hash 对比,检测源文件是否被意外修改 |
| 9 | 页面大小 | 超过 200 行的页面标记为待拆分 |
| 10 | 标签审计 | 列出所有实际使用的 tags,标记不在 SCHEMA.md 标签体系内的野标签 |
| 11 | 日志轮转 | log.md 超过 500 条记录时自动归档为 log-YYYY.md |
| 12 | 建议新建页 | AI 推理项:基于当前 wiki 内容密度和交叉引用模式,建议是否值得新建汇总/对比/交叉分析页面 |
执行流程
cron 每日触发(或用户手动触发)
├── ① 加载 llm-wiki 技能
├── ② git pull 拉取最新
├── ③ 执行 12 项检查(execute_code 扫描,结合 AI 推理)
├── ④ 生成报告:分严重等级 → 列出具体问题 + 文件路径 + 修复建议
├── ⑤ 追加 log.md: ##[YYYY-MM-DD] lint | 发现 N 个问题
├── ⑥ 如果有问题,自动修复可安全修复的项(死链清理、index 同步、标签纠正)
├── ⑦ 推送修复到 bare repo
└── ⑧ 通过微信发送汇总报告给用户
严重等级排序
broken links > orphans > source drift >
contested pages > stale content > style issues
- 死链和孤儿是最严重的问题——会导致导航失败
- 矛盾页面需要人工判断,只标记不自动裁决
- 样式问题(页面过长、标签不规范)自动修复
微信通知
每日 lint 结果通过 Hermes Gateway → iLink Bot 发送微信消息:
📋 Wiki 健康检查 | 2026-05-30
study-wiki: 3 个问题
1. [死链] concepts/rl-basics.md → bellman-equation 目标页不存在
2. [孤儿] concepts/old-troubleshooting.md 没有任何入链
3. [建议] 是否新建 comparison/dqn-vs-ppo.md 对比页?
work-wiki: 1 个问题
1. [索引] entities/new-chip.md 未登记到 index.md
相关页面
- 从零购买并配置云服务器 — 这台服务器从哪来的
- Hermes Agent 安装与踩坑实录 — Hermes Agent 是怎么装上的
- 博客与 Wiki 私有化迁移 — Wiki 和博客的迁移背景
- Hermes 记忆系统详解与 Provider 选型 — Agent 的记忆系统,与此流水线的"状态文件"同理