198 lines
6.1 KiB
Bash
Executable File
198 lines
6.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
##
|
|
# WiFi management for Toes appliance setup.
|
|
# Uses nmcli (NetworkManager) which is standard on Raspberry Pi OS Bookworm.
|
|
#
|
|
# Commands:
|
|
# status - Check WiFi connection state
|
|
# scan - List available WiFi networks
|
|
# connect - Connect to a network (SSID and password as args)
|
|
# hotspot-start - Start the setup hotspot + captive portal DNS
|
|
# hotspot-stop - Stop the hotspot + captive portal DNS
|
|
# has-wifi - Exit 0 if WiFi hardware exists, 1 if not
|
|
|
|
set -euo pipefail
|
|
|
|
HOTSPOT_SSID="Toes Setup"
|
|
HOTSPOT_IFACE="wlan0"
|
|
HOTSPOT_CON="toes-hotspot"
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
CAPTIVE_CONF="$SCRIPT_DIR/wifi-captive.conf"
|
|
DNSMASQ_PID="/tmp/toes-dnsmasq.pid"
|
|
|
|
cmd="${1:-help}"
|
|
|
|
case "$cmd" in
|
|
|
|
status)
|
|
# Returns JSON: { connected, ssid, ip }
|
|
STATE=$(nmcli -t -f GENERAL.STATE device show "$HOTSPOT_IFACE" 2>/dev/null | cut -d: -f2 | xargs)
|
|
SSID=$(nmcli -t -f GENERAL.CONNECTION device show "$HOTSPOT_IFACE" 2>/dev/null | cut -d: -f2 | xargs)
|
|
IP=$(nmcli -t -f IP4.ADDRESS device show "$HOTSPOT_IFACE" 2>/dev/null | head -1 | cut -d: -f2 | cut -d/ -f1 | xargs)
|
|
|
|
CONNECTED="false"
|
|
if echo "$STATE" | grep -qi "connected" && [ "$SSID" != "$HOTSPOT_CON" ] && [ -n "$SSID" ] && [ "$SSID" != "--" ]; then
|
|
CONNECTED="true"
|
|
fi
|
|
|
|
printf '{"connected":%s,"ssid":"%s","ip":"%s"}\n' "$CONNECTED" "${SSID:-}" "${IP:-}"
|
|
;;
|
|
|
|
scan)
|
|
# Force a fresh scan then list networks as JSON array
|
|
nmcli device wifi rescan ifname "$HOTSPOT_IFACE" 2>/dev/null || true
|
|
sleep 1
|
|
nmcli -t -f SSID,SIGNAL,SECURITY device wifi list ifname "$HOTSPOT_IFACE" 2>/dev/null \
|
|
| awk -F: '
|
|
BEGIN { printf "[" }
|
|
NR > 1 { printf "," }
|
|
{
|
|
gsub(/"/, "\\\"", $1)
|
|
if ($1 != "" && $1 != "--") {
|
|
printf "{\"ssid\":\"%s\",\"signal\":%s,\"security\":\"%s\"}", $1, ($2 == "" ? "0" : $2), $3
|
|
}
|
|
}
|
|
END { printf "]\n" }
|
|
' | python3 -c "
|
|
import sys, json
|
|
raw = json.load(sys.stdin)
|
|
# Deduplicate by SSID, keeping the strongest signal
|
|
seen = {}
|
|
for net in raw:
|
|
ssid = net.get('ssid', '')
|
|
if not ssid:
|
|
continue
|
|
if ssid not in seen or net.get('signal', 0) > seen[ssid].get('signal', 0):
|
|
seen[ssid] = net
|
|
result = sorted(seen.values(), key=lambda x: -x.get('signal', 0))
|
|
json.dump(result, sys.stdout)
|
|
print()
|
|
"
|
|
;;
|
|
|
|
connect)
|
|
SSID="${2:-}"
|
|
PASSWORD="${3:-}"
|
|
|
|
if [ -z "$SSID" ]; then
|
|
echo '{"ok":false,"error":"SSID is required"}' >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Stop captive portal DNS
|
|
"$0" dns-stop 2>/dev/null || true
|
|
|
|
# Stop the hotspot first if it's running
|
|
nmcli connection down "$HOTSPOT_CON" 2>/dev/null || true
|
|
nmcli connection delete "$HOTSPOT_CON" 2>/dev/null || true
|
|
sleep 1
|
|
|
|
# Connect to the network
|
|
if [ -n "$PASSWORD" ]; then
|
|
OUTPUT=$(nmcli device wifi connect "$SSID" password "$PASSWORD" ifname "$HOTSPOT_IFACE" 2>&1) || {
|
|
# Connection failed - restart hotspot so user can try again
|
|
"$0" hotspot-start 2>/dev/null || true
|
|
echo "{\"ok\":false,\"error\":\"$(echo "$OUTPUT" | tr '"' "'" | tr '\n' ' ')\"}"
|
|
exit 1
|
|
}
|
|
else
|
|
OUTPUT=$(nmcli device wifi connect "$SSID" ifname "$HOTSPOT_IFACE" 2>&1) || {
|
|
"$0" hotspot-start 2>/dev/null || true
|
|
echo "{\"ok\":false,\"error\":\"$(echo "$OUTPUT" | tr '"' "'" | tr '\n' ' ')\"}"
|
|
exit 1
|
|
}
|
|
fi
|
|
|
|
# Wait for an IP
|
|
for i in $(seq 1 10); do
|
|
IP=$(nmcli -t -f IP4.ADDRESS device show "$HOTSPOT_IFACE" 2>/dev/null | head -1 | cut -d: -f2 | cut -d/ -f1 | xargs)
|
|
if [ -n "$IP" ] && [ "$IP" != "" ]; then
|
|
echo "{\"ok\":true,\"ip\":\"$IP\",\"ssid\":\"$SSID\"}"
|
|
exit 0
|
|
fi
|
|
sleep 1
|
|
done
|
|
|
|
echo "{\"ok\":true,\"ip\":\"\",\"ssid\":\"$SSID\"}"
|
|
;;
|
|
|
|
hotspot-start)
|
|
# Delete any existing hotspot connection
|
|
nmcli connection delete "$HOTSPOT_CON" 2>/dev/null || true
|
|
|
|
# Create the hotspot
|
|
nmcli connection add \
|
|
type wifi \
|
|
ifname "$HOTSPOT_IFACE" \
|
|
con-name "$HOTSPOT_CON" \
|
|
ssid "$HOTSPOT_SSID" \
|
|
autoconnect no \
|
|
wifi.mode ap \
|
|
wifi.band bg \
|
|
wifi-sec.key-mgmt wpa-psk \
|
|
wifi-sec.psk "toessetup" \
|
|
ipv4.method shared \
|
|
ipv4.addresses "10.42.0.1/24" \
|
|
2>/dev/null
|
|
|
|
nmcli connection up "$HOTSPOT_CON" 2>/dev/null
|
|
|
|
# Start captive portal DNS redirect
|
|
"$0" dns-start 2>/dev/null || true
|
|
|
|
echo '{"ok":true,"ssid":"'"$HOTSPOT_SSID"'","ip":"10.42.0.1"}'
|
|
;;
|
|
|
|
hotspot-stop)
|
|
"$0" dns-stop 2>/dev/null || true
|
|
nmcli connection down "$HOTSPOT_CON" 2>/dev/null || true
|
|
nmcli connection delete "$HOTSPOT_CON" 2>/dev/null || true
|
|
echo '{"ok":true}'
|
|
;;
|
|
|
|
dns-start)
|
|
# Start dnsmasq for captive portal DNS (resolves everything to 10.42.0.1)
|
|
# Kill any existing instance first
|
|
"$0" dns-stop 2>/dev/null || true
|
|
|
|
# NetworkManager runs its own dnsmasq on port 53, so we need to stop it
|
|
# for the hotspot interface and run our own
|
|
if [ -f "$CAPTIVE_CONF" ]; then
|
|
sudo dnsmasq --conf-file="$CAPTIVE_CONF" --pid-file="$DNSMASQ_PID" --port=53 2>/dev/null || true
|
|
fi
|
|
;;
|
|
|
|
dns-stop)
|
|
# Stop our captive portal dnsmasq
|
|
if [ -f "$DNSMASQ_PID" ]; then
|
|
sudo kill "$(cat "$DNSMASQ_PID")" 2>/dev/null || true
|
|
sudo rm -f "$DNSMASQ_PID"
|
|
fi
|
|
# Also kill by name in case PID file is stale
|
|
sudo pkill -f "dnsmasq.*wifi-captive.conf" 2>/dev/null || true
|
|
;;
|
|
|
|
has-wifi)
|
|
# Check if wlan0 exists
|
|
if nmcli device show "$HOTSPOT_IFACE" > /dev/null 2>&1; then
|
|
exit 0
|
|
else
|
|
exit 1
|
|
fi
|
|
;;
|
|
|
|
help|*)
|
|
echo "Usage: $0 {status|scan|connect|hotspot-start|hotspot-stop|has-wifi}"
|
|
echo ""
|
|
echo "Commands:"
|
|
echo " status Check WiFi connection state (JSON)"
|
|
echo " scan List available WiFi networks (JSON array)"
|
|
echo " connect SSID [PASSWORD] Connect to a WiFi network"
|
|
echo " hotspot-start Start the Toes Setup hotspot + captive portal"
|
|
echo " hotspot-stop Stop the hotspot + captive portal"
|
|
echo " has-wifi Exit 0 if WiFi hardware present"
|
|
exit 1
|
|
;;
|
|
esac
|