# cfgdrift **Repository Path**: tomlen/cfgdrift ## Basic Information - **Project Name**: cfgdrift - **Description**: Config drift sentinel: sha256 baseline, real line-level diff evidence, accept with audit trail. Zero-dep single file, selftest 14/14. - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-26 - **Last Updated**: 2026-09-27 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # cfgdrift [中文](README.md) | [English](README_EN.md) **配置漂移哨兵** —— 半夜有人改了服务器上的配置,没人知道?等下次故障排查时,答案永远是「最近谁动过配置?」,而日志里查无此人。 cfgdrift 给配置文件建「指纹基线」:谁动了一目了然——改了哪一行(行级 diff)、什么时候被谁接受入基线(事件留痕),全部可复核。 零依赖单文件 CLI,Python 3.8+ 标准库,拷走就能跑。macOS / Linux / Windows 通吃。 ## 解决什么问题 * 值班半夜被叫起来,发现是下午某同事「顺手调了个参数」→ **基线一比对,几点改的、改了哪行,现场还原** * 「我只改了 nginx.conf 一个地方」→ **check 把 CHANGED / ADDED / REMOVED 全列出来,行级 diff 摆在眼前** * 改完没人记录,下次审计说不清 → **accept 强制带 `--note` 和 `--by`,事件追加进 JSONL 日志,永不改写** * 基线本身怕泄密?→ **基线只存 sha256 指纹,绝不存任何一行文件内容(I1),拿去归档/提交都不泄密** * `touch` 一下文件 mtime 变了算不算漂移?→ **不算。sha256 没变就是 UNCHANGED,不制造狼来了** ## 安装 ```bash # 方式一:直接拉单文件(仅标准库,Python 3.8+) curl -fsSLO https://raw.githubusercontent.com/tomlen045/cfgdrift/main/cfgdrift.py # 方式二:git clone 整仓(含 README / selftest 截图) git clone https://github.com/tomlen045/cfgdrift.git ``` ## 30 秒上手 ```bash # 1) 给当前目录的配置建基线(*.conf *.yaml *.yml *.ini *.toml *.env* *.properties,递归深度 ≤6) python3 cfgdrift.py snapshot # 2) 随时体检:谁被动了?exit 0=无漂移 1=有漂移 python3 cfgdrift.py check python3 cfgdrift.py check --json # 机器可读 python3 cfgdrift.py check --quiet # 只要退出码,接 cron # 3) 确认这次改动是合法的 → 带审计信息提升为新基线 python3 cfgdrift.py accept --note "扩容 worker_connections 到 4096" --by ops-len # 4) 查历史 / 看基线 python3 cfgdrift.py log --last 5 python3 cfgdrift.py show ``` crontab 接法(每 30 分钟静默巡检,有漂移才吵你): ```cron */30 * * * * /usr/bin/python3 /path/to/cfgdrift.py --root /srv/app check --quiet ``` ## 子命令 | 子命令 | 作用 | 关键参数 | |---|---|---| | `snapshot` | 建指纹基线(默认 `/.cfgdrift-baseline.json`) | `--baseline` `--paths a,b` `--max-depth N` | | `check` | 当前状态 vs 基线,逐文件判定,CHANGED 带行级 diff | `--json` `--quiet` `--baseline` | | `accept` | 当前状态提升为新基线(必须 `--note` + `--by`,缺一 exit 2) | `--note` `--by` `--baseline` | | `log` | tail 事件日志(append-only JSONL) | `--last N`(默认 10) | | `show` | 基线摘要(文件数 / 建基线时间 / 根目录) | `--baseline` | | `selftest` | 沙箱自证:14 项端到端断言 | — | 跟踪范围:`--paths` 显式逗号清单,或默认模式 `*.conf *.yaml *.yml *.ini *.toml *.env* *.properties`(递归,跳过 `.git` / `node_modules` / `venv` / `.cfgdrift*`,默认最大深度 6)。显式清单里有文件不存在时 `snapshot` 直接报错 exit 2——基线不留糊涂账。 ## 判定与证据 | 判定 | 含义 | 证据 | |---|---|---| | UNCHANGED | sha256 一致 | — | | CHANGED | 文件内容变了 | hunk 结构 + 行号 + 新行全文;旧行以「行号+行散列」占位(见 I1/I5 边界说明) | | ADDED | 基线里没有的新配置文件 | sha256 / 行数 / 字节数 | | REMOVED | 基线里有、现在没了 | 原 sha256 / 行数 | diff 示例(真实 selftest 输出): ```text CHANGED etc/nginx/nginx.conf sha256 5f3a… → 9c21… · lines 20→20 · bytes 512→510 @@ -5,5 +5,5 @@ 6 events { - 7 [old line not stored (I1) · line-sha 9869d4786af2] + 7 worker_connections 512; 8 } ``` ## 退出码 | 退出码 | 含义 | |---|---| | 0 | 无漂移 / 命令正常完成 | | 1 | 有漂移(CHANGED / ADDED / REMOVED 至少其一) | | 2 | 参数/环境错误(缺基线、缺 `--note`/`--by`、乱敲子命令、路径不存在) | ## 六条不变量 | 不变量 | 内容 | 自测 | |---|---|---| | I1 | 基线零内容:只存指纹(sha256/每行散列/行数/字节数/mtime),绝不存文件内容 | ✅ grep 标记词验证 | | I2 | 事件只追加:`.cfgdrift-events.jsonl` 只 append 永不改写,审计链不可篡 | ✅ | | I3 | check 不写盘:对比是纯只读操作,绝不顺手改基线(字节数验证) | ✅ | | I4 | 退出码确定:0/1/2 三值,cron / CI 可以放心接 | ✅ | | I5 | 漂移必带证据:CHANGED 给真实 hunk/行号/新文本,绝不只报「hash 不同」 | ✅ | | I6 | 幂等:连续两次 check 输出逐字节一致,可安全重试 | ✅ | **I1 与 I5 的边界(诚实说明)**:基线不存旧文本,所以 CHANGED 的 diff 里被删/被改的**旧行**以「行号 + 行散列占位符」显示;hunk 结构、行号、`+` 侧新行全文全部来自 difflib 对真实文件的比对,绝非伪造。想要传统全文 diff,把旧版本交给 git;cfgdrift 的定位是不泄密的哨兵,不是版本库。 ## 事件日志格式 `.cfgdrift-events.jsonl`(JSONL,append-only): ```json {"ts": "2026-09-26T21:04:11+08:00", "event": "accept", "actor": "ops-len", "note": "扩容 worker_connections 到 4096", "files_changed": 1, "added": 0, "removed": 0} ``` ## 运维套件第五块拼图 这套自研运维工具链各管一段,cfgdrift 补上「配置」这一环:**cronguard** 先保证任务本身活着(定时任务体检:三源盘点 + 十项检查);任务跑过会留日志,**logwhisperer** 负责日志考古(时间线 / 尖峰检测 / trace 关联);日志里挖出问题后 **debugkit** 负责立案修复(问题登记 → 修复 → 验证留档);修完的发布物交给 **linkpulse** 巡检死活(阅读原文 / CDN / 安装命令的承诺盯起来);而发布物之所以行为突变,最常见的原因是**配置被人动了**——这正是 **cfgdrift** 盯的:基线指纹 → 漂移告警 → accept 留痕,把「谁在半夜改了配置」从悬案变成一行事件。任务、日志、缺陷、发布物、配置,五个哨兵拼成完整闭环。 ## 已知限制(诚实披露) * **不做实时 inotify**:没有文件系统事件监听,漂移在下次 `check` 时才被发现——接 cron 即可,轮询是特性不是缺陷 * **基线不做内容加密**:I1 靠「不存内容」保隐私,不是加密;拿到基线的人能算出各文件散列,可对拿到的可疑文件做指纹比对(且散列不可逆,低熵字段无彩虹表可爆) * **mtime 变化不算漂移**:只认 sha256;`touch` / 复制 / 同步工具刷时间戳不会误报 * **旧行文本不进基线**:I1 的代价,见上文边界说明;`-` 侧旧行无法还原原文 * **默认不跟踪符号链接指向的目标**、不跟 `.gitignore`(那是 git 的事);跳过 `.git` / `node_modules` / `venv` / `.cfgdrift*` * **单机视角**:没有 agent/集中端,多机需各自建基线、各自跑 cron(基线文件可集中归档) * 大仓库(十万+文件)未专门优化,默认深度 6 + 按后缀过滤已覆盖绝大多数配置场景 ## English **cfgdrift** — configuration drift sentinel. Someone changed a config file at 2am and nobody noticed? cfgdrift builds a fingerprint baseline over your config files: any touch is exposed — which line changed (real unified-style diff), and when it was accepted into the baseline (append-only audit events). Zero-dependency single-file CLI (Python 3.8+ stdlib). Works on macOS / Linux / Windows. ```bash python3 cfgdrift.py snapshot # fingerprint baseline (content never stored) python3 cfgdrift.py check # UNCHANGED/ADDED/REMOVED/CHANGED + line diff python3 cfgdrift.py check --json # machine-readable python3 cfgdrift.py accept --note "scaled workers" --by ops # promote, audited (both flags required) python3 cfgdrift.py log --last 5 # tail append-only event log python3 cfgdrift.py selftest # 14 end-to-end assertions ``` Verdicts: UNCHANGED / CHANGED / ADDED / REMOVED. Exit codes 0 (no drift) / 1 (drift) / 2 (usage or environment error). Baselines store fingerprints only — sha256, per-line hashes, line/byte counts, mtime — never a single line of file content (I1). Six invariants (I1–I6) are each covered by selftest. ## License MIT © tomlen045