package app import ( "fmt" "log" "os/exec" "strings" ) // 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) { 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) } for _, line := range strings.Split(string(out), "\n") { fields := strings.Fields(line) hasInet := false hasDynamic := false for _, f := range fields { switch f { case "inet": hasInet = true case "dynamic": hasDynamic = true } } if hasInet && hasDynamic { return true, nil } } return false, 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) 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) } if out, err := exec.Command("systemctl", "start", "wpa_supplicant").CombinedOutput(); err != nil { log.Printf("Failed to start wpa_supplicant: %v: %s", err, strings.TrimSpace(string(out))) } }