Bundle
dsh-thermal-monitor
DSH Web 左侧栏硬件温度看板:CPU / 内存 / GPU / 固态实时温度,含免提权降级模式
- Source
- whiskey1993
- License
- MIT
- Updated
- Updated 5 days ago
Readme
# dsh-thermal-monitor
把本机硬件温度放进 DSH Web 左侧栏的看板插件。
左侧栏多出一个带**实时温度角标**的图标(角标颜色随温度分级:绿 <65°C / 橙 65–79°C / 红 ≥80°C),
点击后在中间列展开完整看板,按 CPU / GPU / 内存 / 固态 / 主板分组显示每个传感器。
## 采集方式
插件会拉起一个常驻的 PowerShell 采集进程(`assets/thermal-collector.ps1`),它每 2 秒采样一次,
把结果写到 `%TEMP%\dsh-thermal-monitor\readings.json`,Host 半边通过
`/api/thermal-monitor/state` 提供给浏览器半边。
进程启动时按顺序尝试两条路径:
| 模式 | 触发条件 | 能读到什么 | 权限 |
|---|---|---|---|
| **lhm** | `assets/LibreHardwareMonitor/` 存在且加载成功 | CPU 封装/核心、内存 DIMM、GPU、NVMe、主板/风扇 | CPU 与内存需管理员 |
| **fallback** | 库缺失或加载失败 | GPU(nvidia-smi)、主板/环境(ACPI 热区) | 无需提权 |
Host 半边先尝试**提权启动**(弹一次 UAC)。如果 20 秒内没有数据产生——典型情况是 UAC 被拒绝——
就自动改用**免提权降级模式**,所以看板在任何情况下都不会是空的。
看板底部的「重新提权」按钮可以重试,不必重启 DSH。
## 安装
```powershell
# 直接从 GitHub 安装(推荐)
dsh plugin --profile web add github:whiskey1993/dsh-thermal-monitor
# 或克隆后从本地目录安装
git clone https://github.com/whiskey1993/dsh-thermal-monitor
dsh plugin --profile web add <克隆下来的目录>
```
安装后**需要重启 dsh(web profile)**,插件才会挂载。
注意:重启后如果左侧栏没有出现图标,**再刷新一次浏览器页面**——插件的启动清单是页面加载时
一次性注入的,服务端重启不会让已打开的页面重新拉取它。
首次启动会弹一次 UAC(读取 CPU 核心与内存温度需要管理员权限);拒绝或不响应会自动降级。
## 卸载
```powershell
dsh plugin --profile web remove dsh-thermal-monitor
```
## 环境要求
- Windows 10 / 11
- Windows PowerShell 5.1(系统自带)
- 可选:[NVIDIA 驱动](https://www.nvidia.com/drivers)(降级模式下用 `nvidia-smi` 读 GPU 温度)
- 无需 Node 依赖:本包 `dependencies` 为空,客户端半边只引用平台基线里的 `react`
## 运行时文件
全部位于 `%TEMP%\dsh-thermal-monitor\`:
| 文件 | 作用 |
|---|---|
| `readings.json` | 最新一次采样结果(Host 提供的就是它) |
| `collector.pid` | 采集进程 PID,用于存活判断与幂等启动 |
| `stop.flag` | 停止信号;插件卸载时写入,采集进程据此优雅退出 |
| `collector.log` | 采集进程诊断日志 |
排查问题时也可以直接读 `/api/thermal-monitor/log`(返回日志尾部)。
## 实现要点
- **采集器脚本是纯 ASCII 且带 UTF-8 BOM。** Windows PowerShell 5.1 对无 BOM 的 `.ps1`
按系统 ANSI 代码页解码,中文字面量会静默乱码。所有面向用户的文案都放在浏览器半边,
采集器只输出稳定的 ASCII 分组 id(`cpu` / `gpu` / `memory` / `storage` / `board`)。
- **客户端半边是手写的 lazy-CJS bundle**,只 `require('react')`(属于平台基线模块),
因此 `package.json` 无需声明 `dsh.client.external`。
- **单一轮询源**:图标与看板共享一个模块级 store 和 `useSyncExternalStore` 订阅,
两个界面同时显示时不会翻倍请求;页面不可见时暂停轮询。
- **看板配色跟随主题**,使用 `--dsw-alias-*` token,自动适配明暗模式。
- **内置的 LibreHardwareMonitor 是精简过的**:上游发布包含整个 GUI 程序,对无界面的采集器
大多是死重。本仓库只保留经实测必需的文件,**43 文件 11.64MB → 17 文件 4.09MB**。
精简集合由 `test/trim-lhm.ps1` 的贪心移除推导得出,明细见
[`assets/LibreHardwareMonitor/README.md`](assets/LibreHardwareMonitor/README.md)。
## 关于 AIDA64
有用户提过一个思路:用 AIDA64 显示温度。**这个思路本身是成立的**——AIDA64 的
「设置 → 外部应用程序」可以把传感器值导出到注册表
(`HKCU\Software\FinalWire\AIDA64\SensorValues`)或共享内存 `AIDA64_SensorValues`,
两者都能在**不提权**的情况下读取,正好绕开本插件唯一的痛点(UAC)。
最终没有采用,原因有二:
1. **本机没装 AIDA64。** 只有 `HKCU\Software\FinalWire\AIDA64` 下上次安装遗留的界面配置
(窗口位置、列宽),没有 `SensorValues` 子键、没有卸载项、磁盘上也没有 `aida64.exe`。
2. **不需要它了。** 实测 LibreHardwareMonitor 在提权后已经能读全 CPU 封装/每核、
DIMM、GPU、NVMe、主板全部传感器,不需要额外的第三方软件,也不需要 PawnIO。
如果以后想彻底去掉 UAC 提示,AIDA64 导出是一条可行的替代路径:在采集器里把 AIDA64 注册表
读取放在 LHM 之前作为首选数据源即可,浏览器半边无需改动。
## 踩坑记录
这几个坑都是实测踩出来并写进代码注释的,改代码前值得先读。
### 1. `spawn(..., { detached: true })` 会让 Windows PowerShell 静默失效
最难查的一个。Windows 上 `detached: true` 用 `DETACHED_PROCESS` 标志创建进程,而
`powershell.exe` 是控制台程序——它拿到 PID,然后**什么都不做**:不弹 UAC、不执行脚本、
不写任何文件。更糟的是 `spawn` 仍报告成功,宿主半边因此会记录一条"已启动"的日志。
实测矩阵:
| spawn 选项 | 结果 |
|---|---|
| `detached:true` + 剥离 PATH | spawn 成功,**文件全没写** |
| `detached:true` + 完整 PATH | spawn 成功,**文件全没写** |
| `detached:false` | 正常工作 |
所以两条启动路径都显式用 `detached: false`。进程仍能比宿主活得久——Windows 不会因为父进程
退出就杀掉子进程,而 teardown 是通过停止信号文件收尾的。
### 2. 必须用 `powershell.exe` 的绝对路径
宿主进程不继承交互式 PATH(这正是本仓库 `cordis.patch.yml` 里给 MCP server 写绝对路径的同一个原因)。
`PATH` 为空或只有 `C:\Windows` 时裸 `powershell.exe` 会 **ENOENT**——它实际位于
`System32\WindowsPowerShell\v1.0\`,既不在 `System32` 也不在 `C:\Windows`。
而且 Node 把这种失败报成**异步 `error` 事件**而非同步抛错,必须监听它,否则会谎报启动成功。
### 3. `.ps1` 必须带 UTF-8 BOM,且最好零非 ASCII 字符
Windows PowerShell 5.1 对无 BOM 的 `.ps1` 按**系统 ANSI 代码页**解码,中文字面量会静默乱码
(实测 `主板 / 环境` 变成 `娑撶粯婢?`)。采集器现在两者都做:纯 ASCII 内容 + 强制 BOM,
所有面向用户的文案都放在浏览器半边。
### 4. 传感器名字里混着不是温度的"温度"
`SensorType.Temperature` 会连带返回这些**非读数**:
| 传感器名 | 实际含义 |
|---|---|
| `Thermal Sensor Critical High Limit` | SPD 上限值(85) |
| `Warning Temperature` | NVMe 告警阈值(82) |
| `Temperature Sensor Resolution` | 分辨率(0.25) |
| `P-Core #N Distance to TjMax` | 距温度墙的**余量**,方向相反 |
不过滤的话,空闲的固态会显示成红色 85°C 告警,CPU 峰值会被伪造成 75°C。
过滤规则刻意收窄:只匹配 `Limit|Resolution|Warning Temperature|Critical Temperature|Distance to TjMax`,
不能用宽泛的 `Max`,否则会误杀真正需要的 `Core Max`。
### 5. `[string]` 参数会把 `$null` 变成空字符串
PowerShell 里 `param([string]$X)` 传入 `$null` 会得到 `''`,于是 `$null -eq $X` 判断失败。
采集器中需要保持 null 语义的参数都不加类型约束,并在函数入口显式归一化。
### 6. 强制杀宿主会留下孤儿采集器
`restart-web.ps1` 用 `Stop-Process -Force` 结束 dsh,插件的 teardown 来不及执行,
采集器会活下来并继续占着 PID 文件——新实例因此不会启动,看板一直显示旧数据。
宿主现在会在启动时"收养"判断:还在正常出数就复用,活着但不再出数就替换掉。
## 测试
```powershell
node test/client-structure.mjs # 客户端 bundle:槽位注册、id/key 匹配、渲染、折叠
node test/host-smoke.mjs # Host 半边:apply()、路由、handler、teardown
node test/spawn-fix.mjs # 回归:剥离 PATH 下采集器能否真正启动
node test/spawn-isolate.mjs # 证据:detached 参数矩阵(见踩坑记录 1)
```
## 许可
本项目为 **MIT License**,见 [LICENSE](LICENSE),Copyright (c) 2026 whiskey1993。
`assets/LibreHardwareMonitor/` 下的
[LibreHardwareMonitor](https://github.com/LibreHardwareMonitor/LibreHardwareMonitor)
为独立第三方项目,同样以 MIT License 发布,本仓库原样分发其子集;
归属声明见 [NOTICE](NOTICE),版本、精简清单与更新方法见
[`assets/LibreHardwareMonitor/README.md`](assets/LibreHardwareMonitor/README.md)。
## 已知限制
- **仅 Windows。** 采集器依赖 Windows PowerShell 5.1、WMI/CIM 与 `nvidia-smi`;
该插件在 Linux/macOS 上不会工作。
- **CPU 与内存温度需要管理员权限。** 拒绝 UAC 会落到免提权降级模式,只能看到
GPU 与主板/环境温度。这是硬件访问的固有限制,不是可以绕过的实现问题。
- **不支持 AMD/Intel 核显温度**(在 NVIDIA 机器上开发和实测)。代码走的是
LibreHardwareMonitor 的 `GpuNvidia`/`GpuAmd`/`GpuIntel` 通用路径,理论上可用,
但未在对应硬件上验证过。
- **服务端重启后需要刷新浏览器页面。** 客户端启动清单是页面加载时注入的,
已打开的标签页不会重新拉取——这一点在[安装](#安装)一节也说明了。
Install
dsh plugin --profile web add github:whiskey1993/dsh-thermal-monitor#3deee6f9a65464e0d2aa9d8645b1b520360d3637
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-thermal-monitor from the hub