generated from ztimson/template
Compare commits
33
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1d7813acff | ||
|
|
4b2769aa98 | ||
|
|
d3e1ab52a9 | ||
|
|
14b08ed70b | ||
|
|
09387088df | ||
|
|
14b8eefb77 | ||
|
|
d1e1842052 | ||
|
|
ca3f2d15a8 | ||
|
|
1107712e75 | ||
|
|
bd19cc87e3 | ||
|
|
1e4b9394db | ||
|
|
b83210d0eb | ||
|
|
9d2987323e | ||
|
|
b319455ef3 | ||
|
|
825fe96dee | ||
|
|
8859375919 | ||
|
|
66b4cedd39 | ||
|
|
3243309bbb | ||
|
|
8b7702e90c | ||
|
|
ed20d616f5 | ||
|
|
0180f81420 | ||
|
|
a47fea04fb | ||
|
|
985c6b4ca7 | ||
|
|
6fca943d2c | ||
|
|
5c6862306d | ||
|
|
96c6b86d40 | ||
|
|
255385bf82 | ||
|
|
d0e2752cd8 | ||
|
|
8758a43088 | ||
|
|
4f79a652e5 | ||
|
|
6e201723d9 | ||
|
|
69372f0b94 | ||
|
|
63a3dec564 |
@@ -1,132 +1,86 @@
|
||||
<!-- Header -->
|
||||
<div id="top" align="center">
|
||||
<br />
|
||||
|
||||
|
||||
<!-- Logo -->
|
||||
<img src="https://git.zakscode.com/repo-avatars/2b4ee6ba1f2e2618bf7694e4a52fb56d1d0ea6abafa2dcbe496ab786b86d5a76" alt="Logo" width="200" height="200">
|
||||
<img src="https://git.zakscode.com/repo-avatars/80775ae9cf0d2c4fb4aec035efb650e631d4d0046ab6fc04c6c041e91ba11d44" alt="Logo" width="200" height="200">
|
||||
|
||||
<!-- Title -->
|
||||
### Template
|
||||
|
||||
### Fallback AP
|
||||
|
||||
<!-- Description -->
|
||||
Simple repository template
|
||||
Automatic fallback Wi-Fi access point for headless Linux devices
|
||||
|
||||
<!-- Repo badges -->
|
||||
[](https://git.zakscode.com/ztimson/template/tags)
|
||||
[](https://git.zakscode.com/ztimson/template/pulls)
|
||||
[](https://git.zakscode.com/ztimson/template/issues)
|
||||
[](https://git.zakscode.com/ztimson/fallback-ap/tags)
|
||||
[](https://git.zakscode.com/ztimson/fallback-ap/pulls)
|
||||
[](https://git.zakscode.com/ztimson/fallback-ap/issues)
|
||||
|
||||
<!-- Links -->
|
||||
|
||||
---
|
||||
<div>
|
||||
<a href="https://git.zakscode.com/ztimson/template/wiki" target="_blank">Documentation</a>
|
||||
• <a href="https://git.zakscode.com/ztimson/template/releases" target="_blank">Release Notes</a>
|
||||
• <a href="https://git.zakscode.com/ztimson/template/issues/new?template=.github%2fissue_template%2fbug.md" target="_blank">Report a Bug</a>
|
||||
• <a href="https://git.zakscode.com/ztimson/template/issues/new?template=.github%2fissue_template%2fenhancement.md" target="_blank">Request a Feature</a>
|
||||
<a href="https://git.zakscode.com/ztimson/fallback-ap/wiki" target="_blank">Documentation</a>
|
||||
• <a href="https://git.zakscode.com/ztimson/fallback-ap/releases" target="_blank">Release Notes</a>
|
||||
• <a href="https://git.zakscode.com/ztimson/fallback-ap/issues/new?template=.github%2fissue_template%2fbug.md" target="_blank">Report a Bug</a>
|
||||
• <a href="https://git.zakscode.com/ztimson/fallback-ap/issues/new?template=.github%2fissue_template%2fenhancement.md" target="_blank">Request a Feature</a>
|
||||
</div>
|
||||
|
||||
---
|
||||
</div>
|
||||
|
||||
## Table of Contents
|
||||
- [Template](#top)
|
||||
- [Fallback AP](#top)
|
||||
- [About](#about)
|
||||
- [Demo](#demo)
|
||||
- [Built With](#built-with)
|
||||
- [Setup](#setup)
|
||||
- [Production](#production)
|
||||
- [Development](#development)
|
||||
- [Installation](#installation)
|
||||
- [Usage](#usage)
|
||||
- [License](#license)
|
||||
|
||||
## About
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
|
||||
A NetworkManager-based script for headless Linux devices (e.g. a Raspberry Pi) that keeps them reachable even when their configured Wi-Fi network isn't available. When the device loses its Wi-Fi connection, it automatically stands up a local access point so you can connect directly and reconfigure it. While that access point has no clients connected, it periodically steps aside for a short window to let NetworkManager try reconnecting to a known network in the background, then brings the access point back up if nothing was found.
|
||||
|
||||
### Demo
|
||||
|
||||
Website: https://git.zakscode.com
|
||||
Runs as a `systemd` service and is managed entirely through `nmcli`/`iw` — no extra daemons (`hostapd`, `dnsmasq`, etc.) required.
|
||||
|
||||
### Built With
|
||||
[](https://angular.io/)
|
||||
[](https://www.android.com/)
|
||||
[](https://www.arduino.cc/)
|
||||
[](https://getbootstrap.com)
|
||||
[](https://en.cppreference.com/w/c/language)
|
||||
[](https://cplusplus.com/)
|
||||
[](https://dotnet.microsoft.com/)
|
||||
[](https://www.w3.org/Style/CSS/Overview.en.html)
|
||||
[](https://www.djangoproject.com/)
|
||||
[](https://docker.com/)
|
||||
[](https://www.electronjs.org/)
|
||||
[](https://firebase.google.com/)
|
||||
[](https://go.dev/)
|
||||
[](https://graphql.org/)
|
||||
[](https://developer.mozilla.org/en-US/docs/Glossary/HTML)
|
||||
[](https://java.com/)
|
||||
[](https://javascript.com/)
|
||||
[](https://jquery.com )
|
||||
[](https://laravel.com)
|
||||
[](https://www.linux.org/)
|
||||
[](https://git.zakscode.com/ztimson/momentum)
|
||||
[](https://www.mongodb.com/)
|
||||
[](https://www.mysql.com/)
|
||||
[](https://nestjs.com/)
|
||||
[](https://dotnet.microsoft.com/)
|
||||
[](https://nextjs.org/)
|
||||
[](https://www.nginx.com/)
|
||||
[](https://nodejs.org/)
|
||||
[](https://p5js.org/)
|
||||
[](https://www.php.net/)
|
||||
[](https://www.postgresql.org/)
|
||||
[](https://www.python.org/)
|
||||
[](https://reactjs.org/)
|
||||
[](https://redis.com/)
|
||||
[](https://sass-lang.com/)
|
||||
[](https://en.wikipedia.org/wiki/Shell_script)
|
||||
[](https://www.microsoft.com/en-ca/sql-server)
|
||||
[](https://www.sqlite.org/index.html)
|
||||
[](https://svelte.dev/)
|
||||
[](https://typescriptlang.org/)
|
||||
[](https://microsoft.com/windows)
|
||||
[](https://vitejs.dev/)
|
||||
[](https://vuejs.org/)
|
||||
[](https://www.linux.org/)
|
||||
|
||||
## Setup
|
||||
|
||||
<details>
|
||||
<details open>
|
||||
<summary>
|
||||
<h3 id="production" style="display: inline">
|
||||
Production
|
||||
<h3 id="installation" style="display: inline">
|
||||
Installation
|
||||
</h3>
|
||||
</summary>
|
||||
|
||||
#### Prerequisites
|
||||
- [Docker](https://docs.docker.com/install/)
|
||||
- A Linux device with a Wi-Fi adapter (`wlan0` by default) — a Raspberry Pi or similar SBC
|
||||
- `NetworkManager` and `iw` (installed automatically if missing)
|
||||
- Root access
|
||||
|
||||
#### Instructions
|
||||
1. Run the docker image: `docker run -p 80:80 git.zakscode.com/ztimson/template:latest`
|
||||
2. Open [http://localhost](http://localhost)
|
||||
1. Download & Install: `curl -sL https://git.zakscode.com/ztimson/fallback-ap/raw/branch/master/install.sh | sudo bash`
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
<h3 id="development" style="display: inline">
|
||||
Development
|
||||
<h3 id="usage" style="display: inline">
|
||||
Usage
|
||||
</h3>
|
||||
</summary>
|
||||
|
||||
#### Prerequisites
|
||||
- [Node.js](https://nodejs.org/en/download)
|
||||
Once installed, the service runs automatically and requires no further interaction. A few commands are available for manual control:
|
||||
|
||||
#### Instructions
|
||||
1. Install the dependencies: `npm install`
|
||||
2. Start the Angular server: `npm run start`
|
||||
3. Open [http://localhost:4200](http://localhost:4200)
|
||||
- `sudo ./pi-fallback-ap.sh status` — show service, connection, and access point state
|
||||
- `sudo ./pi-fallback-ap.sh start` / `stop` — manually bring the access point up or down
|
||||
- `sudo ./pi-fallback-ap.sh uninstall` — remove the service and access point profile
|
||||
|
||||
</details>
|
||||
|
||||
## License
|
||||
Copyright © 2023 Zakary Timson | All Rights Reserved | Available under MIT Licensing
|
||||
|
||||
See the [license](./LICENSE) for more information.
|
||||
Copyright © 2026 Zakary Timson | Available under MIT Licensing
|
||||
|
||||
+510
-75
@@ -3,16 +3,73 @@
|
||||
set -e
|
||||
|
||||
# Configuration =======================================================================================================
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# Everything else (timers, paths) is not meant to be reconfigured per-install
|
||||
# and stays as plain constants.
|
||||
|
||||
WLAN0_IFACE="wlan0"
|
||||
CONFIG_FILE="/etc/fallback-ap.conf"
|
||||
if [ -f "$CONFIG_FILE" ]; then source "$CONFIG_FILE"; fi
|
||||
|
||||
AP_NAME="${HOSTNAME}"
|
||||
AP_PASSWORD='guesswhatitis'
|
||||
AP_ENCRYPTION="wpa-psk" # none | wpa-psk | sae
|
||||
# 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}}"
|
||||
|
||||
AP_CIDR="10.10.10.1/24"
|
||||
DHCP_START="10.10.10.100"
|
||||
DHCP_END="10.10.10.250"
|
||||
# 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 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 - (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 (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.
|
||||
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
|
||||
|
||||
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}}"
|
||||
|
||||
# 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.
|
||||
@@ -26,11 +83,18 @@ 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="pi-fallback-ap"
|
||||
SERVICE_NAME="fallback-ap"
|
||||
SERVICE_FILE="/etc/systemd/system/$SERVICE_NAME.service"
|
||||
|
||||
STATE_DIR="/var/lib/pi-fallback-ap"
|
||||
STATE_DIR="/var/lib/fallback-ap"
|
||||
LAST_WIFI_FILE="$STATE_DIR/last-wifi"
|
||||
LOCK_FILE="$STATE_DIR/lock"
|
||||
STATE_FILE="$STATE_DIR/state"
|
||||
@@ -38,6 +102,18 @@ STATE_FILE="$STATE_DIR/state"
|
||||
# End of Configuration ================================================================================================
|
||||
|
||||
|
||||
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")"
|
||||
|
||||
@@ -68,17 +144,210 @@ 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
|
||||
}
|
||||
|
||||
|
||||
# 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
|
||||
# connected, excluding the fallback AP connection itself.
|
||||
# - 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
|
||||
|
||||
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}'
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
# 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<timeout; i++)); do
|
||||
if check_online; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
sleep 1
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
|
||||
# --- Wi-Fi bookkeeping -----------------------------------------------------------------------------------------------
|
||||
|
||||
get_wifi_connection() {
|
||||
nmcli -t -f DEVICE,TYPE,STATE,CONNECTION device status 2>/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}'
|
||||
}
|
||||
|
||||
|
||||
@@ -130,27 +399,14 @@ wait_for_ap_state() {
|
||||
}
|
||||
|
||||
|
||||
wait_for_wifi() {
|
||||
local timeout="$1"
|
||||
|
||||
for ((i=0; i<timeout*10; i++)); do
|
||||
if is_wifi_connected; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
sleep 0.1
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# --- AP control (always on $WLAN0_IFACE only) ------------------------------------------------------------------------
|
||||
|
||||
start_ap() {
|
||||
if is_ap_on; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Enabling AP: $AP_NAME"
|
||||
log "Enabling AP: $AP_NAME (on $WLAN0_IFACE)"
|
||||
|
||||
nmcli connection up "$CONNECTION_NAME" ifname "$WLAN0_IFACE" >/dev/null 2>&1 || {
|
||||
log "Failed to start AP"
|
||||
@@ -190,32 +446,31 @@ stop_ap() {
|
||||
}
|
||||
|
||||
|
||||
# ==========================================================================
|
||||
# One tick of the control loop. Called once per second, always while
|
||||
# holding the wlan0 lock, so everything here runs to completion before
|
||||
# the next tick starts - there is no interleaving to race against.
|
||||
#
|
||||
# `last_attempt` is intentionally not declared `local` here: it lives in
|
||||
# run()'s scope and this function updates it directly (bash resolves it
|
||||
# via the caller's scope as long as nothing else shadows it).
|
||||
# ==========================================================================
|
||||
# --- 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
|
||||
@@ -227,23 +482,24 @@ tick() {
|
||||
clients="$(get_client_count)"
|
||||
|
||||
if [ "$clients" -gt 0 ]; then
|
||||
# Someone is using the AP right now. Leave it alone and push the
|
||||
# next recovery attempt back so we don't kick them off.
|
||||
last_attempt="$now"
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
# Stop the AP, give NetworkManager a window to autoconnect to any known
|
||||
# network it can see, and bring the AP back if that doesn't happen.
|
||||
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"
|
||||
|
||||
@@ -251,52 +507,164 @@ 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
|
||||
}
|
||||
|
||||
|
||||
save_config() {
|
||||
mkdir -p "$(dirname "$CONFIG_FILE")"
|
||||
cat > "$CONFIG_FILE" <<EOF
|
||||
SAVED_IFACE="$WLAN0_IFACE"
|
||||
SAVED_MONITOR_IFACE="$MONITOR_IFACE"
|
||||
SAVED_IGNORE_IFACE="$IGNORE_IFACE"
|
||||
SAVED_CHECK_MODE="$CHECK_MODE"
|
||||
SAVED_CHECK_TARGETS="$CHECK_TARGETS"
|
||||
SAVED_SSID="$AP_NAME"
|
||||
SAVED_PASSWORD="$AP_PASSWORD"
|
||||
SAVED_ENCRYPTION="$AP_ENCRYPTION"
|
||||
SAVED_CIDR="$FALLBACK_AP_CIDR"
|
||||
SAVED_DHCP_START_OCTET="$FALLBACK_AP_DHCP_START_OCTET"
|
||||
SAVED_DHCP_END_OCTET="$FALLBACK_AP_DHCP_END_OCTET"
|
||||
EOF
|
||||
chmod 600 "$CONFIG_FILE"
|
||||
}
|
||||
|
||||
|
||||
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() {
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo "Run this script as root."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
parse_install_args "$@"
|
||||
|
||||
log "Installing NetworkManager fallback AP"
|
||||
|
||||
apt-get update
|
||||
apt-get install -y network-manager iw
|
||||
apt-get install -y network-manager iw iputils-ping
|
||||
|
||||
systemctl enable NetworkManager
|
||||
systemctl start NetworkManager
|
||||
|
||||
mkdir -p "$STATE_DIR"
|
||||
|
||||
if is_wifi_connected; then
|
||||
remember_wifi
|
||||
fi
|
||||
remember_wifi
|
||||
|
||||
create_connection
|
||||
save_config
|
||||
|
||||
log "Creating recovery service"
|
||||
|
||||
@@ -324,8 +692,17 @@ EOF
|
||||
|
||||
log "Installation complete"
|
||||
log "Script: $SCRIPT_PATH"
|
||||
log "Config file: $CONFIG_FILE"
|
||||
log "SSID: $AP_NAME"
|
||||
log "Interface: $WLAN0_IFACE"
|
||||
log "AP interface: $WLAN0_IFACE"
|
||||
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"
|
||||
if [ "$CHECK_MODE" = "internet" ]; then
|
||||
log "Ping targets: $CHECK_TARGETS"
|
||||
fi
|
||||
log "Network: $AP_CIDR"
|
||||
log "DHCP: $DHCP_START - $DHCP_END"
|
||||
log "Encryption: $AP_ENCRYPTION"
|
||||
@@ -411,6 +788,7 @@ uninstall() {
|
||||
nmcli connection delete "$CONNECTION_NAME" >/dev/null 2>&1 || true
|
||||
|
||||
rm -f "$SERVICE_FILE"
|
||||
rm -f "$CONFIG_FILE"
|
||||
rm -rf "$STATE_DIR"
|
||||
|
||||
systemctl daemon-reload
|
||||
@@ -460,6 +838,59 @@ status() {
|
||||
echo "=== Fallback AP ==="
|
||||
nmcli connection show "$CONNECTION_NAME" 2>/dev/null || echo "Not installed"
|
||||
|
||||
echo
|
||||
echo "=== Config ($CONFIG_FILE) ==="
|
||||
echo "SSID: $AP_NAME"
|
||||
echo "AP interface: $WLAN0_IFACE"
|
||||
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"
|
||||
if [ "$CHECK_MODE" = "internet" ]; then
|
||||
echo "Ping targets: $CHECK_TARGETS"
|
||||
fi
|
||||
echo "Network: $AP_CIDR"
|
||||
echo "DHCP: $DHCP_START - $DHCP_END"
|
||||
|
||||
echo
|
||||
echo "=== Connectivity ($CHECK_MODE) ==="
|
||||
local dev found=0
|
||||
while read -r dev; do
|
||||
if [ -z "$dev" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
found=1
|
||||
|
||||
if [ "$CHECK_MODE" = "internet" ]; then
|
||||
if iface_has_internet "$dev"; then
|
||||
echo "$dev: internet OK"
|
||||
else
|
||||
echo "$dev: connected, NO internet"
|
||||
fi
|
||||
else
|
||||
echo "$dev: connected"
|
||||
fi
|
||||
done < <(get_monitored_devices)
|
||||
|
||||
if [ "$found" -eq 0 ]; then
|
||||
echo "No monitored interface is connected"
|
||||
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 "=== Last Known Wi-Fi ==="
|
||||
get_last_wifi || echo "None"
|
||||
@@ -479,34 +910,36 @@ status() {
|
||||
|
||||
|
||||
run() {
|
||||
# These are shared with tick/update_online/attempt_recovery through
|
||||
# dynamic scoping.
|
||||
local last_attempt=0
|
||||
local last_online_check=0
|
||||
local fail_count=0
|
||||
local online=0
|
||||
local now
|
||||
|
||||
mkdir -p "$STATE_DIR"
|
||||
|
||||
log "Fallback AP service started"
|
||||
log "Script: $SCRIPT_PATH"
|
||||
|
||||
# Open the lock fd once for the life of the process. Using `flock`
|
||||
# directly against this fd (instead of wrapping each tick in its own
|
||||
# `( ... ) 9>"$LOCK_FILE"` subshell) matters: any variable a tick
|
||||
# updates - like last_attempt below - has to survive into the next
|
||||
# loop iteration, and a subshell would silently throw those updates
|
||||
# away when it exits.
|
||||
log "AP interface: $WLAN0_IFACE | monitoring: ${MONITOR_IFACE:-all real interfaces} | mode: $CHECK_MODE"
|
||||
exec 9>"$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
|
||||
@@ -530,7 +963,8 @@ fi
|
||||
|
||||
case "${1:-}" in
|
||||
install)
|
||||
install
|
||||
shift
|
||||
install "$@"
|
||||
;;
|
||||
|
||||
uninstall)
|
||||
@@ -554,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
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_URL="https://git.zakscode.com/ztimson/fallback-ap/raw/branch/master/fallback-ap.sh"
|
||||
INSTALL_PATH="/usr/local/bin/fallback-ap"
|
||||
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo "This installer needs to run as root, e.g.:"
|
||||
echo " curl -sL https://git.zakscode.com/ztimson/fallback-ap/raw/branch/master/install.sh | sudo bash"
|
||||
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/<iface>/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
|
||||
# shellcheck disable=SC1090
|
||||
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"
|
||||
|
||||
# --- 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
|
||||
Reference in New Issue
Block a user