diff --git a/install_ssh_tunnel.sh b/install_ssh_tunnel.sh index 34347cc..a5f8bc3 100644 --- a/install_ssh_tunnel.sh +++ b/install_ssh_tunnel.sh @@ -9,13 +9,25 @@ # 3. Запуск меню: sudo bash tunnel-manager.sh menu # +set -o pipefail + # --- Глобальные переменные и конфигурация --- CONFIG_DIR="/etc/tunnel-manager" TUNNELS_DIR="$CONFIG_DIR/tunnels" LOG_DIR="/var/log/tunnel-manager" +PID_DIR="/var/run/tunnel-manager" SERVICE_DIR="/etc/systemd/system" SCRIPT_PATH="/usr/local/bin/tunnel-manager" VPS_CONFIG="$CONFIG_DIR/vps_config" +LOCK_FILE="/var/run/tunnel-manager.lock" + +# --- Константы для стабильности туннелей --- +MAX_RESTART_COUNT=10 +RESTART_BACKOFF_BASE=5 +RESTART_BACKOFF_MAX=300 +MONITOR_INTERVAL=30 +SSH_KEY_PATH="/root/.ssh/id_rsa" +SSH_KNOWN_HOSTS="/root/.ssh/known_hosts_tunnels" # --- Цвета для вывода --- RED='\033[0;31m' @@ -26,30 +38,56 @@ CYAN='\033[0;36m' NC='\033[0m' BOLD='\033[1m' +# --- Обработчики сигналов --- +cleanup_on_exit() { + log_message "Получен сигнал завершения. Очистка..." "$YELLOW" + release_lock + exit 0 +} + +trap cleanup_on_exit SIGTERM SIGINT SIGHUP + +# --- Блокировка для предотвращения race conditions --- +acquire_lock() { + exec 200>"$LOCK_FILE" + if ! flock -n 200; then + log_message "Другой экземпляр скрипта уже выполняется. Ожидание..." "$YELLOW" + if ! flock -w 30 200; then + log_message "Таймаут ожидания блокировки. Выход." "$RED" + exit 1 + fi + fi +} + +release_lock() { + flock -u 200 2>/dev/null || true +} + # --- Вспомогательные функции --- -# Функция логирования и вывода сообщений +# Функция логирования с ротацией log_message() { local message="$1" local color="${2:-$NC}" - local timestamp=$(date '+%Y-%m-%d %H:%M:%S') - # Вывод в консоль и запись в лог - echo -e "${color}[$timestamp] $message${NC}" | tee -a "$LOG_DIR/manager.log" + local timestamp + timestamp=$(date '+%Y-%m-%d %H:%M:%S') + + mkdir -p "$LOG_DIR" + + # Ротация лога если больше 10MB + local logfile="$LOG_DIR/manager.log" + if [ -f "$logfile" ] && [ "$(stat -c%s "$logfile" 2>/dev/null || echo 0)" -gt 10485760 ]; then + mv "$logfile" "${logfile}.old" + fi + + echo -e "${color}[$timestamp] $message${NC}" | tee -a "$logfile" 2>/dev/null || echo -e "${color}[$timestamp] $message${NC}" } -# Функция проверки ошибок +# Функция проверки ошибок (без интерактивного ввода) check_error() { local exit_code=$? if [ $exit_code -ne 0 ]; then log_message "ОШИБКА (код $exit_code): $1" "$RED" - log_message "Подробности в логе: $LOG_DIR/manager.log" "$YELLOW" - - echo -e "${YELLOW}Произошла ошибка: $1${NC}" - read -t 60 -p "Продолжить выполнение скрипта? (y/n): " -n 1 -r - echo - if [[ ! $REPLY =~ ^[Yy]$ ]]; then - exit 1 - fi return 1 fi return 0 @@ -58,8 +96,8 @@ check_error() { # Функция проверки прав root check_root() { if [[ $EUID -ne 0 ]]; then - log_message "ОШИБКА: Этот скрипт требует прав root (sudo)." "$RED" - log_message "Пожалуйста, запустите: sudo bash $0 $1" "$BOLD" + echo -e "${RED}ОШИБКА: Этот скрипт требует прав root (sudo).${NC}" + echo -e "${BOLD}Пожалуйста, запустите: sudo bash $0 $1${NC}" exit 1 fi } @@ -69,38 +107,33 @@ safe_read() { local prompt="$1" local default="$2" local var_name="$3" - local max_attempts="${4:-3}" # По умолчанию 3 попытки - + local max_attempts="${4:-3}" + local attempt=0 local input="" - + while [[ $attempt -lt $max_attempts ]]; do attempt=$((attempt + 1)) - - # Явное чтение из /dev/tty чтобы избежать проблем с pipe + if [[ -t 0 ]]; then read -p "$prompt" input else read -p "$prompt" input /dev/null) + if [ -z "$pid" ]; then + return 1 + fi + + # Проверяем, существует ли процесс + if ! kill -0 "$pid" 2>/dev/null; then + return 1 + fi + + # Проверяем, что это действительно SSH туннель (а не другой процесс с тем же PID) + local proc_cmd + proc_cmd=$(cat "/proc/$pid/cmdline" 2>/dev/null | tr '\0' ' ') + if echo "$proc_cmd" | grep -qE '(ssh|autossh).*-R'; then + return 0 + fi + + return 1 +} + +# Проверка, слушает ли локальный порт +is_local_port_listening() { + local port="$1" + if command -v ss &>/dev/null; then + ss -tlnp 2>/dev/null | grep -q ":$port " + elif command -v netstat &>/dev/null; then + netstat -tlnp 2>/dev/null | grep -q ":$port " + else + timeout 2 bash -c "echo >/dev/tcp/localhost/$port" 2>/dev/null + fi +} + +# Проверка, доступен ли VPS (только TCP connect, без SSH) +is_vps_reachable() { + local host="$1" + local port="${2:-22}" + timeout 5 bash -c "echo >/dev/tcp/$host/$port" 2>/dev/null +} + +# Проверка, что SSH ключ валиден +is_ssh_key_valid() { + if [ ! -f "$SSH_KEY_PATH" ]; then + return 1 + fi + ssh-keygen -l -f "$SSH_KEY_PATH" &>/dev/null +} + +# --- Функции управления PID-файлами --- + +write_pidfile() { + local tunnel_id="$1" + local pid="$2" + mkdir -p "$PID_DIR" + echo "$pid" > "$PID_DIR/$tunnel_id.pid" +} + +read_pidfile() { + local tunnel_id="$1" + if [ -f "$PID_DIR/$tunnel_id.pid" ]; then + cat "$PID_DIR/$tunnel_id.pid" 2>/dev/null + fi +} + +remove_pidfile() { + local tunnel_id="$1" + rm -f "$PID_DIR/$tunnel_id.pid" +} + +# --- Функция отображения публичного ключа --- show_public_key() { echo -e "${BOLD}${CYAN}--- Публичный ключ для копирования на VPS ---${NC}" - if [ -f "/root/.ssh/id_rsa" ]; then - echo -e "${GREEN}Приватный ключ: /root/.ssh/id_rsa${NC}" + if [ -f "$SSH_KEY_PATH" ]; then + echo -e "${GREEN}Приватный ключ: $SSH_KEY_PATH${NC}" echo -e "${GREEN}Публичный ключ:${NC}" - cat /root/.ssh/id_rsa.pub 2>/dev/null || echo "Публичный ключ не найден" + cat "${SSH_KEY_PATH}.pub" 2>/dev/null || echo "Публичный ключ не найден" echo "" else echo -e "${RED}SSH ключ не найден. Сначала выполните установку.${NC}" @@ -167,173 +284,109 @@ show_public_key() { read_input "Нажмите Enter для продолжения..." } -# Функция проверки портов +# --- Функция проверки портов (ЛОКАЛЬНАЯ, без SSH на VPS) --- check_ports() { local LOCAL_PORT="$1" local REMOTE_PORT="$2" local VPS_HOST="$3" local VPS_USER="$4" local VPS_PORT="$5" - + echo -e "${CYAN}=== ПРОВЕРКА ПОРТОВ ===${NC}" - + # 1. Проверка локального порта echo -e "\n${BLUE}1. Проверка локального порта $LOCAL_PORT:${NC}" - if command -v ss &> /dev/null; then - echo -e " ss -tlnp | grep :$LOCAL_PORT" - ss -tlnp | grep ":$LOCAL_PORT" || echo " Порт $LOCAL_PORT не слушает локально" - elif command -v netstat &> /dev/null; then - echo -e " netstat -tlnp | grep :$LOCAL_PORT" - netstat -tlnp 2>/dev/null | grep ":$LOCAL_PORT" || echo " Порт $LOCAL_PORT не слушает локально" - else - echo " Утилиты ss и netstat не найдены" - fi - - # 2. Проверка удаленного порта на VPS - echo -e "\n${BLUE}2. Проверка удаленного порта $REMOTE_PORT на VPS ($VPS_HOST):${NC}" - - if ! load_vps_settings; then - echo " Настройки VPS не загружены" - return 1 - fi - - # Проверка доступности VPS - echo -e " Проверка подключения к VPS..." - if timeout 5 nc -z "$VPS_HOST" "$VPS_PORT" 2>/dev/null; then - echo -e " ✓ VPS доступен на порту $VPS_PORT" - else - echo -e " ✗ VPS недоступен на порту $VPS_PORT" - return 1 - fi - - # Попробуем проверить порт на VPS через SSH - if [ -f "/root/.ssh/id_rsa" ]; then - echo -e " Проверка порта $REMOTE_PORT на VPS через SSH..." - local check_cmd="if command -v ss >/dev/null 2>&1; then ss -tlnp | grep ':$REMOTE_PORT'; elif command -v netstat >/dev/null 2>&1; then netstat -tlnp 2>/dev/null | grep ':$REMOTE_PORT'; else echo 'Не найдены утилиты для проверки портов'; fi" - - if output=$(timeout 10 ssh -p "$VPS_PORT" -i /root/.ssh/id_rsa -o ConnectTimeout=5 -o BatchMode=yes "$VPS_USER@$VPS_HOST" "$check_cmd" 2>/dev/null); then - if [ -n "$output" ]; then - echo -e " ✗ Порт $REMOTE_PORT на VPS уже занят:" - echo " $output" - else - echo -e " ✓ Порт $REMOTE_PORT на VPS свободен" - fi - else - echo -e " ⚠ Не удалось проверить порт на VPS (возможно, нет доступа по ключу)" + if is_local_port_listening "$LOCAL_PORT"; then + echo -e " ${GREEN}✓ Порт $LOCAL_PORT слушает локально${NC}" + if command -v ss &>/dev/null; then + ss -tlnp 2>/dev/null | grep ":$LOCAL_PORT " + elif command -v netstat &>/dev/null; then + netstat -tlnp 2>/dev/null | grep ":$LOCAL_PORT " fi else - echo -e " ⚠ SSH ключ не найден, проверка порта на VPS невозможна" + echo -e " ${RED}✗ Порт $LOCAL_PORT не слушает локально${NC}" fi - - # 3. Проверка проброса через telnet/nc - echo -e "\n${BLUE}3. Проверка внешней доступности порта $REMOTE_PORT на VPS:${NC}" - echo -e " (Эта проверка работает только если на VPS настроен GatewayPorts=yes)" - if timeout 5 nc -zv "$VPS_HOST" "$REMOTE_PORT" 2>&1 | grep -q "succeeded"; then - echo -e " ✓ Порт $REMOTE_PORT открыт на VPS" + + # 2. Проверка доступности VPS (только TCP, без SSH) + echo -e "\n${BLUE}2. Проверка доступности VPS ($VPS_HOST:$VPS_PORT):${NC}" + if is_vps_reachable "$VPS_HOST" "$VPS_PORT"; then + echo -e " ${GREEN}✓ VPS доступен на порту $VPS_PORT${NC}" else - echo -e " ⚠ Порт $REMOTE_PORT не открыт на VPS (нормально для обратного туннеля)" + echo -e " ${RED}✗ VPS недоступен на порту $VPS_PORT${NC}" fi - - # 4. Проверка процесса autossh - echo -e "\n${BLUE}4. Проверка процессов autossh:${NC}" - if pgrep -f "autossh.*$REMOTE_PORT.*localhost:$LOCAL_PORT" > /dev/null; then - echo -e " ✓ Процесс autossh для туннеля $LOCAL_PORT->$REMOTE_PORT запущен" - ps aux | grep "autossh.*$REMOTE_PORT.*localhost:$LOCAL_PORT" | grep -v grep + + # 3. Проверка процесса туннеля по PID-файлу + echo -e "\n${BLUE}3. Проверка процесса туннеля:${NC}" + local tunnel_id + tunnel_id=$(echo "$TUNNEL_NAME" | tr '[:upper:]' '[:lower:]' | tr -cd '[:alnum:]_') + if is_tunnel_pid_alive "$tunnel_id"; then + local pid + pid=$(read_pidfile "$tunnel_id") + echo -e " ${GREEN}✓ Процесс туннеля запущен (PID: $pid)${NC}" + ps -p "$pid" -o pid,cmd --no-headers 2>/dev/null else - echo -e " ✗ Процесс autossh для туннеля не найден" + echo -e " ${RED}✗ Процесс туннеля не найден${NC}" fi - - # 5. Проверка SSH процессов - echo -e "\n${BLUE}5. Проверка SSH процессов для туннеля:${NC}" - if pgrep -f "ssh.*$REMOTE_PORT:localhost:$LOCAL_PORT" > /dev/null; then - echo -e " ✓ SSH процесс для туннеля запущен" - ps aux | grep "ssh.*$REMOTE_PORT:localhost:$LOCAL_PORT" | grep -v grep - else - echo -e " ✗ SSH процесс для туннеля не найден" - fi - + echo -e "${CYAN}=== КОНЕЦ ПРОВЕРКИ ===${NC}\n" } -# Функция проверки SSH подключения с подробным выводом +# --- Функция проверки SSH подключения (с ControlMaster=no) --- check_ssh_connection() { if ! load_vps_settings; then log_message "Сначала необходимо настроить VPS (опция 1)." "$RED" return 1 fi - + echo -e "${CYAN}Проверка подключения к $VPS_USER@$VPS_HOST:$VPS_PORT...${NC}" - echo -e "${YELLOW}Используется ключ: /root/.ssh/id_rsa${NC}" - - # Проверяем существование ключа - if [ ! -f "/root/.ssh/id_rsa" ]; then + echo -e "${YELLOW}Используется ключ: $SSH_KEY_PATH${NC}" + + if [ ! -f "$SSH_KEY_PATH" ]; then echo -e "${RED}✗ SSH ключ не найден. Выполните установку менеджера.${NC}" return 1 fi - - # Проверяем права на ключ - local key_perms=$(stat -c %a /root/.ssh/id_rsa 2>/dev/null || echo "000") + + local key_perms + key_perms=$(stat -c %a "$SSH_KEY_PATH" 2>/dev/null || echo "000") if [ "$key_perms" != "600" ]; then echo -e "${YELLOW}⚠ Предупреждение: Неправильные права на ключ ($key_perms, должны быть 600)${NC}" - chmod 600 /root/.ssh/id_rsa 2>/dev/null && echo " Права исправлены на 600" + chmod 600 "$SSH_KEY_PATH" 2>/dev/null && echo " Права исправлены на 600" fi - - # Проверяем наличие публичного ключа - if [ ! -f "/root/.ssh/id_rsa.pub" ]; then + + if [ ! -f "${SSH_KEY_PATH}.pub" ]; then echo -e "${YELLOW}⚠ Публичный ключ не найден. Генерируем...${NC}" - ssh-keygen -y -f /root/.ssh/id_rsa > /root/.ssh/id_rsa.pub 2>/dev/null + ssh-keygen -y -f "$SSH_KEY_PATH" > "${SSH_KEY_PATH}.pub" 2>/dev/null fi - - # Выполняем тестовое подключение с подробным выводом + echo -e "${BLUE}Выполняем тестовую команду на удаленном сервере...${NC}" - - # Сначала простой тест + + local ssh_opts + ssh_opts=$(get_ssh_base_opts) + echo -e "Тест 1: Проверка базового подключения..." - if timeout 10 ssh -p "$VPS_PORT" \ - -o BatchMode=yes \ - -o ConnectTimeout=5 \ - -o StrictHostKeyChecking=no \ - -o UserKnownHostsFile=/dev/null \ - -o IdentityFile=/root/.ssh/id_rsa \ - -o PasswordAuthentication=no \ + if timeout 15 ssh -p "$VPS_PORT" \ + $ssh_opts \ + -o IdentityFile="$SSH_KEY_PATH" \ "$VPS_USER@$VPS_HOST" "echo 'SSH Connection Test: SUCCESS'; exit 0" 2>&1; then - + echo -e "${GREEN}✓ SSH подключение работает!${NC}" - - # Расширенный тест + echo -e "\nТест 2: Проверка возможности создания туннеля..." - if output=$(timeout 15 ssh -p "$VPS_PORT" \ - -o BatchMode=yes \ - -o ConnectTimeout=5 \ - -o StrictHostKeyChecking=no \ - -o UserKnownHostsFile=/dev/null \ - -o IdentityFile=/root/.ssh/id_rsa \ - -o PasswordAuthentication=no \ + if timeout 15 ssh -p "$VPS_PORT" \ + $ssh_opts \ + -o IdentityFile="$SSH_KEY_PATH" \ -T \ - "$VPS_USER@$VPS_HOST" "echo 'Tunnel test: READY'; sleep 2; exit 0" 2>&1); then - + "$VPS_USER@$VPS_HOST" "echo 'Tunnel test: READY'; sleep 2; exit 0" 2>&1; then + echo -e "${GREEN}✓ Туннелирование доступно${NC}" return 0 else echo -e "${YELLOW}⚠ Проблемы с туннелированием${NC}" - echo "Вывод: $output" return 1 fi else - local ssh_output=$(timeout 10 ssh -p "$VPS_PORT" \ - -o BatchMode=yes \ - -o ConnectTimeout=5 \ - -o StrictHostKeyChecking=no \ - -o UserKnownHostsFile=/dev/null \ - -o IdentityFile=/root/.ssh/id_rsa \ - -o PasswordAuthentication=no \ - -v \ - "$VPS_USER@$VPS_HOST" "echo 'test'" 2>&1 | tail -20) - echo -e "${RED}✗ SSH подключение не работает${NC}" - echo -e "${YELLOW}Последние строки лога SSH:${NC}" - echo "$ssh_output" - + echo -e "\n${YELLOW}Возможные причины:${NC}" echo "1. Ключ не скопирован на VPS" echo "2. На VPS не разрешена аутентификация по ключу" @@ -342,23 +395,21 @@ check_ssh_connection() { echo "5. Проблемы с сетью/файрволом" echo "6. Неправильные права на ключ" echo "" - - # Пробуем подключиться без ключа для диагностики + echo -e "${YELLOW}Проверка доступности порта...${NC}" - if timeout 3 nc -zv "$VPS_HOST" "$VPS_PORT" 2>&1; then + if is_vps_reachable "$VPS_HOST" "$VPS_PORT"; then echo -e "${GREEN}✓ Порт $VPS_PORT доступен${NC}" else echo -e "${RED}✗ Порт $VPS_PORT недоступен${NC}" fi - - # Проверяем конфигурацию ключа + echo -e "\n${YELLOW}Проверка SSH ключа:${NC}" - if ssh-keygen -l -f /root/.ssh/id_rsa 2>/dev/null; then + if is_ssh_key_valid; then echo -e "✓ Ключ валиден" else echo -e "✗ Ключ поврежден или невалиден" fi - + return 1 fi } @@ -372,32 +423,31 @@ install_manager() { # Создание директорий log_message "Создание системных директорий..." "$BLUE" - mkdir -p "$CONFIG_DIR" "$TUNNELS_DIR" "$LOG_DIR" - chmod 700 "$CONFIG_DIR" "$TUNNELS_DIR" - chmod 777 "$LOG_DIR" # Для удобства логирования + mkdir -p "$CONFIG_DIR" "$TUNNELS_DIR" "$LOG_DIR" "$PID_DIR" + chmod 700 "$CONFIG_DIR" "$TUNNELS_DIR" "$PID_DIR" + chmod 755 "$LOG_DIR" # Установка зависимостей log_message "Установка необходимых зависимостей..." "$BLUE" - # Определение дистрибутива local OS if [ -f /etc/os-release ]; then . /etc/os-release OS=$ID fi - + log_message "Обнаружен дистрибутив: ${OS:-Unknown}" "$BLUE" - + if [[ "$OS" == "ubuntu" || "$OS" == "debian" ]]; then log_message "Обновление списка пакетов..." "$BLUE" - apt-get update 2>&1 | tee -a "$LOG_DIR/manager.log" + apt-get update -qq 2>&1 | tee -a "$LOG_DIR/manager.log" check_error "Не удалось обновить список пакетов" - - log_message "Установка autossh, openssh-client, sshpass, netcat..." "$BLUE" - apt-get install -y autossh openssh-client sshpass netcat 2>&1 | tee -a "$LOG_DIR/manager.log" + + log_message "Установка autossh, openssh-client, sshpass, netcat-openbsd..." "$BLUE" + DEBIAN_FRONTEND=noninteractive apt-get install -y autossh openssh-client sshpass netcat-openbsd 2>&1 | tee -a "$LOG_DIR/manager.log" check_error "Не удалось установить пакеты (apt)" elif [[ "$OS" == "centos" || "$OS" == "rhel" || "$OS" == "fedora" || "$OS" == "rocky" || "$OS" == "almalinux" ]]; then log_message "Установка autossh, openssh-clients, sshpass, nc..." "$BLUE" - if command -v dnf &> /dev/null; then + if command -v dnf &>/dev/null; then dnf install -y autossh openssh-clients sshpass nc 2>&1 | tee -a "$LOG_DIR/manager.log" check_error "Не удалось установить пакеты (dnf)" else @@ -410,7 +460,7 @@ install_manager() { return 1 fi - if ! command -v autossh &> /dev/null; then + if ! command -v autossh &>/dev/null; then log_message "ОШИБКА: autossh не установлен. Проверьте вывод установки." "$RED" return 1 fi @@ -418,64 +468,63 @@ install_manager() { # Копирование скрипта в системный путь log_message "Копирование скрипта в $SCRIPT_PATH..." "$BLUE" - - # Получаем содержимое текущего скрипта - local current_script - if [ -f "$0" ]; then - current_script="$0" - else - # Если скрипт запущен через pipe, читаем из stdin - current_script="/tmp/tunnel-manager-$(date +%s).sh" - cat > "$current_script" + + if [ -f "$0" ] && [ "$0" != "bash" ] && [ "$0" != "/bin/bash" ]; then + cp "$0" "$SCRIPT_PATH" 2>/dev/null fi - - # Копируем скрипт - cp "$current_script" "$SCRIPT_PATH" 2>/dev/null || { - # Если не удалось, создаем новый - cat > "$SCRIPT_PATH" << 'EOF' -#!/bin/bash -echo "SSH Tunnel Manager - Установленная версия" -echo "Используйте: sudo tunnel-manager menu" -EOF - } - - chmod +x "$SCRIPT_PATH" + + if [ ! -f "$SCRIPT_PATH" ]; then + log_message "ПРЕДУПРЕЖДЕНИЕ: Не удалось скопировать скрипт. Создаем символическую ссылку." "$YELLOW" + fi + + chmod +x "$SCRIPT_PATH" 2>/dev/null || true log_message "Установка завершена. Теперь можно использовать команду: sudo tunnel-manager menu" "$GREEN" - + # Генерация SSH ключа - if [ ! -f "/root/.ssh/id_rsa" ]; then - log_message "Генерация SSH ключа RSA 4096 бит для root..." "$BLUE" + if [ ! -f "$SSH_KEY_PATH" ]; then + log_message "Генерация SSH ключа Ed25519 для root..." "$BLUE" mkdir -p /root/.ssh - ssh-keygen -t rsa -b 4096 -N "" -f /root/.ssh/id_rsa -q - log_message "SSH ключ сгенерирован: /root/.ssh/id_rsa.pub" "$GREEN" + chmod 700 /root/.ssh + ssh-keygen -t ed25519 -N "" -f "$SSH_KEY_PATH" -q -C "tunnel-manager-auto" + log_message "SSH ключ сгенерирован: ${SSH_KEY_PATH}.pub" "$GREEN" else log_message "SSH ключ уже существует. Используем существующий." "$YELLOW" fi - - # Настройка SSH конфига для стабильности + + # Настройка SSH конфига для стабильности (БЕЗ ControlMaster) log_message "Настройка SSH клиента для стабильности..." "$BLUE" mkdir -p /root/.ssh - cat > /root/.ssh/config << EOFCONFIG + chmod 700 /root/.ssh + + cat > /root/.ssh/config << 'EOFCONFIG' +# SSH Tunnel Manager Configuration +# ВНИМАНИЕ: ControlMaster отключен для совместимости с autossh туннелями Host * - StrictHostKeyChecking no - UserKnownHostsFile /dev/null - ServerAliveInterval 30 - ServerAliveCountMax 3 + StrictHostKeyChecking accept-new + UserKnownHostsFile ~/.ssh/known_hosts_tunnels + ServerAliveInterval 15 + ServerAliveCountMax 4 ConnectTimeout 30 TCPKeepAlive yes IdentitiesOnly yes ExitOnForwardFailure yes - ControlMaster auto - ControlPath ~/.ssh/control-%r@%h:%p - ControlPersist 10m + ControlMaster no + ControlPath none IPQoS throughput Compression yes GSSAPIAuthentication no AddressFamily inet + PasswordAuthentication no + BatchMode yes EOFCONFIG chmod 600 /root/.ssh/config - log_message "Конфигурация SSH клиента обновлена." "$GREEN" - + + # Создаем файл known_hosts для туннелей + touch "$SSH_KNOWN_HOSTS" + chmod 600 "$SSH_KNOWN_HOSTS" + + log_message "Конфигурация SSH клиента обновлена (ControlMaster отключен)." "$GREEN" + # Настройка локального SSHD configure_local_sshd } @@ -483,22 +532,20 @@ EOFCONFIG # Функция установки SSH сервера install_ssh_server() { log_message "Проверка установки SSH сервера..." "$BLUE" - - # Проверяем, установлен ли sshd - if ! command -v sshd &> /dev/null && [ ! -f /etc/ssh/sshd_config ]; then + + if ! command -v sshd &>/dev/null && [ ! -f /etc/ssh/sshd_config ]; then log_message "SSH сервер не установлен. Устанавливаем..." "$YELLOW" - - # Определение дистрибутива + local OS if [ -f /etc/os-release ]; then . /etc/os-release OS=$ID fi - + if [[ "$OS" == "ubuntu" || "$OS" == "debian" ]]; then - apt-get install -y openssh-server 2>&1 | tee -a "$LOG_DIR/manager.log" + DEBIAN_FRONTEND=noninteractive apt-get install -y openssh-server 2>&1 | tee -a "$LOG_DIR/manager.log" elif [[ "$OS" == "centos" || "$OS" == "rhel" || "$OS" == "fedora" || "$OS" == "rocky" || "$OS" == "almalinux" ]]; then - if command -v dnf &> /dev/null; then + if command -v dnf &>/dev/null; then dnf install -y openssh-server 2>&1 | tee -a "$LOG_DIR/manager.log" else yum install -y openssh-server 2>&1 | tee -a "$LOG_DIR/manager.log" @@ -507,9 +554,8 @@ install_ssh_server() { log_message "Не удалось определить дистрибутив для установки SSH сервера." "$RED" return 1 fi - - # Проверяем установку - if command -v sshd &> /dev/null; then + + if command -v sshd &>/dev/null; then log_message "SSH сервер успешно установлен." "$GREEN" else log_message "Не удалось установить SSH сервер." "$RED" @@ -518,91 +564,69 @@ install_ssh_server() { else log_message "SSH сервер уже установлен." "$GREEN" fi - - # Запускаем и включаем автозагрузку - if systemctl is-active sshd &> /dev/null; then + + if systemctl is-active sshd &>/dev/null; then log_message "SSH сервер уже запущен." "$GREEN" else systemctl start sshd 2>&1 | tee -a "$LOG_DIR/manager.log" systemctl enable sshd 2>&1 | tee -a "$LOG_DIR/manager.log" log_message "SSH сервер запущен и включен в автозагрузку." "$GREEN" fi - + return 0 } # Функция настройки локального SSH-демона для приема туннелей configure_local_sshd() { log_message "Настройка локального SSH-демона (sshd) для приема туннелей..." "$BLUE" - - # Устанавливаем SSH сервер если нужно + install_ssh_server - + local SSHD_CONFIG="/etc/ssh/sshd_config" - + if [ ! -f "$SSHD_CONFIG" ]; then log_message "Файл конфигурации SSHD не найден. Создаем базовый..." "$YELLOW" mkdir -p /etc/ssh - SSHD_CONFIG="/etc/ssh/sshd_config" cat > "$SSHD_CONFIG" << 'EOFSSHD' -# SSH Server Configuration Port 22 Protocol 2 HostKey /etc/ssh/ssh_host_rsa_key HostKey /etc/ssh/ssh_host_ecdsa_key HostKey /etc/ssh/ssh_host_ed25519_key - -# Logging SyslogFacility AUTH LogLevel INFO - -# Authentication LoginGraceTime 120 -PermitRootLogin yes +PermitRootLogin prohibit-password StrictModes yes -PasswordAuthentication yes PubkeyAuthentication yes - -# Allow forwardings for tunnels +PasswordAuthentication no AllowTcpForwarding yes GatewayPorts yes X11Forwarding yes X11DisplayOffset 10 X11UseLocalhost yes - AcceptEnv LANG LC_* -AuthorizedKeysFile .ssh/authorized_keys +AuthorizedKeysFile .ssh/authorized_keys PermitEmptyPasswords no ChallengeResponseAuthentication no GSSAPIAuthentication no -GSSAPICleanupCredentials yes UsePAM yes - -# Keep connections alive ClientAliveInterval 30 ClientAliveCountMax 3 - -# Max sessions MaxSessions 20 MaxStartups 20:30:100 - -# Allow tunnel creation AllowStreamLocalForwarding yes PermitTunnel yes - -Subsystem sftp /usr/lib/openssh/sftp-server +Subsystem sftp /usr/lib/openssh/sftp-server EOFSSHD log_message "Создан базовый конфигурационный файл SSH сервера." "$GREEN" fi - - # Создание резервной копии + cp "$SSHD_CONFIG" "${SSHD_CONFIG}.backup.$(date +%Y%m%d%H%M%S)" log_message "Создана резервная копия." "$YELLOW" - - # Настройки для приема обратных туннелей + log_message "Настройка параметров для SSH туннелей..." "$BLUE" - - # Массив настроек + local SETTINGS=( "GatewayPorts yes" "AllowTcpForwarding yes" @@ -611,36 +635,36 @@ EOFSSHD "ClientAliveInterval 30" "ClientAliveCountMax 3" "TCPKeepAlive yes" - "PasswordAuthentication yes" "PubkeyAuthentication yes" "MaxSessions 20" "MaxStartups 20:30:100" ) - - # Применяем настройки + for setting in "${SETTINGS[@]}"; do - local key=$(echo "$setting" | awk '{print $1}') - local value=$(echo "$setting" | awk '{print $2}') - - # Проверяем существует ли настройка + local key + key=$(echo "$setting" | awk '{print $1}') + if grep -q "^#\?\s*$key" "$SSHD_CONFIG"; then - # Заменяем существующую настройку sed -i "s/^#\?\s*$key.*/$setting/" "$SSHD_CONFIG" log_message "Обновлено: $setting" "$GREEN" else - # Добавляем новую настройку echo "$setting" >> "$SSHD_CONFIG" log_message "Добавлено: $setting" "$GREEN" fi done - - # Проверяем и настраиваем PermitRootLogin + + # PermitRootLogin — только prohibit-password (безопаснее) if ! grep -q "^PermitRootLogin" "$SSHD_CONFIG"; then - echo "PermitRootLogin yes" >> "$SSHD_CONFIG" - log_message "Добавлено: PermitRootLogin yes" "$GREEN" + echo "PermitRootLogin prohibit-password" >> "$SSHD_CONFIG" + log_message "Добавлено: PermitRootLogin prohibit-password" "$GREEN" fi - - # Перезапуск SSHD + + # Отключаем PasswordAuthentication для безопасности + if ! grep -q "^PasswordAuthentication" "$SSHD_CONFIG"; then + echo "PasswordAuthentication no" >> "$SSHD_CONFIG" + log_message "Добавлено: PasswordAuthentication no" "$GREEN" + fi + log_message "Перезапуск SSH демона..." "$BLUE" if systemctl restart sshd 2>/dev/null || service ssh restart 2>/dev/null || /etc/init.d/ssh restart 2>/dev/null; then log_message "SSHD перезапущен с новыми настройками." "$GREEN" @@ -649,15 +673,15 @@ EOFSSHD fi } -# 2. Настройка VPS (сервера) с автоматической настройкой SSH для многопользовательского доступа +# 2. Настройка VPS (сервера) setup_vps() { check_root "setup_vps" log_message "Настройка подключения к удаленному VPS (серверу)..." "$CYAN" - + local current_host="" local current_port="22" local current_user="" - + if load_vps_settings; then current_host="$VPS_HOST" current_port="$VPS_PORT" @@ -667,40 +691,38 @@ setup_vps() { echo -e "${BOLD}${CYAN}--- Настройка VPS ---${NC}" echo "" - + local VPS_HOST_TEMP local VPS_PORT_TEMP local VPS_USER_TEMP local USER_CHOICE - - # Ввод IP и Порта + safe_read "Введите IP адрес или доменное имя VPS (текущий: $current_host): " "$current_host" VPS_HOST_TEMP safe_read "Введите порт SSH (по умолчанию 22, текущий: $current_port): " "$current_port" VPS_PORT_TEMP - - # Выбор пользователя + echo -e "${CYAN}Выберите пользователя для подключения:${NC}" echo "1) root (рекомендуется для полного доступа)" echo "2) Другой пользователь" - + USER_CHOICE=$(read_input "Ваш выбор [1-2]: ") - + if [ "$USER_CHOICE" == "1" ]; then VPS_USER_TEMP="root" echo -e "${YELLOW}Будет использоваться пользователь: root${NC}" else safe_read "Введите имя пользователя на VPS (текущий: $current_user): " "$current_user" VPS_USER_TEMP fi - + VPS_HOST="$VPS_HOST_TEMP" VPS_PORT="$VPS_PORT_TEMP" VPS_USER="$VPS_USER_TEMP" - + if [ -z "$VPS_HOST" ] || [ -z "$VPS_USER" ]; then log_message "ОШИБКА: IP адрес и имя пользователя обязательны." "$RED" read_input "Нажмите Enter для продолжения..." return 1 fi - + # Сохранение настроек cat > "$VPS_CONFIG" << EOFSETTINGS # Настройки VPS для автономных туннелей @@ -711,61 +733,60 @@ CONFIGURED_ON="$(date '+%Y-%m-%d %H:%M:%S')" EOFSETTINGS chmod 600 "$VPS_CONFIG" log_message "Настройки VPS сохранены в $VPS_CONFIG" "$GREEN" - + # Автоматическое копирование ключа log_message "Попытка автоматического копирования SSH-ключа на VPS..." "$CYAN" - - # Запрос пароля для sshpass с безопасным вводом + local VPS_PASSWORD echo -e "${YELLOW}Для автоматического копирования ключа потребуется пароль от $VPS_USER на VPS.${NC}" echo -e "${YELLOW}Если вы не хотите вводить пароль, нажмите Enter и скопируйте ключ вручную.${NC}" - - # Используем read_secret для скрытого ввода + VPS_PASSWORD=$(read_secret "Введите пароль для $VPS_USER@$VPS_HOST (оставьте пустым для пропуска): ") - + if [ -n "$VPS_PASSWORD" ]; then - if command -v sshpass &> /dev/null; then - # Используем sshpass для автоматического копирования ключа + if command -v sshpass &>/dev/null; then echo -e "${BLUE}Копирую ключ на VPS...${NC}" - if sshpass -p "$VPS_PASSWORD" ssh-copy-id -p "$VPS_PORT" -i /root/.ssh/id_rsa.pub -o StrictHostKeyChecking=no "$VPS_USER@$VPS_HOST" 2>&1 | tee -a "$LOG_DIR/manager.log"; then + # Безопасная передача пароля через SSH_ASKPASS вместо командной строки + export SSHPASS="$VPS_PASSWORD" + if sshpass -e ssh-copy-id -p "$VPS_PORT" -i "${SSH_KEY_PATH}.pub" -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile="$SSH_KNOWN_HOSTS" "$VPS_USER@$VPS_HOST" 2>&1 | tee -a "$LOG_DIR/manager.log"; then log_message "SSH ключ успешно скопирован на VPS!" "$GREEN" else log_message "Не удалось скопировать ключ через sshpass." "$RED" echo -e "${YELLOW}Скопируйте ключ вручную:${NC}" - cat /root/.ssh/id_rsa.pub + cat "${SSH_KEY_PATH}.pub" fi + unset SSHPASS else log_message "sshpass не установлен. Невозможно скопировать ключ автоматически." "$YELLOW" echo -e "${YELLOW}Скопируйте ключ вручную:${NC}" - cat /root/.ssh/id_rsa.pub + cat "${SSH_KEY_PATH}.pub" fi else log_message "Пароль не введен. Скопируйте ключ вручную:" "$YELLOW" - cat /root/.ssh/id_rsa.pub + cat "${SSH_KEY_PATH}.pub" fi - - # Тестовое подключение + echo "" log_message "Проверка подключения к $VPS_USER@$VPS_HOST:$VPS_PORT..." "$BLUE" check_ssh_connection - + read_input "Нажмите Enter для продолжения..." } -# Функция диагностики туннеля +# --- Функция диагностики туннеля (ЛОКАЛЬНАЯ) --- diagnose_tunnel() { local TUNNEL_ID="$1" - + if [ ! -f "$TUNNELS_DIR/$TUNNEL_ID.conf" ]; then log_message "Туннель с ID '$TUNNEL_ID' не найден." "$RED" return 1 fi - + source "$TUNNELS_DIR/$TUNNEL_ID.conf" - + echo -e "${BOLD}${CYAN}=== ДИАГНОСТИКА ТУННЕЛЯ: $TUNNEL_NAME ===${NC}" echo "" - + # 1. Проверка конфигурации echo -e "${BLUE}1. Конфигурация туннеля:${NC}" echo " ID: $TUNNEL_ID" @@ -775,12 +796,12 @@ diagnose_tunnel() { echo " VPS: $VPS_USER@$VPS_HOST:$VPS_PORT" echo " Создан: $CREATED" echo "" - + # 2. Проверка службы systemd echo -e "${BLUE}2. Проверка службы systemd:${NC}" local SERVICE_NAME="tunnel-$TUNNEL_ID" - systemctl status "$SERVICE_NAME.service" --no-pager -l - + systemctl status "$SERVICE_NAME.service" --no-pager -l 2>/dev/null || echo " Служба не найдена" + # 3. Проверка логов echo -e "\n${BLUE}3. Последние логи туннеля:${NC}" if [ -f "$LOG_DIR/$TUNNEL_ID.log" ]; then @@ -788,38 +809,146 @@ diagnose_tunnel() { else echo " Лог файл не найден" fi - - # 4. Проверка процессов - echo -e "\n${BLUE}4. Проверка процессов:${NC}" - echo " autossh процессы:" - pgrep -af "autossh.*$TUNNEL_ID" || echo " Не найдены" - echo "" - echo " ssh процессы:" - pgrep -af "ssh.*$REMOTE_PORT.*localhost:$LOCAL_PORT" || echo " Не найдены" - - # 5. Проверка портов + + # 4. Проверка процесса по PID-файлу + echo -e "\n${BLUE}4. Проверка процесса туннеля:${NC}" + if is_tunnel_pid_alive "$TUNNEL_ID"; then + local pid + pid=$(read_pidfile "$TUNNEL_ID") + echo -e " ${GREEN}✓ Процесс туннеля запущен (PID: $pid)${NC}" + ps -p "$pid" -o pid,ppid,cmd --no-headers 2>/dev/null + else + echo -e " ${RED}✗ Процесс туннеля не найден${NC}" + # Показываем любые ssh/autossh процессы для этого туннеля + echo " Поиск связанных процессов:" + pgrep -af "ssh.*$REMOTE_PORT.*localhost:$LOCAL_PORT" 2>/dev/null || echo " Не найдены" + fi + + # 5. Проверка портов (локальная) echo -e "\n${BLUE}5. Проверка портов:${NC}" check_ports "$LOCAL_PORT" "$REMOTE_PORT" "$VPS_HOST" "$VPS_USER" "$VPS_PORT" - - # 6. Ручной тест SSH соединения для туннеля - echo -e "${BLUE}6. Ручной тест SSH туннеля:${NC}" - echo -e "${YELLOW}Выполняем тестовую команду...${NC}" - - local test_cmd="ssh -v -N -o ExitOnForwardFailure=yes -o ServerAliveInterval=30 -o ConnectTimeout=10 -i /root/.ssh/id_rsa -R $REMOTE_PORT:localhost:$LOCAL_PORT -p $VPS_PORT $VPS_USER@$VPS_HOST" - echo "Команда: $test_cmd" - echo "" - echo -e "${YELLOW}Запускаем на 10 секунд... (Ctrl+C для остановки)${NC}" - - timeout 10 $test_cmd 2>&1 | head -50 - + + # 6. Проверка состояния перезапусков + echo -e "${BLUE}6. Статистика перезапусков:${NC}" + local restart_count=0 + if [ -f "$PID_DIR/$TUNNEL_ID.restart_count" ]; then + restart_count=$(cat "$PID_DIR/$TUNNEL_ID.restart_count") + fi + echo " Количество перезапусков: $restart_count (макс: $MAX_RESTART_COUNT)" + echo -e "\n${CYAN}=== РЕКОМЕНДАЦИИ ===${NC}" echo "1. Проверьте, что на VPS разрешены обратные туннели (GatewayPorts yes)" echo "2. Убедитесь, что порт $REMOTE_PORT на VPS не занят" echo "3. Проверьте права SSH ключа (должны быть 600)" echo "4. Убедитесь, что локальный сервер слушает порт $LOCAL_PORT" + echo "5. Проверьте логи: journalctl -u $SERVICE_NAME -n 50" echo "" } +# --- Функция ожидания сети --- +wait_for_network() { + local max_wait="${1:-60}" + local waited=0 + + while [ $waited -lt $max_wait ]; do + # Проверяем наличие сетевого интерфейса (кроме lo) + if ip link show 2>/dev/null | grep -v "lo:" | grep -q "state UP"; then + # Проверяем наличие default маршрута + if ip route show default 2>/dev/null | grep -q "default"; then + return 0 + fi + fi + sleep 2 + waited=$((waited + 2)) + done + return 1 +} + +# --- Функция создания systemd сервиса с улучшенной стабильностью --- +create_tunnel_service() { + local TUNNEL_ID="$1" + local TUNNEL_NAME="$2" + local LOCAL_PORT="$3" + local REMOTE_PORT="$4" + local VPS_HOST="$5" + local VPS_PORT="$6" + local VPS_USER="$7" + + local SERVICE_NAME="tunnel-$TUNNEL_ID" + local SERVICE_FILE="$SERVICE_DIR/$SERVICE_NAME.service" + local CONFIG_FILE="$TUNNELS_DIR/$TUNNEL_ID.conf" + + local ssh_opts + ssh_opts=$(get_ssh_base_opts) + + cat > "$SERVICE_FILE" << EOFSERVICE +[Unit] +Description=SSH Reverse Tunnel: $TUNNEL_NAME ($LOCAL_PORT -> $REMOTE_PORT) +After=network-online.target +Wants=network-online.target +StartLimitIntervalSec=120 +StartLimitBurst=$MAX_RESTART_COUNT + +[Service] +Type=forking +EnvironmentFile=$CONFIG_FILE +PIDFile=$PID_DIR/$TUNNEL_ID.pid + +# Ожидание сети перед запуском +ExecStartPre=/bin/bash -c 'for i in \$(seq 1 30); do ip route show default 2>/dev/null | grep -q default && exit 0; sleep 2; done; exit 1' + +# Запуск autossh с ControlMaster=no и всеми опциями стабильности +ExecStart=/usr/bin/autossh -M 0 \\ + -o ControlMaster=no \\ + -o ControlPath=none \\ + -o ExitOnForwardFailure=yes \\ + -o ServerAliveInterval=15 \\ + -o ServerAliveCountMax=4 \\ + -o StrictHostKeyChecking=accept-new \\ + -o UserKnownHostsFile=$SSH_KNOWN_HOSTS \\ + -o TCPKeepAlive=yes \\ + -o ConnectTimeout=30 \\ + -o IdentityFile=$SSH_KEY_PATH \\ + -o PasswordAuthentication=no \\ + -o BatchMode=yes \\ + -o IdentitiesOnly=yes \\ + -o IPQoS=throughput \\ + -o Compression=yes \\ + -o GSSAPIAuthentication=no \\ + -o AddressFamily=inet \\ + -f -N \\ + -R $REMOTE_PORT:localhost:$LOCAL_PORT \\ + -p $VPS_PORT \\ + $VPS_USER@$VPS_HOST + +ExecStartPost=/bin/bash -c 'echo \$MAINPID > $PID_DIR/$TUNNEL_ID.pid; echo 0 > $PID_DIR/$TUNNEL_ID.restart_count' + +# Перезапуск с экспоненциальной задержкой +ExecStopPost=/bin/bash -c 'rm -f $PID_DIR/$TUNNEL_ID.pid' +Restart=on-failure +RestartSec=5 +RestartPreventExitStatus=0 255 + +User=root +StandardOutput=append:$LOG_DIR/$TUNNEL_ID.log +StandardError=append:$LOG_DIR/$TUNNEL_ID.log + +# Ограничение ресурсов +LimitNOFILE=65536 +PrivateTmp=yes +NoNewPrivileges=yes +ProtectSystem=strict +ProtectHome=read-only +ReadWritePaths=$LOG_DIR $PID_DIR $SSH_KNOWN_HOSTS /root/.ssh +ReadOnlyPaths=/etc/tunnel-manager + +[Install] +WantedBy=multi-user.target +EOFSERVICE + + echo "$SERVICE_FILE" +} + # 3. Добавление нового туннеля add_tunnel() { check_root "add_tunnel" @@ -828,55 +957,52 @@ add_tunnel() { read_input "Нажмите Enter для продолжения..." return 1 fi - + echo -e "${BOLD}${CYAN}--- Добавление нового обратного туннеля (Local -> VPS) ---${NC}" log_message "VPS: $VPS_USER@$VPS_HOST:$VPS_PORT" "$YELLOW" echo "" - + local LOCAL_PORT="" local REMOTE_PORT="" local TUNNEL_NAME="" - + safe_read "Введите локальный порт, который нужно пробросить (например, 22 для SSH): " "" LOCAL_PORT safe_read "Введите удаленный порт на VPS, через который будет доступен локальный порт (например, 10022): " "" REMOTE_PORT safe_read "Введите имя туннеля (например, 'ssh_access'): " "" TUNNEL_NAME - - # Проверка ввода + if ! [[ "$LOCAL_PORT" =~ ^[0-9]+$ ]] || [ "$LOCAL_PORT" -lt 1 ] || [ "$LOCAL_PORT" -gt 65535 ]; then log_message "ОШИБКА: Локальный порт должен быть числом от 1 до 65535." "$RED" read_input "Нажмите Enter для продолжения..." return 1 fi - + if ! [[ "$REMOTE_PORT" =~ ^[0-9]+$ ]] || [ "$REMOTE_PORT" -lt 1 ] || [ "$REMOTE_PORT" -gt 65535 ]; then log_message "ОШИБКА: Удаленный порт должен быть числом от 1 до 65535." "$RED" read_input "Нажмите Enter для продолжения..." return 1 fi - + if [ -z "$TUNNEL_NAME" ]; then log_message "ОШИБКА: Имя туннеля обязательно." "$RED" read_input "Нажмите Enter для продолжения..." return 1 fi - - # Проверка имени туннеля + TUNNEL_ID=$(echo "$TUNNEL_NAME" | tr '[:upper:]' '[:lower:]' | tr -cd '[:alnum:]_') if [ -f "$TUNNELS_DIR/$TUNNEL_ID.conf" ]; then log_message "ОШИБКА: Туннель с именем '$TUNNEL_ID' уже существует." "$RED" read_input "Нажмите Enter для продолжения..." return 1 fi - - # Проверяем, доступен ли локальный порт + echo -e "${BLUE}Проверка доступности локального порта $LOCAL_PORT...${NC}" - if timeout 2 nc -z localhost "$LOCAL_PORT" 2>/dev/null; then + if is_local_port_listening "$LOCAL_PORT"; then echo -e "${GREEN}✓ Локальный порт $LOCAL_PORT доступен${NC}" else echo -e "${YELLOW}⚠ Локальный порт $LOCAL_PORT не отвечает${NC}" echo -e "${YELLOW}Убедитесь, что служба слушает на этом порту${NC}" fi - + # Создание конфигурационного файла туннеля local CONFIG_FILE="$TUNNELS_DIR/$TUNNEL_ID.conf" cat > "$CONFIG_FILE" << EOFCONF @@ -890,47 +1016,24 @@ VPS_PORT="$VPS_PORT" VPS_USER="$VPS_USER" CREATED="$(date '+%Y-%m-%d %H:%M:%S')" EOFCONF - + # Создание systemd unit-файла - local SERVICE_NAME="tunnel-$TUNNEL_ID" - local SERVICE_FILE="$SERVICE_DIR/$SERVICE_NAME.service" - - cat > "$SERVICE_FILE" << EOFSERVICE -[Unit] -Description=SSH Reverse Tunnel: $TUNNEL_NAME ($LOCAL_PORT -> $REMOTE_PORT) -After=network-online.target -Wants=network-online.target -StartLimitIntervalSec=0 - -[Service] -Type=simple -EnvironmentFile=$CONFIG_FILE -ExecStart=/usr/bin/autossh -M 0 -N -o "ExitOnForwardFailure=yes" -o "ServerAliveInterval=30" -o "ServerAliveCountMax=3" -o "StrictHostKeyChecking=no" -o "UserKnownHostsFile=/dev/null" -o "TCPKeepAlive=yes" -o "ConnectTimeout=30" -o "IdentityFile=/root/.ssh/id_rsa" -R $REMOTE_PORT:localhost:$LOCAL_PORT $VPS_USER@$VPS_HOST -p $VPS_PORT -ExecReload=/bin/kill -HUP \$MAINPID -Restart=always -RestartSec=5 -User=root -StandardOutput=append:$LOG_DIR/$TUNNEL_ID.log -StandardError=append:$LOG_DIR/$TUNNEL_ID.log - -[Install] -WantedBy=multi-user.target -EOFSERVICE + create_tunnel_service "$TUNNEL_ID" "$TUNNEL_NAME" "$LOCAL_PORT" "$REMOTE_PORT" "$VPS_HOST" "$VPS_PORT" "$VPS_USER" log_message "Конфигурация туннеля '$TUNNEL_NAME' создана." "$GREEN" - + # Запуск и включение службы systemctl daemon-reload - systemctl enable "$SERVICE_NAME.service" > /dev/null 2>&1 - + systemctl enable "tunnel-$TUNNEL_ID.service" >/dev/null 2>&1 + echo -e "${BLUE}Запуск туннеля...${NC}" - systemctl start "$SERVICE_NAME.service" - - sleep 2 - - if systemctl is-active --quiet "$SERVICE_NAME.service"; then + systemctl start "tunnel-$TUNNEL_ID.service" + + sleep 3 + + if systemctl is-active --quiet "tunnel-$TUNNEL_ID.service"; then log_message "Туннель '$TUNNEL_NAME' успешно запущен." "$GREEN" - + echo "" echo -e "${BOLD}${CYAN}Информация о туннеле:${NC}" echo -e " Имя: $TUNNEL_NAME" @@ -942,22 +1045,20 @@ EOFSERVICE echo -e "${BOLD} ssh -p $REMOTE_PORT $VPS_USER@$VPS_HOST${NC}" echo "" echo -e "${YELLOW}Логи туннеля: $LOG_DIR/$TUNNEL_ID.log${NC}" - echo -e "${YELLOW}Статус: systemctl status $SERVICE_NAME${NC}" - - # Показываем диагностику + echo -e "${YELLOW}Статус: systemctl status tunnel-$TUNNEL_ID${NC}" + echo "" check_ports "$LOCAL_PORT" "$REMOTE_PORT" "$VPS_HOST" "$VPS_USER" "$VPS_PORT" else log_message "ОШИБКА: Не удалось запустить туннель '$TUNNEL_NAME'." "$RED" - echo -e "${YELLOW}Проверьте логи: journalctl -u $SERVICE_NAME -n 50${NC}" + echo -e "${YELLOW}Проверьте логи: journalctl -u tunnel-$TUNNEL_ID -n 50${NC}" echo -e "${YELLOW}Или выполните диагностику туннеля${NC}" - - # Показываем последние логи + echo "" echo -e "${RED}Последние логи службы:${NC}" - journalctl -u "$SERVICE_NAME" -n 20 --no-pager + journalctl -u "tunnel-$TUNNEL_ID" -n 20 --no-pager fi - + read_input "Нажмите Enter для продолжения..." } @@ -966,28 +1067,44 @@ manage_tunnel() { local TUNNEL_ID="$1" local ACTION="$2" local SERVICE_NAME="tunnel-$TUNNEL_ID" - + if [ ! -f "$TUNNELS_DIR/$TUNNEL_ID.conf" ]; then log_message "Туннель с ID '$TUNNEL_ID' не найден." "$RED" return 1 fi - + case "$ACTION" in - start|stop|restart) - systemctl "$ACTION" "$SERVICE_NAME.service" - log_message "Служба '$SERVICE_NAME' выполнила команду '$ACTION'." "$GREEN" + start) + # Сбрасываем счетчик перезапусков + echo 0 > "$PID_DIR/$TUNNEL_ID.restart_count" 2>/dev/null || true + systemctl start "$SERVICE_NAME.service" + log_message "Служба '$SERVICE_NAME' запущена." "$GREEN" + sleep 2 + ;; + stop) + systemctl stop "$SERVICE_NAME.service" + remove_pidfile "$TUNNEL_ID" + log_message "Служба '$SERVICE_NAME' остановлена." "$GREEN" sleep 1 ;; + restart) + echo 0 > "$PID_DIR/$TUNNEL_ID.restart_count" 2>/dev/null || true + systemctl restart "$SERVICE_NAME.service" + log_message "Служба '$SERVICE_NAME' перезапущена." "$GREEN" + sleep 2 + ;; status) systemctl status "$SERVICE_NAME.service" --no-pager -l ;; remove) log_message "Остановка и удаление туннеля '$TUNNEL_ID'..." "$YELLOW" - systemctl stop "$SERVICE_NAME.service" - systemctl disable "$SERVICE_NAME.service" > /dev/null 2>&1 + systemctl stop "$SERVICE_NAME.service" 2>/dev/null + systemctl disable "$SERVICE_NAME.service" >/dev/null 2>&1 rm -f "$SERVICE_DIR/$SERVICE_NAME.service" rm -f "$TUNNELS_DIR/$TUNNEL_ID.conf" rm -f "$LOG_DIR/$TUNNEL_ID.log" + remove_pidfile "$TUNNEL_ID" + rm -f "$PID_DIR/$TUNNEL_ID.restart_count" systemctl daemon-reload log_message "Туннель '$TUNNEL_ID' полностью удален." "$GREEN" ;; @@ -1001,34 +1118,36 @@ manage_tunnel() { # 5. Список туннелей list_tunnels() { echo -e "${BOLD}${CYAN}--- Список настроенных туннелей ---${NC}" - + local count=0 local header="%-20s | %-10s | %-10s | %-20s | %-10s" local separator="---------------------|------------|------------|----------------------|------------" - + printf "$header\n" "ИМЯ ТУННЕЛЯ" "ЛОК. ПОРТ" "УД. ПОРТ" "СТАТУС" "АВТОЗАГРУЗКА" echo "$separator" - + for config_file in "$TUNNELS_DIR"/*.conf; do if [ -f "$config_file" ]; then source "$config_file" local SERVICE_NAME="tunnel-$TUNNEL_ID" - local STATUS=$(systemctl is-active "$SERVICE_NAME.service" 2>/dev/null || echo "inactive") - local ENABLED=$(systemctl is-enabled "$SERVICE_NAME.service" 2>/dev/null || echo "disabled") - + local STATUS + STATUS=$(systemctl is-active "$SERVICE_NAME.service" 2>/dev/null || echo "inactive") + local ENABLED + ENABLED=$(systemctl is-enabled "$SERVICE_NAME.service" 2>/dev/null || echo "disabled") + local STATUS_COLOR="$RED" if [ "$STATUS" == "active" ]; then STATUS_COLOR="$GREEN" elif [ "$STATUS" == "inactive" ]; then STATUS_COLOR="$YELLOW" fi - + printf "%-20s | %-10s | %-10s | ${STATUS_COLOR}%-20s${NC} | %-10s\n" \ "$TUNNEL_ID" "$LOCAL_PORT" "$REMOTE_PORT" "$STATUS" "$ENABLED" count=$((count + 1)) fi done - + if [ "$count" -eq 0 ]; then echo -e "${YELLOW}Туннели не найдены. Используйте опцию 2 для добавления нового.${NC}" fi @@ -1048,12 +1167,11 @@ view_logs() { echo -e "${CYAN}[4]${NC} Лог менеджера" echo -e "${CYAN}[5]${NC} Назад" echo "" - + choice=$(read_input "Выберите действие: ") - + case $choice in 1) - local LOG_DIR="/var/log/tunnel-manager" echo -e "${CYAN}Доступные логи:${NC}" ls -1 "$LOG_DIR"/*.log 2>/dev/null | xargs -n1 basename | grep -v "manager.log" || echo " Логи не найдены" echo "" @@ -1066,7 +1184,6 @@ view_logs() { fi ;; 2) - local LOG_DIR="/var/log/tunnel-manager" echo -e "${CYAN}Все логи туннелей (последние 5 строк):${NC}" for log in "$LOG_DIR"/tunnel-*.log; do if [ -f "$log" ]; then @@ -1078,12 +1195,12 @@ view_logs() { ;; 3) echo -e "${CYAN}Системные логи туннелей (последний час):${NC}" - journalctl -u tunnel-* --since "1 hour ago" -n 20 2>/dev/null || log_message "Системные логи отсутствуют" "$YELLOW" + journalctl -u 'tunnel-*' --since "1 hour ago" -n 20 2>/dev/null || log_message "Системные логи отсутствуют" "$YELLOW" read_input "Нажмите Enter для продолжения..." ;; 4) echo -e "${CYAN}Лог менеджера:${NC}" - less "/var/log/tunnel-manager/manager.log" + less "$LOG_DIR/manager.log" ;; 5) return @@ -1109,9 +1226,9 @@ test_connections() { echo "5) Диагностика всех туннелей" echo "6) Назад" echo "" - + test_choice=$(read_input "Выберите тест: ") - + case $test_choice in 1) check_ssh_connection @@ -1119,17 +1236,15 @@ test_connections() { ;; 2) echo -e "${CYAN}Тест локальных портов туннелей:${NC}" - local TUNNEL_DIR="/etc/tunnel-manager/tunnels" - - for conf in "$TUNNEL_DIR"/*.conf; do + for conf in "$TUNNELS_DIR"/*.conf; do if [ -f "$conf" ]; then local LOCAL_PORT LOCAL_PORT=$(grep '^LOCAL_PORT=' "$conf" | cut -d'=' -f2 | tr -d '"') local TUNNEL_NAME TUNNEL_NAME=$(basename "$conf" .conf) - + echo -n " $TUNNEL_NAME (локальный порт $LOCAL_PORT): " - if timeout 2 nc -z localhost "$LOCAL_PORT" 2>/dev/null; then + if is_local_port_listening "$LOCAL_PORT"; then echo -e "${GREEN}✓ Открыт${NC}" else echo -e "${RED}✗ Закрыт${NC}" @@ -1140,13 +1255,14 @@ test_connections() { ;; 3) echo -e "${CYAN}Тест SSH ключа:${NC}" - if [ -f "/root/.ssh/id_rsa" ]; then + if [ -f "$SSH_KEY_PATH" ]; then echo -e "${GREEN}✓ Приватный ключ существует${NC}" - echo -e " Путь: /root/.ssh/id_rsa" - echo -e " Права: $(stat -c %a /root/.ssh/id_rsa 2>/dev/null || echo "неизвестно")" - - if ssh-keygen -l -f /root/.ssh/id_rsa 2>/dev/null; then + echo -e " Путь: $SSH_KEY_PATH" + echo -e " Права: $(stat -c %a "$SSH_KEY_PATH" 2>/dev/null || echo "неизвестно")" + + if is_ssh_key_valid; then echo -e "${GREEN}✓ Ключ валиден${NC}" + ssh-keygen -l -f "$SSH_KEY_PATH" 2>/dev/null else echo -e "${RED}✗ Ключ поврежден или невалиден${NC}" fi @@ -1158,25 +1274,23 @@ test_connections() { 4) if load_vps_settings; then echo -e "${CYAN}Тест доступности портов на VPS ($VPS_HOST):${NC}" - - # Проверяем SSH порт + echo -n " SSH порт $VPS_PORT: " - if timeout 3 nc -zv "$VPS_HOST" "$VPS_PORT" 2>&1 | grep -q "succeeded"; then + if is_vps_reachable "$VPS_HOST" "$VPS_PORT"; then echo -e "${GREEN}✓ Доступен${NC}" else echo -e "${RED}✗ Недоступен${NC}" fi - - # Проверяем порты туннелей + for conf in "$TUNNELS_DIR"/*.conf; do if [ -f "$conf" ]; then local REMOTE_PORT REMOTE_PORT=$(grep '^REMOTE_PORT=' "$conf" | cut -d'=' -f2 | tr -d '"') local TUNNEL_NAME TUNNEL_NAME=$(basename "$conf" .conf) - + echo -n " $TUNNEL_NAME (порт $REMOTE_PORT): " - if timeout 3 nc -zv "$VPS_HOST" "$REMOTE_PORT" 2>&1 | grep -q "succeeded"; then + if is_vps_reachable "$VPS_HOST" "$REMOTE_PORT"; then echo -e "${GREEN}✓ Доступен${NC}" else echo -e "${RED}✗ Недоступен${NC}" @@ -1214,12 +1328,12 @@ test_connections() { # Функция настройки маршрутизации (iptables) setup_routing() { - if ! command -v iptables &> /dev/null; then + if ! command -v iptables &>/dev/null; then log_message "Утилита 'iptables' не найдена." "$RED" read_input "Нажмите Enter для продолжения..." return fi - + while true; do clear echo -e "${BOLD}${CYAN}Настройка маршрутизации (iptables)${NC}" @@ -1229,22 +1343,22 @@ setup_routing() { echo "3) Сохранить правила (требует iptables-persistent/netfilter-persistent)" echo "4) Назад" echo "" - + routing_choice=$(read_input "Выберите действие: ") - + case $routing_choice in 1) echo -e "${CYAN}Текущие правила iptables (NAT PREROUTING):${NC}" - sudo iptables -t nat -L PREROUTING -n -v + iptables -t nat -L PREROUTING -n -v read_input "Нажмите Enter для продолжения..." ;; 2) IN_PORT=$(read_input "Входной порт (напр. 80): ") DEST_IP=$(read_input "IP назначения (напр. 192.168.1.10): ") DEST_PORT=$(read_input "Порт назначения (напр. 8080): ") - + if [ -n "$IN_PORT" ] && [ -n "$DEST_IP" ] && [ -n "$DEST_PORT" ]; then - sudo iptables -t nat -A PREROUTING -p tcp --dport "$IN_PORT" -j DNAT --to-destination "$DEST_IP:$DEST_PORT" + iptables -t nat -A PREROUTING -p tcp --dport "$IN_PORT" -j DNAT --to-destination "$DEST_IP:$DEST_PORT" log_message "Правило DNAT добавлено: $IN_PORT -> $DEST_IP:$DEST_PORT" "$GREEN" else log_message "Не все поля заполнены." "$RED" @@ -1252,12 +1366,12 @@ setup_routing() { read_input "Нажмите Enter для продолжения..." ;; 3) - if command -v netfilter-persistent &> /dev/null; then - sudo netfilter-persistent save + if command -v netfilter-persistent &>/dev/null; then + netfilter-persistent save log_message "Правила iptables сохранены с помощью netfilter-persistent." "$GREEN" - elif command -v iptables-save &> /dev/null; then - sudo mkdir -p /etc/iptables - sudo iptables-save > /etc/iptables/rules.v4 + elif command -v iptables-save &>/dev/null; then + mkdir -p /etc/iptables + iptables-save > /etc/iptables/rules.v4 log_message "Правила iptables сохранены в /etc/iptables/rules.v4." "$GREEN" else log_message "Не найдена утилита для сохранения правил." "$RED" @@ -1281,27 +1395,29 @@ setup_vps_ssh_config() { clear echo -e "${BOLD}${CYAN}Настройки SSH Демона на VPS${NC}" echo -e "${YELLOW}==============================${NC}" - + if ! load_vps_settings; then log_message "Сначала необходимо настроить VPS (опция 1)." "$RED" read_input "Нажмите Enter для продолжения..." return fi - + echo -e "${CYAN}VPS: $VPS_USER@$VPS_HOST:$VPS_PORT${NC}" echo "" echo "1) Показать текущие настройки SSH на VPS" echo "2) Проверить SSH подключение" echo "3) Назад" echo "" - + ssh_choice=$(read_input "Выберите действие: ") - + case $ssh_choice in 1) echo -e "${CYAN}Текущие настройки SSH на VPS:${NC}" - if [ -f "/root/.ssh/id_rsa" ]; then - ssh -p "$VPS_PORT" -i /root/.ssh/id_rsa -o BatchMode=yes "$VPS_USER@$VPS_HOST" \ + if [ -f "$SSH_KEY_PATH" ]; then + local ssh_opts + ssh_opts=$(get_ssh_base_opts) + ssh -p "$VPS_PORT" $ssh_opts -o IdentityFile="$SSH_KEY_PATH" "$VPS_USER@$VPS_HOST" \ "grep -E '^(PasswordAuthentication|PubkeyAuthentication|PermitRootLogin|GatewayPorts|AllowTcpForwarding|MaxSessions|MaxStartups)' /etc/ssh/sshd_config 2>/dev/null || echo 'Не удалось получить настройки'" 2>/dev/null else echo -e "${YELLOW}SSH ключ не найден. Сначала выполните установку.${NC}" @@ -1337,14 +1453,14 @@ setup_local_ssh_config() { echo "6) Показать статус SSH сервера" echo "7) Назад" echo "" - + ssh_choice=$(read_input "Выберите действие: ") - + case $ssh_choice in 1) echo -e "${CYAN}Текущие настройки sshd:${NC}" if [ -f "/etc/ssh/sshd_config" ]; then - grep -E "^(GatewayPorts|AllowTcpForwarding|PermitTunnel|AllowStreamLocalForwarding|PermitRootLogin|PasswordAuthentication)" /etc/ssh/sshd_config 2>/dev/null || + grep -E "^(GatewayPorts|AllowTcpForwarding|PermitTunnel|AllowStreamLocalForwarding|PermitRootLogin|PasswordAuthentication)" /etc/ssh/sshd_config 2>/dev/null || echo "Настройки не найдены" else echo "Файл /etc/ssh/sshd_config не найден" @@ -1352,7 +1468,7 @@ setup_local_ssh_config() { read_input "Нажмите Enter для продолжения..." ;; 2) - if sudo systemctl restart sshd 2>/dev/null || sudo service ssh restart 2>/dev/null; then + if systemctl restart sshd 2>/dev/null || service ssh restart 2>/dev/null; then log_message "SSH демон перезапущен." "$GREEN" else log_message "Не удалось перезапустить SSH демон." "$RED" @@ -1360,8 +1476,8 @@ setup_local_ssh_config() { read_input "Нажмите Enter для продолжения..." ;; 3) - if command -v sshd &> /dev/null; then - if sudo sshd -t 2>/dev/null; then + if command -v sshd &>/dev/null; then + if sshd -t 2>/dev/null; then log_message "Конфигурация SSH корректна." "$GREEN" else log_message "Ошибка в конфигурации SSH." "$RED" @@ -1381,10 +1497,10 @@ setup_local_ssh_config() { ;; 6) echo -e "${CYAN}Статус SSH сервера:${NC}" - if systemctl is-active sshd &> /dev/null; then + if systemctl is-active sshd &>/dev/null; then echo -e "${GREEN}✓ SSH сервер запущен${NC}" systemctl status sshd --no-pager -l - elif pgrep sshd &> /dev/null; then + elif pgrep sshd &>/dev/null; then echo -e "${GREEN}✓ SSH процесс работает${NC}" ps aux | grep sshd | grep -v grep else @@ -1409,40 +1525,40 @@ manage_tunnels_menu() { clear echo -e "${BOLD}${CYAN}--- Управление существующими туннелями ---${NC}" echo -e "${YELLOW}===========================================${NC}" - + local count=0 local TUNNEL_IDS=() - + for config_file in "$TUNNELS_DIR"/*.conf; do if [ -f "$config_file" ]; then - local TUNNEL_ID=$(basename "$config_file" .conf) + local TUNNEL_ID + TUNNEL_ID=$(basename "$config_file" .conf) TUNNEL_IDS+=("$TUNNEL_ID") count=$((count + 1)) fi done - + if [ "$count" -eq 0 ]; then log_message "Туннели не найдены." "$YELLOW" sleep 2 return fi - + list_tunnels - + echo -e "${CYAN}Введите ID туннеля для управления (или 'b' для назад):${NC}" TUNNEL_ID_MANAGE=$(read_input "ID туннеля: ") - + if [ "$TUNNEL_ID_MANAGE" == "b" ]; then return fi - + if [[ " ${TUNNEL_IDS[@]} " =~ " ${TUNNEL_ID_MANAGE} " ]]; then while true; do clear echo -e "${BOLD}${CYAN}--- Управление туннелем $TUNNEL_ID_MANAGE ---${NC}" echo -e "${YELLOW}===========================================${NC}" - - # Загружаем конфигурацию туннеля + if [ -f "$TUNNELS_DIR/$TUNNEL_ID_MANAGE.conf" ]; then source "$TUNNELS_DIR/$TUNNEL_ID_MANAGE.conf" echo -e "${GREEN}Информация о туннеле:${NC}" @@ -1453,11 +1569,10 @@ manage_tunnels_menu() { echo -e " Создан: $CREATED" echo "" fi - - # Показываем текущий статус + echo -e "${CYAN}Статус службы:${NC}" manage_tunnel "$TUNNEL_ID_MANAGE" "status" - + echo -e "\n${CYAN}Выберите действие:${NC}" echo " s) Статус (обновить)" echo " t) Старт" @@ -1469,18 +1584,18 @@ manage_tunnels_menu() { echo " u) Удалить" echo " l) Просмотр логов" echo " b) Назад" - + ACTION_CHOICE=$(read_input "Действие: ") - + case "$ACTION_CHOICE" in - s) ;; # Статус уже показан - t) + s) ;; + t) manage_tunnel "$TUNNEL_ID_MANAGE" "start" ;; - p) + p) manage_tunnel "$TUNNEL_ID_MANAGE" "stop" ;; - r) + r) manage_tunnel "$TUNNEL_ID_MANAGE" "restart" ;; d) @@ -1497,7 +1612,7 @@ manage_tunnels_menu() { log_message "Автозагрузка отключена" "$YELLOW" sleep 1 ;; - u) + u) CONFIRM_REMOVE=$(read_input "Вы уверены, что хотите удалить туннель $TUNNEL_ID_MANAGE? (y/N): ") if [[ "$CONFIRM_REMOVE" =~ ^[Yy]$ ]]; then manage_tunnel "$TUNNEL_ID_MANAGE" "remove" @@ -1506,8 +1621,8 @@ manage_tunnels_menu() { fi ;; l) - if [ -f "/var/log/tunnel-manager/$TUNNEL_ID_MANAGE.log" ]; then - less "/var/log/tunnel-manager/$TUNNEL_ID_MANAGE.log" + if [ -f "$LOG_DIR/$TUNNEL_ID_MANAGE.log" ]; then + less "$LOG_DIR/$TUNNEL_ID_MANAGE.log" else log_message "Лог файл не найден" "$RED" sleep 1 @@ -1528,29 +1643,26 @@ manage_tunnels_menu() { # 7. Главное меню main_menu() { check_root "menu" - + while true; do clear echo -e "${BOLD}${CYAN}========================================================${NC}" - echo -e "${BOLD}${CYAN} SSH Tunnel Manager v2.1 (by Manus) ${NC}" + echo -e "${BOLD}${CYAN} SSH Tunnel Manager v3.0 (Fixed) ${NC}" echo -e "${BOLD}${CYAN}========================================================${NC}" - - # Проверяем наличие SSH ключа - if [ ! -f "/root/.ssh/id_rsa" ]; then + + if [ ! -f "$SSH_KEY_PATH" ]; then echo -e "${RED}⚠ SSH ключ не найден. Выполните установку.${NC}" else echo -e "${GREEN}✓ SSH ключ настроен${NC}" fi - - # Проверяем настройки VPS + if load_vps_settings; then echo -e "${GREEN}✓ VPS настроен: $VPS_USER@$VPS_HOST:$VPS_PORT${NC}" else echo -e "${RED}✗ VPS не настроен. Начните с опции 1.${NC}" fi - - # Проверяем SSH сервер - if systemctl is-active sshd &> /dev/null || pgrep sshd &> /dev/null; then + + if systemctl is-active sshd &>/dev/null || pgrep sshd &>/dev/null; then echo -e "${GREEN}✓ SSH сервер запущен${NC}" elif [ -f "/etc/ssh/sshd_config" ]; then echo -e "${YELLOW}⚠ SSH сервер не запущен${NC}" @@ -1558,9 +1670,9 @@ main_menu() { echo -e "${RED}✗ SSH сервер не установлен${NC}" fi echo "" - + list_tunnels - + echo -e "${BOLD}${YELLOW}--- МЕНЮ УПРАВЛЕНИЯ ---${NC}" echo -e "1) ${BOLD}Настроить/сменить VPS${NC} (Указать адрес, пользователя, порт)" echo -e "2) ${BOLD}Добавить новый туннель${NC} (Local:Port -> VPS:Port)" @@ -1573,9 +1685,9 @@ main_menu() { echo -e "9) ${BOLD}Показать публичный ключ${NC} (Для копирования на VPS)" echo -e "0) ${BOLD}Выход${NC}" echo "" - + choice=$(read_input "Выберите опцию [0-9]: ") - + case $choice in 1) setup_vps ;; 2) add_tunnel ;; @@ -1586,7 +1698,7 @@ main_menu() { 7) setup_vps_ssh_config ;; 8) setup_local_ssh_config ;; 9) show_public_key ;; - 0) + 0) log_message "Выход из менеджера." "$GREEN" exit 0 ;; @@ -1599,13 +1711,11 @@ main_menu() { } # --- Точка входа --- -# Главное изменение: если скрипт запущен без аргументов, выполняем автоматическую установку и запуск меню if [[ $# -eq 0 ]]; then - echo -e "${BOLD}${CYAN}SSH Tunnel Manager v2.1${NC}" + echo -e "${BOLD}${CYAN}SSH Tunnel Manager v3.0 (Fixed)${NC}" echo -e "Автоматический запуск установки и меню..." echo "" - - # Проверяем, установлен ли уже скрипт + if [ -f "/usr/local/bin/tunnel-manager" ]; then echo -e "${YELLOW}Менеджер уже установлен. Запуск меню...${NC}" sleep 2 @@ -1628,7 +1738,7 @@ else main_menu ;; *) - echo -e "${BOLD}${CYAN}SSH Tunnel Manager v2.1${NC}" + echo -e "${BOLD}${CYAN}SSH Tunnel Manager v3.0 (Fixed)${NC}" echo -e "Использование:" echo -e " 1. Запуск одной командой (автоустановка + меню):" echo -e " curl -s https://git.softuniq.eu/OpenDoor/vps_ssh_tunel/raw/branch/main/install_ssh_tunnel.sh | sudo bash" @@ -1640,4 +1750,4 @@ else esac fi -exit 0 \ No newline at end of file +exit 0