generated from ztimson/template
Update fallback-ap.sh
This commit is contained in:
+227
-30
@@ -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
|
# Which interfaces to watch for connectivity. If any of them is online, the
|
||||||
# AP stays off. Comma-separated list (e.g. "eth0,wlan1,wwan0").
|
# 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
|
# Note the missing colon in the expansion: an explicitly empty
|
||||||
# FALLBACK_AP_MONITOR_IFACE="" overrides a value saved in the config file.
|
# FALLBACK_AP_MONITOR_IFACE="" overrides a value saved in the config file.
|
||||||
MONITOR_IFACE="${FALLBACK_AP_MONITOR_IFACE-${SAVED_MONITOR_IFACE-}}"
|
MONITOR_IFACE="${FALLBACK_AP_MONITOR_IFACE-${SAVED_MONITOR_IFACE-}}"
|
||||||
MONITOR_IFACE="${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:
|
# 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
|
# 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}}"
|
CHECK_MODE="${FALLBACK_AP_CHECK_MODE:-${SAVED_CHECK_MODE:-link}}"
|
||||||
|
|
||||||
# Hosts pinged (space-separated, any one answering counts) in internet mode.
|
# 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_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}}"
|
FALLBACK_AP_DHCP_END_OCTET="${FALLBACK_AP_DHCP_END_OCTET:-${SAVED_DHCP_END_OCTET:-250}}"
|
||||||
|
|
||||||
AP_CIDR="$FALLBACK_AP_CIDR"
|
# Values derived from the CIDR / DHCP settings. A function, because the
|
||||||
AP_NETWORK_BASE="${AP_CIDR%%/*}" # e.g. "10.10.10.1" out of "10.10.10.1/24"
|
# `install` command-line flags can change those settings and need to
|
||||||
AP_NETWORK_BASE="${AP_NETWORK_BASE%.*}" # e.g. "10.10.10"
|
# recompute these.
|
||||||
DHCP_START="${AP_NETWORK_BASE}.${FALLBACK_AP_DHCP_START_OCTET}"
|
compute_derived() {
|
||||||
DHCP_END="${AP_NETWORK_BASE}.${FALLBACK_AP_DHCP_END_OCTET}"
|
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
|
# 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.
|
# NetworkManager a chance to reconnect to a known Wi-Fi network.
|
||||||
@@ -83,13 +102,17 @@ STATE_FILE="$STATE_DIR/state"
|
|||||||
# End of Configuration ================================================================================================
|
# End of Configuration ================================================================================================
|
||||||
|
|
||||||
|
|
||||||
case "$CHECK_MODE" in
|
validate_check_mode() {
|
||||||
link|internet) ;;
|
case "$CHECK_MODE" in
|
||||||
*)
|
link|internet) ;;
|
||||||
echo "Invalid FALLBACK_AP_CHECK_MODE '$CHECK_MODE' (use: link | internet)"
|
*)
|
||||||
exit 1
|
echo "Invalid check mode '$CHECK_MODE' (use: link | internet)"
|
||||||
;;
|
exit 1
|
||||||
esac
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_check_mode
|
||||||
|
|
||||||
# Resolve the actual location of this script.
|
# Resolve the actual location of this script.
|
||||||
SCRIPT_PATH="$(readlink -f "$0")"
|
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/<iface>/, 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
|
# Prints (one per line) the monitored devices that NetworkManager reports as
|
||||||
# connected, excluding the fallback AP connection itself.
|
# 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).
|
# - Otherwise: only the devices named in MONITOR_IFACE (any type).
|
||||||
# The state is matched with a prefix because nmcli reports values such as
|
# The state is matched with a prefix because nmcli reports values such as
|
||||||
# "connected (externally)" or "connected (site only)".
|
# "connected (externally)" or "connected (site only)".
|
||||||
get_monitored_devices() {
|
get_monitored_devices() {
|
||||||
|
local dev type
|
||||||
local all=0
|
local all=0
|
||||||
|
|
||||||
if monitor_all; then
|
if monitor_all; then
|
||||||
all=1
|
all=1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
nmcli -t -f DEVICE,TYPE,STATE,CONNECTION device status 2>/dev/null |
|
while IFS=: read -r dev type; do
|
||||||
awk -F: -v ap="$CONNECTION_NAME" -v list=",${MONITOR_IFACE}," -v all="$all" '
|
if [ -z "$dev" ]; then
|
||||||
$3 ~ /^connected/ && $4 != ap {
|
continue
|
||||||
if (all) {
|
fi
|
||||||
if ($2 ~ /^(ethernet|wifi|gsm|cdma|bond|vlan)$/) print $1
|
|
||||||
} else if (index(list, "," $1 ",")) {
|
if [ "$all" -eq 1 ]; then
|
||||||
print $1
|
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" <<EOF
|
cat > "$CONFIG_FILE" <<EOF
|
||||||
SAVED_IFACE="$WLAN0_IFACE"
|
SAVED_IFACE="$WLAN0_IFACE"
|
||||||
SAVED_MONITOR_IFACE="$MONITOR_IFACE"
|
SAVED_MONITOR_IFACE="$MONITOR_IFACE"
|
||||||
|
SAVED_IGNORE_IFACE="$IGNORE_IFACE"
|
||||||
SAVED_CHECK_MODE="$CHECK_MODE"
|
SAVED_CHECK_MODE="$CHECK_MODE"
|
||||||
SAVED_CHECK_TARGETS="$CHECK_TARGETS"
|
SAVED_CHECK_TARGETS="$CHECK_TARGETS"
|
||||||
SAVED_SSID="$AP_NAME"
|
SAVED_SSID="$AP_NAME"
|
||||||
@@ -469,12 +553,104 @@ EOF
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
usage_install() {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: $SCRIPT_PATH install [options]
|
||||||
|
|
||||||
|
Options (each overrides the matching FALLBACK_AP_* env var and any saved value):
|
||||||
|
--iface NAME Interface the AP is created on (default: wlan0)
|
||||||
|
--monitor-iface LIST Comma-separated interfaces to watch for connectivity.
|
||||||
|
Blank ("") monitors all REAL interfaces; virtual ones
|
||||||
|
(docker, veth, bridges, ...) are skipped (default: blank)
|
||||||
|
--ignore-iface LIST Comma-separated physical interfaces to ignore when
|
||||||
|
monitoring all; globs allowed, e.g. "enx*"
|
||||||
|
(default: none)
|
||||||
|
--check-mode MODE link | internet (default: link)
|
||||||
|
--check-targets "H.." Space-separated hosts to ping in internet mode
|
||||||
|
(default: "1.1.1.1 8.8.8.8")
|
||||||
|
--ssid NAME AP name (default: hostname)
|
||||||
|
--password PASS AP passphrase (default: abcd1234)
|
||||||
|
--encryption TYPE none | wpa-psk | sae (default: wpa-psk)
|
||||||
|
--cidr ADDR/MASK AP address and subnet (default: 10.10.10.1/24)
|
||||||
|
--dhcp-start OCTET First DHCP address, last octet (default: 100)
|
||||||
|
--dhcp-end OCTET Last DHCP address, last octet (default: 250)
|
||||||
|
-h, --help Show this help
|
||||||
|
|
||||||
|
Both "--opt value" and "--opt=value" are accepted. Precedence, highest first:
|
||||||
|
flags, FALLBACK_AP_* env vars, saved config file, built-in defaults.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
$SCRIPT_PATH install
|
||||||
|
$SCRIPT_PATH install --monitor-iface eth0,wwan0
|
||||||
|
$SCRIPT_PATH install --iface wlan1 --monitor-iface "" --check-mode internet
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
parse_install_args() {
|
||||||
|
local opt val
|
||||||
|
|
||||||
|
while [ $# -gt 0 ]; do
|
||||||
|
opt="$1"
|
||||||
|
|
||||||
|
case "$opt" in
|
||||||
|
-h|--help)
|
||||||
|
usage_install
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
--*=*)
|
||||||
|
val="${opt#*=}"
|
||||||
|
opt="${opt%%=*}"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--*)
|
||||||
|
if [ $# -lt 2 ]; then
|
||||||
|
echo "Option $opt requires a value."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
val="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown argument: $opt"
|
||||||
|
usage_install
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
case "$opt" in
|
||||||
|
--iface) WLAN0_IFACE="$val" ;;
|
||||||
|
--monitor-iface) MONITOR_IFACE="${val// /}" ;;
|
||||||
|
--ignore-iface) IGNORE_IFACE="${val// /}" ;;
|
||||||
|
--check-mode) CHECK_MODE="$val" ;;
|
||||||
|
--check-targets) CHECK_TARGETS="$val" ;;
|
||||||
|
--ssid) AP_NAME="$val" ;;
|
||||||
|
--password) AP_PASSWORD="$val" ;;
|
||||||
|
--encryption) AP_ENCRYPTION="$val" ;;
|
||||||
|
--cidr) FALLBACK_AP_CIDR="$val" ;;
|
||||||
|
--dhcp-start) FALLBACK_AP_DHCP_START_OCTET="$val" ;;
|
||||||
|
--dhcp-end) FALLBACK_AP_DHCP_END_OCTET="$val" ;;
|
||||||
|
*)
|
||||||
|
echo "Unknown option: $opt"
|
||||||
|
usage_install
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
validate_check_mode
|
||||||
|
compute_derived
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
install() {
|
install() {
|
||||||
if [ "$EUID" -ne 0 ]; then
|
if [ "$EUID" -ne 0 ]; then
|
||||||
echo "Run this script as root."
|
echo "Run this script as root."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
parse_install_args "$@"
|
||||||
|
|
||||||
log "Installing NetworkManager fallback AP"
|
log "Installing NetworkManager fallback AP"
|
||||||
|
|
||||||
apt-get update
|
apt-get update
|
||||||
@@ -519,7 +695,10 @@ EOF
|
|||||||
log "Config file: $CONFIG_FILE"
|
log "Config file: $CONFIG_FILE"
|
||||||
log "SSID: $AP_NAME"
|
log "SSID: $AP_NAME"
|
||||||
log "AP interface: $WLAN0_IFACE"
|
log "AP interface: $WLAN0_IFACE"
|
||||||
log "Monitored interfaces: ${MONITOR_IFACE:-all}"
|
log "Monitored interfaces: ${MONITOR_IFACE:-all real interfaces}"
|
||||||
|
if monitor_all && [ -n "$IGNORE_IFACE" ]; then
|
||||||
|
log "Ignored interfaces: $IGNORE_IFACE"
|
||||||
|
fi
|
||||||
log "Check mode: $CHECK_MODE"
|
log "Check mode: $CHECK_MODE"
|
||||||
if [ "$CHECK_MODE" = "internet" ]; then
|
if [ "$CHECK_MODE" = "internet" ]; then
|
||||||
log "Ping targets: $CHECK_TARGETS"
|
log "Ping targets: $CHECK_TARGETS"
|
||||||
@@ -663,7 +842,10 @@ status() {
|
|||||||
echo "=== Config ($CONFIG_FILE) ==="
|
echo "=== Config ($CONFIG_FILE) ==="
|
||||||
echo "SSID: $AP_NAME"
|
echo "SSID: $AP_NAME"
|
||||||
echo "AP interface: $WLAN0_IFACE"
|
echo "AP interface: $WLAN0_IFACE"
|
||||||
echo "Monitored interfaces: ${MONITOR_IFACE:-all}"
|
echo "Monitored interfaces: ${MONITOR_IFACE:-all real interfaces}"
|
||||||
|
if monitor_all && [ -n "$IGNORE_IFACE" ]; then
|
||||||
|
echo "Ignored interfaces: $IGNORE_IFACE"
|
||||||
|
fi
|
||||||
echo "Check mode: $CHECK_MODE"
|
echo "Check mode: $CHECK_MODE"
|
||||||
if [ "$CHECK_MODE" = "internet" ]; then
|
if [ "$CHECK_MODE" = "internet" ]; then
|
||||||
echo "Ping targets: $CHECK_TARGETS"
|
echo "Ping targets: $CHECK_TARGETS"
|
||||||
@@ -696,6 +878,19 @@ status() {
|
|||||||
echo "No monitored interface is connected"
|
echo "No monitored interface is connected"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "=== Connected but NOT counted (virtual / ignored / unmonitored) ==="
|
||||||
|
local monitored
|
||||||
|
monitored="$(get_monitored_devices)"
|
||||||
|
nmcli -t -f DEVICE,STATE,CONNECTION device status 2>/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
|
||||||
echo "=== Last Known Wi-Fi ==="
|
echo "=== Last Known Wi-Fi ==="
|
||||||
get_last_wifi || echo "None"
|
get_last_wifi || echo "None"
|
||||||
@@ -727,7 +922,7 @@ run() {
|
|||||||
|
|
||||||
log "Fallback AP service started"
|
log "Fallback AP service started"
|
||||||
log "Script: $SCRIPT_PATH"
|
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"
|
exec 9>"$LOCK_FILE"
|
||||||
|
|
||||||
flock 9
|
flock 9
|
||||||
@@ -768,7 +963,8 @@ fi
|
|||||||
|
|
||||||
case "${1:-}" in
|
case "${1:-}" in
|
||||||
install)
|
install)
|
||||||
install
|
shift
|
||||||
|
install "$@"
|
||||||
;;
|
;;
|
||||||
|
|
||||||
uninstall)
|
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
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
Reference in New Issue
Block a user