概念 study · 研习 #server, devops, tooling 2026-05-30

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 分钟执行一次。

为什么用它而不是持续监听: 有两个方案可选:

  1. 高频轮询(每 5 分钟):适合需要近乎实时的场景,但 token 消耗大
  2. 低频批处理(每天一次):与每日 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)而不是一个

这是常见的"生产者-消费者"模式:

两个角色完全解耦——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 做全面体检。

触发方式

两种触发:

检查项目(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

相关页面