#!/usr/bin/env bash # Zustandsaufnahme fuer die Reboot-/Reconnect-Probe (Intent Schritt 6). # Von tnix aus laufen lassen, einmal vor und einmal nach der Unterbrechung: # # scripts/pinix-reachability-probe.sh vorher > /tmp/probe-vorher.txt # scripts/pinix-reachability-probe.sh nachher > /tmp/probe-nachher.txt # diff -u /tmp/probe-vorher.txt /tmp/probe-nachher.txt # # Absichtlich ohne `set -e`: ein Fehlschlag ist hier das Messergebnis, kein # Abbruchgrund. Jeder Schritt hat ein Timeout, damit die Aufnahme auch dann # durchlaeuft, wenn ein Pfad tot ist. Nur lesende Befehle. # # Die externen Tests muessen zwingend ueber EXT_IF (Hotspot) laufen: tnix # haengt gleichzeitig im Mango-LAN, und die DNAT-Regeln haben # `reflection=0`. Ein Test ohne --interface nimmt sonst den LAN-Weg und # meldet einen Fehlschlag, der keiner ist -- genau diese Verwechslung hat # am 12.09.2026 Zeit gekostet. set -u LABEL="${1:-unbenannt}" PINIX_LAN="${PINIX_LAN:-192.168.8.244}" PINIX_ULA="${PINIX_ULA:-fdee:3c40:44f0:0:dea6:32ff:fe48:c6c8}" MANGO="${MANGO:-192.168.8.1}" EXT_NAME="${EXT_NAME:-ipv6-test.0ff.space}" EXT_IF="${EXT_IF:-wlan0}" SSH_PINIX="ssh -o LogLevel=ERROR -o ConnectTimeout=8 -o BatchMode=yes -o HostKeyAlias=pinix drone@${PINIX_LAN}" SSH_MANGO="ssh -o LogLevel=ERROR -o ConnectTimeout=8 -o BatchMode=yes root@${MANGO}" say() { printf '\n== %s\n' "$1"; } try() { timeout "${1}" bash -c "${2}" 2>&1 || echo "FEHLSCHLAG (exit $?)"; } printf '# Probe "%s"\n' "$LABEL" # Kein date +%s im Vergleich: die Uhrzeit unterscheidet sich immer und # wuerde jeden diff verrauschen. Nur zur Protokollierung, bewusst am Rand. printf '# Zeit: %s\n' "$(date '+%Y-%m-%d %H:%M:%S %Z')" say 'pinix: Generationen (booted muss nach der Probe == current sein)' try 15 "$SSH_PINIX 'readlink -f /run/booted-system; readlink -f /run/current-system' | sed 's|/nix/store/||'" say 'pinix: Systemzustand und fehlgeschlagene Units' try 15 "$SSH_PINIX 'systemctl is-system-running; systemctl --failed --no-legend --no-pager | cat'" say 'pinix: Dienste' try 15 "$SSH_PINIX 'for u in bind traefik mango-dns.timer tailscaled; do printf \"%s=%s\n\" \$u \$(systemctl is-active \$u 2>/dev/null); done'" say 'pinix: stabile SLAAC-ULA (Ziel aller DNAT-Regeln) vorhanden?' # Die Adresse ist aus der MAC abgeleitet und damit deterministisch. Fehlt # sie, zeigen alle Mango-Weiterleitungen ins Leere. try 15 "$SSH_PINIX 'ip -6 addr show dev end0 | grep -q \"${PINIX_ULA}\" && echo \"${PINIX_ULA} vorhanden\" || echo \"FEHLT: ${PINIX_ULA}\"'" say 'pinix: IPv6-Default-Route per RA (der dhcpcd/noipv6rs-Fallstrick)' try 15 "$SSH_PINIX 'ip -6 route show default | sed \"s/expires [0-9]*sec/expires N/\" || echo KEINE'" say 'pinix: Resolver (muss Mango bleiben, nicht localhost)' try 15 "$SSH_PINIX 'grep nameserver /etc/resolv.conf'" say 'pinix: BIND antwortet autoritativ ueber ::1' try 20 "$SSH_PINIX 'H=\$(ls -d /nix/store/*-bind-*-host/bin/host 2>/dev/null | head -1); \$H -t SOA 0ff.space ::1 | grep \"has SOA\"'" say 'Mango: oeffentliche Adresse auf usb0' try 15 "$SSH_MANGO 'ip -6 addr show dev usb0 scope global | awk \"/inet6/{print \\\$2}\"'" say 'Mango: Weiterleitungen persistent vorhanden?' try 15 "$SSH_MANGO 'for r in pinix_web_80 pinix_web_443 pinix_web_8080 pinix_ssh pinix_dns; do printf \"%s=%s\n\" \$r \"\$(uci -q get firewall.\$r.target || echo FEHLT)\"; done'" say 'Mango: DNAT-Trefferzaehler (nur Existenz der Regeln, Zahlen driften)' try 15 "$SSH_MANGO 'nft list chain inet fw4 dstnat_wan6 2>/dev/null | grep -oE \"dport [0-9]+\" | sort -u'" say "Cloudflare-AAAA von ${EXT_NAME} gegen tatsaechliche Adresse" # mango-dns fuehrt nur diesen einen Eintrag nach. Weichen die beiden # nach einem Reconnect dauerhaft ab, hat der Timer sein Ziel verfehlt. try 15 "host -t AAAA ${EXT_NAME} | awk '/has IPv6/{print \"AAAA:\", \$NF}'" try 20 "$SSH_PINIX 'curl -6 --fail --silent --max-time 12 https://api6.ipify.org' | sed 's/^/tatsaechlich: /'" say "extern ueber ${EXT_IF}: Web und SSH" SRC=$(ip -6 addr show dev "$EXT_IF" scope global 2>/dev/null | awk '/inet6 2a0/{split($2,a,"/"); print a[1]; exit}') if [ -z "${SRC:-}" ]; then echo "FEHLSCHLAG: keine globale IPv6 auf ${EXT_IF} -- externer Test nicht moeglich" else echo "Quelladresse: ${SRC}" try 25 "curl -6 -s --interface ${EXT_IF} -o /dev/null -w 'https=%{http_code}\n' --max-time 18 https://${EXT_NAME}/" try 20 "curl -6 -s --interface ${EXT_IF} -o /dev/null -w 'http=%{http_code}\n' --max-time 15 http://${EXT_NAME}/" try 15 "nc -6 -s ${SRC} -z -w 8 ${EXT_NAME} 2222 >/dev/null 2>&1 && echo 'ssh2222=offen' || echo 'ssh2222=zu'" say "extern ueber ${EXT_IF}: DNS (erst nach configure-mango-dns.sh sinnvoll)" try 20 "host -W 8 -t SOA 0ff.space ${SRC%%/*} >/dev/null 2>&1; A=\$(host -t AAAA ${EXT_NAME} | awk '/has IPv6/{print \$NF; exit}'); host -W 8 -t SOA 0ff.space \$A | grep -E 'has SOA|not found|timed out' | head -2" fi say 'Zertifikat: Restlaufzeit (ACME-Zustand hat den Reboot ueberlebt?)' try 25 "echo | timeout 15 openssl s_client -6 -connect [${PINIX_ULA}]:443 -servername ${EXT_NAME} 2>/dev/null | openssl x509 -noout -enddate -issuer" printf '\n# Ende Probe "%s"\n' "$LABEL"