Files
ansible-worker/files/files/scroll24.sh
ansible user 110301f862 first commit
2025-10-22 14:11:49 +02:00

372 lines
12 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/sh
# wifiscan24.sh — 2.4 GHz channel scout for ath1 (BusyBox-safe)
# Default: probe 1/6/11 (+ current), print recommendation, restore original.
#
# Flags:
# --goforit : scan + apply best, policy prefers 1/6/11, verify
# --force : as above, but pre-reset AP (disable/enable) before applying
# --allbymyself : try like --goforit; if apply fails, auto-bounce AP and retry once
# --keepbadchannels : allow 2–5 or 7–10 if they win (only matters with goforit/force/allbymyself)
# --chan <N> : override — switch directly to channel N (ignores scan & policy)
# works alone (no reset) or with --force (preflight reset)
# --deep : deeper scan (longer dwell); works with apply modes
#
# Score: score = 7*OBSSavg + 3*CUavg (lower is better)
# Ties: lower OBSSmax → lower (more negative) noise → lower CUavg
#
# Apply policy:
# By default apply only to {1,6,11}. To allow middle channels {2..5,7..10} add --keepbadchannels
sleep 20
IFACE="${IFACE:-ath1}"
CHANNELS="${CHANNELS:-1 6 11}"
CSA_BEACONS="${CSA_BEACONS:-10}" # CSA duration (~1s at default beacon interval)
WAIT_AFTER_SWITCH="${WAIT_AFTER_SWITCH:-2}" # settle time after CSA during scan/apply
SAMPLES="${SAMPLES:-5}" # samples per channel
SLEEP_BETWEEN="${SLEEP_BETWEEN:-1}" # seconds between samples
# Deep-scan presets (used only with --deep)
DEEP_WAIT="2"
DEEP_SAMPLES="10"
DEEP_SLEEP="1"
GOOD_CH_SET="1 6 11" # policy-preferred set
GOFORIT=0
FORCE=0
ALLBY=0
DEEP=0
KEEPBAD=0
OVERRIDE_CH=""
# ---- arg parse ----
while [ $# -gt 0 ]; do
case "$1" in
--goforit|--go|--apply) GOFORIT=1 ;;
--force) FORCE=1 ;;
--allbymyself) ALLBY=1 ;;
--deep) DEEP=1 ;;
--keepbadchannels) KEEPBAD=1 ;;
--chan)
shift
[ -n "$1" ] || { echo "Error: --chan requires a channel number." >&2; exit 1; }
OVERRIDE_CH="$1"
;;
-h|--help)
cat <<EOF
Usage: $0 [--goforit|--force|--allbymyself] [--keepbadchannels] [--deep] [--chan N]
Apply modes (choose one):
--goforit scan + apply best (policy prefers 1/6/11), verify
--force as above, but pre-reset AP before applying
--allbymyself like --goforit; if apply fails, auto-bounce AP and retry once
Extras:
--keepbadchannels allow 2–5/7–10 if they win (only with apply modes)
--deep deeper scan dwell (more stable numbers)
--chan N override: switch immediately to channel N (ignores scan & policy)
EOF
exit 0
;;
*)
echo "Unknown option: $1" >&2
exit 1
;;
esac
shift
done
# If multiple apply flags passed, pick strongest precedence: FORCE > ALLBY > GOFORIT
if [ "$FORCE" -eq 1 ]; then
GOFORIT=0
ALLBY=0
elif [ "$ALLBY" -eq 1 ]; then
GOFORIT=0
fi
# Deep dwell
if [ "$DEEP" -eq 1 ]; then
WAIT_AFTER_SWITCH="$DEEP_WAIT"
SAMPLES="$DEEP_SAMPLES"
SLEEP_BETWEEN="$DEEP_SLEEP"
fi
# ---- helpers ----
freq_from_ch() { ch="$1"; [ "$ch" -eq 14 ] && { echo 2484; return; }; echo $((2412 + 5*(ch-1))); }
ch_from_freq() { f="$1"; [ "$f" -eq 2484 ] && { echo 14; return; }; echo $(( (f-2412)/5 + 1 )); }
valid_ch() {
case "$1" in
''|*[!0-9]*) return 1 ;;
*) [ "$1" -ge 1 ] && [ "$1" -le 14 ] ;;
esac
}
in_good_set() {
case " $GOOD_CH_SET " in
*" $1 "*) return 0 ;;
*) return 1 ;;
esac
}
get_current_freq() {
f="$(hostapd_cli -i "$IFACE" status 2>/dev/null | awk -F= '/^freq=/{print $2; exit}')"
[ -n "$f" ] || f="$(wifitool "$IFACE" get_rrmutil 2>/dev/null | awk -F': *' '/^channel/{print $2; exit}')"
echo "$f"
}
measure_once() {
# prints: "chan_util obss_util noise"
wifitool "$IFACE" get_rrmutil 2>/dev/null | awk -F': *' '
/^chan util/ {cu=$2+0}
/^obss util/ {ob=$2+0}
/^noise floor/ {nf=$2+0}
END {printf "%d %d %d\n", cu, ob, nf}
'
}
# CSA wrapper (quiet)
do_csa() {
hostapd_cli -i "$IFACE" chan_switch "$CSA_BEACONS" "$1" ht bandwidth=20 blocktx >/dev/null 2>&1
}
# Verify helper
verify_freq() {
target="$1"
sleep "$WAIT_AFTER_SWITCH"
cur="$(get_current_freq)"
[ "$cur" = "$target" ]
}
# Self-bounce helper
ap_bounce() {
hostapd_cli -i "$IFACE" disable >/dev/null 2>&1
sleep 2
hostapd_cli -i "$IFACE" enable >/dev/null 2>&1
sleep 2
}
# ---- prereqs ----
command -v hostapd_cli >/dev/null 2>&1 || { echo "hostapd_cli not found"; exit 1; }
command -v wifitool >/dev/null 2>&1 || { echo "wifitool not found"; exit 1; }
orig_freq="$(get_current_freq)"; orig_ch="$(ch_from_freq "$orig_freq")"
# ---------- OVERRIDE PATH: --chan N ----------
if [ -n "$OVERRIDE_CH" ]; then
valid_ch "$OVERRIDE_CH" || { echo "Invalid channel: $OVERRIDE_CH (expected 1..14)"; exit 1; }
target_freq="$(freq_from_ch "$OVERRIDE_CH")"
if [ "$target_freq" = "$orig_freq" ]; then
echo "Already on channel $OVERRIDE_CH ($target_freq MHz). No switch needed."
exit 0
fi
if [ "$FORCE" -eq 1 ]; then
echo "Pre-resetting AP before override…"
ap_bounce
fi
echo "Applying override: hostapd_cli -i $IFACE chan_switch $CSA_BEACONS $target_freq ht bandwidth=20 blocktx"
do_csa "$target_freq"
if verify_freq "$target_freq"; then
echo "Switched to ch $OVERRIDE_CH ($target_freq MHz)."
exit 0
else
echo "Override failed to take effect. Try: --chan $OVERRIDE_CH --force"
exit 1
fi
fi
# ---------- SCAN PATH ----------
# unique set incl. current
unique=""; add(){ case " $unique " in *" $1 "*) : ;; *) unique="$unique $1";; esac; }
for c in $CHANNELS; do add "$c"; done; add "$orig_ch"
echo "Interface: $IFACE"
echo "Original: ch $orig_ch ($orig_freq MHz)"
[ "$DEEP" -eq 1 ] && echo "Mode: deep scan (WAIT=$WAIT_AFTER_SWITCH s, SAMPLES=$SAMPLES, SLEEP=$SLEEP_BETWEEN s)"
echo "Testing: $unique"
echo
tmp="$(mktemp)"
RESTORE=1
cleanup() {
if [ "$RESTORE" = 1 ]; then
do_csa "$orig_freq"
fi
rm -f "$tmp"
}
trap 'cleanup' EXIT INT TERM
measure_channel() {
ch="$1"; freq="$(freq_from_ch "$ch")"
curf="$(get_current_freq)"
if [ "$curf" != "$freq" ]; then
do_csa "$freq"
sleep "$WAIT_AFTER_SWITCH"
fi
cu_min=9999; cu_max=0; cu_sum=0
ob_min=9999; ob_max=0; ob_sum=0
nf_min=9999; nf_max=-9999; nf_sum=0
i=0
while [ "$i" -lt "$SAMPLES" ]; do
set -- $(measure_once) # $1=cu $2=ob $3=nf
cu="$1"; ob="$2"; nf="$3"
[ "$cu" -lt "$cu_min" ] && cu_min="$cu"; [ "$cu" -gt "$cu_max" ] && cu_max="$cu"; cu_sum=$((cu_sum+cu))
[ "$ob" -lt "$ob_min" ] && ob_min="$ob"; [ "$ob" -gt "$ob_max" ] && ob_max="$ob"; ob_sum=$((ob_sum+ob))
[ "$nf" -lt "$nf_min" ] && nf_min="$nf"; [ "$nf" -gt "$nf_max" ] && nf_max="$nf"; nf_sum=$((nf_sum+nf))
i=$((i+1))
[ "$i" -lt "$SAMPLES" ] && sleep "$SLEEP_BETWEEN"
done
cu_avg=$((cu_sum / SAMPLES)); ob_avg=$((ob_sum / SAMPLES)); nf_avg=$((nf_sum / SAMPLES))
printf "ch %2d (%4d MHz): OBSS min/avg/max=%3d/%3d/%3d CHutil min/avg/max=%3d/%3d/%3d Noise avg=%4d dBm\n" \
"$ch" "$freq" "$ob_min" "$ob_avg" "$ob_max" "$cu_min" "$cu_avg" "$cu_max" "$nf_avg"
# machine line: ch ob_avg cu_avg ob_max nf_avg
echo "__RESULT__ $ch $ob_avg $cu_avg $ob_max $nf_avg"
}
# Measure all
for ch in $unique; do
out="$(measure_channel "$ch")"
printf "%s\n" "$out" | head -n 1
printf "%s\n" "$out" | awk '/^__RESULT__/ {print}' >>"$tmp"
done
# Pick best overall and best among 1/6/11
best_any_ch=""; best_any_score=999999; best_any_obmax=9999; best_any_nf=0; best_any_cu=9999
best_good_ch=""; best_good_score=999999; best_good_obmax=9999; best_good_nf=0; best_good_cu=9999
while read -r _ ch ob cu obmax nf; do
score=$(( 7*ob + 3*cu ))
# overall best
if [ "$score" -lt "$best_any_score" ]; then
best_any_score="$score"; best_any_ch="$ch"; best_any_obmax="$obmax"; best_any_nf="$nf"; best_any_cu="$cu"
elif [ "$score" -eq "$best_any_score" ]; then
# Tie-breaker without command groups — ash-safe
if [ "$obmax" -lt "$best_any_obmax" ]; then
best_any_ch="$ch"; best_any_obmax="$obmax"; best_any_nf="$nf"; best_any_cu="$cu"
elif [ "$obmax" -eq "$best_any_obmax" ]; then
if [ "$nf" -lt "$best_any_nf" ]; then
best_any_ch="$ch"; best_any_obmax="$obmax"; best_any_nf="$nf"; best_any_cu="$cu"
elif [ "$nf" -eq "$best_any_nf" ] && [ "$cu" -lt "$best_any_cu" ]; then
best_any_ch="$ch"; best_any_obmax="$obmax"; best_any_nf="$nf"; best_any_cu="$cu"
fi
fi
fi
# best among 1/6/11
if in_good_set "$ch"; then
if [ "$score" -lt "$best_good_score" ]; then
best_good_score="$score"; best_good_ch="$ch"; best_good_obmax="$obmax"; best_good_nf="$nf"; best_good_cu="$cu"
elif [ "$score" -eq "$best_good_score" ]; then
# Tie-breaker without command groups — ash-safe
if [ "$obmax" -lt "$best_good_obmax" ]; then
best_good_ch="$ch"; best_good_obmax="$obmax"; best_good_nf="$nf"; best_good_cu="$cu"
elif [ "$obmax" -eq "$best_good_obmax" ]; then
if [ "$nf" -lt "$best_good_nf" ]; then
best_good_ch="$ch"; best_good_obmax="$obmax"; best_good_nf="$nf"; best_good_cu="$cu"
elif [ "$nf" -eq "$best_good_nf" ] && [ "$cu" -lt "$best_good_cu" ]; then
best_good_ch="$ch"; best_good_obmax="$obmax"; best_good_nf="$nf"; best_good_cu="$cu"
fi
fi
fi
fi
done <"$tmp"
best_any_freq="$(freq_from_ch "$best_any_ch")"
best_good_freq=""; [ -n "$best_good_ch" ] && best_good_freq="$(freq_from_ch "$best_good_ch")"
echo
echo ">>> Best by score: ch $best_any_ch ($best_any_freq MHz) [score=${best_any_score} = 7*OBSSavg + 3*CUavg]"
# Apply target by policy
apply_ch=""; apply_freq=""; note="policy prefers 1/6/11"
if [ "$KEEPBAD" -eq 1 ]; then
apply_ch="$best_any_ch"; apply_freq="$best_any_freq"; note="--keepbadchannels set (allow 2–5/7–10)"
else
if [ -n "$best_good_ch" ]; then
apply_ch="$best_good_ch"; apply_freq="$best_good_freq"
else
apply_ch="$best_any_ch"; apply_freq="$best_any_freq"; note="no scorable 1/6/11; falling back to overall best"
fi
fi
echo ">>> Policy target (apply): ch $apply_ch ($apply_freq MHz) [$note]"
[ "$apply_freq" = "$orig_freq" ] && APPLY_SAME=1 || APPLY_SAME=0
# ---------- APPLY SECTION ----------
if [ "$GOFORIT" -eq 1 ] || [ "$FORCE" -eq 1 ] || [ "$ALLBY" -eq 1 ]; then
if [ "$APPLY_SAME" -eq 1 ]; then
echo "Already on the policy target channel. No switch needed."
RESTORE=0
exit 0
fi
fi
# Common apply attempt
try_apply_once() {
tgt="$1"
echo "Applying (no reset): hostapd_cli -i $IFACE chan_switch $CSA_BEACONS $tgt ht bandwidth=20 blocktx"
do_csa "$tgt"
if verify_freq "$tgt"; then
return 0
fi
return 1
}
if [ "$FORCE" -eq 1 ]; then
echo "Pre-resetting AP (force)…"
ap_bounce
echo "Applying after reset: hostapd_cli -i $IFACE chan_switch $CSA_BEACONS $apply_freq ht bandwidth=20 blocktx"
do_csa "$apply_freq"
if verify_freq "$apply_freq"; then
echo "Switched to ch $apply_ch ($apply_freq MHz) after AP reset."
RESTORE=0
exit 0
else
echo "Unable to switch automatically at this time. Keeping the original channel."
exit 1
fi
elif [ "$ALLBY" -eq 1 ]; then
# First: normal CSA
if try_apply_once "$apply_freq"; then
echo "Switched to ch $apply_ch ($apply_freq MHz)."
RESTORE=0
exit 0
fi
# Second: bounce then retry once
echo "Apply failed; self-bouncing AP and retrying once…"
ap_bounce
if try_apply_once "$apply_freq"; then
echo "Switched to ch $apply_ch ($apply_freq MHz) after self-bounce."
RESTORE=0
exit 0
fi
echo "Retry after self-bounce also failed. Keeping the original channel."
exit 1
elif [ "$GOFORIT" -eq 1 ]; then
if try_apply_once "$apply_freq"; then
echo "Switched to ch $apply_ch ($apply_freq MHz)."
RESTORE=0
exit 0
fi
echo "Cannot switch now; the radio needs a brief reset. Re-run with --allbymyself or --force${KEEPBAD:+ --keepbadchannels}."
exit 1
fi
# ---------- SCAN-ONLY ----------
echo "Scan-only mode."
echo "To apply (prefer 1/6/11): $0 --goforit"
echo "To self-heal on failure: $0 --allbymyself"
echo "To apply with reset: $0 --force"
echo "To allow middle chans: add --keepbadchannels with one of the above"
echo "To override to a chan: $0 --chan N (optionally add --force)"