#!/bin/sh # {{ ansible_managed }} # # Keeps the label print proxy reachable. # # Normally the Pi is a station on the home SSID. If that network is gone -- the # password changed, the AP died, the Pi was carried somewhere else -- it brings # up its own rescue access point so there is still a way in to reconfigure it. # It keeps checking, and hands the radio back the moment the home SSID returns. # # The 3B+ has one radio and brcmfmac will not hold an AP and a station link at # the same time, so this cannot listen for the home SSID while the AP is up. # Instead the AP drops for a few seconds every {{ labelprint_ap_rescan_secs }}s # to scan, and comes straight back if the home network is still missing. # # If you are working over the rescue AP and do not want the radio pulled out # from under your SSH session: # # touch /run/wifi-rescue.hold # # The hold expires by itself after {{ (labelprint_hold_max_age_secs / 60) | int }} minutes, so a forgotten hold file # cannot strand the Pi permanently. set -eu HOME_CON=home-wifi AP_CON=rescue-ap SSID='{{ stickah_ssid }}' AP_SSID='{{ stickah_ssid_rescue }}' IFACE=wlan0 HOLD=/run/wifi-rescue.hold STAMP=/run/wifi-rescue.ap-since RESCAN_SECS={{ labelprint_ap_rescan_secs }} HOLD_MAX_AGE={{ labelprint_hold_max_age_secs }} log() { logger -t wifi-rescue -- "$@"; } con_active() { nmcli -t -f NAME connection show --active | grep -qxF "$1"; } # A default IPv4 route is the honest test for "on a real network". The rescue # AP uses NetworkManager's shared mode, which hands out addresses but installs # no default route, so the AP can never make this look true. online() { [ -n "$(ip -4 route show default 2>/dev/null)" ]; } held() { [ -e "$HOLD" ] || return 1 age=$(( $(date +%s) - $(stat -c %Y "$HOLD" 2>/dev/null || echo 0) )) if [ "$age" -lt "$HOLD_MAX_AGE" ]; then return 0 fi log "hold file is ${age}s old; expiring it" rm -f "$HOLD" return 1 } start_ap() { con_active "$AP_CON" && return 0 log "starting rescue AP '$AP_SSID' on {{ labelprint_ap_addr }}" if nmcli --wait 20 connection up "$AP_CON" >/dev/null 2>&1; then date +%s > "$STAMP" else log "ERROR: rescue AP failed to start" fi } join_home() { # Bounded: the default 90s wait outlives the watchdog interval, and an # SSID that is not there is not going to appear in the next minute. # # --wait is a GLOBAL nmcli option and has to precede the subcommand. Written # as `connection up --wait 20` it is rejected outright with "invalid # extra argument" -- and only for a connection that exists, so it looks fine # against a typo'd name. That failure mode is silent and total: every join # returns failure and the rescue AP never starts. nmcli --wait 20 connection up "$HOME_CON" >/dev/null 2>&1 || return 1 online } held && exit 0 if online; then if con_active "$AP_CON"; then log "back on the network; shutting the rescue AP down" nmcli connection down "$AP_CON" >/dev/null 2>&1 || true rm -f "$STAMP" fi exit 0 fi if con_active "$AP_CON"; then since=$(cat "$STAMP" 2>/dev/null || echo 0) [ $(( $(date +%s) - since )) -lt "$RESCAN_SECS" ] && exit 0 log "rescue AP up for ${RESCAN_SECS}s; dropping it to scan for '$SSID'" nmcli connection down "$AP_CON" >/dev/null 2>&1 || true sleep 2 nmcli device wifi rescan ifname "$IFACE" >/dev/null 2>&1 || true sleep 5 scan=$(nmcli -t -f SSID device wifi list ifname "$IFACE" 2>/dev/null || true) if printf '%s\n' "$scan" | grep -qxF "$SSID"; then log "'$SSID' is back; rejoining" try=yes elif [ -z "$(printf '%s' "$scan" | tr -d '[:space:]')" ]; then # Nothing at all came back. Either the radio has not settled after # dropping the AP, or the home SSID is hidden and will never show up in # a scan. Worth 20 seconds to find out. log "scan came back empty; trying '$SSID' anyway" try=yes else # Other networks are visible and ours is not, so it really is gone. # Straight back to the AP -- no point spending the join timeout. try=no fi if [ "$try" = yes ]; then if join_home; then log "rejoined '$SSID'" rm -f "$STAMP" exit 0 fi log "join failed; returning to the rescue AP" fi start_ap exit 0 fi log "offline and no rescue AP; trying '$SSID'" if join_home; then log "joined '$SSID'" exit 0 fi start_ap