thermal: van-thermal daemon (CPU+NVMe monitor, alert, log)

Single sysfs sample loop drives three jobs off one daemon (modeled on
van-failover):

- live state at /run/van-thermal/state.json; new Cockpit "Temperatures"
  card reads it in the existing 5s loop (no per-refresh `sensors` shell-out)
- journal alerts on ok<->warn<->crit crossings, with clear_margin hysteresis
- throttled, self-rotating CSV history at /var/log/van-thermal.csv

Sensors resolved by hwmon name+label (coretemp/Package id 0, nvme/Composite),
never by hwmonN index (not stable across boots). Defaults: CPU warn 80 /
crit 95, NVMe warn 65 / crit 70. deploy.sh installs + enables the service.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Andreas Wrede
2026-06-28 21:32:13 -04:00
co-authored by Claude Opus 4.8
parent 35466f0141
commit ef6c1ce182
8 changed files with 289 additions and 3 deletions
+33 -1
View File
@@ -66,6 +66,37 @@ function renderAP(ap) {
document.getElementById("ap").innerHTML = html;
}
/* ---------- Temperatures (van-thermal daemon state) ---------- */
async function readThermal() {
try {
return JSON.parse(await run(["cat", "/run/van-thermal/state.json"]));
} catch (e) {
return null;
}
}
function renderThermal(th) {
const el = document.getElementById("thermal");
if (!th || !th.sensors) {
el.innerHTML = `<p class="muted">van-thermal daemon not running (no state file).</p>`;
return;
}
const pillClass = { ok: "ok", warn: "warn", crit: "bad" };
let html = `<table><thead><tr><th>Sensor</th><th>Temp</th><th>Status</th><th>Warn / Crit</th></tr></thead><tbody>`;
Object.keys(th.sensors).forEach(name => {
const s = th.sensors[name];
const temp = s.temp == null ? "—" : `${esc(s.temp)} °C`;
const lvl = s.level || "ok";
html += `<tr><td>${esc(name.toUpperCase())}</td><td>${temp}</td>` +
`<td><span class="pill ${pillClass[lvl] || ""}">${esc(lvl)}</span></td>` +
`<td class="muted">${esc(s.warn)} / ${esc(s.crit)} °C</td></tr>`;
});
html += `</tbody></table>`;
html += `<p class="muted">History: <code>/var/log/van-thermal.csv</code> · alerts: <code>journalctl -u van-thermal</code></p>`;
el.innerHTML = html;
}
/* ---------- WAN failover (van-failover daemon state) ---------- */
async function readFailover() {
@@ -194,8 +225,9 @@ async function restartAP() {
async function refresh() {
try {
const [ap, fo, wan] = await Promise.all([readAP(), readFailover(), readWAN()]);
const [ap, th, fo, wan] = await Promise.all([readAP(), readThermal(), readFailover(), readWAN()]);
renderAP(ap);
renderThermal(th);
renderFailover(fo);
renderWAN(wan);
document.getElementById("updated").textContent = "updated " + new Date().toLocaleTimeString();