#!/usr/bin/env bash # Переключение режима выхода в интернет через SSH SOCKS (macOS / Linux). # См. docs/connectivity/vpn/SSH_EXIT_MODES.md 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}" 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 </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() { stop_dynamic_tunnels echo "SSH SOCKS на портах ($SSH_EXIT_STOP_PORTS) остановлены. Выход в интернет — с локального IP (если в приложениях отключён SOCKS-прокси)." } 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 lsof -i :"$SSH_EXIT_MAIN_PORT" -sTCP:LISTEN -t >/dev/null 2>&1; then echo "Ошибка: порт $SSH_EXIT_MAIN_PORT всё ещё занят. Закройте процесс вручную или: $0 local" exit 1 fi } cmd_ru() { ensure_port_free # shellcheck disable=SC2086 ssh -D "$SSH_EXIT_MAIN_PORT" -f -N $SSH_SSH_RU_OPTS "$SSH_EXIT_RU_HOST" echo "SOCKS 127.0.0.1:$SSH_EXIT_MAIN_PORT → $SSH_EXIT_RU_HOST (выход с IP 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 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 не найден — статус портов недоступен." exit 1 fi 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 } cmd_verify() { echo "Без прокси (локальный выход):" 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 } 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