Files
local_machine/docs/connectivity/scripts/macos-hotspot-network-recover.sh

319 lines
11 KiB
Bash
Executable File
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
# Сброс сети после переподключения Android-хотспота (macOS).
#
# RCA (почему старый recover не помогал):
# - Ядерный сброс (route flush, ifconfig down) выполнялся только при sudo -n (passwordless).
# - Без NOPASSWD в sudoers скрипт делал лишь toggle WiFi — ARP REJECT и битые маршруты оставались.
# - Manual IPv4 на WiFi (Joplin 10.219.227.100) конфликтует с новой подсетью хотспота (10.36.x).
#
# Решение:
# - WiFi всегда 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
#
# Переменные:
# 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
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
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 " WiFi Manual $(wifi_manual_ip)$(wifi_manual_router) — переводим на DHCP..."
networksetup -setdhcp "Wi-Fi" 2>/dev/null || sudo_run networksetup -setdhcp "Wi-Fi"
else
echo " WiFi уже 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() {
cat <<EOF
Usage: $(basename "$0") status|recover
recover — PAC/SOCKS off, DHCP, ядерный сброс (sudo), Joplin alias *.100
status — диагностика
Полная команда из любой папки:
bash $SCRIPT_SELF recover
EOF
exit 1
}
[ "$#" -eq 1 ] || usage
cmd=$1
case "$cmd" in
status)
echo "=== Wi-Fi device ==="
wifi_device || true
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 (Wi-Fi) ==="
networksetup -getsocksfirewallproxy "Wi-Fi" 2>/dev/null || true
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 -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 "=== 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
fi
echo "2) SOCKS off..."
networksetup -setsocksfirewallproxystate "Wi-Fi" off 2>/dev/null || true
echo "3) WiFi → DHCP (снимаем конфликтный Manual Joplin)..."
ensure_dhcp_wifi
dev="$(wifi_device)"
if [ -z "$dev" ]; then
echo "Ошибка: WiFi device не найден" >&2
exit 1
fi
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 WiFi (legacy)..."
setup_joplin_manual || true
;;
*)
echo "6) Joplin: пропуск (нет $JOPLIN_ENV)"
;;
esac
echo ""
echo "=== Итог ==="
"$SCRIPT_SELF" status
echo ""
echo "Captive portal: http://neverssl.com"
;;
*)
usage
;;
esac