#!/bin/sh # Autonome Flash- + Recovery-Orchestrierung für ALL-SG8316M. # LÄUFT AUF DEM MAC MINI (10.40.41.167, en0, L2-lokal zum Switch). # Zweck: Claude stößt es detached an und ist während des Switch-Reboots OFFLINE. # Das Script zieht die ganze Kette autonom durch und loggt alles. # # Kette: pre-flight -> flash -> warte down -> warte up(.1.1|.239) # -> (falls .1.1) IP zurück auf .239 [cmd dynamisch entdeckt] # -> VLANs (best-effort) -> save-to-flash -> verify # # Aufruf: ./flash-and-recover.sh preflight # nur Checks, kein Flash # ./flash-and-recover.sh run # echter Flash + Recovery set -u SW_PROD=10.40.41.239 SW_DEFAULT=192.168.1.1 PROD_MASK=255.255.255.0 PROD_GW=10.40.41.254 ALIAS_IP=192.168.1.2 VLANS=10,20,30,40,100 IMG="${IMG:-$HOME/bf-switch-flash/all-sg8316m-version_2.2.1_vmlinux.bix}" IMG_SHA=e3f160983e8b17962dc70a52360b7f560c92da8197928d702f5266027a0fc7fc LOG="${LOG:-$HOME/bf-switch-flash/flash-$(date +%Y%m%d-%H%M%S).log}" MODE="${1:-preflight}" mkdir -p "$(dirname "$LOG")" log(){ printf '%s %s\n' "$(date '+%H:%M:%S')" "$*" | tee -a "$LOG"; } # Liveness: Webserver-Root antwortet (firmware-stabiler als ein bestimmter cmd) http_code(){ curl -s -o /dev/null -w '%{http_code}' --max-time 5 "http://$1/" 2>/dev/null; } up(){ [ "$(http_code "$1")" = "200" ]; } fw_version(){ curl -s --max-time 6 "http://$1/cgi-bin/dispatcher.cgi?cmd=512" \ | sed -e 's/<[^>]*>/ /g' | grep -oiE 'Firmware Version[^0-9]*[0-9.]+' | head -1; } wait_up(){ _e=$(( $(date +%s) + $2 )); while [ "$(date +%s)" -lt "$_e" ]; do up "$1" && return 0; sleep 4; done; return 1; } wait_down(){ _e=$(( $(date +%s) + $2 )); while [ "$(date +%s)" -lt "$_e" ]; do up "$1" || return 0; sleep 4; done; return 1; } # IP zurücksetzen — cmd + Seite dynamisch aus der Live-UI entdecken (firmware-drift-sicher) restore_ip(){ _host=$1 _menu=$(curl -s --max-time 8 "http://$_host/cgi-bin/dispatcher.cgi?cmd=2") _ipcmd=$(printf '%s' "$_menu" | grep -i 'IP Address' | grep -oE 'cmd=[0-9]+' | head -1 | cut -d= -f2) [ -z "$_ipcmd" ] && _ipcmd=517 _form=$(curl -s --max-time 8 "http://$_host/cgi-bin/dispatcher.cgi?cmd=$_ipcmd") _post=$(printf '%s' "$_form" | grep -i 'name="cmd"' | grep -oE 'value="[0-9]+"' | head -1 | grep -oE '[0-9]+') [ -z "$_post" ] && _post=518 log "restore_ip: ip-page=$_ipcmd post-cmd=$_post -> set $SW_PROD" curl -s --max-time 10 -X POST "http://$_host/cgi-bin/dispatcher.cgi" \ --data "mode=0" --data "ip=$SW_PROD" --data "netmask=$PROD_MASK" \ --data "gateway=$PROD_GW" --data "cmd=$_post" >/dev/null 2>&1 } add_vlans(){ curl -s --max-time 10 -X POST "http://$1/cgi-bin/dispatcher.cgi" \ --data "vlanlist=$VLANS" --data "vlanAction=0" --data "name=" --data "cmd=1284" >/dev/null 2>&1; } save_flash(){ curl -s --max-time 15 "http://$1/cgi-bin/dispatcher.cgi?cmd=4358" >/dev/null 2>&1; } # ---------- PRE-FLIGHT ---------- log "=== PRE-FLIGHT (mode=$MODE) ===" fail=0 if [ -f "$IMG" ]; then h=$(shasum -a 256 "$IMG" 2>/dev/null | awk '{print $1}') if [ "$h" = "$IMG_SHA" ]; then log "OK image sha256"; else log "FAIL image sha256: $h"; fail=1; fi else log "FAIL image missing: $IMG"; fail=1; fi if ifconfig en0 2>/dev/null | grep -q "$ALIAS_IP"; then log "OK recovery-alias $ALIAS_IP on en0"; else log "FAIL alias $ALIAS_IP missing -> sudo ifconfig en0 alias $ALIAS_IP 255.255.255.0"; fail=1; fi if up "$SW_PROD"; then log "OK switch up at $SW_PROD (FW: $(fw_version "$SW_PROD"))"; else log "FAIL switch not reachable at $SW_PROD"; fail=1; fi if [ "$fail" -ne 0 ]; then log "PRE-FLIGHT FAILED -> abort"; exit 2; fi log "PRE-FLIGHT OK" [ "$MODE" = "preflight" ] && { log "preflight-only -> done (kein Flash)"; exit 0; } # ---------- FLASH ---------- MARKER="$HOME/bf-switch-flash/.flashed-2.2.1" if [ -f "$MARKER" ] && up "$SW_PROD"; then log "Marker vorhanden + Switch up @ $SW_PROD -> Flash übersprungen (nur reconfig)"; skip=1 else skip=0; fi if [ "$skip" -eq 0 ]; then log "=== FLASH: lade Image hoch ($IMG) ===" curl -s --max-time 200 -F "type=0" -F "http_file=@$IMG" -F "cmd=4364" \ "http://$SW_PROD/cgi-bin/httpupload.cgi" >>"$LOG" 2>&1 log "Upload abgeschlossen, warte auf Reboot (down)..." # Der Reboot selbst ist das verlässliche Flash-Signal (Flash erzwingt Neustart). if wait_down "$SW_PROD" 180; then log "Switch down (Reboot) — Flash hat gegriffen" else log "FAIL kein Reboot binnen 180s — Flash vermutlich nicht angenommen (Upload/Format/Auth?)." log " Switch unverändert @ $SW_PROD, Netz intakt, KEIN Reset. Abbruch." exit 3 fi fi # ---------- WARTEN AUF WIEDERKEHR ---------- log "=== RECOVERY: warte auf Switch (max 6 min, .1.1 ODER .239) ===" target=""; _e=$(( $(date +%s) + 360 )) while [ "$(date +%s)" -lt "$_e" ]; do if up "$SW_DEFAULT"; then target=$SW_DEFAULT; break; fi if up "$SW_PROD"; then target=$SW_PROD; break; fi sleep 4 done if [ -z "$target" ]; then log "FAIL Switch kam binnen 6min nicht auf $SW_DEFAULT noch $SW_PROD zurück." log " -> MANUELLE Recovery nötig (Reset-Knopf 10s, dann erneut)." exit 4 fi log "Switch zurück an $target" # ---------- IP WIEDERHERSTELLEN ---------- if [ "$target" = "$SW_DEFAULT" ]; then log "Switch auf Default $SW_DEFAULT -> stelle $SW_PROD wieder her" restore_ip "$SW_DEFAULT" if wait_up "$SW_PROD" 60; then log "OK Switch wieder auf $SW_PROD" else log "FAIL set-ip griff nicht — FW 2.2.1 erzwingt vermutlich Login (FW1.0 war offen)." log " LAN/Internet laufen TROTZDEM (L2-Forwarding). Nur die Mgmt-IP hängt auf $SW_DEFAULT." log " MANUELL: Browser http://$SW_DEFAULT Login admin/admin -> Network/IP Address" log " -> $SW_PROD Maske $PROD_MASK GW $PROD_GW -> Save to Flash." exit 5 fi else log "Switch hat $SW_PROD behalten (Firmware bewahrte IP)" fi # ---------- RECONFIG (best-effort, unkritisch — online nachholbar) ---------- log "=== reconfig (best-effort) ===" if add_vlans "$SW_PROD"; then log "VLANs $VLANS abgeschickt"; else log "WARN VLAN-Add fehlgeschlagen (online nachholen)"; fi if save_flash "$SW_PROD"; then log "save-to-flash abgeschickt"; else log "WARN save fehlgeschlagen (online nachholen!)"; fi # ---------- VERIFY ---------- if up "$SW_PROD"; then touch "$HOME/bf-switch-flash/.flashed-2.2.1" log "=== SUCCESS: Switch up @ $SW_PROD (Marker gesetzt) ===" exit 0 else log "=== WARN: Switch am Ende nicht bestätigt up ===" exit 6 fi