app: Surface Ethernet uplink in the UI and gate wpa_supplicant access
When the configured Ethernet interface holds a DHCP-assigned IPv4 at
startup, the app now skips wifi.Initialize / StartEventMonitoring and
guards every wifi.* wrapper against a nil backend. This prevents D-Bus
calls to fi.w1.wpa_supplicant1 from re-activating the daemon via
dbus-activation, honoring the "do nothing" intent of the Ethernet path.
The probed state is exposed in SystemStatus and rendered in the header
as a third pill ("Ethernet · <IP>"); a new "disabled" connectionState
covers the WiFi pill in this mode.
This commit is contained in:
parent
5a3942f351
commit
8b1debdddc
7 changed files with 166 additions and 37 deletions
|
|
@ -57,20 +57,28 @@ func (a *App) Initialize(cfg *config.Config) error {
|
|||
// Store config reference
|
||||
a.Config = cfg
|
||||
|
||||
// If Ethernet uplink is not already providing connectivity (no DHCP lease
|
||||
// on the configured interface), bring up wpa_supplicant so the WiFi
|
||||
// backend has something to talk to.
|
||||
ensureUplink(cfg.EthernetInterface)
|
||||
// Decide whether the Ethernet uplink already provides connectivity. When
|
||||
// it does we deliberately skip every wpa_supplicant interaction below —
|
||||
// the daemon is dbus-activatable, so any call into the WiFi backend
|
||||
// would re-spawn it and undo this choice.
|
||||
eth := ensureUplink(cfg.EthernetInterface)
|
||||
a.StatusMutex.Lock()
|
||||
a.Status.EthernetStatus = eth
|
||||
a.StatusMutex.Unlock()
|
||||
|
||||
// Initialize WiFi backend
|
||||
if err := wifi.Initialize(cfg.WifiInterface, cfg.WifiBackend); err != nil {
|
||||
return err
|
||||
}
|
||||
if !eth.Active {
|
||||
// Initialize WiFi backend
|
||||
if err := wifi.Initialize(cfg.WifiInterface, cfg.WifiBackend); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Start WiFi event monitoring
|
||||
if err := wifi.StartEventMonitoring(); err != nil {
|
||||
log.Printf("Warning: WiFi event monitoring failed: %v", err)
|
||||
// Don't fail - polling fallback still works
|
||||
// Start WiFi event monitoring
|
||||
if err := wifi.StartEventMonitoring(); err != nil {
|
||||
log.Printf("Warning: WiFi event monitoring failed: %v", err)
|
||||
// Don't fail - polling fallback still works
|
||||
}
|
||||
} else {
|
||||
log.Printf("Skipping WiFi backend init: Ethernet uplink active on %s", eth.Interface)
|
||||
}
|
||||
|
||||
// Initialize station backend
|
||||
|
|
@ -210,10 +218,32 @@ func (a *App) periodicStatusUpdate() {
|
|||
case <-ticker.C:
|
||||
}
|
||||
|
||||
var eth *models.EthernetStatus
|
||||
if a.Config != nil {
|
||||
if e, err := probeEthernet(a.Config.EthernetInterface); err == nil {
|
||||
eth = e
|
||||
}
|
||||
}
|
||||
|
||||
a.StatusMutex.Lock()
|
||||
a.Status.Connected = wifi.IsConnected()
|
||||
a.Status.ConnectionState = wifi.GetConnectionState()
|
||||
a.Status.ConnectedSSID = wifi.GetConnectedSSID()
|
||||
if eth != nil {
|
||||
a.Status.EthernetStatus = eth
|
||||
}
|
||||
|
||||
// Skip every wifi.* call when the Ethernet uplink is the chosen
|
||||
// path: the WiFi backend isn't initialized in that mode and the
|
||||
// wrappers would otherwise return zero values; either way we don't
|
||||
// want to risk dbus-activating wpa_supplicant from this hot loop.
|
||||
ethActive := a.Status.EthernetStatus != nil && a.Status.EthernetStatus.Active
|
||||
if !ethActive {
|
||||
a.Status.Connected = wifi.IsConnected()
|
||||
a.Status.ConnectionState = wifi.GetConnectionState()
|
||||
a.Status.ConnectedSSID = wifi.GetConnectedSSID()
|
||||
} else {
|
||||
a.Status.Connected = false
|
||||
a.Status.ConnectionState = "disabled"
|
||||
a.Status.ConnectedSSID = ""
|
||||
}
|
||||
a.Status.Uptime = getSystemUptime()
|
||||
|
||||
// Get detailed hotspot status
|
||||
|
|
|
|||
|
|
@ -5,50 +5,64 @@ import (
|
|||
"log"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/nemunaire/repeater/internal/models"
|
||||
)
|
||||
|
||||
// hasDHCPAddress reports whether iface holds an IPv4 address that ip(8) marks
|
||||
// as "dynamic" — the flag set on addresses with a finite valid_lft, which is
|
||||
// how DHCP-assigned leases appear (static addresses are "permanent").
|
||||
func hasDHCPAddress(iface string) (bool, error) {
|
||||
// probeEthernet reports the wired uplink state of iface by parsing
|
||||
// `ip -4 -o addr show dev <iface>`. An IPv4 line carrying the "dynamic" flag
|
||||
// (set by ip(8) on addresses with a finite valid_lft) is treated as a
|
||||
// DHCP-assigned lease — that's the signal we use to decide whether the box
|
||||
// already has connectivity through Ethernet.
|
||||
func probeEthernet(iface string) (*models.EthernetStatus, error) {
|
||||
out, err := exec.Command("ip", "-4", "-o", "addr", "show", "dev", iface).Output()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("ip addr show %s: %w", iface, err)
|
||||
return nil, fmt.Errorf("ip addr show %s: %w", iface, err)
|
||||
}
|
||||
for _, line := range strings.Split(string(out), "\n") {
|
||||
fields := strings.Fields(line)
|
||||
var addr string
|
||||
hasInet := false
|
||||
hasDynamic := false
|
||||
for _, f := range fields {
|
||||
for i, f := range fields {
|
||||
switch f {
|
||||
case "inet":
|
||||
hasInet = true
|
||||
if i+1 < len(fields) {
|
||||
addr = fields[i+1]
|
||||
}
|
||||
case "dynamic":
|
||||
hasDynamic = true
|
||||
}
|
||||
}
|
||||
if hasInet && hasDynamic {
|
||||
return true, nil
|
||||
if slash := strings.IndexByte(addr, '/'); slash >= 0 {
|
||||
addr = addr[:slash]
|
||||
}
|
||||
return &models.EthernetStatus{Active: true, Interface: iface, IPv4: addr}, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
return &models.EthernetStatus{Active: false, Interface: iface}, nil
|
||||
}
|
||||
|
||||
// ensureUplink boots the WiFi uplink only if Ethernet is not already providing
|
||||
// connectivity: when iface has no DHCP-assigned IPv4, start wpa_supplicant so
|
||||
// the WiFi backend can take over.
|
||||
func ensureUplink(iface string) {
|
||||
has, err := hasDHCPAddress(iface)
|
||||
// ensureUplink returns the current Ethernet status, and starts wpa_supplicant
|
||||
// when no DHCP-assigned address is present. When Ethernet is already
|
||||
// providing connectivity the WiFi backend is left alone — and crucially the
|
||||
// caller must avoid any D-Bus call to fi.w1.wpa_supplicant1, which would
|
||||
// otherwise re-activate the daemon via dbus-activation.
|
||||
func ensureUplink(iface string) *models.EthernetStatus {
|
||||
eth, err := probeEthernet(iface)
|
||||
if err != nil {
|
||||
log.Printf("Could not probe %s for DHCP address (%v); starting wpa_supplicant as fallback", iface, err)
|
||||
} else if has {
|
||||
log.Printf("DHCP address present on %s, leaving wpa_supplicant alone", iface)
|
||||
return
|
||||
} else {
|
||||
log.Printf("No DHCP address on %s, starting wpa_supplicant", iface)
|
||||
log.Printf("Could not probe %s (%v); starting wpa_supplicant as fallback", iface, err)
|
||||
eth = &models.EthernetStatus{Active: false, Interface: iface}
|
||||
}
|
||||
|
||||
if eth.Active {
|
||||
log.Printf("DHCP address %s on %s, leaving wpa_supplicant alone", eth.IPv4, iface)
|
||||
return eth
|
||||
}
|
||||
log.Printf("No DHCP address on %s, starting wpa_supplicant", iface)
|
||||
if out, err := exec.Command("service", "wpa_supplicant", "start").CombinedOutput(); err != nil {
|
||||
log.Printf("Failed to start wpa_supplicant: %v: %s", err, strings.TrimSpace(string(out)))
|
||||
}
|
||||
return eth
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue