andrewarrow/dialtone-watcher
GitHub: andrewarrow/dialtone-watcher
一个轻量级的macOS/Linux设备遥测代理,透明地采集进程、网络端点和硬件配置信息并生成可读摘要。
Stars: 0 | Forks: 0
# dialtone-watcher
`dialtone-watcher` 是一个用于 macOS 和 Linux 的小型 Go 代理,它用于监控机器的运行状态,并且不对此进行任何神秘化的伪装。
它会采样:
- 运行中的进程
- CPU 和内存使用情况
- 出站和入站网络端点
- 粗略的协议猜测,例如 `HTTPS`、`DNS`、`QUIC` 和 `Postgres`
- 机器的标准化硬件配置文件
它会在本地存储摘要,并且可以定期将紧凑的汇总数据 POST 到 Dialtone,以便演示中的数据变得真实:
- 演示:
- 开发者: Andrew Arrow
- 编程代理: Codex ("Dex"), GPT-5.4 medium
这个项目以一种有用的方式持有自己的观点:收集足够的信息使机器配置文件变得有趣,但不要收集太多,以免导致后端崩溃或让用户不得不怀疑应用是否在进行秘密的恶意行为。
## 为什么会有这个项目
大多数工具只解决了这个问题的一小部分:
- 活动监视器和 `htop` 显示本地进程。
- Wireshark 和 Little Snitch 显示网络活动。
- Geekbench 比较硬件。
- 企业级 EDR 工具收集机器遥测数据,但隐藏在公司防火墙之后。
`dialtone-watcher` 试图将这些想法组合成更具人情味的东西:
这是 Dialtone 演示的核心。
## 它现在能做什么
watcher 作为后台进程运行,并维护当前机器的紧凑内存模型。
它目前支持:
- 使用 `gopsutil` 收集硬件配置文件
- 进程轮询
- 按域名的网络流量摘要
- 按 `pid + protocol + domain` 对每个进程的连接进行分组
- 公网 IP 的反向查找,并在内存中缓存
- 持久化存储在磁盘上的本地机器身份
- 定期上传有限大小的 JSON 摘要到 `https://dialtoneapp.com/api/v1/watcher`
本地 CLI 是有意设计得非常朴素:
```
./dialtone-watcher start
./dialtone-watcher stop
./dialtone-watcher summary
./dialtone-watcher help
```
当你运行 `summary` 时,你会得到一份简洁的机器报告,而不是一个 900 行的进程转储。
## 摘要示例
这个仓库中的一个早期 prompt 捕捉了我们想要达到的形态:
```
Watcher running: true
Watcher pid: 96964
Polls completed: 3
Tracked processes: 976
Tracked domains: 13
Hardware: aas-MacBook-Pro.local on darwin (Apple M4 Max, 36.0 GB RAM, 14 logical cores)
Interesting processes:
1. pid=83479 name=com.apple.Virtualization.VirtualMachine cpu=5.67% rss=2705.3 MB seen=3 polls
2. pid=70214 name=plugin-container cpu=5.28% rss=1999.4 MB seen=3 polls
Interesting domains:
1. domain=17.57.144.121 rx=70.2 KiB tx=165.8 KiB seen=3 polls
```
那直接导致了两个改进:
- top-6 进程和域名摘要,而不是单个“有趣”的项目
- 带缓存的异步 IP 到域名解析,以便摘要输出保持快速
## 演示形态
当前的上传格式旨在支持 Dialtone 演示视图,特别是资产表和单机配置文件。
来自 `~/dev/dialtoneapp/src/pages/Demo/index.jsx` 的一些演示数据,转换为 markdown:
### 资产概览
| Metric | Value | Detail |
| --- | ---: | --- |
| Active machines | 20,000 | 30-day rolling sample |
| Connections classified | 1.84B | process + domain + protocol |
| Process snapshots | 412M | CPU, memory, runtime state |
| Traffic attributed | 96.2% | mapped back to an app or service |
### Top Destinations
| Domain | Machines | Fleet Share | Traffic | Trend |
| --- | ---: | ---: | ---: | ---: |
| googlevideo.com | 14,882 | 74.4% | 318.2 TB | +12% |
| cloudflare.com | 13,906 | 69.5% | 281.7 TB | +9% |
| apple.com | 11,104 | 55.5% | 92.4 TB | +4% |
| github.com | 7,262 | 36.3% | 61.8 TB | +18% |
| openai.com | 5,418 | 27.1% | 33.7 TB | +31% |
### Top Processes
| Process | Machines | Avg CPU | Avg Memory | Persistence |
| --- | ---: | ---: | ---: | --- |
| Google Chrome Helper | 9,844 | 11.8% | 1.3 GB | high |
| Slack Helper | 7,408 | 2.1% | 428 MB | very high |
| Docker Desktop | 4,222 | 6.4% | 2.7 GB | high |
| Cursor | 2,861 | 8.7% | 1.0 GB | medium |
| Telegram | 2,513 | 0.7% | 392 MB | very high |
watcher 的上传负载故意比那些表格暗示的要小。每台机器发送一个有限大小的摘要窗口,后端负责计算资产数据。
## 代理如何上传数据
上传的是一段时间内的紧凑汇总,而不是对所有轮询数据的无限流式传输。
负载包括:
- `schema_version`
- `sent_at`
- `period.started_at`
- `period.ended_at`
- `period.duration_seconds`
- `period.polls`
- 机器元数据
- 标准化的硬件元数据
- 窗口的摘要计数和总字节数
- 窗口内的 top 进程
- 窗口内的 top 域名
- 窗口内的 top 连接
请求包括:
- HTTP header: `machine_id`
- endpoint: `https://dialtoneapp.com/api/v1/watcher`
示例形态:
```
{
"schema_version": 1,
"sent_at": "2026-03-15T17:05:00Z",
"period": {
"started_at": "2026-03-15T17:00:00Z",
"ended_at": "2026-03-15T17:05:00Z",
"duration_seconds": 300,
"polls": 60
},
"machine": {
"hostname": "aas-MacBook-Pro.local",
"os": "darwin",
"platform": "macOS",
"platform_version": "15.x",
"kernel_version": "24.x",
"hardware": {
"cpu": {
"model": "Apple M4 Max",
"model_normalized": "apple_m4_max",
"physical_cores": 14,
"logical_cores": 14,
"frequency_mhz": 0
},
"memory": { "total_gb": 36.0 },
"disk": { "total_gb": 0 }
}
},
"summary": {
"running": true,
"poll_count": 60,
"tracked_process_count": 980,
"tracked_domain_count": 24,
"tracked_connection_count": 24,
"total_rx_bytes": 11264000,
"total_tx_bytes": 786432
},
"processes": [
{
"pid": 61421,
"name": "firefox",
"command": "/Applications/Firefox.app/Contents/MacOS/firefox",
"average_cpu_percent": 2.57,
"peak_cpu_percent": 7.11,
"average_memory_rss_mb": 843.0,
"peak_memory_rss_mb": 912.4,
"polls_seen": 60
},
{
"pid": 83479,
"name": "com.apple.Virtualization.VirtualMachine",
"command": "/System/Library/Frameworks/Virtualization.framework/...",
"average_cpu_percent": 5.93,
"peak_cpu_percent": 11.26,
"average_memory_rss_mb": 6900.0,
"peak_memory_rss_mb": 7024.2,
"polls_seen": 60
}
],
"domains": [
{
"domain": "104.16.132.229",
"display_name": "cloudflare.com",
"rx_bytes": 11239424,
"tx_bytes": 51200,
"polls_seen": 18
},
{
"domain": "microsoft.com",
"rx_bytes": 997171,
"tx_bytes": 146944,
"polls_seen": 16
}
],
"connections": [
{
"pid": 61421,
"process_name": "firefox",
"domain": "104.16.132.229",
"display_name": "cloudflare.com",
"protocol": "HTTPS",
"rx_bytes": 11239424,
"tx_bytes": 51200,
"polls_seen": 18
},
{
"pid": 70214,
"process_name": "com.docker.backend",
"domain": "microsoft.com",
"protocol": "HTTPS",
"rx_bytes": 997171,
"tx_bytes": 146944,
"polls_seen": 16
}
]
}
```
在实际实现中,这些数组是有界的,以保持上传量可预测:
- `processes`: top 12
- `domains`: top 20
- `connections`: top 20
间隔是可配置的:
- 生产环境默认值: `15m`
- 测试模式默认值: `15s`
环境变量:
```
DIALTONE_WATCHER_UPLOAD_URL
DIALTONE_WATCHER_UPLOAD_INTERVAL
DIALTONE_WATCHER_UPLOAD_TIMEOUT
DIALTONE_WATCHER_DISABLE_UPLOAD
DIALTONE_WATCHER_HOME
```
## 隐私,像成年人一样解释
如果你对隐私敏感,这一节是重点。
该项目是有意可被检查的:
- 本地摘要以 JSON 格式写入 watcher 主目录
- 机器 ID 存储在磁盘上的 `machine-id.json` 中
- 上传负载在 `internal/watcher/upload.go` 中组装
- 磁盘上的摘要模型在 `internal/watcher/state.go` 中
- 进程和网络收集代码位于 `internal/watcher/*.go`
本地收集的内容:
- 主机名
- OS、平台、内核版本
- CPU 型号和核心数
- 总内存和磁盘估算
- 进程名
- 进程命令行
- 进程 CPU 百分比和 RSS 内存
- 每个域名的 RX/TX 字节总数
- 按 PID、协议和域名分组的每个连接组
- 公网 IP 的缓存反向 DNS 结果
当前实现中远程发送的内容:
- 机器元数据和标准化的硬件配置文件
- 上传周期的聚合计数
- 周期内的 top 进程摘要
- 周期内的 top 域名摘要
- 周期内的 top 连接摘要
当前未实现的内容:
- 数据包负载捕获
- TLS 握手解析
- 完整的浏览历史重建
- 击键记录、屏幕截图或内容捕获
你仍然应该将进程名、域名和命令行视为敏感遥测数据。重点不在于“相信我们,可能没问题”。重点在于你可以阅读代码并确切知道发生了什么。
## 它是如何构建的
这个仓库异常诚实,因为 prompt 轨迹仍然保留在 [`prompts/`](https://github.com/andrewarrow/dialtone-watcher/tree/main/prompts) 中。
大致顺序:
1. 从一个简单的 CLI 开始,可以 `start`、`stop` 和 `summary`。
2. 轮询进程并保留内存中的硬件配置文件。
3. 从一个“有趣的进程”扩展到 top 列表。
4. 添加网络观察和域名摘要。
5. 将 IP 解析回主机名并缓存它们。
6. 添加机器身份持久化。
7. 定义一个可扩展到 20,000 台机器的紧凑上传 schema。
这段历史中有几个精彩时刻。
从最初的 prompt 开始:
合理。极力反对过度设计。良好的直觉。
从摘要细化 prompt 开始:
这是正确的产品约束。原始转储在人类必须阅读之前感觉很高效。
从反向查找 prompt 开始:
也是正确的。最好的 CLI 输出是不会为了试图聪明而停滞的输出。
从身份讨论开始:
我最初脑海中有更复杂自定义身份故事的轮廓。Andrew 将项目推回了更简单的原语。最好的例子是 UUID 生成:这个仓库没有编写不必要的自定义逻辑,而是使用了 Google 的 UUID 库。那是正确类型的修正。如果标准库或稳定的依赖项解决了它,就接受胜利并继续前进。
## 人机协作笔记
Andrew Arrow 在这里设定了产品标准:
- 保持 CLI 简单
- 使摘要可读
- 收集足够的信号使其有用
- 提前考虑资产比较,而不将代理变成间谍软件
我在该合作中的工作主要是将这些约束转化为工作代码:
- 构建 watcher 服务
- 添加特定于平台的网络收集器
- 保持状态持久化整洁
- 设计上传负载,以便后端可以高效地聚合它
- 围绕负载形态和发布行为添加测试
这是一种富有成效的模式:
- 人类决定什么重要
- 机器编写管道
- 人类在废话变成架构之前将其删除
坦率地说,这就是更多软件应该被构建的方式。
## 运行它
构建:
```
go build .
```
启动 watcher:
```
./dialtone-watcher start
```
检查当前本地摘要:
```
./dialtone-watcher summary
```
停止它:
```
./dialtone-watcher stop
```
Linux 说明和基于 Docker 的 Linux 测试步骤在 [`linux.md`](https://github.com/andrewarrow/dialtone-watcher/blob/main/linux.md) 中。
## 接下来是什么
下一个有用的层已经在 [`next_steps.md`](https://github.com/andrewarrow/dialtone-watcher/blob/main/next_steps.md) 中勾勒出来了:
- 连接生命周期跟踪
- 进程级网络总量
- 更丰富的协议和 TLS 元数据
- 遥测启发式
- 更清晰的导出/比较边界
如果这发展成一个严肃的比较产品,困难的部分将不是收集更多数据。困难的部分将是选择不收集什么。
标签:CPU内存监控, DIY Quantified Self, gopsutil, Go语言, Homebrew安装, Mr. Robot, Strava风格, 后台服务, 后渗透, 威胁情报, 应用使用追踪, 开发者工具, 性能分析, 数字化效能, 文档结构分析, 无线安全, 日志审计, 流量嗅探, 监控代理, 程序破解, 端点检测, 系统画像, 系统遥测, 网络安全审计, 网络活动监控, 网络设备安全