Track day metrics/anamnesis HARD rules under .cursor/rules and personal docs so each screening updates days/{KIN}, dynamics, and big-data ledger.
Co-authored-by: Cursor <cursoragent@cursor.com>
216 lines
8.0 KiB
Bash
Executable File
216 lines
8.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# Переключение режима выхода в интернет через SSH SOCKS (macOS / Linux).
|
||
# См. docs/connectivity/vpn/SSH_EXIT_MODES.md
|
||
#
|
||
# Типичный кейс из‑за рубежа (RU‑сервисы режут иностранный IP):
|
||
# ./scripts/vpn/exit-via-ru.sh # SOCKS + системный прокси macOS → IP RU
|
||
# ./scripts/vpn/exit-via-local.sh # стоп туннелей + системный SOCKS off
|
||
|
||
set -e
|
||
|
||
SSH_EXIT_MAIN_PORT="${SSH_EXIT_MAIN_PORT:-10809}"
|
||
SSH_EXIT_STOP_PORTS="${SSH_EXIT_STOP_PORTS:-10809 1081 1082}"
|
||
SSH_EXIT_RU_HOST="${SSH_EXIT_RU_HOST:-hsites-ahau}"
|
||
# На macOS по умолчанию включаем системный SOCKS (Safari/Chrome и т.п.).
|
||
# Отключить: SSH_EXIT_SYSTEM_PROXY=0
|
||
if [ -z "${SSH_EXIT_SYSTEM_PROXY+x}" ]; then
|
||
if [ "$(uname -s)" = "Darwin" ]; then
|
||
SSH_EXIT_SYSTEM_PROXY=1
|
||
else
|
||
SSH_EXIT_SYSTEM_PROXY=0
|
||
fi
|
||
fi
|
||
SSH_EXIT_NET_SERVICE="${SSH_EXIT_NET_SERVICE:-Wi-Fi}"
|
||
SSH_SSH_STABLE_OPTS="-o TCPKeepAlive=yes -o IPQoS=throughput -o ServerAliveInterval=60 -o ServerAliveCountMax=4"
|
||
SSH_SSH_RU_OPTS="-o TCPKeepAlive=yes -o ServerAliveInterval=30 -o ServerAliveCountMax=6"
|
||
|
||
usage() {
|
||
cat <<EOF
|
||
Usage: $(basename "$0") COMMAND
|
||
|
||
local | off | stop Остановить SSH SOCKS на портах: $SSH_EXIT_STOP_PORTS
|
||
+ на macOS выключить системный SOCKS (если SSH_EXIT_SYSTEM_PROXY=1).
|
||
|
||
ru SOCKS 127.0.0.1:$SSH_EXIT_MAIN_PORT → выход с RU ($SSH_EXIT_RU_HOST).
|
||
+ на macOS включить системный SOCKS → браузер с IP RU.
|
||
|
||
ru-do SOCKS 127.0.0.1:$SSH_EXIT_MAIN_PORT → Mac → RU → DO
|
||
(telegram-socks-via-do). Снаружи IP DO.
|
||
|
||
status Порты + системный SOCKS (macOS).
|
||
|
||
verify IP без SOCKS и через 127.0.0.1:$SSH_EXIT_MAIN_PORT.
|
||
|
||
Env:
|
||
SSH_EXIT_MAIN_PORT Порт SOCKS (по умолчанию 10809).
|
||
SSH_EXIT_STOP_PORTS Порты для local (пробелы).
|
||
SSH_EXIT_RU_HOST Хост только-RU (по умолчанию hsites-ahau).
|
||
SSH_EXIT_SYSTEM_PROXY 1 = трогать системный SOCKS на macOS (по умолчанию 1 на Darwin).
|
||
SSH_EXIT_NET_SERVICE Сервис networksetup (по умолчанию Wi-Fi).
|
||
|
||
Требуется ~/.ssh/config: $SSH_EXIT_RU_HOST, telegram-socks-via-do.
|
||
EOF
|
||
}
|
||
|
||
is_darwin() {
|
||
[ "$(uname -s)" = "Darwin" ]
|
||
}
|
||
|
||
system_proxy_wanted() {
|
||
[ "$SSH_EXIT_SYSTEM_PROXY" = "1" ] && is_darwin && command -v networksetup >/dev/null 2>&1
|
||
}
|
||
|
||
enable_system_socks() {
|
||
system_proxy_wanted || return 0
|
||
# PAC поверх SOCKS часто ломает выход — гасим.
|
||
networksetup -setautoproxystate "$SSH_EXIT_NET_SERVICE" off 2>/dev/null || true
|
||
networksetup -setsocksfirewallproxy "$SSH_EXIT_NET_SERVICE" 127.0.0.1 "$SSH_EXIT_MAIN_PORT"
|
||
networksetup -setsocksfirewallproxystate "$SSH_EXIT_NET_SERVICE" on
|
||
echo "Системный SOCKS ($SSH_EXIT_NET_SERVICE): 127.0.0.1:$SSH_EXIT_MAIN_PORT ON"
|
||
}
|
||
|
||
disable_system_socks() {
|
||
system_proxy_wanted || return 0
|
||
networksetup -setsocksfirewallproxystate "$SSH_EXIT_NET_SERVICE" off 2>/dev/null || true
|
||
echo "Системный SOCKS ($SSH_EXIT_NET_SERVICE): OFF"
|
||
}
|
||
|
||
print_system_socks() {
|
||
if ! is_darwin || ! command -v networksetup >/dev/null 2>&1; then
|
||
return 0
|
||
fi
|
||
echo "=== Системный SOCKS ($SSH_EXIT_NET_SERVICE) ==="
|
||
networksetup -getsocksfirewallproxy "$SSH_EXIT_NET_SERVICE" 2>/dev/null || true
|
||
}
|
||
|
||
stop_dynamic_tunnels() {
|
||
local p
|
||
for p in $SSH_EXIT_STOP_PORTS; do
|
||
pkill -f "ssh.*-D ${p} " 2>/dev/null || true
|
||
pkill -f "ssh.*-D ${p}\$" 2>/dev/null || true
|
||
done
|
||
sleep 0.4
|
||
for p in $SSH_EXIT_STOP_PORTS; do
|
||
if command -v lsof >/dev/null 2>&1; then
|
||
# shellcheck disable=SC2046
|
||
lsof -ti ":$p" -sTCP:LISTEN 2>/dev/null | xargs kill 2>/dev/null || true
|
||
fi
|
||
done
|
||
}
|
||
|
||
cmd_local() {
|
||
# Сначала снимаем системный SOCKS — иначе при мёртвом туннеле сеть «висит».
|
||
disable_system_socks
|
||
stop_dynamic_tunnels
|
||
echo "SSH SOCKS на портах ($SSH_EXIT_STOP_PORTS) остановлены. Выход — с локального публичного IP."
|
||
}
|
||
|
||
ensure_port_free() {
|
||
if command -v lsof >/dev/null 2>&1; then
|
||
if lsof -i :"$SSH_EXIT_MAIN_PORT" -sTCP:LISTEN -t >/dev/null 2>&1; then
|
||
echo "Порт $SSH_EXIT_MAIN_PORT занят — освобождаю..."
|
||
pkill -f "ssh.*-D ${SSH_EXIT_MAIN_PORT} " 2>/dev/null || true
|
||
sleep 0.4
|
||
# shellcheck disable=SC2046
|
||
lsof -ti ":$SSH_EXIT_MAIN_PORT" -sTCP:LISTEN 2>/dev/null | xargs kill 2>/dev/null || true
|
||
sleep 0.3
|
||
fi
|
||
fi
|
||
if command -v lsof >/dev/null 2>&1 && lsof -i :"$SSH_EXIT_MAIN_PORT" -sTCP:LISTEN -t >/dev/null 2>&1; then
|
||
echo "Ошибка: порт $SSH_EXIT_MAIN_PORT всё ещё занят. Закройте процесс вручную или: $0 local"
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
wait_socks_ready() {
|
||
local i
|
||
for i in 1 2 3 4 5 6 7 8 9 10; do
|
||
if command -v lsof >/dev/null 2>&1; then
|
||
if lsof -i :"$SSH_EXIT_MAIN_PORT" -sTCP:LISTEN -t >/dev/null 2>&1; then
|
||
return 0
|
||
fi
|
||
else
|
||
sleep 0.5
|
||
return 0
|
||
fi
|
||
sleep 0.3
|
||
done
|
||
echo "Ошибка: SOCKS на :$SSH_EXIT_MAIN_PORT не поднялся." >&2
|
||
exit 1
|
||
}
|
||
|
||
cmd_ru() {
|
||
ensure_port_free
|
||
# shellcheck disable=SC2086
|
||
ssh -D "$SSH_EXIT_MAIN_PORT" -f -N $SSH_SSH_RU_OPTS "$SSH_EXIT_RU_HOST"
|
||
wait_socks_ready
|
||
enable_system_socks
|
||
echo "SOCKS 127.0.0.1:$SSH_EXIT_MAIN_PORT → $SSH_EXIT_RU_HOST (выход с IP RU-прокси)."
|
||
echo "Проверка: $0 verify | в браузере: https://ipinfo.io (ожидается RU)"
|
||
}
|
||
|
||
cmd_ru_do() {
|
||
ensure_port_free
|
||
if ! ssh -o BatchMode=yes -o ConnectTimeout=6 -o ConnectionAttempts=1 "$SSH_EXIT_RU_HOST" "exit 0" 2>/dev/null; then
|
||
echo "Ошибка: $SSH_EXIT_RU_HOST недоступен. Режим ru-do только Mac→RU→DO; прямой Mac→DO не используется." >&2
|
||
exit 1
|
||
fi
|
||
# shellcheck disable=SC2086
|
||
ssh -D "$SSH_EXIT_MAIN_PORT" -f -N $SSH_SSH_STABLE_OPTS telegram-socks-via-do
|
||
wait_socks_ready
|
||
enable_system_socks
|
||
echo "SOCKS 127.0.0.1:$SSH_EXIT_MAIN_PORT → $SSH_EXIT_RU_HOST → DO (telegram-socks-via-do). Снаружи виден IP DO."
|
||
}
|
||
|
||
cmd_status() {
|
||
local p any=0
|
||
if ! command -v lsof >/dev/null 2>&1; then
|
||
echo "lsof не найден — статус портов недоступен."
|
||
else
|
||
for p in $SSH_EXIT_STOP_PORTS; do
|
||
if lsof -i :"$p" -sTCP:LISTEN -t >/dev/null 2>&1; then
|
||
echo "--- :$p ---"
|
||
lsof -i :"$p" -sTCP:LISTEN
|
||
any=1
|
||
fi
|
||
done
|
||
if [ "$any" = 0 ]; then
|
||
echo "Ни один из портов не слушает: $SSH_EXIT_STOP_PORTS"
|
||
fi
|
||
fi
|
||
echo ""
|
||
print_system_socks
|
||
}
|
||
|
||
cmd_verify() {
|
||
echo "Без прокси (локальный выход / curl игнорирует системный SOCKS):"
|
||
curl -sS --max-time 12 https://ipinfo.io/ip 2>/dev/null || echo "(ошибка curl)"
|
||
echo ""
|
||
echo "Через SOCKS socks5h://127.0.0.1:$SSH_EXIT_MAIN_PORT:"
|
||
curl -sS --max-time 12 -x "socks5h://127.0.0.1:$SSH_EXIT_MAIN_PORT" https://ipinfo.io/ip 2>/dev/null || echo "(нет ответа — туннель не поднят или порт другой)"
|
||
echo ""
|
||
echo "Кратко через SOCKS (ip + страна):"
|
||
curl -sS --max-time 12 -x "socks5h://127.0.0.1:$SSH_EXIT_MAIN_PORT" https://ipinfo.io/json 2>/dev/null \
|
||
| sed -n 's/.*"ip": *"\([^"]*\)".*/IP: \1/p; s/.*"country": *"\([^"]*\)".*/country: \1/p' 2>/dev/null | head -4 || true
|
||
echo ""
|
||
print_system_socks
|
||
}
|
||
|
||
case "${1:-}" in
|
||
local|off|stop) cmd_local ;;
|
||
ru) cmd_ru ;;
|
||
ru-do|ru_do) cmd_ru_do ;;
|
||
status) cmd_status ;;
|
||
verify) cmd_verify ;;
|
||
-h|--help|help) usage ;;
|
||
"")
|
||
usage
|
||
exit 1
|
||
;;
|
||
*)
|
||
echo "Неизвестная команда: $1"
|
||
usage
|
||
exit 1
|
||
;;
|
||
esac
|