Initial import: nasmon health monitor, split out of home_nas

Go service (nasmon/) plus nfpm packaging (packaging/), carried over from
the home_nas monorepo. Adds rpm packaging alongside the existing deb
build (Fedora/RHEL/Rocky dependency names via nfpm overrides, per-format
systemd unit path, dpkg+rpm-compatible maintainer scripts) and a Gitea
Actions workflow that builds both formats on every push.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-19 10:24:34 +03:00
co-authored by Claude Sonnet 5
commit 09159a5f31
61 changed files with 7882 additions and 0 deletions
+89
View File
@@ -0,0 +1,89 @@
package collect
import (
"os"
"path/filepath"
"sort"
"strconv"
"strings"
)
type Temp struct {
Chip string `json:"chip"`
Label string `json:"label"`
C float64 `json:"c"`
}
// ReadTemps lists every hwmon temperature input; falls back to thermal zones.
func ReadTemps() []Temp {
temps := readHwmon("/sys/class/hwmon")
if len(temps) == 0 {
temps = readThermalZones("/sys/class/thermal")
}
sort.Slice(temps, func(i, j int) bool {
if temps[i].Chip != temps[j].Chip {
return temps[i].Chip < temps[j].Chip
}
return temps[i].Label < temps[j].Label
})
return temps
}
func readHwmon(root string) []Temp {
var out []Temp
chips, _ := filepath.Glob(filepath.Join(root, "hwmon*"))
for _, chip := range chips {
name := readTrim(filepath.Join(chip, "name"))
inputs, _ := filepath.Glob(filepath.Join(chip, "temp*_input"))
for _, in := range inputs {
v, err := strconv.ParseFloat(readTrim(in), 64)
if err != nil {
continue
}
base := strings.TrimSuffix(in, "_input")
label := readTrim(base + "_label")
if label == "" {
label = filepath.Base(base)
}
out = append(out, Temp{Chip: name, Label: label, C: v / 1000})
}
}
return out
}
func readThermalZones(root string) []Temp {
var out []Temp
zones, _ := filepath.Glob(filepath.Join(root, "thermal_zone*"))
for _, z := range zones {
v, err := strconv.ParseFloat(readTrim(filepath.Join(z, "temp")), 64)
if err != nil {
continue
}
out = append(out, Temp{Chip: "thermal", Label: readTrim(filepath.Join(z, "type")), C: v / 1000})
}
return out
}
// CPUTemp picks the most plausible CPU package temperature from the list.
func CPUTemp(temps []Temp) (float64, bool) {
best, found := 0.0, false
for _, t := range temps {
chip := strings.ToLower(t.Chip)
lbl := strings.ToLower(t.Label)
isCPU := chip == "coretemp" || chip == "k10temp" || chip == "zenpower" ||
strings.Contains(lbl, "package") || strings.Contains(lbl, "tctl") || strings.Contains(lbl, "tdie") ||
strings.Contains(lbl, "x86_pkg_temp") || strings.Contains(lbl, "cpu")
if isCPU && (!found || t.C > best) {
best, found = t.C, true
}
}
return best, found
}
func readTrim(p string) string {
b, err := os.ReadFile(p)
if err != nil {
return ""
}
return strings.TrimSpace(string(b))
}