Files
local_machine/scripts/vpn/ssh-exit-mode.sh
ahauimix 0238aa7c02 Add Object B and self-growth canons with Cursor ledger rules; refresh connectivity and VPN runbooks.
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>
2026-07-26 10:22:56 +08:00

216 lines
8.0 KiB
Bash
Executable File
Raw Permalink 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.
#!/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