wren-linux / wifi.sh
#!/bin/bash
# Connect to Wi-Fi using whatever this system has, from a bare Debian
# install upward. Asks for the network and password, connects, and sets
# it up to reconnect at boot.
#
# bash wifi.sh (uses sudo, or run it as root after: su -)
# bash wifi.sh --remove undo it: remove the Wi-Fi settings wifi.sh wrote, and
# offer to remove NetworkManager and the random Wi-Fi
# address too, for a computer that doesn't need Wi-Fi
#
# Backends, first one usable wins:
# NetworkManager -> iwd -> wpa_supplicant (+ ifupdown or systemd-networkd)
# If none is installed, it installs wpasupplicant from offline/debs (made by
# prep/fetch-offline-debs.sh), or over Ethernet / USB tethering.
#
# Passwords are never put on a command line, where any user on the machine
# could read them. If your nmcli doesn't take one on its standard input,
# WREN_PASSWORD_ON_COMMAND_LINE=1 bash wifi.sh uses the old way.
. "$(dirname -- "${BASH_SOURCE[0]}")/lib.sh"
. "$WREN_DIR/lib/offline.sh"
. "$WREN_DIR/lib/live-extras.sh"
if [ "$(id -u)" -ne 0 ]; then
if command -v sudo >/dev/null; then
exec sudo bash "$WREN_DIR/wifi.sh" "$@"
fi
die "Needs root. Run: su - then: bash $WREN_DIR/wifi.sh"
fi
# ------------------------------------------------------------------ --remove
if [ "${1:-}" = --remove ]; then
say "Removing the Wi-Fi setup"
remove_wifi_config
# NetworkManager and the random address are parts, run as your own user
pieces=()
is_installed network-manager && pieces+=(network-manager)
[ -e /etc/NetworkManager/conf.d/90-wren-random-mac.conf ] && pieces+=(random-mac)
if [ ${#pieces[@]} -gt 0 ]; then
note "Also set up by Wren Linux for Wi-Fi: ${pieces[*]}"
if [ -n "${SUDO_USER:-}" ] && ask "Remove those too? (This computer will then have only its wired connection.)" n; then
for p in random-mac network-manager; do
case " ${pieces[*]} " in *" $p "*) sudo -u "$SUDO_USER" bash "$WREN_DIR/parts/$p.sh" --remove ;; esac
done
else
note "To remove them later, as your own user: bash parts/random-mac.sh --remove, then bash parts/network-manager.sh --remove"
fi
fi
exit 0
fi
IF=
SSID=
PASS=
HIDDEN=false
WPA_CONF=
has_ipv4() { ip -4 -o addr show dev "$1" scope global 2>/dev/null | grep -q inet; }
# ---------------------------------------------------------------- hardware
reload_wifi_drivers() {
local dev mod
for dev in /sys/bus/pci/devices/*; do
case $(cat "$dev/class") in 0x0280*) ;; *) continue ;; esac
[ -e "$dev/driver/module" ] || continue
mod=$(basename "$(readlink -f "$dev/driver/module")")
note "Reloading driver $mod"
modprobe -r "$mod" 2>/dev/null && modprobe "$mod" || true
done
sleep 3
}
# Install Wi-Fi packages from offline/debs, a local apt repo made by
# prep/fetch-offline-debs.sh. apt picks only what this system is missing.
# These packages arrive on a USB stick and go in as root, so each one is
# first matched to Debian's signed package lists (lib/offline.sh).
install_offline_debs() {
local tmp p want=() rc=0
[ -f "$OFFLINE_DIR/debs/Packages" ] || return 1
tmp=$(mktemp -d)
# Check and install the same copy
cp -r "$OFFLINE_DIR/debs" "$tmp/repo"
if [ ! -d "$tmp/repo/verify" ] ||
! verify_offline_lists "$tmp/repo/verify" "$tmp/sums" ||
! check_offline_debs "$tmp/repo" "$tmp/sums"; then
warn "The packages in $OFFLINE_DIR/debs can't be matched to Debian's signed"
warn "lists, so they were not installed. Make them again on a machine you trust:"
warn " bash prep/fetch-offline-debs.sh"
rm -rf "$tmp"
return 1
fi
mkdir -p "$tmp/lists/partial" "$tmp/parts"
# trusted=yes: the index is local and unsigned, but every file in it
# was checked against Debian's signed lists above
echo "deb [trusted=yes] file:$tmp/repo ./" >"$tmp/sources.list"
local opts=(-o "Dir::Etc::SourceList=$tmp/sources.list" -o "Dir::Etc::SourceParts=$tmp/parts"
-o "Dir::State::Lists=$tmp/lists")
for p in wpasupplicant iw wireless-regdb $(awk '/^Package: firmware-/ { print $2 }' "$tmp/repo/Packages"); do
is_installed "$p" || want+=("$p")
done
if [ ${#want[@]} -eq 0 ]; then
rm -rf "$tmp"
return 1
fi
say "Installing from $OFFLINE_DIR/debs: ${want[*]}"
apt-get "${opts[@]}" update >/dev/null &&
apt-get "${opts[@]}" install -y --no-install-recommends "${want[@]}" || rc=$?
rm -rf "$tmp"
return "$rc"
}
find_iface() {
local list
mapfile -t list < <(wifi_ifaces)
if [ ${#list[@]} -eq 0 ]; then
warn "No Wi-Fi device found. This usually means missing firmware."
if install_offline_debs; then
reload_wifi_drivers
mapfile -t list < <(wifi_ifaces)
fi
fi
if [ ${#list[@]} -eq 0 ]; then
command -v lspci >/dev/null && lspci -nnk | grep -iA3 'network controller' >&2
dmesg 2>/dev/null | grep -i 'firmware' | grep -iE 'fail|error' | tail -5 >&2 || true
die "Still no Wi-Fi device. Check for a hardware Wi-Fi switch, or copy the
firmware package for your card (e.g. firmware-iwlwifi, firmware-realtek)
into $OFFLINE_DIR/debs and run this again. A reboot may also be needed."
fi
IF=${list[0]}
if [ ${#list[@]} -gt 1 ]; then
read -r -p "Wi-Fi devices: ${list[*]}. Use which? [$IF] " pick </dev/tty || pick=
IF=${pick:-$IF}
fi
[ -e "/sys/class/net/$IF" ] || die "No device called $IF."
# A driver DKMS builds is rebuilt by it for every new kernel: without dkms
# the Wi-Fi would stop after the next kernel update. parts/no-live-extras.sh
# keeps dkms when it builds any module; say so here, where it matters.
local drv
if drv=$(dkms_wifi_driver "$IF"); then
note "Your Wi-Fi driver ($drv) is built by DKMS, so dkms stays installed: it rebuilds the driver for each new kernel."
fi
say "Using Wi-Fi device $IF"
}
unblock_wifi() {
local r
for r in /sys/class/rfkill/rfkill*; do
[ "$(cat "$r/type" 2>/dev/null)" = wlan ] || continue
if [ "$(cat "$r/hard")" = 1 ]; then
die "Wi-Fi is switched off in hardware. Use the switch or Fn key, then run this again."
fi
if [ "$(cat "$r/soft")" = 1 ]; then
echo 0 >"$r/soft" 2>/dev/null || rfkill unblock wifi 2>/dev/null || true
note "Turned Wi-Fi on (was blocked in software)."
fi
done
ip link set "$IF" up 2>/dev/null || true
}
# --------------------------------------------------------------- IP + DNS
set_dns() {
[ $# -gt 0 ] || return 0
# parts/secure-dns.sh's switch points it at Unbound, which asks over an encrypted
# connection; the network's DNS server would go around that (and be refused)
secure_dns_on && return 0
# A symlinked resolv.conf belongs to resolved or similar; leave it
[ -L /etc/resolv.conf ] && return 0
if command -v resolvconf >/dev/null; then
printf 'nameserver %s\n' "$@" | resolvconf -a "$IF.wren"
return 0
fi
[ -e /etc/resolv.conf.wren.bak ] || cp -a /etc/resolv.conf /etc/resolv.conf.wren.bak 2>/dev/null || true
printf 'nameserver %s\n' "$@" >/etc/resolv.conf
}
# DHCP through systemd-networkd, which every Debian has. "persist" keeps
# the config across reboots; otherwise it lives in /run.
networkd_dhcp() {
local ifc=$1 dir=/run/systemd/network lease dns i
[ "${2:-}" = persist ] && dir=/etc/systemd/network
mkdir -p "$dir"
printf '[Match]\nName=%s\n\n[Network]\nDHCP=yes\n\n[DHCPv4]\nSendHostname=no\n\n[DHCPv6]\nSendHostname=no\n' "$ifc" >"$dir/50-wren-$ifc.network"
if [ "${2:-}" = persist ]; then
systemctl enable systemd-networkd >/dev/null 2>&1 || true
systemctl disable systemd-networkd-wait-online.service >/dev/null 2>&1 || true
fi
systemctl restart systemd-networkd || return 1
for i in $(seq 30); do has_ipv4 "$ifc" && break; sleep 1; done
has_ipv4 "$ifc" || return 1
lease=/run/systemd/netif/leases/$(cat "/sys/class/net/$ifc/ifindex")
for i in $(seq 5); do [ -f "$lease" ] && break; sleep 1; done
dns=$(sed -n 's/^DNS=//p' "$lease" 2>/dev/null)
# Home routers usually answer DNS themselves
[ -n "$dns" ] || dns=$(ip -4 route show default dev "$ifc" | awk '{ print $3; exit }')
# shellcheck disable=SC2086 # one or more servers, meant to split
set_dns $dns
}
busybox_dhcp() {
local ifc=$1 script=/run/wren-udhcpc.sh
command -v busybox >/dev/null || return 1
cat >"$script" <<'EOF'
#!/bin/sh
case "$1" in
deconfig) ip -4 addr flush dev "$interface"; ip link set "$interface" up ;;
bound|renew)
ip -4 addr flush dev "$interface"
ip addr add "$ip/${mask:-24}" dev "$interface"
if [ -n "$router" ]; then
ip route del default dev "$interface" 2>/dev/null
ip route add default via "${router%% *}" dev "$interface"
fi
# (not while secure DNS, parts/secure-dns.sh, points it at Unbound)
if [ -n "$dns" ] && [ ! -L /etc/resolv.conf ] &&
! grep -qE '^# Written by Wren Linux (parts|extras)/secure-dns\.sh' /etc/resolv.conf 2>/dev/null; then
: >/etc/resolv.conf
for d in $dns; do echo "nameserver $d" >>/etc/resolv.conf; done
fi ;;
esac
EOF
chmod 755 "$script"
busybox udhcpc -i "$ifc" -s "$script" -n -q -t 10
}
# Get an address on $1 right now with any DHCP client there is
dhcp_now() {
local ifc=$1
dhcp_no_name on
ip link set "$ifc" up
if command -v dhcpcd >/dev/null && dhcpcd -4 -w -t 30 "$ifc"; then return 0; fi
if command -v dhclient >/dev/null && dhclient -4 -1 "$ifc"; then return 0; fi
if networkd_dhcp "$ifc"; then return 0; fi
busybox_dhcp "$ifc"
}
# ------------------------------------------------- getting wpa_supplicant
wired_ifaces() {
local d n
for d in /sys/class/net/*; do
n=${d##*/}
[ "$n" = lo ] && continue
[ -e "$d/device" ] || continue
if [ -d "$d/wireless" ] || [ -e "$d/phy80211" ]; then continue; fi
echo "$n"
done
}
online_via_wire() {
local n reply
while :; do
for n in $(wired_ifaces); do
ip link set "$n" up 2>/dev/null || true
done
sleep 3
for n in $(wired_ifaces); do
[ "$(cat "/sys/class/net/$n/carrier" 2>/dev/null)" = 1 ] || continue
note "Cable or tether found on $n, getting an address"
if dhcp_now "$n" && have_internet; then return 0; fi
done
echo
echo " Plug in an Ethernet cable, or connect your phone by USB and turn on"
echo " USB tethering (Android: Settings > Network > Hotspot > USB tethering)."
read -r -p " Press Enter to try again, or q to quit: " reply </dev/tty || reply=q
[ "$reply" != q ] || die "No way to install wpasupplicant. See README.md, 'Before you reinstall'."
done
}
get_wpa_supplicant() {
warn "No Wi-Fi software is installed (no NetworkManager, iwd or wpa_supplicant)."
if install_offline_debs && command -v wpa_supplicant >/dev/null; then
return 0
fi
if ! have_internet; then
online_via_wire
fi
say "Installing wpasupplicant and iw"
apt-get update
apt-get install -y --no-install-recommends wpasupplicant iw wireless-regdb
}
# ----------------------------------------------------------- choose backend
pick_backend() {
local state
if command -v nmcli >/dev/null && systemctl is-active --quiet NetworkManager; then
state=$(nmcli -t -f DEVICE,STATE device 2>/dev/null | awk -F: -v i="$IF" '$1 == i { print $2 }')
# unmanaged = ifupdown owns it (installer setup); use that instead
if [ -n "$state" ] && [ "$state" != unmanaged ]; then echo nm; return; fi
fi
if command -v iwctl >/dev/null; then
systemctl is-active --quiet iwd || systemctl start iwd 2>/dev/null || true
if systemctl is-active --quiet iwd; then echo iwd; return; fi
fi
if command -v wpa_supplicant >/dev/null; then echo wpa; return; fi
echo none
}
# ----------------------------------------------------------- scan + prompt
# Every scan has a time limit: right after a boot (the install stick's first
# login) a driver can start a scan and never report it finished, and "iw scan"
# then waits for ever at "Scanning for networks". (WREN_SCAN_TIMEOUT: tests)
SCAN_TIMEOUT=${WREN_SCAN_TIMEOUT:-20}
scan_ssids() {
case $BACKEND in
nm)
timeout "$SCAN_TIMEOUT" nmcli --escape no -t -f SSID device wifi list ifname "$IF" --rescan yes 2>/dev/null ;;
iwd)
timeout "$SCAN_TIMEOUT" iwctl station "$IF" scan >/dev/null 2>&1 || true
sleep 4
timeout "$SCAN_TIMEOUT" iwctl station "$IF" get-networks 2>/dev/null |
sed 's/\x1b\[[0-9;]*m//g' |
grep -vE '^[[:space:]]*$|^[[:space:]-]+$|Available networks|Network name' |
sed -E 's/^[[:space:]>]+//; s/[[:space:]]+(psk|open|8021x|wep)[[:space:]]+[*]*[[:space:]]*$//' ;;
*)
if command -v iw >/dev/null; then
timeout "$SCAN_TIMEOUT" iw dev "$IF" scan 2>/dev/null |
awk '/^[[:space:]]*signal:/ { s = $2 }
/^[[:space:]]*SSID: / { sub(/^[[:space:]]*SSID: /, ""); print s "\t" $0 }' |
sort -t "$(printf '\t')" -k1,1 -gr | cut -f2-
fi ;;
esac | awk 'NF && !seen[$0]++' | head -20
}
ask_network() {
local ssids=() i choice
say "Scanning for networks"
mapfile -t ssids < <(scan_ssids || true)
if [ ${#ssids[@]} -eq 0 ]; then
# The first scan after a boot often finds nothing (the card is still
# waking up, or the scan was cut off): one more, after a moment
sleep 3
ip link set "$IF" up 2>/dev/null || true
mapfile -t ssids < <(scan_ssids || true)
fi
if [ ${#ssids[@]} -gt 0 ]; then
# (a network's name is whatever its owner typed: shown without control characters)
for i in "${!ssids[@]}"; do printf ' %2d) %s\n' $((i + 1)) "$(printf '%s' "${ssids[i]}" | printable)"; done
else
note "Couldn't scan (or nothing found). Type the network name."
fi
while [ -z "$SSID" ]; do
read -r -p "Network number or name: " choice </dev/tty || die "No input."
if [[ $choice =~ ^[0-9]+$ ]] && [ "$choice" -ge 1 ] && [ "$choice" -le ${#ssids[@]} ]; then
SSID=${ssids[choice - 1]}
else
SSID=$choice
fi
done
HIDDEN=false
if ! printf '%s\n' "${ssids[@]}" | grep -qxF -- "$SSID"; then
if ask "\"$(printf '%s' "$SSID" | printable)\" wasn't in the scan. Is it a hidden network?" y; then HIDDEN=true; fi
fi
}
ask_password() {
while :; do
read -r -s -p "Password for \"$(printf '%s' "$SSID" | printable)\" (Enter if it has none): " PASS </dev/tty || PASS=
echo
local n=${#PASS}
if [ "$n" -eq 0 ] || { [ "$n" -ge 8 ] && [ "$n" -le 63 ]; } ||
[[ $PASS =~ ^[0-9A-Fa-f]{64}$ ]]; then
return 0
fi
warn "Wi-Fi passwords are 8 to 63 characters. Try again."
done
}
# ----------------------------------------------------------------- connect
connect_nm() {
local args=(device wifi connect "$SSID" ifname "$IF")
$HIDDEN && args+=(hidden yes)
nmcli connection delete id "$SSID" >/dev/null 2>&1 || true
if [ -z "$PASS" ]; then
nmcli --wait 45 "${args[@]}"
elif [ -n "${WREN_PASSWORD_ON_COMMAND_LINE:-}" ]; then
nmcli --wait 45 "${args[@]}" password "$PASS"
else
# --ask takes the password from standard input
printf '%s\n' "$PASS" | nmcli --ask --wait 45 "${args[@]}"
fi
}
# iwd reads a known network's password from a file in /var/lib/iwd, so it
# is written there instead of being given to iwctl on the command line.
# The old file, if any, is kept until the new password has worked.
iwd_profile() {
local dir=/var/lib/iwd name
if [[ $SSID =~ ^[A-Za-z0-9\ _-]+$ ]]; then
name=$SSID
else
name="=$(printf '%s' "$SSID" | od -An -tx1 -v | tr -d ' \n')"
fi
echo "$dir/$name.psk"
}
write_iwd_profile() {
local file=$1
mkdir -p "${file%/*}"
(
umask 077
{
echo "[Security]"
if [[ $PASS =~ ^[0-9A-Fa-f]{64}$ ]]; then
echo "PreSharedKey=$PASS"
else
echo "Passphrase=$PASS"
fi
if $HIDDEN; then printf '\n[Settings]\nHidden=true\n'; fi
} >"$file"
)
}
connect_iwd() {
local cmd=connect i file='' old=''
$HIDDEN && cmd=connect-hidden
if [ -n "$PASS" ]; then
file=$(iwd_profile)
if [ -f "$file" ]; then
old=$(mktemp)
cp -p "$file" "$old"
fi
write_iwd_profile "$file"
fi
if ! iwctl station "$IF" "$cmd" "$SSID"; then
if [ -n "$file" ]; then
if [ -n "$old" ]; then mv "$old" "$file"; else rm -f "$file"; fi
fi
return 1
fi
if [ -n "$old" ]; then rm -f "$old"; fi
for i in $(seq 20); do
iwctl station "$IF" show 2>/dev/null | grep -q 'State.*connected' && break
sleep 1
done
for i in $(seq 10); do has_ipv4 "$IF" && return 0; sleep 1; done
# iwd remembers the network; give it an address at boot as well
mkdir -p /etc/iwd
if ! grep -qs '^EnableNetworkConfiguration=true' /etc/iwd/main.conf; then
printf '[General]\nEnableNetworkConfiguration=true\n' >>/etc/iwd/main.conf
fi
dhcp_now "$IF"
}
write_wpa_conf() {
local hex group=
WPA_CONF=/etc/wpa_supplicant/wpa_supplicant-$IF.conf
getent group netdev >/dev/null && group=" GROUP=netdev"
hex=$(printf '%s' "$SSID" | od -An -tx1 | tr -d ' \n')
mkdir -p /etc/wpa_supplicant
(
umask 077
{
echo "# Written by Wren Linux wifi.sh"
echo "ctrl_interface=DIR=/run/wpa_supplicant$group"
echo "update_config=1"
echo "network={"
echo " ssid=$hex"
$HIDDEN && echo " scan_ssid=1"
if [ -z "$PASS" ]; then
echo " key_mgmt=NONE"
elif [[ $PASS =~ ^[0-9A-Fa-f]{64}$ ]]; then
echo " psk=$PASS"
elif [[ $PASS == *\"* ]]; then
# The file can't quote a password that has a " in it, so it
# gets the key derived from it (which WPA3 networks can't use)
echo " psk=$(printf '%s\n' "$PASS" | wpa_passphrase "$SSID" | sed -n 's/^[[:space:]]*psk=//p')"
echo " key_mgmt=WPA-PSK WPA-PSK-SHA256"
echo " ieee80211w=1"
else
echo " psk=\"$PASS\""
echo " key_mgmt=WPA-PSK WPA-PSK-SHA256 SAE"
echo " ieee80211w=1"
fi
echo "}"
} >"$WPA_CONF"
)
}
stop_wpa_on_iface() {
ifdown --force "$IF" >/dev/null 2>&1 || true
systemctl stop "wpa_supplicant@$IF.service" >/dev/null 2>&1 || true
wpa_cli -i "$IF" terminate >/dev/null 2>&1 || true
pkill -f "wpa_supplicant.*-i ?$IF( |$)" 2>/dev/null || true
sleep 1
}
# Try the password with a throwaway wpa_supplicant: 0 ok, 2 wrong password
test_credentials() {
local log pid i rc=1
log=$(mktemp)
wpa_supplicant -i "$IF" -c "$WPA_CONF" -D nl80211,wext >"$log" 2>&1 &
pid=$!
for i in $(seq 30); do
sleep 1
if grep -q CTRL-EVENT-CONNECTED "$log"; then rc=0; break; fi
if grep -qE 'WRONG_KEY|pre-shared key may be incorrect' "$log"; then rc=2; break; fi
if ! kill -0 "$pid" 2>/dev/null; then rc=3; break; fi
done
kill "$pid" 2>/dev/null || true
wait "$pid" 2>/dev/null || true
[ "$rc" -ne 3 ] || tail -5 "$log" >&2
rm -f "$log"
return "$rc"
}
connect_wpa() {
local rc
stop_wpa_on_iface
write_wpa_conf
note "Checking the password"
rc=0
test_credentials || rc=$?
case $rc in
0) ;;
2) warn "Wrong password."; return 2 ;;
*) warn "Couldn't connect to \"$(printf '%s' "$SSID" | printable)\" (out of range, or wrong name?)."; return 1 ;;
esac
sleep 1
if command -v ifup >/dev/null && { command -v dhcpcd || command -v dhclient; } >/dev/null; then
dhcp_no_name on
# Debian's own way: ifupdown starts wpa_supplicant and DHCP at boot
drop_ifupdown_iface "$IF"
grep -Eqs '^[[:space:]]*source(-directory)?[[:space:]]+/etc/network/interfaces\.d' \
/etc/network/interfaces ||
printf '\nsource /etc/network/interfaces.d/*\n' >>/etc/network/interfaces
mkdir -p /etc/network/interfaces.d
cat >"/etc/network/interfaces.d/wren-wifi-$IF" <<EOF
# Written by Wren Linux wifi.sh
allow-hotplug $IF
iface $IF inet dhcp
wpa-conf $WPA_CONF
EOF
note "Saved for boot in /etc/network/interfaces.d/wren-wifi-$IF"
ifup "$IF"
else
# No ifupdown DHCP client: systemd units instead
systemctl enable --now "wpa_supplicant@$IF.service"
note "Saved for boot: wpa_supplicant@$IF.service + systemd-networkd"
networkd_dhcp "$IF" persist
fi
}
# keep_wired IFACE: IFACE (a wired port) comes up by itself at boot too, with
# DHCP through ifupdown, unless something already sets it up. (WREN_NETWORK_DIR
# is where /etc/network is, for tests.)
keep_wired() {
local ifc=$1 dir=${WREN_NETWORK_DIR:-/etc/network}
command -v ifup >/dev/null && { command -v dhcpcd || command -v dhclient; } >/dev/null || return 0
grep -Eqs "^[[:space:]]*(iface|allow-hotplug|auto)[[:space:]]+${ifc}([[:space:]]|$)" "$dir/interfaces" "$dir"/interfaces.d/* && return 0
grep -Eqs '^[[:space:]]*source(-directory)?[[:space:]]+/etc/network/interfaces\.d' "$dir/interfaces" ||
printf '\nsource /etc/network/interfaces.d/*\n' >>"$dir/interfaces"
mkdir -p "$dir/interfaces.d"
dhcp_no_name on
printf '%s\n' "# Written by Wren Linux wifi.sh: the cable on $ifc comes up by itself" \
"allow-hotplug $ifc" "iface $ifc inet dhcp" >"$dir/interfaces.d/wren-wired-$ifc"
note "Saved for boot in $dir/interfaces.d/wren-wired-$ifc"
}
# online_by_cable: with no Wi-Fi device at all, a wired port with a cable in
# (or a phone tethering over USB) is the way online: use it, and keep it for
# boot. Fails if there is no such port or it gets no address.
online_by_cable() {
local n
for n in $(wired_ifaces); do ip link set "$n" up 2>/dev/null || true; done
sleep 3
for n in $(wired_ifaces); do
[ "$(cat "/sys/class/net/$n/carrier" 2>/dev/null)" = 1 ] || continue
if ! { has_ipv4 "$n" && have_internet; }; then
note "No Wi-Fi device, but a cable on $n: getting an address"
dhcp_now "$n" && have_internet || continue
fi
keep_wired "$n"
say "Online through the cable on $n."
return 0
done
return 1
}
# -------------------------------------------------------------------- main
# No Wi-Fi device, but a cable: that is enough to go on. (A laptop whose Wi-Fi
# needs firmware can set it up later: run this again with the cable out.)
if [ -z "$(wifi_ifaces)" ] && online_by_cable; then
note "No Wi-Fi device was found here. If this computer has Wi-Fi (missing firmware is the"
note "usual cause), unplug the cable and run this again once you are set up."
exit 0
fi
find_iface
unblock_wifi
if has_ipv4 "$IF" && have_internet; then
say "Already online through $IF."
ask "Connect to a different network?" n || exit 0
fi
BACKEND=$(pick_backend)
if [ "$BACKEND" = none ]; then
get_wpa_supplicant
BACKEND=$(pick_backend)
[ "$BACKEND" != none ] || die "wpa_supplicant still isn't available."
ip link set "$IF" up 2>/dev/null || true
fi
note "Connecting with: $BACKEND"
ask_network
for attempt in 1 2 3; do
ask_password
rc=0
case $BACKEND in
nm) connect_nm || rc=$? ;;
iwd) connect_iwd || rc=$? ;;
wpa) connect_wpa || rc=$? ;;
esac
[ "$rc" -eq 0 ] && break
[ "$attempt" -lt 3 ] || die "Giving up after 3 tries."
if ! ask "Try again?" y; then exit 1; fi
if ask "Pick a different network?" n; then SSID=; ask_network; fi
done
say "Checking the connection"
for i in $(seq 15); do
if has_ipv4 "$IF" && have_internet; then
say "Online: $(printf '%s' "$SSID" | printable) on $IF ($(ip -4 -o addr show dev "$IF" scope global | awk '{ print $4; exit }'))"
exit 0
fi
sleep 1
done
if has_ipv4 "$IF"; then
die "Connected with an address, but names don't resolve. Check /etc/resolv.conf."
fi
die "Joined \"$(printf '%s' "$SSID" | printable)\" but got no address from it."