14 Commits

Author SHA1 Message Date
8859375919 Update fallback-ap.sh
All checks were successful
Code review / review (pull_request) Successful in 30s
2026-09-12 19:47:42 -04:00
66b4cedd39 Update install.sh 2026-09-12 19:47:26 -04:00
3243309bbb Merge pull request 'Update fallback-ap.sh' (#8) from ztimson-patch-8 into master
Reviewed-on: #8
2026-09-12 19:46:03 -04:00
8b7702e90c Update fallback-ap.sh
All checks were successful
Code review / review (pull_request) Successful in 35s
2026-09-12 19:45:56 -04:00
ed20d616f5 Merge pull request 'Update install.sh' (#7) from ztimson-patch-7 into master
Reviewed-on: #7
2026-09-12 19:40:29 -04:00
0180f81420 Update install.sh
All checks were successful
Code review / review (pull_request) Successful in 32s
2026-09-12 19:40:23 -04:00
a47fea04fb Merge pull request 'ztimson-patch-6' (#6) from ztimson-patch-6 into master
Reviewed-on: #6
2026-09-12 19:31:08 -04:00
985c6b4ca7 Update fallback-ap.sh
All checks were successful
Code review / review (pull_request) Successful in 32s
2026-09-12 19:30:45 -04:00
6fca943d2c Update install.sh 2026-09-12 19:30:06 -04:00
5c6862306d Merge pull request 'Update install.sh' (#5) from ztimson-patch-5 into master
Reviewed-on: #5
2026-09-12 19:22:30 -04:00
96c6b86d40 Update install.sh
All checks were successful
Code review / review (pull_request) Successful in 21s
2026-09-12 19:22:25 -04:00
255385bf82 Merge pull request 'Update README.md' (#4) from ztimson-patch-4 into master
Reviewed-on: #4
2026-09-12 19:20:50 -04:00
d0e2752cd8 Update README.md
All checks were successful
Code review / review (pull_request) Successful in 27s
2026-09-12 19:20:45 -04:00
8758a43088 Merge pull request 'Add install.sh' (#3) from ztimson-patch-3 into master
Reviewed-on: #3
2026-09-12 19:18:55 -04:00
3 changed files with 72 additions and 39 deletions

View File

@@ -3,7 +3,7 @@
<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 -->
### Fallback AP
@@ -63,9 +63,7 @@ Runs as a `systemd` service and is managed entirely through `nmcli`/`iw` — no
- Root access
#### Instructions
1. Download the script: `curl -sL https://git.zakscode.com/ztimson/fallback-ap/raw/branch/master/pi-fallback-ap.sh > /usr/local/bin/fallback-ap`
2. Make it executable: `chmod +x /usr/local/bin/fallback-ap`
3. Install and start the service: `sudo fallback-ap install`
1. Download & Install: `curl -sL https://git.zakscode.com/ztimson/fallback-ap/raw/branch/master/install.sh | sudo bash`
</details>

View File

@@ -3,16 +3,34 @@
set -e
# Configuration =======================================================================================================
#
# Connection settings (SSID, passphrase, subnet, DHCP range, interface) 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
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"
AP_NAME="${FALLBACK_AP_SSID:-${SAVED_SSID:-$HOSTNAME}}"
AP_PASSWORD="${FALLBACK_AP_PASSWORD:-${SAVED_PASSWORD:-abc123}}"
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}}"
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}"
# 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.
@@ -27,10 +45,10 @@ RECOVERY_WINDOW=20
STATE_SETTLE_DELAY=1
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"
@@ -189,16 +207,6 @@ stop_ap() {
return 0
}
# ==========================================================================
# 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).
# ==========================================================================
tick() {
local now="$1"
@@ -227,8 +235,6 @@ 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
@@ -239,9 +245,6 @@ tick() {
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)"
@@ -276,6 +279,21 @@ attempt_recovery() {
}
save_config() {
mkdir -p "$(dirname "$CONFIG_FILE")"
cat > "$CONFIG_FILE" <<EOF
SAVED_IFACE="$WLAN0_IFACE"
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"
}
install() {
if [ "$EUID" -ne 0 ]; then
echo "Run this script as root."
@@ -297,6 +315,7 @@ install() {
fi
create_connection
save_config
log "Creating recovery service"
@@ -324,6 +343,7 @@ EOF
log "Installation complete"
log "Script: $SCRIPT_PATH"
log "Config file: $CONFIG_FILE"
log "SSID: $AP_NAME"
log "Interface: $WLAN0_IFACE"
log "Network: $AP_CIDR"
@@ -411,6 +431,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 +481,13 @@ 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 "Interface: $WLAN0_IFACE"
echo "Network: $AP_CIDR"
echo "DHCP: $DHCP_START - $DHCP_END"
echo
echo "=== Last Known Wi-Fi ==="
get_last_wifi || echo "None"
@@ -486,13 +514,6 @@ run() {
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.
exec 9>"$LOCK_FILE"
flock 9

View File

@@ -2,7 +2,7 @@
set -e
SCRIPT_URL="https://git.zakscode.com/ztimson/fallback-ap/raw/branch/master/pi-fallback-ap.sh"
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
@@ -11,10 +11,24 @@ if [ "$EUID" -ne 0 ]; then
exit 1
fi
prompt() {
local __var="$1" __question="$2" __default="$3" __answer=""
if [ -e /dev/tty ]; then
read -r -p "$__question [$__default]: " __answer < /dev/tty || __answer=""
fi
printf -v "$__var" '%s' "${__answer:-$__default}"
}
[ -z "${FALLBACK_AP_SSID:-}" ] && prompt FALLBACK_AP_SSID "Access point SSID" "$(hostname)"
[ -z "${FALLBACK_AP_PASSWORD:-}" ] && prompt FALLBACK_AP_PASSWORD "Access point passphrase" "abc123"
[ -z "${FALLBACK_AP_CIDR:-}" ] && prompt FALLBACK_AP_CIDR "Access point subnet (CIDR)" "10.10.10.1/24"
[ -z "${FALLBACK_AP_DHCP_START_OCTET:-}" ] && prompt FALLBACK_AP_DHCP_START_OCTET "DHCP range start (last octet)" "100"
[ -z "${FALLBACK_AP_DHCP_END_OCTET:-}" ] && prompt FALLBACK_AP_DHCP_END_OCTET "DHCP range end (last octet)" "250"
export FALLBACK_AP_SSID FALLBACK_AP_PASSWORD FALLBACK_AP_CIDR FALLBACK_AP_DHCP_START_OCTET FALLBACK_AP_DHCP_END_OCTET
echo "Downloading fallback-ap..."
curl -fsSL "$SCRIPT_URL" -o "$INSTALL_PATH"
chmod +x "$INSTALL_PATH"
echo "Running installer..."
"$INSTALL_PATH" install