Benutzer-Werkzeuge

Webseiten-Werkzeuge


infrastruktur:openwrtone_skripte

openwrtone: Skripte und Anpassungen (Quelltexte)

Stand 10.10.2026. Alle Eigenbauten des Routers openwrtone (10.10.11.1, OpenWrt 25.12.5) mit Erklärung. Sie stehen alle in /etc/sysupgrade.conf und überstehen ein Update; das Skript anpassungen-anwenden stellt die Änderungen an Systemdateien nach einem Update wieder her. Passwörter und Webhook-IDs sind hier nicht enthalten (Webhook-ID im Watchdog als <WEBHOOK-ID> ersetzt, die anderen stehen in /etc/tunnel-hook und /etc/landal-hook auf dem Router).

Übersicht:

Datei Aufgabe
/usr/sbin/uplink-watchdog wählt den Uplink (Huawei-Stick oder Landal-WLAN) und setzt die Route zum Tunnel-Endpunkt
/usr/sbin/tunnel-failover schaltet zwischen Tunnel zur FRITZ!Box, Tunnel zum Proxmox und direktem Internet
/usr/sbin/uplink-test-run + CGI uplink-test manueller Geschwindigkeitstest beider Uplinks, Ergebnis wird übernommen
/usr/sbin/anpassungen-anwenden spielt die Änderungen an OpenWrt-Systemdateien (Landal-VHT-Abschaltung) nach Updates wieder ein
/usr/sbin/landal-vergleich + CGI misst HT gegen VHT am Landal-Client und schreibt die CSV
/usr/sbin/landal-auswertung wertet die CSV aus
Cron, supplicant.uc-Patch zeitgesteuerte Messung, täglicher Neustart, Landal erzwingt HT

Läuft als Dienst (procd). Prüft alle 10 s beide Uplinks per Ping, misst gelegentlich die Geschwindigkeit und setzt die Host-Route zum Tunnel-Endpunkt über den besseren Uplink. /tmp/uplink-mode (auto|huawei|landal) erzwingt einen Uplink, /tmp/uplink-test.pref enthält das Ergebnis des manuellen Tests. Meldet Wechsel per Webhook an HA Arcen.

/usr/sbin/uplink-watchdog
#!/bin/sh
# Haelt den WireGuard-Tunnel "fritzbox" auf dem besseren Uplink.
# Regel (seit 09.10.2026): Landal (wwan2), solange es schneller ist; Huawei (wwan),
# wenn Landal langsamer als der Huawei ist oder kein Internet hat. Faellt einer aus,
# laeuft der Tunnel ueber den anderen.
# Der gesamte LAN-Verkehr laeuft durch den Tunnel, deshalb genuegt es, die Route
# zum Tunnel-Endpunkt umzulegen.
# Zustand: cat /tmp/uplink-watchdog.state     Log: logread -e uplink-watchdog
 
. /lib/functions/network.sh
 
WG=fritzbox
HUAWEI=wwan
LANDAL=wwan2
TARGETS="1.1.1.1 8.8.8.8 9.9.9.9"
INTERVAL=10
DOWN_AFTER=3	# so viele Fehlversuche in Folge, dann gilt ein Uplink als gestoert
UP_AFTER=6	# so viele Erfolge in Folge, dann gilt er wieder als in Ordnung
RESOLVE_EVERY=30
STATE=/tmp/uplink-watchdog.state
# Vorgabe aus Home Assistant (auto|huawei|landal), geschrieben von /www/cgi-bin/uplink-mode.
# Fehlt die Datei (z. B. nach einem Neustart), gilt auto.
MODEFILE=/tmp/uplink-mode
# Home Assistant Arcen bekommt den aktuellen Uplink gemeldet (Automation "System: Router-Uplink uebernehmen")
HA_HOOK=http://10.10.11.12/api/webhook/router-uplink-<WEBHOOK-ID>
 
# Geschwindigkeitsvergleich (braucht das Paket curl; ohne curl bleibt es bei Landal, solange es Internet hat)
SPEED_HOST="http://speed.cloudflare.com/__down?bytes="
SPEED_EVERY=90		# alle 90 Durchlaeufe = 15 Minuten
PROBE_BYTES=1000000	# Landal allein: 1 MB (kostet kein Huawei-Volumen)
CMP_BYTES=300000	# Vergleich beider Leitungen: je 300 kB
FAST_KBS=1000		# ist Landal schneller als 1000 kB/s (8 Mbit/s), wird der Huawei nur
HUAWEI_EVERY=4		# bei jeder 4. Messung (stuendlich) mitgemessen, das spart Volumen:
			# ca. 7 MB pro Tag, sonst (Landal langsam) ca. 29 MB pro Tag
 
EP_HOST=$(uci -q get network.@wireguard_$WG[0].endpoint_host)
EP_PORT=$(uci -q get network.@wireguard_$WG[0].endpoint_port)
 
log() { logger -t uplink-watchdog "$*"; }
 
check() {
	local t
	for t in $TARGETS; do
		ping -c 1 -W 2 -I "$1" "$t" >/dev/null 2>&1 && return 0
	done
	return 1
}
 
# speed <geraet> <bytes>: Download-Geschwindigkeit in kB/s direkt ueber dieses Geraet (am Tunnel vorbei)
speed() {
	local s
	command -v curl >/dev/null || return 1
	s=$(curl -s -m 10 --interface "$1" -o /dev/null -w '%{speed_download}' "$SPEED_HOST$2" 2>/dev/null)
	s=${s%%.*}
	case "$s" in ''|*[!0-9]*) return 1 ;; esac
	echo $((s / 1000))
}
 
# track <h|l> <geraet> <gateway> <name>: Erreichbarkeit eines Uplinks mit Entprellung fuehren
track() {
	local up fails oks
	eval "up=\$${1}_up; fails=\$${1}_fails; oks=\$${1}_oks"
	if [ -n "$2" ] && [ -n "$3" ] && check "$2"; then
		fails=0
		oks=$((oks + 1))
		if [ $up = 0 ] && [ $oks -ge $UP_AFTER ]; then up=1; log "$4 hat wieder Internet"; fi
	else
		oks=0
		fails=$((fails + 1))
		if [ $up = 1 ] && [ $fails -ge $DOWN_AFTER ]; then up=0; log "$4 ohne Internet"; fi
	fi
	eval "${1}_up=$up; ${1}_fails=$fails; ${1}_oks=$oks"
}
 
h_up=1; h_fails=0; h_oks=0
l_up=1; l_fails=0; l_oks=0
pref=landal
sp=0
hkbs=""
lkbs=""
sent=""
cur=""
mode=auto
ep_ip=""
n=0
# Ergebnis des Knopfs "Uplinks jetzt testen" (geschrieben von /usr/sbin/uplink-test-run)
TESTFILE=/tmp/uplink-test.pref
TEST_HOLD=1800		# so lange (s) nach einem Test macht der Router keine eigene Messung
testnote=""
testtime=0
 
while :; do
	hdev=""; hgw=""; ldev=""; lgw=""
	network_flush_cache
	network_get_device hdev $HUAWEI
	network_get_gateway hgw $HUAWEI
	network_get_device ldev $LANDAL
	network_get_gateway lgw $LANDAL
 
	track h "$hdev" "$hgw" Huawei
	track l "$ldev" "$lgw" Landal
 
	# Ergebnis eines von Hand gestarteten Tests uebernehmen
	if [ -r $TESTFILE ]; then
		T_PREF=""; T_H=""; T_L=""; T_NOTE=""
		. $TESTFILE
		rm -f $TESTFILE
		if [ -n "$T_PREF" ]; then
			pref=$T_PREF
			[ -n "$T_H" ] && hkbs=$T_H
			[ -n "$T_L" ] && lkbs=$T_L
			testnote=$T_NOTE; testtime=$(date +%s)
			log "Test: bevorzugt jetzt $pref (Landal ${lkbs:-?} kB/s, Huawei ${hkbs:-?} kB/s)"
		fi
	fi
 
	# Geschwindigkeit: Landal jedes Mal (kostenlos), Huawei stuendlich oder wenn Landal nicht klar schnell ist
	if [ $((n % SPEED_EVERY)) = 3 ] && [ $(( $(date +%s) - testtime )) -ge $TEST_HOLD ] && command -v curl >/dev/null; then
		was=$pref
		sp=$((sp + 1))
		l=""; h=""
		if [ $l_up = 1 ] && [ -n "$lgw" ]; then l=$(speed "$ldev" $PROBE_BYTES) || l=0; fi
		if [ $h_up = 1 ] && [ -n "$hgw" ]; then
			if [ -z "$l" ] || [ "$l" -lt $FAST_KBS ] || [ -z "$hkbs" ] || [ $((sp % HUAWEI_EVERY)) = 0 ]; then
				h=$(speed "$hdev" $CMP_BYTES) || h=0
			fi
		fi
		if [ -n "$l" ] && [ -n "$h" ]; then
			# knapper Fall: Landal mit derselben Dateigroesse messen wie den Huawei
			if [ "$l" -lt $FAST_KBS ] && l2=$(speed "$ldev" $CMP_BYTES); then l=$l2; fi
			if [ $pref = landal ]; then
				# erst wechseln, wenn der Huawei deutlich (20 %) schneller ist
				[ "$h" -gt $((l * 12 / 10)) ] && pref=huawei
			else
				[ "$l" -gt "$h" ] && pref=landal
			fi
		fi
		[ -n "$l" ] && lkbs=$l
		[ -n "$h" ] && hkbs=$h
		[ $pref != $was ] && log "Bevorzugt jetzt $pref (Landal ${lkbs:-?} kB/s, Huawei ${hkbs:-?} kB/s)"
	fi
 
	# Vorgabe aus Home Assistant: fester Uplink oder auto. Auch bei fester Vorgabe gilt:
	# hat der gewaehlte Uplink kein Internet, laeuft der Tunnel ueber den anderen.
	m=auto
	[ -r $MODEFILE ] && read m < $MODEFILE
	case "$m" in huawei|landal) ;; *) m=auto ;; esac
	if [ "$m" != "$mode" ]; then log "Vorgabe jetzt: $m"; mode=$m; fi
	case $mode in
	huawei) order="h l" ;;
	landal) order="l h" ;;
	*) if [ $pref = landal ]; then order="l h"; else order="h l"; fi ;;
	esac
	use=""
	for x in $order; do
		eval "xup=\$${x}_up; xdev=\$${x}dev; xgw=\$${x}gw"
		if [ $xup = 1 ] && [ -n "$xgw" ]; then use=$x; dev=$xdev; gw=$xgw; break; fi
	done
	if [ -z "$use" ]; then
		# keiner gilt als in Ordnung: nimm den ersten, der ueberhaupt verbunden ist
		for x in $order; do
			eval "xdev=\$${x}dev; xgw=\$${x}gw"
			if [ -n "$xgw" ]; then use=$x; dev=$xdev; gw=$xgw; break; fi
		done
	fi
 
	peer=$(wg show $WG peers 2>/dev/null | head -n 1)
 
	# Endpunkt-Adresse der FRITZ!Box (MyFRITZ) regelmaessig neu aufloesen
	if [ -z "$ep_ip" ] || [ $((n % RESOLVE_EVERY)) = 0 ]; then
		new=$(resolveip -4 -t 5 "$EP_HOST" 2>/dev/null | head -n 1)
		[ -z "$new" ] && [ -z "$ep_ip" ] && new=$(wg show $WG endpoints 2>/dev/null | head -n 1 | cut -f 2 | cut -d : -f 1)
		if [ -n "$new" ] && [ "$new" != "$ep_ip" ]; then
			[ -n "$ep_ip" ] && ip route del "$ep_ip/32" 2>/dev/null
			ep_ip=$new
			[ -n "$peer" ] && wg set $WG peer "$peer" endpoint "$ep_ip:$EP_PORT"
			log "Tunnel-Endpunkt ist $ep_ip"
		fi
	fi
	n=$((n + 1))
 
	if [ -n "$use" ] && [ -n "$ep_ip" ]; then
		case "$(ip -4 route show "$ep_ip/32" | head -n 1)" in
		*"via $gw dev $dev"*) ;;
		*)
			ip route replace "$ep_ip/32" via "$gw" dev "$dev"
			# alte NAT-Zuordnung des Tunnels verwerfen, sonst geht er mit falscher Absenderadresse raus
			echo "$ep_ip" > /proc/net/nf_conntrack 2>/dev/null
			;;
		esac
		if [ "$use" != "$cur" ]; then
			log "Tunnel laeuft jetzt ueber $([ $use = h ] && echo Huawei || echo Landal) ($dev, Gateway $gw)"
			cur=$use
		fi
	fi
 
	case "$cur" in
	l)
		label=Landal
		if [ $mode = landal ]; then
			grund="Manuell eingestellt"
		elif [ $mode = auto ] && [ $pref = landal ]; then
			grund="Landal schneller"
		elif [ $h_up = 0 ]; then
			grund="Huawei ohne Internet"
		else
			grund="Huawei nicht verbunden"
		fi
		;;
	h)
		label=Huawei
		if [ $mode = huawei ]; then
			grund="Manuell eingestellt"
		elif [ $mode = auto ] && [ $pref = huawei ]; then
			grund="Landal langsamer"
		elif [ -z "$lgw" ]; then
			grund="Landal nicht verbunden"
		else
			grund="Landal ohne Internet"
		fi
		;;
	*)
		label="Kein Uplink"
		grund="Router hat keine Verbindung"
		;;
	esac
 
	if [ -n "$testnote" ]; then
		if [ $(( $(date +%s) - testtime )) -lt $TEST_HOLD ]; then grund="$grund ($testnote)"; else testnote=""; fi
	fi
 
	hs=$(wg show $WG latest-handshakes 2>/dev/null | head -n 1 | cut -f 2)
	now=$(date +%s)
	{
		echo "uplink=$label"
		echo "vorgabe=$mode"
		echo "bevorzugt=$pref"
		echo "huawei_internet=$h_up"
		echo "landal_internet=$l_up"
		echo "huawei_kbs=${hkbs:--}"
		echo "landal_kbs=${lkbs:--}"
		echo "grund=$grund"
		echo "endpunkt=$ep_ip"
		echo "handshake_vor_s=$([ -n "$hs" ] && [ "$hs" != 0 ] && echo $((now - hs)) || echo -)"
		echo "geprueft=$(date '+%d.%m.%Y %H:%M:%S')"
	} > $STATE.tmp && mv $STATE.tmp $STATE
 
	# bei Aenderung sofort an Home Assistant melden, sonst alle 5 Minuten zur Auffrischung
	msg="$label|$grund|$hkbs|$lkbs"
	if [ "$msg" != "$sent" ] || [ $((n % RESOLVE_EVERY)) = 0 ]; then
		wget -q -T 5 -O /dev/null --post-data="uplink=$(echo "$label" | tr ' ' '+')&grund=$(echo "$grund" | tr ' ' '+')&huawei=$hkbs&landal=$lkbs" "$HA_HOOK" 2>/dev/null && sent=$msg
	fi
 
	sleep $INTERVAL
done

Startet den Watchdog beim Booten.

/etc/init.d/uplink-watchdog
#!/bin/sh /etc/rc.common
# Uplink-Umschaltung Huawei/Landal fuer den WireGuard-Tunnel
 
START=99
USE_PROCD=1
 
start_service() {
	procd_open_instance
	procd_set_param command /usr/sbin/uplink-watchdog
	procd_set_param respawn 3600 5 0
	procd_set_param stderr 1
	procd_close_instance
}

Tunnel-Failover

Drei Stufen: A = WireGuard zur FRITZ!Box, B = WireGuard zum Proxmox (pve), C = direkt ins Internet ohne VPN. Mit Hysterese (3 Fehlschläge → abwärts, 6 Erfolge → zurück zur besseren Stufe). Prüft über die jeweiligen Policy-Routing-Tabellen (100/101). Zustand in /tmp/tunnel-failover.state, Meldung per Webhook (/etc/tunnel-hook) an HA.

/usr/sbin/tunnel-failover
#!/bin/sh
# Tunnel-Umschaltung fuer den Router in Arcen (angelegt 10.10.2026).
#
# Stufe A: Tunnel "fritzbox" (FRITZ!Box Raderbroich)          = Normalbetrieb
# Stufe B: Tunnel "pve" (WireGuard-Server in Proxmox)         = wenn A nicht geht
# Stufe C: direkt ins Internet ueber Huawei/Landal, ohne VPN  = wenn weder A noch B gehen
# Zurueck zu A bzw. B, sobald der Tunnel wieder stabil geht (6 Pruefungen in Folge, ca. 60 s).
#
# Geprueft wird je Tunnel per Ping durch genau diesen Tunnel (Policy-Routing nach Quelladresse),
# unabhaengig davon, welche Stufe gerade aktiv ist:
#   fritzbox: 192.168.192.1 (FRITZ!Box) und 1.0.0.1 (Internet) mit Quelle 10.10.11.1
#   pve:      10.10.20.1 (Server) und 1.0.0.1 (Internet) mit Quelle 10.10.20.2
# Aufruf mit DRY=1 schreibt nur ins Log und aendert keine Routen.
 
WG1=fritzbox
WG2=pve
SRC1=10.10.11.1
SRC2=10.10.20.2
LAN_PROBE=192.168.192.1
NET_PROBE=1.0.0.1
SRV_PROBE=10.10.20.1
INTERVAL=10
FAIL_N=3
OK_N=6
PVE_PORT=51820
STATE=/tmp/tunnel-failover.state
SAVED=/tmp/tunnel-failover.routes
UPSTATE=/tmp/uplink-watchdog.state
DRY=${DRY:-0}
 
log() { logger -t tunnel-failover "$*"; echo "$(date +%H:%M:%S) $*"; }
 
ping_via() { ping -c1 -W2 -I "$1" "$2" >/dev/null 2>&1; }
 
ensure_rules() {
	ip rule | grep -q "from $SRC1 to $LAN_PROBE lookup 100" || ip rule add from $SRC1 to $LAN_PROBE lookup 100 priority 100
	ip rule | grep -q "from $SRC1 to $NET_PROBE lookup 100" || ip rule add from $SRC1 to $NET_PROBE lookup 100 priority 101
	ip rule | grep -q "from $SRC2 lookup 101" || ip rule add from $SRC2 lookup 101 priority 102
	ip route replace default dev $WG1 table 100 2>/dev/null
	ip route replace default dev $WG2 table 101 2>/dev/null
}
 
save_routes() {
	# Routen des FRITZ!Box-Tunnels merken, solange Stufe A aktiv ist
	r=$(ip route show dev $WG1 | grep -v "proto kernel")
	[ -n "$r" ] && echo "$r" > $SAVED
}
 
restore_routes() {
	[ -f $SAVED ] || return
	while read -r line; do
		[ -n "$line" ] && ip route replace $line dev $WG1 2>/dev/null
	done < $SAVED
}
 
drop_default() { ip route del default dev $1 2>/dev/null; ip route del default via "$2" dev "$3" metric 5 2>/dev/null; }
 
direct_gw() {
	# Uplink, den uplink-watchdog gerade bevorzugt (Landal oder Huawei)
	u=$(grep '^uplink=' $UPSTATE 2>/dev/null | cut -d= -f2)
	case "$u" in
		Landal) DDEV=phy1-sta0;;
		*)      DDEV=phy0-sta0;;
	esac
	DGW=$(ip route show default dev $DDEV 2>/dev/null | awk '/via/{print $3; exit}')
	if [ -z "$DGW" ]; then
		# anderen Uplink versuchen
		[ "$DDEV" = phy1-sta0 ] && DDEV=phy0-sta0 || DDEV=phy1-sta0
		DGW=$(ip route show default dev $DDEV 2>/dev/null | awk '/via/{print $3; exit}')
	fi
}
 
apply_mode() {
	# $1 = A|B|C, $2 = LAN-Sonde ueber fritzbox ok (1/0)
	[ "$DRY" = 1 ] && return
	case "$1" in
	A)
		ip route del default dev $WG2 2>/dev/null
		ip route del 192.168.192.0/24 dev $WG2 2>/dev/null
		direct_gw; [ -n "$DGW" ] && ip route del default via "$DGW" dev "$DDEV" metric 5 2>/dev/null
		ip route show default dev $WG1 | grep -q . || restore_routes
		save_routes
		;;
	B)
		# FRITZ!Box-Tunnel entlasten, Standard und Raderbroich-LAN ueber pve
		ip route del default dev $WG1 2>/dev/null
		ip route del 192.168.192.0/24 dev $WG1 2>/dev/null
		direct_gw; [ -n "$DGW" ] && ip route del default via "$DGW" dev "$DDEV" metric 5 2>/dev/null
		ip route replace default dev $WG2 2>/dev/null
		ip route replace 192.168.192.0/24 dev $WG2 2>/dev/null
		;;
	C)
		ip route del default dev $WG1 2>/dev/null
		ip route del default dev $WG2 2>/dev/null
		ip route del 192.168.192.0/24 dev $WG2 2>/dev/null
		# Raderbroich-LAN ueber den FRITZ!Box-Tunnel nur behalten, wenn er das LAN noch erreicht
		if [ "$2" = 1 ]; then ip route replace 192.168.192.0/24 dev $WG1 proto static scope link 2>/dev/null; else ip route del 192.168.192.0/24 dev $WG1 2>/dev/null; fi
		direct_gw
		[ -n "$DGW" ] && ip route replace default via "$DGW" dev "$DDEV" metric 5
		;;
	esac
}
 
HOOKFILE=/etc/tunnel-hook
report() {
	[ "$DRY" = 1 ] && return
	[ -f $HOOKFILE ] || return
	H=$(cat $HOOKFILE)
	wget -q -T 6 -O /dev/null --post-data="stufe=$mode&info=fritzbox:$f_state,+pve:$p_state" "$H" 2>/dev/null
}
 
sync_pve_endpoint() {
	[ "$DRY" = 1 ] && return
	ep=$(wg show $WG1 endpoints 2>/dev/null | head -n 1 | cut -f2 | cut -d: -f1)
	pk=$(wg show $WG2 peers 2>/dev/null | head -n 1)
	cur=$(wg show $WG2 endpoints 2>/dev/null | head -n 1 | cut -f2 | cut -d: -f1)
	[ -n "$ep" ] && [ -n "$pk" ] && [ "$ep" != "$cur" ] && wg set $WG2 peer "$pk" endpoint "$ep:$PVE_PORT" && log "Endpunkt $WG2 auf $ep gesetzt"
}
 
cleanup() { log "beendet, stelle Stufe A her"; [ "$DRY" = 1 ] || apply_mode A 1; exit 0; }
trap cleanup TERM INT
 
f_state=up; p_state=down
f_bad=0; f_good=0; p_bad=0; p_good=0
mode=A
last_reported=""
rcount=0
since=$(date +%s)
log "gestartet (DRY=$DRY)"
 
while true; do
	ensure_rules
	sync_pve_endpoint
 
	# Pruefung fritzbox
	f_lan=0
	if ping_via $SRC1 $LAN_PROBE; then f_lan=1; fi
	if [ $f_lan = 1 ] && ping_via $SRC1 $NET_PROBE; then f_probe=1; else f_probe=0; fi
	# Pruefung pve
	if ping_via $SRC2 $SRV_PROBE && ping_via $SRC2 $NET_PROBE; then p_probe=1; else p_probe=0; fi
 
	# Hysterese fritzbox
	if [ $f_probe = 1 ]; then f_good=$((f_good+1)); f_bad=0; else f_bad=$((f_bad+1)); f_good=0; fi
	if [ $f_state = up ] && [ $f_bad -ge $FAIL_N ]; then f_state=down; log "$WG1 gestoert (LAN=$f_lan)"; fi
	if [ $f_state = down ] && [ $f_good -ge $OK_N ]; then f_state=up; log "$WG1 wieder stabil"; fi
	# Hysterese pve
	if [ $p_probe = 1 ]; then p_good=$((p_good+1)); p_bad=0; else p_bad=$((p_bad+1)); p_good=0; fi
	if [ $p_state = up ] && [ $p_bad -ge $FAIL_N ]; then p_state=down; log "$WG2 gestoert"; fi
	if [ $p_state = down ] && [ $p_good -ge $OK_N ]; then p_state=up; log "$WG2 wieder stabil"; fi
 
	if [ $f_state = up ]; then want=A
	elif [ $p_state = up ]; then want=B
	else want=C; fi
 
	if [ "$want" != "$mode" ]; then
		case $want in
			A) log "Stufe A: Tunnel $WG1 (FRITZ!Box)";;
			B) log "Stufe B: Tunnel $WG2 (Server in Proxmox), $WG1 ausgefallen";;
			C) log "Stufe C: direkt ins Internet ohne VPN, beide Tunnel ausgefallen";;
		esac
		mode=$want; since=$(date +%s)
	fi
	apply_mode $mode $f_lan
 
	rcount=$((rcount+1))
	if [ "$mode" != "$last_reported" ] || [ $rcount -ge 30 ]; then report; last_reported=$mode; rcount=0; fi
 
	{
		echo "stufe=$mode"
		echo "fritzbox=$f_state"
		echo "pve=$p_state"
		echo "fritzbox_lan=$f_lan"
		echo "seit=$(date -d @$since '+%d.%m.%Y %H:%M:%S' 2>/dev/null || echo $since)"
		echo "geprueft=$(date '+%d.%m.%Y %H:%M:%S')"
		echo "dry=$DRY"
	} > $STATE.tmp && mv $STATE.tmp $STATE
 
	sleep $INTERVAL
done

Init-Skript Tunnel-Failover

Startet die Tunnel-Umschaltung beim Booten und beendet sie nicht beim Stop (damit bei einem Neustart des Dienstes keine Routen verloren gehen).

/etc/init.d/tunnel-failover
#!/bin/sh /etc/rc.common
# Tunnel-Umschaltung fritzbox -> pve -> direkt
START=99
USE_PROCD=1
 
start_service() {
	procd_open_instance
	procd_set_param command /usr/sbin/tunnel-failover
	procd_set_param respawn 3600 5 0
	procd_set_param stderr 1
	procd_close_instance
}
 
stop_service() {
	:
}

Misst beide Uplinks nacheinander und schreibt das Ergebnis in /tmp/uplink-test.pref; der Watchdog übernimmt es danach für 30 Minuten (TEST_HOLD). Wird vom HA-Button „Uplink testen“ über das CGI gestartet (nur von 10.10.11.12).

/usr/sbin/uplink-test-run
#!/bin/sh
# Misst beide Uplinks direkt (am Tunnel vorbei) und meldet das Ergebnis an Home Assistant Arcen.
# Verbraucht ca. 2 x 1 MB ueber Landal und 2 x 1 MB ueber den Huawei.
HA_HOOK=$(sed -n 's/^HA_HOOK=\(.*\)/\1/p' /usr/sbin/uplink-watchdog | head -1)
URL="http://speed.cloudflare.com/__down?bytes=1000000"
mess() { # $1 = Geraet; Ausgabe: mittlere kB/s und Ping ms
	d=$1; sum=0; n=0
	for i in 1 2; do
		s=$(curl -s -m 20 --interface $d -o /dev/null -w '%{speed_download}' "$URL" 2>/dev/null); s=${s%%.*}; [ -z "$s" ] && s=0
		sum=$((sum + s)); n=$((n + 1))
	done
	kb=$((sum / n / 1000))
	p=$(ping -c 4 -W 2 -I $d 1.1.1.1 2>/dev/null | sed -n 's/.*= [0-9.]*\/\([0-9.]*\)\/.*/\1/p')
	echo "$kb ${p%%.*}"
}
set -- $(mess phy1-sta0); L_KB=$1; L_MS=${2:-0}
set -- $(mess phy0-sta0); H_KB=$1; H_MS=${2:-0}
PREV=$(sed -n 's/^bevorzugt=//p' /tmp/uplink-watchdog.state 2>/dev/null | head -1)
# wer deutlich (10 %) schneller ist, wird bevorzugt; sonst bleibt es wie es ist
if [ "$L_KB" -ge $((H_KB * 11 / 10)) ]; then P=landal
elif [ "$H_KB" -ge $((L_KB * 11 / 10)) ]; then P=huawei
else P=${PREV:-landal}; fi
N="Test $(date +%H:%M): Landal $((L_KB * 8 / 1000)) Mbit/s, Ping ${L_MS} ms, Huawei $((H_KB * 8 / 1000)) Mbit/s, Ping ${H_MS} ms"
echo "$(date '+%d.%m.%Y %H:%M:%S') $N -> bevorzugt $P" > /tmp/uplink-test.result
logger -t uplink-test "$N -> bevorzugt $P"
# der uplink-watchdog uebernimmt das Ergebnis und meldet es an Home Assistant
printf 'T_PREF=%s\nT_H=%s\nT_L=%s\nT_NOTE="%s"\n' "$P" "$H_KB" "$L_KB" "$N" > /tmp/uplink-test.pref

Startet den Test im Hintergrund, nur von HA Arcen (10.10.11.12) erlaubt, mit Sperre gegen Doppelstart.

/www/cgi-bin/uplink-test
#!/bin/sh
echo "Content-Type: text/plain"; echo
case "$REMOTE_ADDR" in
	10.10.11.12) ;;
	*) echo "nicht erlaubt"; exit 0 ;;
esac
LOCK=/tmp/uplink-test.lock
if [ -e $LOCK ] && [ $(( $(date +%s) - $(cat $LOCK) )) -lt 150 ]; then echo "laeuft schon"; exit 0; fi
date +%s > $LOCK
( /usr/sbin/uplink-test-run >/dev/null 2>&1; rm -f $LOCK ) >/dev/null 2>&1 &
echo "Test gestartet"

Anpassungen nach Update (anpassungen-anwenden)

Idempotent, kann beliebig oft laufen. Setzt: Landal ohne VHT (hostapd.sh, supplicant.uc), multicast_to_unicast Standard 0. Nach einem Update einmal ausführen, dann wifi down radio1; sleep 3; wifi up radio1. (wifi reload reicht nicht.)

/usr/sbin/anpassungen-anwenden
#!/bin/sh
# Eigene Anpassungen (09.10.2026) nach einem Firmware-Update erneut anwenden. Idempotent.
# Pfade lassen sich zum Testen ueberschreiben: H=... W=... anpassungen-anwenden
H="${H:-/lib/netifd/hostapd.sh}"
W="${W:-/lib/netifd/netifd-wireless.sh}"
MARK="Eigene Anpassung 09.10.2026"
OK=0
 
# 1) Landal-Client: VHT (und damit HE) abschalten
if [ -f "$H" ] && ! grep -q "$MARK" "$H"; then
	awk -v mark="$MARK" '
		{ print }
		/^wpa_supplicant_add_network\(\)/ { f = 1 }
		f && !d && /\[ -n "\$ocv" \] && append network_data "ocv=\$ocv"/ {
			print ""
			print "\t# " mark ": Die Landal-Zugangspunkte kuendigen HE an, antworten aber nur mit VHT;"
			print "\t# der Treiber bricht dann ab. Ohne VHT (und damit ohne HE) verbindet sich der Client per HT."
			print "\t[ \"$_w_mode\" = \"sta\" ] && [ \"$ssid\" = \"Free WiFi Landal\" ] && append network_data \"disable_vht=1\" \"$N$T\""
			d = 1
		}' "$H" > "$H.neu" && grep -q "$MARK" "$H.neu" && sh -n "$H.neu" && {
			cp "$H" "$H.orig-vor-anpassung"; cat "$H.neu" > "$H"; OK=$((OK+1)); }
	rm -f "$H.neu"
fi
 
# 2) WLAN-Geraete am selben AP: multicast_to_unicast Vorgabe 0
if [ -f "$W" ] && ! grep -q "$MARK" "$W"; then
	sed 's|^\(\t\)\(\[ \${multicast_to_unicast:-\)1\(} -gt 0 -o \${proxy_arp:-0} -gt 0 \] \&\& json_add_boolean isolate 1\)|\1# '"$MARK"': Vorgabe 0 statt 1, damit sich WLAN-Geraete am selben Zugangspunkt erreichen\n\1\20\3|' "$W" > "$W.neu" \
		&& grep -q "$MARK" "$W.neu" && sh -n "$W.neu" && {
			cp "$W" "$W.orig-vor-anpassung"; cat "$W.neu" > "$W"; OK=$((OK+1)); }
	rm -f "$W.neu"
fi
# 3) OpenWrt 25.12: Landal-Client ohne VHT (die Client-Konfiguration entsteht in supplicant.uc)
U="${U:-/usr/share/ucode/wifi/supplicant.uc}"
if [ -f "$U" ] && ! grep -q "$MARK\|Eigene Anpassung 10.10.2026" "$U"; then
	awk '{ print }
		/^\tconfig\.scan_ssid = 1;$/ && !d {
			print ""
			print "\t/* Eigene Anpassung 10.10.2026: Die Landal-Zugangspunkte kuendigen HE an, antworten aber nur mit VHT."
			print "\t * Ohne VHT (und damit ohne HE) verbindet sich der Client per HT und laeuft stabil. */"
			print "\tif (config.mode == '"'"'sta'"'"' && config.ssid == '"'"'Free WiFi Landal'"'"')"
			print "\t\tconfig.disable_vht = true;"
			d = 1
		}' "$U" > "$U.neu" && grep -q "Eigene Anpassung 10.10.2026" "$U.neu" && {
			cp "$U" "$U.orig-vor-anpassung"; cat "$U.neu" > "$U"; OK=$((OK+1)); NEEDS_WIFI=1; }
	rm -f "$U.neu"
fi
 
logger -t anpassungen "angewendet: $OK Anpassung(en)"
[ "$NEEDS_WIFI" = 1 ] && echo "WLAN-Client neu starten: wifi down radio1; sleep 3; wifi up radio1"
echo "angewendet: $OK"

Landal: Änderung in supplicant.uc

Unter OpenWrt 25.12 wird die WLAN-Client-Konfiguration in /usr/share/ucode/wifi/supplicant.uc erzeugt, nicht mehr per Shell. Der Landal-Zugangspunkt kündigt HE an, antwortet aber nur mit VHT, dann bricht die Verbindung ab. Der Patch schaltet VHT (und damit HE) für diese SSID aus, der Client läuft per HT. Eingefügt in setup_sta nach config.scan_ssid = 1;. Das Original liegt als /root/backup-20261010/supplicant.uc.orig.

supplicant.uc (Ausschnitt)
	/* Eigene Anpassung 10.10.2026: Die Landal-Zugangspunkte kuendigen HE an, antworten aber nur mit VHT.
	 * Ohne VHT (und damit ohne HE) verbindet sich der Client per HT und laeuft stabil. */
	if (config.mode == 'sta' && config.ssid == 'Free WiFi Landal')
		config.disable_vht = true;

Landal HT/VHT-Vergleich (landal-vergleich)

Siehe auch landal_wlan. Ablauf: Sperrdatei, Prüfung (Landal verbunden? Huawei online? nicht schon auf Landal erzwungen?), Uplink auf Huawei stellen, je Modus mit wpa_cli set_network disable_vht + reassociate neu verbinden, 2 × 1 MB Download, 10 Pings, Sendefehlerzähler; danach alles zurückstellen und eine Zeile an die CSV anhängen. Umgebungsvariablen: LV_QUELLE (cron|ha), LV_ALARM (scharf|huelle|unscharf).

/usr/sbin/landal-vergleich
#!/bin/sh
# Vergleicht den Landal-WLAN-Client in HT (disable_vht=1) und VHT (disable_vht=0).
# Laeuft lokal auf dem Router: per Cron (07:45, 12:45, 19:45) und stuendlich von Home Assistant,
# wenn die Alarmanlage scharf ist (CGI /www/cgi-bin/landal-vergleich). Unabhaengig vom Mac.
#
# Ablauf:
#  1. Tunnel vorab auf den Huawei legen (Vorgabe "huawei" in /tmp/uplink-mode, wartet bis der
#     uplink-watchdog umgeschaltet hat). Dadurch ist der Tunnel waehrend der Messung nicht gestoert.
#  2. Landal-Client in HT bzw. VHT stellen, je Modus 2 x 1 MB Download (direkt ueber phy1-sta0)
#     und 10 Pings, dazu Sendefehlerzaehler.
#  3. Grundzustand wiederherstellen, Vorgabe freigeben, Daten speichern und an HA melden.
# Daten: /root/landal-vergleich.csv (eine Zeile je Lauf mit Kopfzeile), Auswertung: landal-auswertung
# Braucht wpa_cli und curl.
 
D=phy1-sta0
WPA="wpa_cli -p /var/run/wpa_supplicant -i $D"
CSV=/root/landal-vergleich.csv
LOCK=/tmp/landal-vergleich.lock
CONF=/var/run/wpa-supplicant-phy1-sta0.conf
HOOKFILE=/etc/landal-hook
MODEFILE=/tmp/uplink-mode
STATE=/tmp/uplink-watchdog.state
URL="http://speed.cloudflare.com/__down?bytes=1000000"
KEEP7=168                       # Statistik "7 Tage" = letzte 168 Laeufe (bei stuendlicher Messung)
SRC="${LV_QUELLE:-cron}"        # cron oder ha
ALARM="${LV_ALARM:-}"           # von HA: scharf, huelle, unscharf
HEADER="epoch;datum;zeit;wochentag;stunde;tageszeit;quelle;alarm;reihenfolge;bssid;freq_mhz;signal_dbm;uplink_vorher;ht_mbit10;ht_dl1_kbs;ht_dl2_kbs;ht_fehler_pm;ht_ping_avg;ht_ping_max;ht_pakete;ht_fehler;ht_rate_mbit;vht_mbit10;vht_dl1_kbs;vht_dl2_kbs;vht_fehler_pm;vht_ping_avg;vht_ping_max;vht_pakete;vht_fehler;vht_rate_mbit;ht_pingverlust_pct;vht_pingverlust_pct"
 
log() { logger -t landal-vergleich "$*"; echo "$(date +%H:%M:%S) $*"; }
 
if [ -e $LOCK ] && [ $(( $(date +%s) - $(cat $LOCK) )) -lt 900 ]; then exit 0; fi
command -v wpa_cli >/dev/null && command -v curl >/dev/null || { log "wpa_cli oder curl fehlt"; exit 1; }
iw dev $D link | grep -q Connected || { log "Landal nicht verbunden, Messung uebersprungen"; exit 0; }
NID=$($WPA list_networks | awk '/Landal/{print $1; exit}')
[ -n "$NID" ] || { log "Landal-Netzwerk nicht gefunden"; exit 1; }
BASE=0; grep -q "disable_vht=1" $CONF 2>/dev/null && BASE=1     # 1 = HT erzwungen, 0 = VHT erlaubt
 
# Vorgabe merken. Steht sie von Hand auf Landal, wird nicht gemessen.
PREV_SET=0; PREV=""
if [ -r $MODEFILE ]; then PREV=$(cat $MODEFILE); PREV_SET=1; fi
[ "$PREV" = landal ] && { log "Vorgabe steht von Hand auf Landal, Messung uebersprungen"; exit 0; }
[ "$(sed -n 's/^huawei_internet=//p' $STATE 2>/dev/null)" = 1 ] || { log "Huawei ohne Internet, Messung uebersprungen"; exit 0; }
 
date +%s > $LOCK
cleanup() {
	$WPA set_network $NID disable_vht $BASE >/dev/null 2>&1; $WPA reassociate >/dev/null 2>&1
	if [ $PREV_SET = 1 ]; then echo "$PREV" > $MODEFILE; else rm -f $MODEFILE; fi
	rm -f $LOCK
}
trap cleanup EXIT
 
# 1. Tunnel auf den Huawei legen
UPL_VORHER=$(sed -n 's/^uplink=//p' $STATE 2>/dev/null)
echo huawei > $MODEFILE
cur=""
for i in 1 2 3 4 5 6 7 8 9 10 11 12; do
	sleep 5
	cur=$(sed -n 's/^uplink=//p' $STATE 2>/dev/null)
	[ "$cur" = Huawei ] && break
done
[ "$cur" = Huawei ] || { log "Tunnel ist nicht auf den Huawei gewechselt, Messung abgebrochen"; exit 0; }
sleep 8   # Tunnel auf dem Huawei kurz einschwingen lassen
 
stat() { iw dev $D station dump | awk '/tx packets:/{p=$3} /tx failed:/{f=$3} END{print p+0, f+0}'; }
 
set_mode() {   # $1 = 1 (HT) oder 0 (VHT); wartet, bis die Neuverbindung wirklich steht
	$WPA set_network $NID disable_vht $1 >/dev/null
	$WPA reassociate >/dev/null
	sleep 2
	for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15; do
		ct=$(iw dev $D station dump 2>/dev/null | awk '/connected time/{print $3; exit}')
		if [ -n "$ct" ] && [ "$ct" -le 8 ] && ip -4 addr show $D | grep -q inet; then break; fi
		sleep 2
	done
	sleep 6
}
 
messen() {     # Ausgabe: mbit10 dl1 dl2 fehler_promille ping_avg ping_max pakete fehler rate ping_verlust_prozent ; Fehler -1 = ungueltig
	set -- $(stat); p0=$1; f0=$2
	ct0=$(iw dev $D station dump 2>/dev/null | awk '/connected time/{print $3; exit}'); ct0=${ct0:-0}
	sum=0; n=0; dl1=0; dl2=0
	for i in 1 2; do
		s=$(curl -s -m 25 --interface $D -o /dev/null -w '%{speed_download}' "$URL" 2>/dev/null); s=${s%%.*}
		[ -z "$s" ] && s=0
		[ $i = 1 ] && dl1=$((s / 1000)) || dl2=$((s / 1000))
		sum=$((sum + s)); n=$((n + 1))
	done
	m10=$(( sum / n * 8 / 100000 ))
	PO=$(ping -c 10 -W 2 -I $D 1.1.1.1 2>/dev/null)
	P=$(echo "$PO" | sed -n 's/.*= \([0-9.]*\)\/\([0-9.]*\)\/\([0-9.]*\).*/\2 \3/p')
	ploss=$(echo "$PO" | sed -n 's/.* \([0-9]*\)% packet loss.*/\1/p'); ploss=${ploss:-100}
	set -- $P; pavg=${1%%.*}; pmax=${2%%.*}; pavg=${pavg:-0}; pmax=${pmax:-0}
	rate=$(iw dev $D link | sed -n 's/.*tx bitrate: \([0-9.]*\) MBit.*/\1/p'); rate=${rate:-0}
	set -- $(stat); p1=$1; f1=$2
	ct1=$(iw dev $D station dump 2>/dev/null | awk '/connected time/{print $3; exit}'); ct1=${ct1:-0}
	dp=$((p1 - p0)); df=$((f1 - f0))
	if [ $dp -lt 0 ] || [ $df -lt 0 ] || [ $ct1 -lt $ct0 ]; then echo "0 0 0 -1 0 0 0 0 0 0"; return; fi
	tot=$((dp + df)); [ $tot -lt 1 ] && tot=1
	echo "$m10 $dl1 $dl2 $(( df * 1000 / tot )) $pavg $pmax $tot $df $rate $ploss"
}
 
messen_gueltig() {
	r=$(messen)
	case "$r" in "0 0 0 -1 "*) sleep 10; r=$(messen);; esac
	echo "$r"
}
 
# Kopfzeile / altes Format wegsichern
if [ -f $CSV ] && [ "$(head -n 1 $CSV | cut -d';' -f1)" != "epoch" ]; then mv $CSV $CSV.alt; fi
[ -f $CSV ] || echo "$HEADER" > $CSV
NL=$(grep -c -v '^epoch;' $CSV)
if [ $(( NL % 2 )) = 0 ]; then FIRST=1; SECOND=0; ORD="HT-VHT"; else FIRST=0; SECOND=1; ORD="VHT-HT"; fi
 
log "Start ($SRC, Alarm=${ALARM:--}, Reihenfolge $ORD, Grundzustand $([ $BASE = 1 ] && echo HT || echo VHT))"
BSSID=$(iw dev $D link | sed -n 's/^Connected to \([0-9a-f:]*\).*/\1/p')
FREQ=$(iw dev $D link | sed -n 's/.*freq: \([0-9]*\).*/\1/p')
SIG=$(iw dev $D link | sed -n 's/.*signal: \(-[0-9]*\).*/\1/p')
 
set_mode $FIRST;  R1=$(messen_gueltig)
set_mode $SECOND; R2=$(messen_gueltig)
if [ $FIRST = 1 ]; then HT="$R1"; VHT="$R2"; else HT="$R2"; VHT="$R1"; fi
set -- $HT;  HM=$1; HD1=$2; HD2=$3; HF=$4; HPA=$5; HPM=$6; HPK=$7; HFL=$8; HR=$9; HLO=${10}
set -- $VHT; VM=$1; VD1=$2; VD2=$3; VF=$4; VPA=$5; VPM=$6; VPK=$7; VFL=$8; VR=$9; VLO=${10}
if [ "$HF" -lt 0 ] || [ "$VF" -lt 0 ]; then log "Messung ungueltig (Verbindung baute sich waehrend der Messung neu auf), nicht gespeichert"; exit 0; fi
 
H=$(date +%H); H=${H#0}; H=${H:-0}
if [ "$H" -lt 6 ]; then B=nachts; elif [ "$H" -lt 12 ]; then B=morgens; elif [ "$H" -lt 18 ]; then B=mittags; else B=abends; fi
WT=$(date +%a)
echo "$(date +%s);$(date +%d.%m.%Y);$(date +%H:%M);$WT;$H;$B;$SRC;${ALARM:--};$ORD;$BSSID;$FREQ;$SIG;${UPL_VORHER:--};$HM;$HD1;$HD2;$HF;$HPA;$HPM;$HPK;$HFL;$HR;$VM;$VD1;$VD2;$VF;$VPA;$VPM;$VPK;$VFL;$VR;$HLO;$VLO" >> $CSV
# Datei begrenzen (ca. 1,5 Jahre bei stuendlicher Messung)
[ "$(wc -l < $CSV)" -gt 15000 ] && { cp $CSV $CSV.$(date +%Y%m%d); { echo "$HEADER"; tail -n 8000 $CSV; } > $CSV.neu && mv $CSV.neu $CSV; }
 
stat_text() {  # $1 = Spalte Mbit10, $2 = Fehler-Promille, $3 = Ping avg ; $4 = Anzahl letzte Laeufe (0 = alle)
	if [ "$4" = 0 ]; then grep -v '^epoch;' $CSV; else grep -v '^epoch;' $CSV | tail -n $4; fi | awk -F';' -v m=$1 -v f=$2 -v p=$3 '
		{ n++; sm+=$m; sf+=$f; sp+=$p; b=$6; bn[b]++; bs[b]+=$m }
		END {
			if (n == 0) { print "keine Daten"; exit }
			printf "Mittel %.1f Mbit/s, Fehler %.1f Prozent, Ping %.0f ms, %d Laeufe. Nach Tageszeit: nachts %.1f, morgens %.1f, mittags %.1f, abends %.1f Mbit/s",
				sm/n/10, sf/n/10, sp/n, n,
				(bn["nachts"] ? bs["nachts"]/bn["nachts"]/10 : 0),
				(bn["morgens"] ? bs["morgens"]/bn["morgens"]/10 : 0),
				(bn["mittags"] ? bs["mittags"]/bn["mittags"]/10 : 0),
				(bn["abends"]  ? bs["abends"]/bn["abends"]/10   : 0)
		}' | sed 's/\./,/g'
}
ST_HT=$(stat_text 14 17 18 $KEEP7)
ST_VHT=$(stat_text 23 26 27 $KEEP7)
ST_ALLE="Seit Beginn. HT: $(stat_text 14 17 18 0 | cut -c1-110) | VHT: $(stat_text 23 26 27 0 | cut -c1-110)"
ST_ALLE=$(echo "$ST_ALLE" | cut -c1-250)
 
num() { awk -v v="$1" -v d="$2" 'BEGIN{printf "%.1f", v/d}'; }
INFO="Messung $(date +%d.%m.%H:%M) ($B, $SRC$([ -n "$ALARM" ] && echo ", Alarm $ALARM")): HT $(num $HM 10) Mbit/s (Fehler $(num $HF 10) Prozent, Ping $HPA ms, Verlust $HLO Prozent), VHT $(num $VM 10) Mbit/s (Fehler $(num $VF 10) Prozent, Ping $VPA ms, Verlust $VLO Prozent)"
log "$INFO"
 
if [ -f $HOOKFILE ]; then
	HOOK=$(cat $HOOKFILE)
	DATA="ht_mbit=$(num $HM 10)&vht_mbit=$(num $VM 10)&ht_fehler=$(num $HF 10)&vht_fehler=$(num $VF 10)&ht_ping=$HPA&vht_ping=$VPA"
	DATA="$DATA&info=$(echo "$INFO" | tr ' ' '+')&stat_ht=$(echo "$ST_HT" | tr ' ' '+')&stat_vht=$(echo "$ST_VHT" | tr ' ' '+')&stat_alle=$(echo "$ST_ALLE" | tr ' ' '+')"
	wget -q -T 10 -O /dev/null --post-data="$DATA" "$HOOK" 2>/dev/null && log "an Home Assistant gemeldet" || log "Meldung an Home Assistant fehlgeschlagen"
fi

CGI Landal-Vergleich

Wird stündlich von HA Arcen aufgerufen, wenn die Alarmanlage scharf ist (Automation „Landal-Vergleich stündlich bei scharfer Anlage“, Aufruf über downloader.download_file).

/www/cgi-bin/landal-vergleich
#!/bin/sh
# Startet den Landal-Vergleich (HT gegen VHT). Aufruf nur von Home Assistant Arcen (10.10.11.12).
# Parameter: ?alarm=scharf|huelle|unscharf
echo "Content-Type: text/plain"; echo
case "$REMOTE_ADDR" in
	10.10.11.12) ;;
	*) echo "nicht erlaubt"; exit 0 ;;
esac
A=$(echo "$QUERY_STRING" | sed -n 's/.*alarm=\([a-z]*\).*/\1/p')
( LV_QUELLE=ha LV_ALARM="$A" /usr/sbin/landal-vergleich >/dev/null 2>&1 & )
echo "gestartet"

Auswertung (landal-auswertung)

Aufruf: landal-auswertung [Tage]. Ausgabe: Gesamt, nach Tageszeit, Alarmstatus, Stunde und je Tag; Mbit/s Durchschnitt, Sendefehler in %, Ping und Anteil der Läufe, in denen VHT schneller war.

/usr/sbin/landal-auswertung
#!/bin/sh
# Auswertung von /root/landal-vergleich.csv (HT gegen VHT am Landal-Client). Aufruf: landal-auswertung [Tage]
# Ohne Angabe: alle Daten. Spalten siehe Kopfzeile der CSV (Mbit/s sind dort mal 10, Fehler in Promille).
CSV=/root/landal-vergleich.csv
DAYS="${1:-0}"
[ -f $CSV ] || { echo "keine Daten: $CSV"; exit 1; }
SINCE=0; [ "$DAYS" -gt 0 ] && SINCE=$(( $(date +%s) - DAYS * 86400 ))
grep -v '^epoch;' $CSV | awk -F';' -v since=$SINCE '
function pr(name, n, hm, vm, hf, vf, hp, vp, vw) {
	if (n + 0 == 0) return
	printf "%-22s n=%3d | HT %5.1f Mbit/s  F %4.1f%%  Ping %4.0f | VHT %5.1f Mbit/s  F %4.1f%%  Ping %4.0f | VHT schneller in %2.0f%%\n",
		name, n, hm/n/10, hf/n/10, hp/n, vm/n/10, vf/n/10, vp/n, vw*100/n
}
$1 >= since {
	n++; hm+=$14; vm+=$23; hf+=$17; vf+=$26; hp+=$18; vp+=$27; vw+=($23>$14)
	b=$6;  bn[b]++; bhm[b]+=$14; bvm[b]+=$23; bhf[b]+=$17; bvf[b]+=$26; bhp[b]+=$18; bvp[b]+=$27; bvw[b]+=($23>$14)
	a=$8;  an[a]++; ahm[a]+=$14; avm[a]+=$23; ahf[a]+=$17; avf[a]+=$26; ahp[a]+=$18; avp[a]+=$27; avw[a]+=($23>$14)
	h=$5+0; hn[h]++; hhm[h]+=$14; hvm[h]+=$23; hhf[h]+=$17; hvf[h]+=$26; hhp[h]+=$18; hvp[h]+=$27; hvw[h]+=($23>$14)
	d=$2;  dn[d]++; dhm[d]+=$14; dvm[d]+=$23; dhf[d]+=$17; dvf[d]+=$26; dhp[d]+=$18; dvp[d]+=$27; dvw[d]+=($23>$14)
	if (!(d in dord)) { dord[d]=++nd; dname[nd]=d }
}
END {
	print "=== Gesamt ==="; pr("alle Laeufe", n, hm, vm, hf, vf, hp, vp, vw)
	print "\n=== nach Tageszeit ==="
	split("nachts morgens mittags abends", bl, " "); for (i = 1; i <= 4; i++) { b = bl[i]; pr(b, bn[b], bhm[b], bvm[b], bhf[b], bvf[b], bhp[b], bvp[b], bvw[b]) }
	print "\n=== nach Alarmstatus (scharf/huelle/unscharf, - = Cron) ==="
	for (a in an) pr(a, an[a], ahm[a], avm[a], ahf[a], avf[a], ahp[a], avp[a], avw[a])
	print "\n=== nach Stunde ==="
	for (h = 0; h < 24; h++) pr(sprintf("%02d Uhr", h), hn[h], hhm[h], hvm[h], hhf[h], hvf[h], hhp[h], hvp[h], hvw[h])
	print "\n=== je Tag ==="
	for (i = 1; i <= nd; i++) { d = dname[i]; pr(d, dn[d], dhm[d], dvm[d], dhf[d], dvf[d], dhp[d], dvp[d], dvw[d]) }
}'

Cron (/etc/crontabs/root)

Täglicher Neustart um 04:30 und die drei Messungen (07:45, 12:45, 19:45). Zusätzlich stündliche Läufe durch HA bei scharfer Anlage.

/etc/crontabs/root
30 4 * * * sleep 70 && touch /etc/banner && reboot
45 7 * * * /usr/sbin/landal-vergleich >/dev/null 2>&1
45 12 * * * /usr/sbin/landal-vergleich >/dev/null 2>&1
45 19 * * * /usr/sbin/landal-vergleich >/dev/null 2>&1

Hinweise

  • Benötigte Pakete: wpa-cli (für den Vergleich), curl.
  • SSH-Zugang: Schlüssel ~/.ssh/openwrt_claude (Mac). Nach dem Einspielen von Archiven vom Mac chown -R root:root, sonst lehnt Dropbear authorized_keys ab.
  • Sicherungen der Router-Konfiguration: backup. Siehe auch openwrtone, wireguard.
infrastruktur/openwrtone_skripte.txt · Zuletzt geändert: von nbadm

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki