fix(agent): find macmon if /opt/homebrew/bin is not in path (#1746)

This commit is contained in:
henrygd
2026-03-27 13:35:04 -04:00
parent c7261b56f1
commit b53fdbe0ef
5 changed files with 48 additions and 28 deletions

View File

@@ -461,7 +461,7 @@ func (gm *GPUManager) discoverGpuCapabilities() gpuCapabilities {
caps.hasNvtop = true
}
if runtime.GOOS == "darwin" {
if _, err := exec.LookPath(macmonCmd); err == nil {
if _, err := utils.LookPathHomebrew(macmonCmd); err == nil {
caps.hasMacmon = true
}
if _, err := exec.LookPath(powermetricsCmd); err == nil {

View File

@@ -13,6 +13,7 @@ import (
"strings"
"time"
"github.com/henrygd/beszel/agent/utils"
"github.com/henrygd/beszel/internal/entities/system"
)
@@ -171,7 +172,11 @@ type macmonSample struct {
}
func (gm *GPUManager) collectMacmonPipe() (err error) {
cmd := exec.Command(macmonCmd, "pipe", "-i", strconv.Itoa(macmonIntervalMs))
macmonPath, err := utils.LookPathHomebrew(macmonCmd)
if err != nil {
return err
}
cmd := exec.Command(macmonPath, "pipe", "-i", strconv.Itoa(macmonIntervalMs))
// Avoid blocking if macmon writes to stderr.
cmd.Stderr = io.Discard
stdout, err := cmd.StdoutPipe()

View File

@@ -1104,32 +1104,21 @@ func (sm *SmartManager) parseSmartForNvme(output []byte) (bool, int) {
// detectSmartctl checks if smartctl is installed, returns an error if not
func (sm *SmartManager) detectSmartctl() (string, error) {
isWindows := runtime.GOOS == "windows"
// Load embedded smartctl.exe for Windows amd64 builds.
if isWindows && runtime.GOARCH == "amd64" {
if path, err := ensureEmbeddedSmartctl(); err == nil {
return path, nil
if runtime.GOOS == "windows" {
// Load embedded smartctl.exe for Windows amd64 builds.
if runtime.GOARCH == "amd64" {
if path, err := ensureEmbeddedSmartctl(); err == nil {
return path, nil
}
}
}
if path, err := exec.LookPath("smartctl"); err == nil {
return path, nil
}
locations := []string{}
if isWindows {
locations = append(locations,
"C:\\Program Files\\smartmontools\\bin\\smartctl.exe",
)
} else {
locations = append(locations, "/opt/homebrew/bin/smartctl")
}
for _, location := range locations {
// Try to find smartctl in the default installation location
const location = "C:\\Program Files\\smartmontools\\bin\\smartctl.exe"
if _, err := os.Stat(location); err == nil {
return location, nil
}
}
return "", errors.New("smartctl not found")
return utils.LookPathHomebrew("smartctl")
}
// isNvmeControllerPath checks if the path matches an NVMe controller pattern

View File

@@ -4,6 +4,9 @@ import (
"io"
"math"
"os"
"os/exec"
"path/filepath"
"runtime"
"strconv"
"strings"
)
@@ -86,3 +89,24 @@ func ReadUintFile(path string) (uint64, bool) {
}
return parsed, true
}
// LookPathHomebrew is like exec.LookPath but also checks Homebrew paths.
func LookPathHomebrew(file string) (string, error) {
foundPath, lookPathErr := exec.LookPath(file)
if lookPathErr == nil {
return foundPath, nil
}
var homebrewPath string
switch runtime.GOOS {
case "darwin":
homebrewPath = filepath.Join("/opt", "homebrew", "bin", file)
case "linux":
homebrewPath = filepath.Join("/home", "linuxbrew", ".linuxbrew", "bin", file)
}
if homebrewPath != "" {
if _, err := os.Stat(homebrewPath); err == nil {
return homebrewPath, nil
}
}
return "", lookPathErr
}

View File

@@ -620,11 +620,13 @@ const SmartDevicesTable = memo(function SmartDevicesTable({
return <SmartDeviceTableRow key={row.id} row={row} virtualRow={virtualRow} openSheet={openSheet} />
})
) : (
<TableRow>
<TableCell colSpan={colLength} className="h-24 text-center pointer-events-none">
{data ? t`No results.` : <LoaderCircleIcon className="animate-spin size-10 opacity-60 mx-auto" />}
</TableCell>
</TableRow>
<TableCell colSpan={colLength} className="h-37 text-center pointer-events-none">
{data ? (
<Trans>No results.</Trans>
) : (
<LoaderCircleIcon className="animate-spin size-10 opacity-60 mx-auto" />
)}
</TableCell>
)}
</TableBody>
</table>