From 14b08ed70be6493926de3120725e33879875dada Mon Sep 17 00:00:00 2001 From: Zakary Timson Date: Mon, 26 Oct 2026 15:31:26 -0400 Subject: [PATCH 1/3] Update fallback-ap.sh --- fallback-ap.sh | 309 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 263 insertions(+), 46 deletions(-) diff --git a/fallback-ap.sh b/fallback-ap.sh index 9769ba8..c8ab983 100644 --- a/fallback-ap.sh +++ b/fallback-ap.sh @@ -4,8 +4,9 @@ set -e # Configuration ======================================================================================================= # -# Connection settings (SSID, passphrase, subnet, DHCP range, interface) can -# be supplied three ways, highest priority first: +# Connection settings (SSID, passphrase, subnet, DHCP range, interface, +# monitor interfaces, check mode) can be supplied three ways, highest +# priority first: # 1. Environment variables (FALLBACK_AP_*) - used for headless installs. # 2. The persisted config file below, written by `install`. # 3. The hard-coded defaults in this block. @@ -16,8 +17,27 @@ set -e CONFIG_FILE="/etc/fallback-ap.conf" if [ -f "$CONFIG_FILE" ]; then source "$CONFIG_FILE"; fi +# The interface the AP is created on. The AP is NEVER created on any other +# interface, regardless of what is being monitored. WLAN0_IFACE="${FALLBACK_AP_IFACE:-${SAVED_IFACE:-wlan0}}" +# Which interfaces to watch for connectivity. If any of them is online, the +# AP stays off. Comma-separated list (e.g. "eth0,wlan1,wwan0"). +# Leave BLANK to monitor ALL interfaces (ethernet, wifi, gsm, cdma, bond, vlan). +# Note the missing colon in the expansion: an explicitly empty +# FALLBACK_AP_MONITOR_IFACE="" overrides a value saved in the config file. +MONITOR_IFACE="${FALLBACK_AP_MONITOR_IFACE-${SAVED_MONITOR_IFACE-}}" +MONITOR_IFACE="${MONITOR_IFACE// /}" + +# What "online" means: +# link - a monitored interface is connected (fast, never flaps) +# internet - a monitored interface is connected AND can ping a target +# out of that specific interface (detects dead uplinks) +CHECK_MODE="${FALLBACK_AP_CHECK_MODE:-${SAVED_CHECK_MODE:-link}}" + +# Hosts pinged (space-separated, any one answering counts) in internet mode. +CHECK_TARGETS="${FALLBACK_AP_CHECK_TARGETS:-${SAVED_CHECK_TARGETS:-1.1.1.1 8.8.8.8}}" + AP_NAME="${FALLBACK_AP_SSID:-${SAVED_SSID:-$HOSTNAME}}" AP_PASSWORD="${FALLBACK_AP_PASSWORD:-${SAVED_PASSWORD:-abcd1234}}" AP_ENCRYPTION="${FALLBACK_AP_ENCRYPTION:-${SAVED_ENCRYPTION:-wpa-psk}}" # none | wpa-psk | sae @@ -44,6 +64,13 @@ RECOVERY_WINDOW=20 # we query it again. STATE_SETTLE_DELAY=1 +# Internet mode only: how often to run the ping checks, how many consecutive +# failed checks are needed before declaring "offline" (prevents the AP from +# flapping on a single dropped ping), and the per-ping timeout in seconds. +ONLINE_CHECK_INTERVAL=5 +FAIL_THRESHOLD=3 +PING_TIMEOUT=2 + CONNECTION_NAME="Fallback AP" SERVICE_NAME="fallback-ap" SERVICE_FILE="/etc/systemd/system/$SERVICE_NAME.service" @@ -56,6 +83,14 @@ STATE_FILE="$STATE_DIR/state" # End of Configuration ================================================================================================ +case "$CHECK_MODE" in + link|internet) ;; + *) + echo "Invalid FALLBACK_AP_CHECK_MODE '$CHECK_MODE' (use: link | internet)" + exit 1 + ;; +esac + # Resolve the actual location of this script. SCRIPT_PATH="$(readlink -f "$0")" @@ -86,17 +121,150 @@ is_ap_on() { } -is_wifi_connected() { - nmcli -t -f DEVICE,TYPE,STATE,CONNECTION device status 2>/dev/null | - awk -F: -v iface="$WLAN0_IFACE" -v ap="$CONNECTION_NAME" \ - '$1 == iface && $2 == "wifi" && $3 == "connected" && $4 != ap {found=1} END {exit !found}' +# --- Connectivity monitoring ----------------------------------------------------------------------------------------- + +# True if no monitor interface is configured (= monitor everything). +monitor_all() { + [ -z "$MONITOR_IFACE" ] } +# True if the AP interface is among the monitored interfaces. If it is not, +# a "recovery attempt" (taking the AP down to let wlan0 reconnect to Wi-Fi) +# could never change the result, so it is skipped. +monitors_wlan0() { + if monitor_all; then + return 0 + fi + + case ",${MONITOR_IFACE}," in + *",${WLAN0_IFACE},"*) return 0 ;; + esac + + return 1 +} + + +# Prints (one per line) the monitored devices that NetworkManager reports as +# connected, excluding the fallback AP connection itself. +# - Blank MONITOR_IFACE: every device of a real network type. +# - Otherwise: only the devices named in MONITOR_IFACE (any type). +# The state is matched with a prefix because nmcli reports values such as +# "connected (externally)" or "connected (site only)". +get_monitored_devices() { + local all=0 + + if monitor_all; then + all=1 + fi + + nmcli -t -f DEVICE,TYPE,STATE,CONNECTION device status 2>/dev/null | + awk -F: -v ap="$CONNECTION_NAME" -v list=",${MONITOR_IFACE}," -v all="$all" ' + $3 ~ /^connected/ && $4 != ap { + if (all) { + if ($2 ~ /^(ethernet|wifi|gsm|cdma|bond|vlan)$/) print $1 + } else if (index(list, "," $1 ",")) { + print $1 + } + }' +} + + +# Can this specific interface reach the internet? Pings are forced out of the +# given interface (-I) so another interface cannot answer on its behalf. +iface_has_internet() { + local dev="$1" + local target + + for target in $CHECK_TARGETS; do + if ping -I "$dev" -c 1 -W "$PING_TIMEOUT" "$target" >/dev/null 2>&1; then + return 0 + fi + done + + return 1 +} + + +# Is ANY monitored interface online right now (according to CHECK_MODE)? +check_online() { + local dev + + while read -r dev; do + if [ -z "$dev" ]; then + continue + fi + + if [ "$CHECK_MODE" = "link" ]; then + return 0 + fi + + if iface_has_internet "$dev"; then + return 0 + fi + done < <(get_monitored_devices) + + return 1 +} + + +# Debounced wrapper around check_online, used by the main loop. Updates the +# `online`, `fail_count` and `last_online_check` variables of run(). +# link mode: checked every tick, no debounce (cheap). +# internet mode: checked every ONLINE_CHECK_INTERVAL seconds, and only +# declared offline after FAIL_THRESHOLD consecutive failures. +# Coming back online is detected on the first success. +update_online() { + local now="$1" + local interval=0 + local threshold=1 + + if [ "$CHECK_MODE" = "internet" ]; then + interval="$ONLINE_CHECK_INTERVAL" + threshold="$FAIL_THRESHOLD" + fi + + if (( now - last_online_check < interval )); then + return 0 + fi + + last_online_check="$now" + + if check_online; then + fail_count=0 + online=1 + else + fail_count=$((fail_count + 1)) + + if (( fail_count >= threshold )); then + online=0 + fi + fi +} + + +wait_for_online() { + local timeout="$1" + local i + + for ((i=0; i/dev/null | awk -F: -v iface="$WLAN0_IFACE" \ - '$1 == iface && $2 == "wifi" && $3 == "connected" {print $4; exit}' + '$1 == iface && $2 == "wifi" && $3 ~ /^connected/ {print $4; exit}' } @@ -148,27 +316,14 @@ wait_for_ap_state() { } -wait_for_wifi() { - local timeout="$1" - - for ((i=0; i/dev/null 2>&1 || { log "Failed to start AP" @@ -207,23 +362,32 @@ stop_ap() { return 0 } + +# --- Main loop logic ------------------------------------------------------------------------------------------------- + +# Called once per second from run(). Relies on run()'s local variables +# (last_attempt, online, fail_count, last_online_check) via dynamic scoping. tick() { local now="$1" - if is_wifi_connected; then + update_online "$now" + + if (( online )); then remember_wifi if is_ap_on; then - stop_ap + log "Connectivity available, shutting down fallback AP" + stop_ap || true fi - set_state "wifi" + set_state "online" last_attempt="$now" return fi if ! is_ap_on; then - start_ap + log "No connectivity on monitored interface(s), starting fallback AP" + start_ap || true set_state "fallback" last_attempt="$now" return @@ -239,14 +403,20 @@ tick() { return fi + # Recovery only makes sense if wlan0 is one of the monitored interfaces: + # it works by briefly freeing wlan0 so it can reconnect to known Wi-Fi. + if ! monitors_wlan0; then + return + fi + if (( now - last_attempt >= RECOVERY_CHECK_INTERVAL )); then last_attempt="$now" - attempt_recovery + attempt_recovery || true fi } attempt_recovery() { - log "No AP clients, checking for known Wi-Fi (AP down for up to ${RECOVERY_WINDOW}s)" + log "No AP clients, checking for connectivity (AP down for up to ${RECOVERY_WINDOW}s)" set_state "checking" @@ -254,25 +424,27 @@ attempt_recovery() { if is_ap_on; then log "AP did not go down, aborting recovery attempt" - start_ap + start_ap || true set_state "fallback" return 1 fi - if wait_for_wifi "$RECOVERY_WINDOW"; then + if wait_for_online "$RECOVERY_WINDOW"; then sleep "$STATE_SETTLE_DELAY" - if is_wifi_connected; then - log "Wi-Fi reconnected" + if check_online; then + log "Connectivity restored" remember_wifi - set_state "wifi" + online=1 + fail_count=0 + set_state "online" return 0 fi fi - log "No Wi-Fi available, restoring AP" + log "No connectivity available, restoring AP" - start_ap + start_ap || true set_state "fallback" return 1 @@ -283,6 +455,9 @@ save_config() { mkdir -p "$(dirname "$CONFIG_FILE")" cat > "$CONFIG_FILE" <"$LOCK_FILE" flock 9 - if is_wifi_connected; then + if check_online; then + online=1 remember_wifi - set_state "wifi" - log "Wi-Fi already connected" + set_state "online" + log "Connectivity already available" else - log "No Wi-Fi connection, starting fallback AP" - start_ap + log "No connectivity on monitored interface(s), starting fallback AP" + start_ap || true set_state "fallback" fi + last_online_check="$(date +%s)" + flock -u 9 while true; do From d3e1ab52a9737c6c2b933f2ee1ba766d1022b806 Mon Sep 17 00:00:00 2001 From: Zakary Timson Date: Mon, 26 Oct 2026 15:38:30 -0400 Subject: [PATCH 2/3] Update fallback-ap.sh --- fallback-ap.sh | 257 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 227 insertions(+), 30 deletions(-) diff --git a/fallback-ap.sh b/fallback-ap.sh index c8ab983..05fcf61 100644 --- a/fallback-ap.sh +++ b/fallback-ap.sh @@ -23,16 +23,28 @@ WLAN0_IFACE="${FALLBACK_AP_IFACE:-${SAVED_IFACE:-wlan0}}" # Which interfaces to watch for connectivity. If any of them is online, the # AP stays off. Comma-separated list (e.g. "eth0,wlan1,wwan0"). -# Leave BLANK to monitor ALL interfaces (ethernet, wifi, gsm, cdma, bond, vlan). +# Leave BLANK to monitor all REAL interfaces: physical ethernet, wifi and +# cellular modems. Virtual interfaces (docker0, br-*, veth*, virbr*, tun/tap, +# wireguard, vlan, bridges, bonds, ...) are detected automatically and never +# count, so Docker/LXC/VM networking cannot keep the AP off. # Note the missing colon in the expansion: an explicitly empty # FALLBACK_AP_MONITOR_IFACE="" overrides a value saved in the config file. MONITOR_IFACE="${FALLBACK_AP_MONITOR_IFACE-${SAVED_MONITOR_IFACE-}}" MONITOR_IFACE="${MONITOR_IFACE// /}" +# Extra interfaces to ignore when monitoring all (comma-separated, globs +# allowed, e.g. "enx*,usb0"). Only needed for physical NICs you do not want +# to count; virtual interfaces are already excluded. Has no effect when +# MONITOR_IFACE names interfaces explicitly. +IGNORE_IFACE="${FALLBACK_AP_IGNORE_IFACE-${SAVED_IGNORE_IFACE-}}" +IGNORE_IFACE="${IGNORE_IFACE// /}" + # What "online" means: -# link - a monitored interface is connected (fast, never flaps) +# link - (default) a monitored interface is connected. An outage here is +# local and fixable; an upstream outage is not our problem, so +# the AP stays off. Fast and never flaps. # internet - a monitored interface is connected AND can ping a target -# out of that specific interface (detects dead uplinks) +# out of that specific interface (also detects dead uplinks) CHECK_MODE="${FALLBACK_AP_CHECK_MODE:-${SAVED_CHECK_MODE:-link}}" # Hosts pinged (space-separated, any one answering counts) in internet mode. @@ -46,11 +58,18 @@ FALLBACK_AP_CIDR="${FALLBACK_AP_CIDR:-${SAVED_CIDR:-10.10.10.1/24}}" FALLBACK_AP_DHCP_START_OCTET="${FALLBACK_AP_DHCP_START_OCTET:-${SAVED_DHCP_START_OCTET:-100}}" FALLBACK_AP_DHCP_END_OCTET="${FALLBACK_AP_DHCP_END_OCTET:-${SAVED_DHCP_END_OCTET:-250}}" -AP_CIDR="$FALLBACK_AP_CIDR" -AP_NETWORK_BASE="${AP_CIDR%%/*}" # e.g. "10.10.10.1" out of "10.10.10.1/24" -AP_NETWORK_BASE="${AP_NETWORK_BASE%.*}" # e.g. "10.10.10" -DHCP_START="${AP_NETWORK_BASE}.${FALLBACK_AP_DHCP_START_OCTET}" -DHCP_END="${AP_NETWORK_BASE}.${FALLBACK_AP_DHCP_END_OCTET}" +# Values derived from the CIDR / DHCP settings. A function, because the +# `install` command-line flags can change those settings and need to +# recompute these. +compute_derived() { + AP_CIDR="$FALLBACK_AP_CIDR" + AP_NETWORK_BASE="${AP_CIDR%%/*}" # e.g. "10.10.10.1" out of "10.10.10.1/24" + AP_NETWORK_BASE="${AP_NETWORK_BASE%.*}" # e.g. "10.10.10" + DHCP_START="${AP_NETWORK_BASE}.${FALLBACK_AP_DHCP_START_OCTET}" + DHCP_END="${AP_NETWORK_BASE}.${FALLBACK_AP_DHCP_END_OCTET}" +} + +compute_derived # How often, while the AP has no clients, to stop the AP and give # NetworkManager a chance to reconnect to a known Wi-Fi network. @@ -83,13 +102,17 @@ STATE_FILE="$STATE_DIR/state" # End of Configuration ================================================================================================ -case "$CHECK_MODE" in - link|internet) ;; - *) - echo "Invalid FALLBACK_AP_CHECK_MODE '$CHECK_MODE' (use: link | internet)" - exit 1 - ;; -esac +validate_check_mode() { + case "$CHECK_MODE" in + link|internet) ;; + *) + echo "Invalid check mode '$CHECK_MODE' (use: link | internet)" + exit 1 + ;; + esac +} + +validate_check_mode # Resolve the actual location of this script. SCRIPT_PATH="$(readlink -f "$0")" @@ -145,28 +168,88 @@ monitors_wlan0() { } +# A "real" interface is backed by hardware. Linux exposes this as a `device` +# link under /sys/class/net//, which physical NICs (PCI, USB, SDIO, +# virtio, ...) have and virtual ones (docker0, veth*, br-*, tun*, wg*, vlan, +# bridges, bonds, dummy) do not. Cellular modems are the exception: NetworkManager +# names them after their control port (e.g. cdc-wdm0), which is not in +# /sys/class/net, so gsm/cdma types are accepted as-is. +is_real_interface() { + local dev="$1" + local type="$2" + + case "$type" in + gsm|cdma) return 0 ;; + esac + + [ -e "/sys/class/net/$dev/device" ] +} + + +# True if the device matches an entry of IGNORE_IFACE (globs allowed). +is_ignored_interface() { + local dev="$1" + local pattern + local -a patterns=() + + IFS=',' read -r -a patterns <<< "$IGNORE_IFACE" || true + + for pattern in "${patterns[@]}"; do + if [ -n "$pattern" ] && [[ "$dev" == $pattern ]]; then + return 0 + fi + done + + return 1 +} + + # Prints (one per line) the monitored devices that NetworkManager reports as # connected, excluding the fallback AP connection itself. -# - Blank MONITOR_IFACE: every device of a real network type. +# - Blank MONITOR_IFACE: every real (hardware-backed) ethernet, wifi or +# cellular device that is not in IGNORE_IFACE. # - Otherwise: only the devices named in MONITOR_IFACE (any type). # The state is matched with a prefix because nmcli reports values such as # "connected (externally)" or "connected (site only)". get_monitored_devices() { + local dev type local all=0 if monitor_all; then all=1 fi - nmcli -t -f DEVICE,TYPE,STATE,CONNECTION device status 2>/dev/null | - awk -F: -v ap="$CONNECTION_NAME" -v list=",${MONITOR_IFACE}," -v all="$all" ' - $3 ~ /^connected/ && $4 != ap { - if (all) { - if ($2 ~ /^(ethernet|wifi|gsm|cdma|bond|vlan)$/) print $1 - } else if (index(list, "," $1 ",")) { - print $1 - } - }' + while IFS=: read -r dev type; do + if [ -z "$dev" ]; then + continue + fi + + if [ "$all" -eq 1 ]; then + case "$type" in + ethernet|wifi|gsm|cdma) ;; + *) continue ;; + esac + + if ! is_real_interface "$dev" "$type"; then + continue + fi + + if is_ignored_interface "$dev"; then + continue + fi + else + case ",${MONITOR_IFACE}," in + *",${dev},"*) ;; + *) continue ;; + esac + fi + + echo "$dev" + done < <( + nmcli -t -f DEVICE,TYPE,STATE,CONNECTION device status 2>/dev/null | + awk -F: -v ap="$CONNECTION_NAME" \ + '$3 ~ /^connected/ && $4 != ap {print $1 ":" $2}' + ) } @@ -456,6 +539,7 @@ save_config() { cat > "$CONFIG_FILE" </dev/null | + awk -F: -v ap="$CONNECTION_NAME" \ + '$2 ~ /^connected/ && $3 != ap {print $1}' | + while read -r dev; do + if ! grep -Fxq "$dev" <<< "$monitored"; then + echo "$dev" + fi + done + echo echo "=== Last Known Wi-Fi ===" get_last_wifi || echo "None" @@ -727,7 +922,7 @@ run() { log "Fallback AP service started" log "Script: $SCRIPT_PATH" - log "AP interface: $WLAN0_IFACE | monitoring: ${MONITOR_IFACE:-all} | mode: $CHECK_MODE" + log "AP interface: $WLAN0_IFACE | monitoring: ${MONITOR_IFACE:-all real interfaces} | mode: $CHECK_MODE" exec 9>"$LOCK_FILE" flock 9 @@ -768,7 +963,8 @@ fi case "${1:-}" in install) - install + shift + install "$@" ;; uninstall) @@ -792,7 +988,8 @@ case "${1:-}" in ;; *) - echo "Usage: $SCRIPT_PATH {install|uninstall|start|stop|status|run}" + echo "Usage: $SCRIPT_PATH {install [options]|uninstall|start|stop|status|run}" + echo " $SCRIPT_PATH install --help for install options" exit 1 ;; esac \ No newline at end of file From 4b2769aa9858f0099bddf01646aba7d5bb5baaa0 Mon Sep 17 00:00:00 2001 From: Zakary Timson Date: Mon, 26 Oct 2026 15:41:34 -0400 Subject: [PATCH 3/3] Update install.sh --- install.sh | 108 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 106 insertions(+), 2 deletions(-) diff --git a/install.sh b/install.sh index 57319c1..90e93e1 100644 --- a/install.sh +++ b/install.sh @@ -11,12 +11,57 @@ if [ "$EUID" -ne 0 ]; then exit 1 fi +# Ask a question; Enter accepts the default. prompt() { local __var="$1" __question="$2" __default="$3" __answer="" read -r -p "$__question [$__default]: " __answer < /dev/tty || __answer="" printf -v "$__var" '%s' "${__answer:-$__default}" } +# Like prompt, but "blank" is a legitimate value. Enter accepts the default; +# typing the clear word (e.g. "all" or "none") sets the value to empty. +prompt_blankable() { + local __var="$1" __question="$2" __default="$3" __clear="$4" __answer="" + read -r -p "$__question [${__default:-$__clear}]: " __answer < /dev/tty || __answer="" + + case "$__answer" in + "") __answer="$__default" ;; + "$__clear") __answer="" ;; + esac + + printf -v "$__var" '%s' "${__answer// /}" +} + +# Ask until the answer is one of the allowed choices. Enter accepts the default. +prompt_choice() { + local __var="$1" __question="$2" __default="$3" __answer="" __opt + shift 3 + + while true; do + read -r -p "$__question [$__default]: " __answer < /dev/tty || __answer="" + __answer="${__answer:-$__default}" + + for __opt in "$@"; do + if [ "$__answer" = "$__opt" ]; then + printf -v "$__var" '%s' "$__answer" + return 0 + fi + done + + echo " Please enter one of: $*" + done +} + +# Lists wireless interfaces (those with a /sys/class/net//wireless dir). +detect_wireless() { + local dir + for dir in /sys/class/net/*; do + if [ -d "$dir/wireless" ]; then + basename "$dir" + fi + done +} + EXISTING_CONFIG="/etc/fallback-ap.conf" if [ -f "$EXISTING_CONFIG" ]; then @@ -24,22 +69,81 @@ if [ -f "$EXISTING_CONFIG" ]; then source "$EXISTING_CONFIG" fi +DETECTED_WIFI="$(detect_wireless | tr '\n' ' ')" +DETECTED_WIFI="${DETECTED_WIFI% }" + DEFAULT_SSID="${SAVED_SSID:-$(hostname)}" DEFAULT_PASSWORD="${SAVED_PASSWORD:-abcd1234}" +DEFAULT_IFACE="${SAVED_IFACE:-${DETECTED_WIFI%% *}}" +DEFAULT_IFACE="${DEFAULT_IFACE:-wlan0}" DEFAULT_CIDR="${SAVED_CIDR:-10.10.10.1/24}" DEFAULT_DHCP_START_OCTET="${SAVED_DHCP_START_OCTET:-100}" DEFAULT_DHCP_END_OCTET="${SAVED_DHCP_END_OCTET:-250}" +DEFAULT_MONITOR_IFACE="${SAVED_MONITOR_IFACE-}" +DEFAULT_IGNORE_IFACE="${SAVED_IGNORE_IFACE-}" +DEFAULT_CHECK_MODE="${SAVED_CHECK_MODE:-link}" +DEFAULT_CHECK_TARGETS="${SAVED_CHECK_TARGETS:-1.1.1.1 8.8.8.8}" + +# --- Access point ---------------------------------------------------------------------------------------------------- [ -z "${FALLBACK_AP_SSID:-}" ] && prompt FALLBACK_AP_SSID "Access point SSID" "$DEFAULT_SSID" [ -z "${FALLBACK_AP_PASSWORD:-}" ] && prompt FALLBACK_AP_PASSWORD "Access point passphrase" "$DEFAULT_PASSWORD" + +if [ -z "${FALLBACK_AP_IFACE:-}" ]; then + if [ -n "$DETECTED_WIFI" ]; then + echo "Detected Wi-Fi interfaces: $DETECTED_WIFI" + fi + prompt FALLBACK_AP_IFACE "Interface to create the access point on" "$DEFAULT_IFACE" +fi + [ -z "${FALLBACK_AP_CIDR:-}" ] && prompt FALLBACK_AP_CIDR "Access point subnet (CIDR)" "$DEFAULT_CIDR" [ -z "${FALLBACK_AP_DHCP_START_OCTET:-}" ] && prompt FALLBACK_AP_DHCP_START_OCTET "DHCP range start (last octet)" "$DEFAULT_DHCP_START_OCTET" [ -z "${FALLBACK_AP_DHCP_END_OCTET:-}" ] && prompt FALLBACK_AP_DHCP_END_OCTET "DHCP range end (last octet)" "$DEFAULT_DHCP_END_OCTET" -export FALLBACK_AP_SSID FALLBACK_AP_PASSWORD FALLBACK_AP_CIDR FALLBACK_AP_DHCP_START_OCTET FALLBACK_AP_DHCP_END_OCTET +# --- Connectivity monitoring ----------------------------------------------------------------------------------------- +# +# The AP only starts when none of the monitored interfaces are online, and it +# is only ever created on the interface chosen above. +# +# These use "unset" (not "empty") to decide whether to ask, because an empty +# FALLBACK_AP_MONITOR_IFACE / FALLBACK_AP_IGNORE_IFACE is a valid answer +# meaning "all real interfaces" / "ignore nothing". +if [ -z "${FALLBACK_AP_MONITOR_IFACE+x}" ]; then + echo + echo "Which interfaces should count as 'online'? Enter a comma-separated list" + echo "(e.g. eth0,wlan0), or 'all' for every real interface. Virtual interfaces" + echo "(docker, veth, bridges, VPNs) are skipped automatically." + prompt_blankable FALLBACK_AP_MONITOR_IFACE "Interfaces to monitor" "$DEFAULT_MONITOR_IFACE" "all" +fi + +if [ -z "$FALLBACK_AP_MONITOR_IFACE" ] && [ -z "${FALLBACK_AP_IGNORE_IFACE+x}" ]; then + echo + echo "Any physical interfaces to ignore (globs allowed, e.g. enx*,usb0)?" + prompt_blankable FALLBACK_AP_IGNORE_IFACE "Interfaces to ignore" "$DEFAULT_IGNORE_IFACE" "none" +fi + +if [ -z "${FALLBACK_AP_CHECK_MODE:-}" ]; then + echo + echo "How should 'online' be determined?" + echo " link - the interface is connected (an upstream outage keeps the AP off)" + echo " internet - the interface is connected AND can ping out" + prompt_choice FALLBACK_AP_CHECK_MODE "Check mode" "$DEFAULT_CHECK_MODE" link internet +fi + +if [ "$FALLBACK_AP_CHECK_MODE" = "internet" ] && [ -z "${FALLBACK_AP_CHECK_TARGETS:-}" ]; then + prompt FALLBACK_AP_CHECK_TARGETS "Hosts to ping (space-separated)" "$DEFAULT_CHECK_TARGETS" +fi + +export FALLBACK_AP_SSID FALLBACK_AP_PASSWORD FALLBACK_AP_IFACE FALLBACK_AP_CIDR FALLBACK_AP_DHCP_START_OCTET FALLBACK_AP_DHCP_END_OCTET +export FALLBACK_AP_CHECK_MODE +[ -n "${FALLBACK_AP_MONITOR_IFACE+x}" ] && export FALLBACK_AP_MONITOR_IFACE +[ -n "${FALLBACK_AP_IGNORE_IFACE+x}" ] && export FALLBACK_AP_IGNORE_IFACE +[ -n "${FALLBACK_AP_CHECK_TARGETS+x}" ] && export FALLBACK_AP_CHECK_TARGETS + +echo echo "Downloading fallback-ap..." curl -fsSL "$SCRIPT_URL" -o "$INSTALL_PATH" chmod +x "$INSTALL_PATH" echo "Running installer..." -"$INSTALL_PATH" install +"$INSTALL_PATH" install \ No newline at end of file