@blurname/debrief
v0.1.0
Published
Debrief your Debian box: snapshot every installed package (plus loose non-dpkg binaries) and track what drifts
Maintainers
Readme
debrief
Debrief 你的 Debian 机器——问它到底装了什么,并追踪它的变化。
(名字取自 deb + debrief「事后汇报」:把整台机器的安装情况汇报上来。)
主线用法:收集当前机器上装了什么,变成一份清单,然后追踪它的变化。 软件你(或某个
agent)照常用 apt 直接装,debrief 不负责装、负责"看和管"——collect 把系统快照成
debrief.json + debrief.lock,status 告诉你系统相对快照又多了/少了/升级了什么。
概念映射
| npm | debrief |
| --- | --- |
| package.json(直接依赖) | debrief.json — 你主动装的包(apt-mark showmanual) |
| package-lock.json(完整依赖树) | debrief.lock — 全部已装包 + 确切版本(含依赖) |
| node_modules | 系统 dpkg 状态(通过后端读写) |
| npm registry | apt sources / Debian 镜像 |
| pip freeze / dpkg --get-selections | debrief collect — 从系统反向生成清单 |
| git status | debrief status — 系统当前状态 vs 快照 |
| npm install | debrief install — 把清单里缺的装到另一台机器 |
底层就是 Debian 原生的 dpkg-query(装了什么)+ apt-mark showmanual(哪些是你主动装的、
而非被依赖带进来的)。后者恰好等价于"直接依赖 vs 传递依赖":主动装的进 manifest,依赖只进 lock。
也能抓"非 dpkg"装的裸二进制
有些软件根本不走 apt——curl … | sh、手动下载、brew/cargo 之类,直接把一个可执行文件丢到
PATH 上(比如 herdr 就是 curl -fsSL https://herdr.dev/install.sh | sh)。
collect 会顺带扫 /usr/local/bin、~/.local/bin 等目录,对每个可执行文件用 dpkg -S 反查:
dpkg 不认领的,就是这类"裸二进制",记进 lock 的 binaries(含内容 sha256,所以自更新会被
status 认出来;加 --probe-versions 还能跑 --version 取版本)。
$ debrief status
non-dpkg binaries (curl|sh / manual / brew / ...):
+ herdr /usr/local/bin/herdr
~ bun /usr/local/bin/bun (contents changed)--no-binaries 关掉这个扫描;--scan-dirs a:b:c 指定要扫的目录。
主线:收集 & 追踪
debrief collect # 把当前系统快照进 debrief.json(主动装的)+ debrief.lock(全部)
debrief collect --pin # manifest 里记确切版本(而非默认的 "*")
debrief status # 对比:系统相对上次 collect 多了/少了/升级了什么
debrief list # 清单里的包及其状态、可升级情况
debrief outdated # 有新版本可升级的包典型场景:把 debrief.json + debrief.lock 提交进你的 dotfiles / 配置仓库。之后 agent 直接
apt install 了什么新东西,debrief status 一眼就能看出来;确认无误就 debrief collect 重新快照。
$ debrief status
+ ripgrep 14.1.0-1
+ libpcre2-16-0 10.42-1 (dep)
~ nginx 1.24.0-2 → 1.26.0-1
- jq 1.6-2.1
+2 added ~1 changed -1 removed (vs snapshot; run `debrief collect` to update it)设计取向:友好封装(friendly wrapper)
这是刻意选择的最保守语义:
install只增不改:只安装 manifest 里尚未安装的包。已经装了的,即使版本不满足约束 (drift),也只警告、绝不改动。不做整机对齐(no reconcile),不会去卸载"多余"的包。add/remove是显式动作:add会安装并写入 manifest;remove会卸载并从 manifest 移除。- lockfile 给你可复现性:
install时若锁定的版本仍然可用且满足约束,就装锁定的那个 (输出里标↺),而不是最新的——这样在另一台机器上install得到同样的结果。
后端(可插拔)
所有逻辑都只依赖一个 Backend 接口,因此同一套代码能在有没有 apt 的机器上跑:
apt— 真实 Debian,底层调用dpkg-query/apt-cache/apt-get(改动系统的操作会在 非 root 时自动加sudo)。mock— 内存/文件模拟的假仓库,自带一批仿真包版本。在没有 dpkg 的机器(比如 NixOS)上 默认启用,用于开发和测试。
选择顺序:--backend > $DEBRIEF_BACKEND > lockfile 里记录的后端 > 自动探测(有 apt-get 就用
apt,否则 mock)。
反向:把清单装到另一台机器(可选)
collect 出来的清单也能反过来用——在一台新机器上把清单里缺的装齐。这条线是刻意保守的 "友好封装":
install只增不改:只安装清单里尚未安装的包。已装的即使版本不满足约束(drift), 也只警告、绝不改动;绝不卸载"多余"的包。add/remove是显式动作:add会安装并写入 manifest;remove会卸载并移除。- lockfile 给可复现性:
install时若锁定版本仍可用且满足约束,就装锁定的那个(标↺)。
debrief install # 把 manifest 里缺的装上(优先按 lock 复现)
debrief add nginx@^1.24 git # 显式安装并记录
debrief add htop=3.2.1-1 # 钉死一个确切版本
debrief remove git # 卸载 + 从 manifest 移除
debrief remove git --save-only # 只从 manifest 移除,保留已安装
debrief init # 创建一个空的 debrief.json
debrief --dry-run <cmd> # 预演,不改动任何东西安装用 bun:bun link 后可直接用 debrief;或 bun run bin/debrief.ts <command> 直接跑。
版本约束(npm 风格,作用在真实 Debian 版本号上)
约束语法借用 npm,但比较用的是 dpkg 自己的版本算法(epoch、~ 预发布排序等都正确处理):
| 写法 | 含义 |
| --- | --- |
| nginx | 最新候选版;manifest 里写成 ^<装上的版本> |
| nginx@^1.24 | >=1.24 且 <2(caret:允许非 major 升级) |
| nginx@~1.24 | >=1.24 且 <1.25(tilde:钉住 minor) |
| nginx@'>=1.22 <1.26' | 空格分隔 = AND 组合区间 |
| nginx=1.24.0-1 | 精确的 Debian 版本 |
开发
bun install
bun test # 单元 + 集成测试(全程用 mock 后端,任何机器都能跑)
bun run typecheck分发
代码只用 node: 内置模块(通过 src/sys.ts 这层薄封装),所以同一份代码在 Bun 和 Node
(≥18)上都能跑。默认分发方式:打成一个几十 KB 的 JS,要求目标机有 bun 或 node——比把整个
运行时塞进二进制(~100MB)划算得多。
bun run build # → dist/debrief.js(~40KB,单文件,含 node shebang)
node dist/debrief.js collect # 用 node 跑
bun dist/debrief.js collect # 用 bun 跑- npm:
npm i -g debrief(或bunx debrief)——bin指向dist/debrief.js,prepack会自动构建。npm pack出来的包只有 16KB / 3 个文件。 curl | sh:curl -fsSL <host>/install.sh | sh—— 拉下dist/debrief.js和一个 exec node/bun 的启动器丢到~/.local/bin(见scripts/install.sh)。注意:这样装的debrief自己就是个"裸二进制",会被它自己的collect扫描逮到。
已在真实 Debian 上验证:
- 两种运行时结果一致 ——
bun run test:apt(Bun)、bun run test:node(纯 Node)。 npm pack→ 在干净的 node 机器上npm i -g→debrief collect/add hello对着真 apt 跑通。install.sh拉取 + 启动器 + 运行,链路验证过。
真正
npm publish/ 建 Release / 建 apt 仓库还没做(需要账号和授权)——上面验证的是"发出去就能用"。
想要"零运行时依赖"?(体积换取)
如果目标机连 node/bun 都不想装,可以退回自包含二进制 / .deb(每个 ~100MB,因为把运行时也打进去了):
bun run build:bin # → dist/debrief-linux-{x64,arm64}(自包含,无需运行时)
bun run build:deb # → dist/debrief_<ver>_amd64.deb(需 dpkg-deb;NixOS 上在容器里打)
sudo apt install ./dist/debrief_0.1.0_amd64.deb.deb 有个额外的好处:装完 debrief 就是个正经 dpkg 包,会出现在它自己的 collect 里(吃自己的狗粮)。
已知边界
- 管理的是系统级的包(不是项目本地隔离);这是 apt 的本性。
apt后端已在 Debian 13 (trixie) 容器里对着真实镜像端到端验证过(见下);开发主机 (NixOS)日常用 mock 后端顶替。- 依赖解析交给 apt 自己:manifest 只记你主动装的包,依赖只进 lock——正如 npm 只把直接依赖
写进
package.json。
在真实 Debian 上验证
单元测试全程用 mock 后端,任何机器都能跑。apt 后端要在真 Debian 上验证,用容器即可:
docker run --rm -v "$PWD":/app:ro oven/bun:debian bash /app/test/apt-e2e.sh
# 受限网络下加 --dns 1.1.1.1已验证:collect/status 正确解析 dpkg-query + apt-mark showmanual(手动装的包进
manifest、依赖标 auto:true 只进 lock、带 arch);status 能发现绕过工具、直接用 apt 装/删
的包;add/remove 走真实 apt-get install/remove。
MIT
