Files
op-packages/beszel-agent/patches/001-implement-openwrt-ap-wifi-collection.patch
T

144 lines
3.8 KiB
Diff

From 128555126155413ce32b134435c425ad9bb24501 Mon Sep 17 00:00:00 2001
From: Jackie Han <jackie.han@gmail.com>
Date: Wed, 14 Oct 2026 07:40:03 +0800
Subject: [PATCH] Refactor collect function to handle wireless devices
---
agent/wifi/wifi_linux.go | 111 ++++++++++++++++++++++++++++++++++++---
1 file changed, 105 insertions(+), 6 deletions(-)
diff --git a/agent/wifi/wifi_linux.go b/agent/wifi/wifi_linux.go
index 926d658fe..ea42b1f2b 100644
--- a/agent/wifi/wifi_linux.go
+++ b/agent/wifi/wifi_linux.go
@@ -5,8 +5,12 @@ package wifi
import (
"context"
"errors"
+ "math"
"net"
"os"
+ "os/exec"
+ "regexp"
+ "strconv"
"time"
"github.com/henrygd/beszel/internal/entities/system"
@@ -99,15 +103,110 @@ func hasWirelessDevice(dir string) bool {
}
func collect(ctx context.Context) map[string]system.WiFi {
- if !hasWirelessDevice(wiphyClassDir) {
- return nil
+ results := make(map[string]system.WiFi)
+
+ if hasWirelessDevice(wiphyClassDir) {
+ if client, err := newNL80211Client(); err == nil {
+ for k, v := range collectLinux(ctx, client) {
+ results[k] = v
+ }
+ client.Close()
+ }
}
- client, err := newNL80211Client()
- if err != nil {
+
+ if _, err := exec.LookPath("iwinfo"); err == nil {
+ for k, v := range collectOpenWrtAP(ctx) {
+ results[k] = v
+ }
+ }
+
+ if len(results) == 0 {
return nil
}
- defer client.Close()
- return collectLinux(ctx, client)
+ return results
+}
+
+var (
+ iwinfoIfaceRe = regexp.MustCompile(`(?m)^(\S+)[ \t]+ESSID:`)
+ iwinfoModeRe = regexp.MustCompile(`Mode:[ \t]*Master`)
+ iwinfoSSIDRe = regexp.MustCompile(`ESSID:\s*"([^"]+)"`)
+ iwinfoAssocRe = regexp.MustCompile(`(?m)^(?:[0-9a-fA-F]{2}[:-]){5}[0-9a-fA-F]{2}.*?(-?\d+)\s+dBm`)
+)
+
+type iwinfoBlock struct {
+ name string
+ text string
+}
+
+// splitIwinfoBlocks splits bare `iwinfo` output into one block per interface.
+func splitIwinfoBlocks(out string) []iwinfoBlock {
+ idx := iwinfoIfaceRe.FindAllStringSubmatchIndex(out, -1)
+ blocks := make([]iwinfoBlock, 0, len(idx))
+ for i, m := range idx {
+ end := len(out)
+ if i+1 < len(idx) {
+ end = idx[i+1][0]
+ }
+ blocks = append(blocks, iwinfoBlock{name: out[m[2]:m[3]], text: out[m[0]:end]})
+ }
+ return blocks
+}
+
+// parseIwinfoInfo reports whether a block is a Master (AP) interface and its SSID.
+func parseIwinfoInfo(block string) (ssid string, isAP bool) {
+ if !iwinfoModeRe.MatchString(block) {
+ return "", false
+ }
+ if m := iwinfoSSIDRe.FindStringSubmatch(block); len(m) > 1 {
+ ssid = m[1]
+ }
+ return ssid, true
+}
+
+// parseAssocRSSI returns the rounded average RSSI of associated clients.
+// ok is false when no valid RSSI value was found.
+func parseAssocRSSI(out string) (avg float64, ok bool) {
+ sum, count := 0, 0
+ for _, m := range iwinfoAssocRe.FindAllStringSubmatch(out, -1) {
+ rssi, err := strconv.Atoi(m[1])
+ if err != nil || rssi >= 0 || rssi < -150 {
+ continue
+ }
+ sum += rssi
+ count++
+ }
+ if count == 0 {
+ return 0, false
+ }
+ return math.Round(float64(sum) / float64(count)), true
+}
+
+func collectOpenWrtAP(ctx context.Context) map[string]system.WiFi {
+ results := make(map[string]system.WiFi)
+
+ out, err := exec.CommandContext(ctx, "iwinfo").Output()
+ if err != nil && len(out) == 0 {
+ return results
+ }
+
+ for _, b := range splitIwinfoBlocks(string(out)) {
+ if ctx.Err() != nil {
+ return results
+ }
+ ssid, isAP := parseIwinfoInfo(b.text)
+ if !isAP {
+ continue
+ }
+
+ reading := system.WiFi{SSID: validSSID(ssid)}
+ if assocOut, err := exec.CommandContext(ctx, "iwinfo", b.name, "assoclist").Output(); err == nil {
+ if avg, ok := parseAssocRSSI(string(assocOut)); ok {
+ reading.Signal = &avg
+ }
+ }
+ results[b.name] = reading
+ }
+ return results
}
func collectLinux(ctx context.Context, client linuxClient) map[string]system.WiFi {