WiFi: adapter chooser, optional wifis, wrong-password diagnosis

- Menu asks which adapter when several exist (shows USB/PCI); the web UI
  helper prefers the adapter already configured.
- Stale-entry cleanup also marks remaining installer wifis optional so a
  dead WiFi entry can't hold up boot.
- After a failed connect, read wpa_supplicant's log to say wrong password /
  network not found / rejected.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A97KUggSE3rBL8veDM5Mr4
This commit is contained in:
Claude
2026-10-16 15:56:46 +00:00
parent 903a1e45e4
commit f91dbe4d5f
2 changed files with 130 additions and 5 deletions
@@ -107,8 +107,40 @@ except Exception:
pass' "$WIFI_NETPLAN_FILE" 2>/dev/null
}
# Why a connect attempt failed, from wpa_supplicant's own log lines in
# the journal (netplan runs it as netplan-wpa-<iface>.service). Prints a
# short human-readable reason, or nothing if the log says nothing useful.
diagnose_failure() {
local log
log=$(journalctl -b -u "netplan-wpa-$1" -n 150 --no-pager 2>/dev/null) || return 0
if grep -q "reason=WRONG_KEY\|pre-shared key may be incorrect" <<< "$log"; then
echo "wrong password (the network rejected the key)"
elif grep -q "CTRL-EVENT-NETWORK-NOT-FOUND" <<< "$log"; then
echo "network not found - wrong name, or out of range of this adapter"
elif grep -q "CTRL-EVENT-ASSOC-REJECT\|CTRL-EVENT-AUTH-REJECT" <<< "$log"; then
echo "the access point refused the connection (MAC filter, or WPA3-only/unsupported security)"
fi
}
# Prefer the adapter our netplan file already configures (if it's still
# there) over "first one found", so a built-in card plus a USB dongle
# don't swap places between calls.
prefer_configured_iface() {
[[ -f "$WIFI_NETPLAN_FILE" ]] || return 1
python3 -I -c '
import sys, os, yaml
try:
d = yaml.safe_load(open(sys.argv[1]))
for n in (d.get("network", {}).get("wifis") or {}):
if os.path.exists(sys.argv[2] + "/net/" + str(n)):
print(n); sys.exit(0)
except Exception:
pass
sys.exit(1)' "$WIFI_NETPLAN_FILE" "$SYS_CLASS" 2>/dev/null
}
need_iface() {
IFACE=$(detect_iface) || reply_error "No WiFi adapter found. If it's a USB adapter, check it's plugged in."
IFACE=$(prefer_configured_iface || detect_iface) || reply_error "No WiFi adapter found. If it's a USB adapter, check it's plugged in."
command -v iw >/dev/null || reply_error "WiFi support (iw, wpasupplicant) isn't installed - run Core Settings -> WiFi once from the admin menu"
}
@@ -191,11 +223,18 @@ try:
del w[n]
if not w:
del d["network"]["wifis"]
# Not "optional" = the boot waits (up to ~2 min) for it to get an address.
opt = [n for n, v in w.items() if isinstance(v, dict) and not v.get("optional")]
for n in opt:
w[n]["optional"] = True
if stale or opt:
if not stale:
shutil.copy2(p, p + ".kiosk-bak")
mode = os.stat(p).st_mode & 0o777
with open(p, "w") as fh:
yaml.safe_dump(d, fh, default_flow_style=False)
os.chmod(p, mode)
print("Removed stale WiFi entries from %s: %s" % (p, ", ".join(map(str, stale))))
print("Fixed WiFi entries in %s (removed: %s)" % (p, ", ".join(map(str, stale)) or "none"))
except Exception:
pass
PYEOF
@@ -271,6 +310,11 @@ do_connect() {
if [[ -z "$addr" ]]; then
restore "$backup"
local why
why=$(diagnose_failure "$IFACE")
if [[ -n "$why" ]]; then
reply_error "Couldn't connect to \"$ssid\": $why."
fi
if [[ -n "$backup" ]]; then
reply_error "Couldn't connect to \"$ssid\" - wrong password, or out of range. The previous WiFi setting was put back."
fi