Add Joplin, old Mac, and Fedora WiFi runbooks; simplify JA3 Whisky scripts and hotspot recovery.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,32 +1,244 @@
|
||||
#!/bin/sh
|
||||
# Сброс сетевого стека macOS после смены хотспота / «интернет не поднимается без перезагрузки».
|
||||
# Типичная причина в связке с docs/connectivity: системный PAC на localhost:1089 при мёртвом/ещё не
|
||||
# поднятом PAC-сервере + кэш DNS / Wi‑Fi. См. HOTSPOT_NETWORK_EMERGENCY.md
|
||||
# Сброс сети после переподключения Android-хотспота (macOS).
|
||||
#
|
||||
# Использование (из корня репозитория):
|
||||
# ./docs/connectivity/scripts/macos-hotspot-network-recover.sh status
|
||||
# ./docs/connectivity/scripts/macos-hotspot-network-recover.sh recover
|
||||
# RCA (почему старый recover не помогал):
|
||||
# - Ядерный сброс (route flush, ifconfig down) выполнялся только при sudo -n (passwordless).
|
||||
# - Без NOPASSWD в sudoers скрипт делал лишь toggle Wi‑Fi — ARP REJECT и битые маршруты оставались.
|
||||
# - Manual IPv4 на Wi‑Fi (Joplin 10.219.227.100) конфликтует с новой подсетью хотспота (10.36.x).
|
||||
#
|
||||
# Решение:
|
||||
# - Wi‑Fi всегда DHCP (правильный шлюз после reconnect).
|
||||
# - Joplin: вторичный IP (alias) *.100 в текущей подсети — не трогает default route.
|
||||
# - recover всегда запрашивает sudo (пароль) для полного сброса.
|
||||
#
|
||||
# Из любой папки (одна команда, чинит интернет + Joplin alias):
|
||||
# bash /Users/eternal/code/local_machine/docs/connectivity/scripts/macos-hotspot-network-recover.sh recover
|
||||
#
|
||||
# Переменные:
|
||||
# MACOS_PAC_SERVICES — как в macos-system-pac.sh (по умолчанию Wi-Fi|Thunderbolt Bridge 2)
|
||||
# WIFI_DEVICE — например en0; иначе авто: первый Device у Hardware Port: Wi-Fi
|
||||
# HOTSPOT_JOPLIN_MODE=alias|manual|off alias (по умолчанию при ~/.config/joplin-webdav/env), manual, off
|
||||
# HOTSPOT_RECOVER_SKIP_JOPLIN=1 не трогать Joplin alias
|
||||
# JOPLIN_STATIC_IP последний октет .100 из env; подсеть — текущая
|
||||
# MACOS_PAC_SERVICES, WIFI_DEVICE
|
||||
|
||||
set -e
|
||||
ROOT="$(cd "$(dirname "$0")" && pwd)"
|
||||
PAC_SCRIPT="$ROOT/macos-system-pac.sh"
|
||||
JOPLIN_ENV="${JOPLIN_WEBDAV_CONFIG:-$HOME/.config/joplin-webdav/env}"
|
||||
JOPLIN_ALIAS_SCRIPT="${JOPLIN_ALIAS_SCRIPT:-$(cd "$ROOT/../../apps/joplin/scripts" 2>/dev/null && pwd)/joplin-hotspot-alias.sh}"
|
||||
SCRIPT_SELF="$ROOT/$(basename "$0")"
|
||||
|
||||
wifi_device() {
|
||||
if [ -n "${WIFI_DEVICE:-}" ]; then
|
||||
printf '%s\n' "$WIFI_DEVICE"
|
||||
return
|
||||
fi
|
||||
# "Device: en0" под блоком "Hardware Port: Wi-Fi"
|
||||
networksetup -listallhardwareports 2>/dev/null \
|
||||
| awk '/Hardware Port: Wi-Fi$/ {p=1; next} p && /^Device: / {print $2; exit}'
|
||||
}
|
||||
|
||||
ipv4_subnet() {
|
||||
printf '%s\n' "$1" | awk -F. '{print $1"."$2"."$3}'
|
||||
}
|
||||
|
||||
ipv4_last_octet() {
|
||||
printf '%s\n' "$1" | awk -F. '{print $4}'
|
||||
}
|
||||
|
||||
load_joplin_env() {
|
||||
if [ -f "$JOPLIN_ENV" ]; then
|
||||
# shellcheck disable=SC1090
|
||||
. "$JOPLIN_ENV"
|
||||
fi
|
||||
}
|
||||
|
||||
wifi_config_method() {
|
||||
networksetup -getinfo "Wi-Fi" 2>/dev/null | awk 'NR==1 {print $1; exit}'
|
||||
}
|
||||
|
||||
wifi_manual_router() {
|
||||
networksetup -getinfo "Wi-Fi" 2>/dev/null | awk -F': ' '/^Router: / {print $2; exit}'
|
||||
}
|
||||
|
||||
wifi_manual_ip() {
|
||||
networksetup -getinfo "Wi-Fi" 2>/dev/null | awk -F': ' '/^IP address: / {print $2; exit}'
|
||||
}
|
||||
|
||||
default_gateway() {
|
||||
route -n get default 2>/dev/null | awk '/gateway:/{print $2; exit}'
|
||||
}
|
||||
|
||||
joplin_mode() {
|
||||
if [ "${HOTSPOT_RECOVER_SKIP_JOPLIN:-0}" = "1" ]; then
|
||||
echo off
|
||||
return
|
||||
fi
|
||||
if [ -n "${HOTSPOT_JOPLIN_MODE:-}" ]; then
|
||||
echo "$HOTSPOT_JOPLIN_MODE"
|
||||
return
|
||||
fi
|
||||
# mDNS (joplin-mac.local) — URL на телефоне не зависит от подсети; alias не нужен
|
||||
echo off
|
||||
}
|
||||
|
||||
joplin_target_ip() {
|
||||
load_joplin_env
|
||||
gw="$(default_gateway 2>/dev/null || true)"
|
||||
[ -n "$gw" ] || return 1
|
||||
subnet="$(ipv4_subnet "$gw")"
|
||||
if [ -n "${JOPLIN_STATIC_IP:-}" ]; then
|
||||
last="$(ipv4_last_octet "$JOPLIN_STATIC_IP")"
|
||||
else
|
||||
last=100
|
||||
fi
|
||||
printf '%s\n' "${subnet}.${last}"
|
||||
}
|
||||
|
||||
list_en0_alias_ips() {
|
||||
dev="${1:-en0}"
|
||||
ifconfig "$dev" 2>/dev/null | awk '/inet / && /alias/ {print $2}'
|
||||
}
|
||||
|
||||
sudo_run() {
|
||||
echo " \$ sudo $*"
|
||||
sudo "$@"
|
||||
}
|
||||
|
||||
ensure_dhcp_wifi() {
|
||||
method="$(wifi_config_method 2>/dev/null || true)"
|
||||
if [ "$method" = "Manual" ]; then
|
||||
echo " Wi‑Fi Manual $(wifi_manual_ip) → $(wifi_manual_router) — переводим на DHCP..."
|
||||
networksetup -setdhcp "Wi-Fi" 2>/dev/null || sudo_run networksetup -setdhcp "Wi-Fi"
|
||||
else
|
||||
echo " Wi‑Fi уже DHCP (или не Manual)."
|
||||
fi
|
||||
}
|
||||
|
||||
nuclear_network_reset() {
|
||||
dev="$1"
|
||||
echo " Ядерный сброс на $dev (нужен пароль sudo)..."
|
||||
sudo_run ifconfig "$dev" down
|
||||
sudo_run route -n flush
|
||||
sudo_run dscacheutil -flushcache
|
||||
sudo_run killall -HUP mDNSResponder
|
||||
sudo_run ifconfig "$dev" up
|
||||
sudo_run ipconfig set "$dev" DHCP
|
||||
}
|
||||
|
||||
wait_for_connectivity() {
|
||||
dev="$1"
|
||||
i=0
|
||||
while [ "$i" -lt 30 ]; do
|
||||
gw="$(default_gateway 2>/dev/null || true)"
|
||||
ip="$(ipconfig getifaddr "$dev" 2>/dev/null || true)"
|
||||
if [ -n "$gw" ] && [ -n "$ip" ]; then
|
||||
if ping -c 1 -W 1500 "$gw" >/dev/null 2>&1; then
|
||||
echo " Связность OK: IP=$ip gateway=$gw (${i}s)"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
sleep 1
|
||||
i=$((i + 1))
|
||||
done
|
||||
echo " WARN: gateway/IP не поднялись за 30с — проверьте captive portal" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
remove_joplin_aliases() {
|
||||
dev="$1"
|
||||
target="$2"
|
||||
for ip in $(list_en0_alias_ips "$dev"); do
|
||||
case "$ip" in
|
||||
*.*.*.100|"$target")
|
||||
echo " Удаляем alias $ip на $dev"
|
||||
sudo_run ifconfig "$dev" inet "$ip" -alias 2>/dev/null || true
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
setup_joplin_alias() {
|
||||
dev="$1"
|
||||
target="$(joplin_target_ip 2>/dev/null || true)"
|
||||
[ -n "$target" ] || { echo " Joplin alias: нет gateway" >&2; return 1; }
|
||||
primary="$(ipconfig getifaddr "$dev" 2>/dev/null || true)"
|
||||
if [ "$primary" = "$target" ]; then
|
||||
echo " Joplin: primary IP уже $target"
|
||||
return 0
|
||||
fi
|
||||
remove_joplin_aliases "$dev" "$target"
|
||||
if ifconfig "$dev" 2>/dev/null | grep -q "inet $target "; then
|
||||
echo " Joplin alias $target уже на $dev"
|
||||
return 0
|
||||
fi
|
||||
echo " Joplin alias: $target/24 на $dev (primary DHCP: ${primary:-?})"
|
||||
sudo_run ifconfig "$dev" inet "$target" netmask 255.255.255.0 alias
|
||||
echo " URL на телефоне: http://${target}:8080"
|
||||
if [ -f "$JOPLIN_ENV" ] && [ -n "${JOPLIN_STATIC_IP:-}" ]; then
|
||||
cur_sub="$(ipv4_subnet "$target")"
|
||||
env_sub="$(ipv4_subnet "$JOPLIN_STATIC_IP")"
|
||||
if [ "$cur_sub" != "$env_sub" ]; then
|
||||
echo " ⚠ Подсеть сменилась ($env_sub.x → $cur_sub.x) — обновите URL в Joplin на телефоне"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
setup_joplin_manual() {
|
||||
gw="$(default_gateway 2>/dev/null || true)"
|
||||
[ -n "$gw" ] || return 1
|
||||
target="$(joplin_target_ip 2>/dev/null || true)"
|
||||
echo " Joplin manual (legacy): $target → router $gw"
|
||||
networksetup -setmanual "Wi-Fi" "$target" 255.255.255.0 "$gw"
|
||||
}
|
||||
|
||||
print_manual_ipv4_status() {
|
||||
info="$(networksetup -getinfo "Wi-Fi" 2>/dev/null || true)"
|
||||
echo "$info" | head -5
|
||||
method="$(printf '%s\n' "$info" | awk 'NR==1 {print $1; exit}')"
|
||||
dev="$(wifi_device 2>/dev/null || echo en0)"
|
||||
current_ip="$(ipconfig getifaddr "$dev" 2>/dev/null || true)"
|
||||
gw="$(default_gateway 2>/dev/null || true)"
|
||||
echo "--- runtime $dev ---"
|
||||
echo "primary IP: ${current_ip:-(none)}"
|
||||
echo "default gateway: ${gw:-(none)}"
|
||||
aliases="$(list_en0_alias_ips "$dev" | tr '\n' ' ')"
|
||||
if [ -n "$aliases" ]; then
|
||||
echo "aliases: $aliases"
|
||||
fi
|
||||
if [ -f "$JOPLIN_ENV" ]; then
|
||||
target="$(joplin_target_ip 2>/dev/null || true)"
|
||||
echo "joplin target: ${target:-?} (mode: $(joplin_mode))"
|
||||
if [ -n "$target" ] && ! ifconfig "$dev" 2>/dev/null | grep -q "inet $target "; then
|
||||
echo "⚠ Joplin IP $target не на интерфейсе — телефон не достучится"
|
||||
fi
|
||||
fi
|
||||
if [ "$method" = "Manual" ]; then
|
||||
manual_ip="$(printf '%s\n' "$info" | awk -F': ' '/^IP address: / {print $2; exit}')"
|
||||
manual_router="$(printf '%s\n' "$info" | awk -F': ' '/^Router: / {print $2; exit}')"
|
||||
if [ -n "$manual_router" ] && [ -n "$gw" ]; then
|
||||
if [ "$(ipv4_subnet "$manual_router")" != "$(ipv4_subnet "$gw")" ]; then
|
||||
echo "⚠ MISMATCH: Manual router $(ipv4_subnet "$manual_router").x vs gateway $(ipv4_subnet "$gw").x"
|
||||
echo " → запустите: bash $SCRIPT_SELF recover"
|
||||
elif [ -n "$current_ip" ] && [ "$current_ip" != "$manual_ip" ]; then
|
||||
echo "⚠ MISMATCH: Manual IP $manual_ip, runtime $current_ip"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
if [ -n "$gw" ]; then
|
||||
echo "--- gateway $gw ---"
|
||||
arp -n "$gw" 2>/dev/null || true
|
||||
route -n get "$gw" 2>/dev/null | grep -E 'interface|flags' || true
|
||||
fi
|
||||
}
|
||||
|
||||
usage() {
|
||||
printf '%s\n' "Usage: $(basename "$0") status|recover" >&2
|
||||
cat <<EOF
|
||||
Usage: $(basename "$0") status|recover
|
||||
|
||||
recover — PAC/SOCKS off, DHCP, ядерный сброс (sudo), Joplin alias *.100
|
||||
status — диагностика
|
||||
|
||||
Полная команда из любой папки:
|
||||
bash $SCRIPT_SELF recover
|
||||
EOF
|
||||
exit 1
|
||||
}
|
||||
|
||||
@@ -35,69 +247,70 @@ cmd=$1
|
||||
|
||||
case "$cmd" in
|
||||
status)
|
||||
echo "=== Wi-Fi device (hardware) ==="
|
||||
echo "=== Wi-Fi device ==="
|
||||
wifi_device || true
|
||||
echo "=== networksetup -listallnetworkservices (excerpt) ==="
|
||||
networksetup -listallnetworkservices 2>/dev/null | head -15 || true
|
||||
echo "=== PAC / autoproxy (Wi-Fi + Thunderbolt Bridge 2 if present) ==="
|
||||
echo "=== IPv4 ==="
|
||||
print_manual_ipv4_status || true
|
||||
echo "=== PAC ==="
|
||||
if [ -x "$PAC_SCRIPT" ]; then
|
||||
MACOS_PAC_SERVICES="${MACOS_PAC_SERVICES:-Wi-Fi|Thunderbolt Bridge 2}" "$PAC_SCRIPT" status || true
|
||||
fi
|
||||
echo "=== SOCKS firewall (Wi-Fi) ==="
|
||||
echo "=== SOCKS (Wi-Fi) ==="
|
||||
networksetup -getsocksfirewallproxy "Wi-Fi" 2>/dev/null || true
|
||||
echo "=== Web proxy (Wi-Fi) ==="
|
||||
networksetup -getwebproxy "Wi-Fi" 2>/dev/null || true
|
||||
echo "=== Listener on 1089 (PAC URL default) ==="
|
||||
if lsof -i :1089 -sTCP:LISTEN 2>/dev/null | head -5; then
|
||||
:
|
||||
else
|
||||
echo "(none — при Enabled PAC в Safari/системе возможны сбои)"
|
||||
fi
|
||||
echo "=== PAC listener :1089 ==="
|
||||
lsof -i :1089 -sTCP:LISTEN 2>/dev/null | head -3 || echo "(none)"
|
||||
echo "=== Default route ==="
|
||||
route -n get default 2>/dev/null | head -12 || true
|
||||
echo "=== Quick reachability (ICMP may be blocked on some hotspots) ==="
|
||||
ping -c 1 -W 2000 1.1.1.1 >/dev/null 2>&1 && echo "ping 1.1.1.1: ok" || echo "ping 1.1.1.1: fail"
|
||||
route -n get default 2>/dev/null | head -8 || echo "(no default route)"
|
||||
gw="$(default_gateway 2>/dev/null || true)"
|
||||
if [ -n "$gw" ]; then
|
||||
ping -c 1 -W 2000 "$gw" >/dev/null 2>&1 && echo "ping gateway: ok" || echo "ping gateway: fail"
|
||||
fi
|
||||
curl -sS --max-time 5 -o /dev/null https://1.1.1.1 2>/dev/null && echo "curl: ok" || echo "curl: fail"
|
||||
;;
|
||||
recover)
|
||||
echo "1) Отключаем автоматический PAC на выбранных сервисах..."
|
||||
echo "=== Hotspot recover (DHCP + route flush) ==="
|
||||
echo "Скрипт запросит пароль sudo для сброса маршрутов."
|
||||
echo ""
|
||||
echo "1) PAC off..."
|
||||
if [ -x "$PAC_SCRIPT" ]; then
|
||||
MACOS_PAC_SERVICES="${MACOS_PAC_SERVICES:-Wi-Fi|Thunderbolt Bridge 2}" "$PAC_SCRIPT" off
|
||||
else
|
||||
echo "WARN: нет $PAC_SCRIPT — вручную: Системные настройки → Сеть → Wi‑Fi → Подробнее → Прокси → снять «Автоматическая настройка прокси»" >&2
|
||||
fi
|
||||
echo "2) Отключаем системный SOCKS-прокси на Wi-Fi (если был включён Shadowsocks и т.п.)..."
|
||||
if networksetup -listallnetworkservices 2>/dev/null | grep -Fqx "Wi-Fi"; then
|
||||
networksetup -setsocksfirewallproxystate "Wi-Fi" off 2>/dev/null || true
|
||||
fi
|
||||
echo "3) Сброс DNS-кэша (нужен sudo)..."
|
||||
if sudo -n true 2>/dev/null; then
|
||||
sudo dscacheutil -flushcache 2>/dev/null || true
|
||||
sudo killall -HUP mDNSResponder 2>/dev/null || true
|
||||
echo " mDNSResponder перезагружен."
|
||||
else
|
||||
echo " Пропуск (нет sudo без пароля). Выполните вручную:"
|
||||
echo " sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder"
|
||||
fi
|
||||
echo "2) SOCKS off..."
|
||||
networksetup -setsocksfirewallproxystate "Wi-Fi" off 2>/dev/null || true
|
||||
echo "3) Wi‑Fi → DHCP (снимаем конфликтный Manual Joplin)..."
|
||||
ensure_dhcp_wifi
|
||||
dev="$(wifi_device)"
|
||||
if [ -n "$dev" ]; then
|
||||
echo "4) Обновление DHCP на $dev (нужен sudo)..."
|
||||
if sudo -n true 2>/dev/null; then
|
||||
sudo ipconfig set "$dev" DHCP 2>/dev/null || true
|
||||
else
|
||||
echo " Пропуск. Вручную: sudo ipconfig set $dev DHCP"
|
||||
fi
|
||||
echo "5) Краткое выключение/включение Wi‑Fi радиомодуля ($dev)..."
|
||||
networksetup -setairportpower "$dev" off 2>/dev/null || true
|
||||
sleep 2
|
||||
networksetup -setairportpower "$dev" on 2>/dev/null || true
|
||||
else
|
||||
echo "4–5) Не удалось определить Wi‑Fi device — пропуск DHCP/airport power. Вручную выключите/включите Wi‑Fi в меню."
|
||||
if [ -z "$dev" ]; then
|
||||
echo "Ошибка: Wi‑Fi device не найден" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "=== Готово. Проверка: ==="
|
||||
"$0" status
|
||||
echo "4) Ядерный сброс $dev..."
|
||||
nuclear_network_reset "$dev"
|
||||
echo "5) Ожидание DHCP..."
|
||||
wait_for_connectivity "$dev" || true
|
||||
mode="$(joplin_mode)"
|
||||
case "$mode" in
|
||||
alias)
|
||||
echo "6) Joplin: alias IP (не ломает маршрутизацию)..."
|
||||
if [ -x "$JOPLIN_ALIAS_SCRIPT" ]; then
|
||||
"$JOPLIN_ALIAS_SCRIPT" ensure || true
|
||||
else
|
||||
setup_joplin_alias "$dev" || true
|
||||
fi
|
||||
;;
|
||||
manual)
|
||||
echo "6) Joplin: manual Wi‑Fi (legacy)..."
|
||||
setup_joplin_manual || true
|
||||
;;
|
||||
*)
|
||||
echo "6) Joplin: пропуск (нет $JOPLIN_ENV)"
|
||||
;;
|
||||
esac
|
||||
echo ""
|
||||
echo "Если интернет всё ещё нет: откройте хотспот в браузере (captive portal), затем при необходимости снова включите PAC:"
|
||||
echo " ./docs/connectivity/scripts/macos-pac-on.sh"
|
||||
echo "=== Итог ==="
|
||||
"$SCRIPT_SELF" status
|
||||
echo ""
|
||||
echo "Captive portal: http://neverssl.com"
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
|
||||
Reference in New Issue
Block a user