- ControlMaster конфликт: явный ControlMaster=no + ControlPath=none - PID tracking: flock + pidfile вместо хрупкого pgrep - Мониторинг: локальные проверки вместо 3 SSH-подключений за цикл - Перезапуск: exponential backoff + StartLimitBurst вместо бесконечного цикла - Безопасность: SSHPASS env var вместо пароля в командной строке - Ed25519 ключи вместо RSA 4096 - Signal handling: cleanup при SIGTERM/SIGINT/SIGHUP - Ротация логов при превышении 10MB
1754 lines
70 KiB
Bash
1754 lines
70 KiB
Bash
#!/bin/bash
|
||
#
|
||
# SSH Tunnel Manager - Профессиональное решение для управления обратными SSH-туннелями
|
||
# Использует autossh и systemd для обеспечения стабильного и постоянного соединения.
|
||
#
|
||
# Использование:
|
||
# 1. Запуск одной командой (автоустановка + меню): curl -s https://.../install_ssh_tunnel.sh | sudo bash
|
||
# 2. Установка: sudo bash tunnel-manager.sh install
|
||
# 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'
|
||
GREEN='\033[0;32m'
|
||
YELLOW='\033[1;33m'
|
||
BLUE='\033[0;34m'
|
||
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
|
||
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"
|
||
return 1
|
||
fi
|
||
return 0
|
||
}
|
||
|
||
# Функция проверки прав root
|
||
check_root() {
|
||
if [[ $EUID -ne 0 ]]; then
|
||
echo -e "${RED}ОШИБКА: Этот скрипт требует прав root (sudo).${NC}"
|
||
echo -e "${BOLD}Пожалуйста, запустите: sudo bash $0 $1${NC}"
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
# Функция безопасного ввода с ограничением попыток
|
||
safe_read() {
|
||
local prompt="$1"
|
||
local default="$2"
|
||
local var_name="$3"
|
||
local max_attempts="${4:-3}"
|
||
|
||
local attempt=0
|
||
local input=""
|
||
|
||
while [[ $attempt -lt $max_attempts ]]; do
|
||
attempt=$((attempt + 1))
|
||
|
||
if [[ -t 0 ]]; then
|
||
read -p "$prompt" input
|
||
else
|
||
read -p "$prompt" input </dev/tty
|
||
fi
|
||
|
||
input="${input:-$default}"
|
||
|
||
if [ -n "$input" ]; then
|
||
eval "$var_name=\"$input\""
|
||
return 0
|
||
elif [ -z "$default" ] && [[ $attempt -lt $max_attempts ]]; then
|
||
echo -e "${YELLOW}Поле не может быть пустым! Попытка $attempt из $max_attempts${NC}"
|
||
elif [ -n "$default" ]; then
|
||
eval "$var_name=\"$default\""
|
||
return 0
|
||
fi
|
||
done
|
||
|
||
echo -e "${RED}Превышено максимальное количество попыток ввода. Выход.${NC}"
|
||
exit 1
|
||
}
|
||
|
||
# Функция чтения ввода с поддержкой pipe
|
||
read_input() {
|
||
local prompt="$1"
|
||
local timeout="${2:-0}"
|
||
local input
|
||
|
||
if [[ -t 0 ]]; then
|
||
if [[ $timeout -gt 0 ]]; then
|
||
read -t "$timeout" -p "$prompt" input
|
||
else
|
||
read -p "$prompt" input
|
||
fi
|
||
else
|
||
if [[ $timeout -gt 0 ]]; then
|
||
read -t "$timeout" -p "$prompt" input </dev/tty
|
||
else
|
||
read -p "$prompt" input </dev/tty
|
||
fi
|
||
fi
|
||
|
||
echo "$input"
|
||
}
|
||
|
||
# Функция чтения скрытого ввода (для паролей)
|
||
read_secret() {
|
||
local prompt="$1"
|
||
local secret
|
||
|
||
if [[ -t 0 ]]; then
|
||
read -s -p "$prompt" secret
|
||
echo
|
||
else
|
||
read -s -p "$prompt" secret </dev/tty
|
||
echo
|
||
fi
|
||
echo "$secret"
|
||
}
|
||
|
||
# Функция загрузки настроек VPS
|
||
load_vps_settings() {
|
||
if [ -f "$VPS_CONFIG" ]; then
|
||
source "$VPS_CONFIG"
|
||
return 0
|
||
fi
|
||
return 1
|
||
}
|
||
|
||
# --- Базовые SSH опции для стабильности (ControlMaster ВСЕГДА отключен) ---
|
||
get_ssh_base_opts() {
|
||
echo "-o ControlMaster=no -o ControlPath=none -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=$SSH_KNOWN_HOSTS -o ServerAliveInterval=15 -o ServerAliveCountMax=4 -o TCPKeepAlive=yes -o ConnectTimeout=30 -o ExitOnForwardFailure=yes -o IdentitiesOnly=yes -o PasswordAuthentication=no -o BatchMode=yes -o IPQoS=throughput -o Compression=yes"
|
||
}
|
||
|
||
# --- Функции проверки состояния (ЛОКАЛЬНЫЕ, без SSH на VPS) ---
|
||
|
||
# Проверка, жив ли процесс туннеля по PID-файлу
|
||
is_tunnel_pid_alive() {
|
||
local tunnel_id="$1"
|
||
local pidfile="$PID_DIR/$tunnel_id.pid"
|
||
|
||
if [ ! -f "$pidfile" ]; then
|
||
return 1
|
||
fi
|
||
|
||
local pid
|
||
pid=$(cat "$pidfile" 2>/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 "$SSH_KEY_PATH" ]; then
|
||
echo -e "${GREEN}Приватный ключ: $SSH_KEY_PATH${NC}"
|
||
echo -e "${GREEN}Публичный ключ:${NC}"
|
||
cat "${SSH_KEY_PATH}.pub" 2>/dev/null || echo "Публичный ключ не найден"
|
||
echo ""
|
||
else
|
||
echo -e "${RED}SSH ключ не найден. Сначала выполните установку.${NC}"
|
||
fi
|
||
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 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 " ${RED}✗ Порт $LOCAL_PORT не слушает локально${NC}"
|
||
fi
|
||
|
||
# 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 " ${RED}✗ VPS недоступен на порту $VPS_PORT${NC}"
|
||
fi
|
||
|
||
# 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 " ${RED}✗ Процесс туннеля не найден${NC}"
|
||
fi
|
||
|
||
echo -e "${CYAN}=== КОНЕЦ ПРОВЕРКИ ===${NC}\n"
|
||
}
|
||
|
||
# --- Функция проверки 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}Используется ключ: $SSH_KEY_PATH${NC}"
|
||
|
||
if [ ! -f "$SSH_KEY_PATH" ]; then
|
||
echo -e "${RED}✗ SSH ключ не найден. Выполните установку менеджера.${NC}"
|
||
return 1
|
||
fi
|
||
|
||
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 "$SSH_KEY_PATH" 2>/dev/null && echo " Права исправлены на 600"
|
||
fi
|
||
|
||
if [ ! -f "${SSH_KEY_PATH}.pub" ]; then
|
||
echo -e "${YELLOW}⚠ Публичный ключ не найден. Генерируем...${NC}"
|
||
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 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 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
|
||
|
||
echo -e "${GREEN}✓ Туннелирование доступно${NC}"
|
||
return 0
|
||
else
|
||
echo -e "${YELLOW}⚠ Проблемы с туннелированием${NC}"
|
||
return 1
|
||
fi
|
||
else
|
||
echo -e "${RED}✗ SSH подключение не работает${NC}"
|
||
|
||
echo -e "\n${YELLOW}Возможные причины:${NC}"
|
||
echo "1. Ключ не скопирован на VPS"
|
||
echo "2. На VPS не разрешена аутентификация по ключу"
|
||
echo "3. Порт $VPS_PORT закрыт на VPS"
|
||
echo "4. SSH сервер на VPS не запущен"
|
||
echo "5. Проблемы с сетью/файрволом"
|
||
echo "6. Неправильные права на ключ"
|
||
echo ""
|
||
|
||
echo -e "${YELLOW}Проверка доступности порта...${NC}"
|
||
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 is_ssh_key_valid; then
|
||
echo -e "✓ Ключ валиден"
|
||
else
|
||
echo -e "✗ Ключ поврежден или невалиден"
|
||
fi
|
||
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
# --- Основные функции ---
|
||
|
||
# 1. Установка зависимостей и настройка системы
|
||
install_manager() {
|
||
check_root "install"
|
||
log_message "Начало установки SSH Tunnel Manager..." "$CYAN"
|
||
|
||
# Создание директорий
|
||
log_message "Создание системных директорий..." "$BLUE"
|
||
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 -qq 2>&1 | tee -a "$LOG_DIR/manager.log"
|
||
check_error "Не удалось обновить список пакетов"
|
||
|
||
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
|
||
dnf install -y autossh openssh-clients sshpass nc 2>&1 | tee -a "$LOG_DIR/manager.log"
|
||
check_error "Не удалось установить пакеты (dnf)"
|
||
else
|
||
yum install -y autossh openssh-clients sshpass nc 2>&1 | tee -a "$LOG_DIR/manager.log"
|
||
check_error "Не удалось установить пакеты (yum)"
|
||
fi
|
||
else
|
||
log_message "ОШИБКА: Не удалось определить менеджер пакетов." "$RED"
|
||
log_message "Пожалуйста, установите autossh и openssh-client вручную." "$YELLOW"
|
||
return 1
|
||
fi
|
||
|
||
if ! command -v autossh &>/dev/null; then
|
||
log_message "ОШИБКА: autossh не установлен. Проверьте вывод установки." "$RED"
|
||
return 1
|
||
fi
|
||
log_message "Зависимости успешно установлены." "$GREEN"
|
||
|
||
# Копирование скрипта в системный путь
|
||
log_message "Копирование скрипта в $SCRIPT_PATH..." "$BLUE"
|
||
|
||
if [ -f "$0" ] && [ "$0" != "bash" ] && [ "$0" != "/bin/bash" ]; then
|
||
cp "$0" "$SCRIPT_PATH" 2>/dev/null
|
||
fi
|
||
|
||
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 "$SSH_KEY_PATH" ]; then
|
||
log_message "Генерация SSH ключа Ed25519 для root..." "$BLUE"
|
||
mkdir -p /root/.ssh
|
||
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 конфига для стабильности (БЕЗ ControlMaster)
|
||
log_message "Настройка SSH клиента для стабильности..." "$BLUE"
|
||
mkdir -p /root/.ssh
|
||
chmod 700 /root/.ssh
|
||
|
||
cat > /root/.ssh/config << 'EOFCONFIG'
|
||
# SSH Tunnel Manager Configuration
|
||
# ВНИМАНИЕ: ControlMaster отключен для совместимости с autossh туннелями
|
||
Host *
|
||
StrictHostKeyChecking accept-new
|
||
UserKnownHostsFile ~/.ssh/known_hosts_tunnels
|
||
ServerAliveInterval 15
|
||
ServerAliveCountMax 4
|
||
ConnectTimeout 30
|
||
TCPKeepAlive yes
|
||
IdentitiesOnly yes
|
||
ExitOnForwardFailure yes
|
||
ControlMaster no
|
||
ControlPath none
|
||
IPQoS throughput
|
||
Compression yes
|
||
GSSAPIAuthentication no
|
||
AddressFamily inet
|
||
PasswordAuthentication no
|
||
BatchMode yes
|
||
EOFCONFIG
|
||
chmod 600 /root/.ssh/config
|
||
|
||
# Создаем файл known_hosts для туннелей
|
||
touch "$SSH_KNOWN_HOSTS"
|
||
chmod 600 "$SSH_KNOWN_HOSTS"
|
||
|
||
log_message "Конфигурация SSH клиента обновлена (ControlMaster отключен)." "$GREEN"
|
||
|
||
# Настройка локального SSHD
|
||
configure_local_sshd
|
||
}
|
||
|
||
# Функция установки SSH сервера
|
||
install_ssh_server() {
|
||
log_message "Проверка установки SSH сервера..." "$BLUE"
|
||
|
||
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
|
||
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
|
||
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"
|
||
fi
|
||
else
|
||
log_message "Не удалось определить дистрибутив для установки SSH сервера." "$RED"
|
||
return 1
|
||
fi
|
||
|
||
if command -v sshd &>/dev/null; then
|
||
log_message "SSH сервер успешно установлен." "$GREEN"
|
||
else
|
||
log_message "Не удалось установить SSH сервер." "$RED"
|
||
return 1
|
||
fi
|
||
else
|
||
log_message "SSH сервер уже установлен." "$GREEN"
|
||
fi
|
||
|
||
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"
|
||
|
||
install_ssh_server
|
||
|
||
local SSHD_CONFIG="/etc/ssh/sshd_config"
|
||
|
||
if [ ! -f "$SSHD_CONFIG" ]; then
|
||
log_message "Файл конфигурации SSHD не найден. Создаем базовый..." "$YELLOW"
|
||
mkdir -p /etc/ssh
|
||
cat > "$SSHD_CONFIG" << 'EOFSSHD'
|
||
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
|
||
SyslogFacility AUTH
|
||
LogLevel INFO
|
||
LoginGraceTime 120
|
||
PermitRootLogin prohibit-password
|
||
StrictModes yes
|
||
PubkeyAuthentication yes
|
||
PasswordAuthentication no
|
||
AllowTcpForwarding yes
|
||
GatewayPorts yes
|
||
X11Forwarding yes
|
||
X11DisplayOffset 10
|
||
X11UseLocalhost yes
|
||
AcceptEnv LANG LC_*
|
||
AuthorizedKeysFile .ssh/authorized_keys
|
||
PermitEmptyPasswords no
|
||
ChallengeResponseAuthentication no
|
||
GSSAPIAuthentication no
|
||
UsePAM yes
|
||
ClientAliveInterval 30
|
||
ClientAliveCountMax 3
|
||
MaxSessions 20
|
||
MaxStartups 20:30:100
|
||
AllowStreamLocalForwarding yes
|
||
PermitTunnel yes
|
||
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"
|
||
"PermitTunnel yes"
|
||
"AllowStreamLocalForwarding yes"
|
||
"ClientAliveInterval 30"
|
||
"ClientAliveCountMax 3"
|
||
"TCPKeepAlive yes"
|
||
"PubkeyAuthentication yes"
|
||
"MaxSessions 20"
|
||
"MaxStartups 20:30:100"
|
||
)
|
||
|
||
for setting in "${SETTINGS[@]}"; do
|
||
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 — только prohibit-password (безопаснее)
|
||
if ! grep -q "^PermitRootLogin" "$SSHD_CONFIG"; then
|
||
echo "PermitRootLogin prohibit-password" >> "$SSHD_CONFIG"
|
||
log_message "Добавлено: PermitRootLogin prohibit-password" "$GREEN"
|
||
fi
|
||
|
||
# Отключаем 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"
|
||
else
|
||
log_message "ПРЕДУПРЕЖДЕНИЕ: Не удалось перезапустить SSHD." "$YELLOW"
|
||
fi
|
||
}
|
||
|
||
# 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"
|
||
current_user="$VPS_USER"
|
||
log_message "Текущая конфигурация: $current_user@$current_host:$current_port" "$YELLOW"
|
||
fi
|
||
|
||
echo -e "${BOLD}${CYAN}--- Настройка VPS ---${NC}"
|
||
echo ""
|
||
|
||
local VPS_HOST_TEMP
|
||
local VPS_PORT_TEMP
|
||
local VPS_USER_TEMP
|
||
local USER_CHOICE
|
||
|
||
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 для автономных туннелей
|
||
VPS_HOST="$VPS_HOST"
|
||
VPS_PORT="$VPS_PORT"
|
||
VPS_USER="$VPS_USER"
|
||
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"
|
||
|
||
local VPS_PASSWORD
|
||
echo -e "${YELLOW}Для автоматического копирования ключа потребуется пароль от $VPS_USER на VPS.${NC}"
|
||
echo -e "${YELLOW}Если вы не хотите вводить пароль, нажмите Enter и скопируйте ключ вручную.${NC}"
|
||
|
||
VPS_PASSWORD=$(read_secret "Введите пароль для $VPS_USER@$VPS_HOST (оставьте пустым для пропуска): ")
|
||
|
||
if [ -n "$VPS_PASSWORD" ]; then
|
||
if command -v sshpass &>/dev/null; then
|
||
echo -e "${BLUE}Копирую ключ на VPS...${NC}"
|
||
# Безопасная передача пароля через 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 "${SSH_KEY_PATH}.pub"
|
||
fi
|
||
unset SSHPASS
|
||
else
|
||
log_message "sshpass не установлен. Невозможно скопировать ключ автоматически." "$YELLOW"
|
||
echo -e "${YELLOW}Скопируйте ключ вручную:${NC}"
|
||
cat "${SSH_KEY_PATH}.pub"
|
||
fi
|
||
else
|
||
log_message "Пароль не введен. Скопируйте ключ вручную:" "$YELLOW"
|
||
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"
|
||
echo " Имя: $TUNNEL_NAME"
|
||
echo " Локальный порт: $LOCAL_PORT"
|
||
echo " Удаленный порт: $REMOTE_PORT"
|
||
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 2>/dev/null || echo " Служба не найдена"
|
||
|
||
# 3. Проверка логов
|
||
echo -e "\n${BLUE}3. Последние логи туннеля:${NC}"
|
||
if [ -f "$LOG_DIR/$TUNNEL_ID.log" ]; then
|
||
tail -20 "$LOG_DIR/$TUNNEL_ID.log"
|
||
else
|
||
echo " Лог файл не найден"
|
||
fi
|
||
|
||
# 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. Проверка состояния перезапусков
|
||
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"
|
||
if ! load_vps_settings; then
|
||
log_message "Сначала необходимо настроить VPS (опция 1)." "$RED"
|
||
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 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
|
||
# Конфигурация туннеля: $TUNNEL_NAME
|
||
TUNNEL_ID="$TUNNEL_ID"
|
||
TUNNEL_NAME="$TUNNEL_NAME"
|
||
LOCAL_PORT="$LOCAL_PORT"
|
||
REMOTE_PORT="$REMOTE_PORT"
|
||
VPS_HOST="$VPS_HOST"
|
||
VPS_PORT="$VPS_PORT"
|
||
VPS_USER="$VPS_USER"
|
||
CREATED="$(date '+%Y-%m-%d %H:%M:%S')"
|
||
EOFCONF
|
||
|
||
# Создание systemd unit-файла
|
||
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 "tunnel-$TUNNEL_ID.service" >/dev/null 2>&1
|
||
|
||
echo -e "${BLUE}Запуск туннеля...${NC}"
|
||
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"
|
||
echo -e " Локальный порт: $LOCAL_PORT"
|
||
echo -e " Удаленный порт на VPS: $REMOTE_PORT"
|
||
echo -e " VPS: $VPS_USER@$VPS_HOST:$VPS_PORT"
|
||
echo ""
|
||
echo -e "${GREEN}Для доступа через VPS используйте:${NC}"
|
||
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 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 tunnel-$TUNNEL_ID -n 50${NC}"
|
||
echo -e "${YELLOW}Или выполните диагностику туннеля${NC}"
|
||
|
||
echo ""
|
||
echo -e "${RED}Последние логи службы:${NC}"
|
||
journalctl -u "tunnel-$TUNNEL_ID" -n 20 --no-pager
|
||
fi
|
||
|
||
read_input "Нажмите Enter для продолжения..."
|
||
}
|
||
|
||
# 4. Управление туннелем (запуск, остановка, перезапуск, удаление)
|
||
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)
|
||
# Сбрасываем счетчик перезапусков
|
||
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" 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"
|
||
;;
|
||
*)
|
||
log_message "Неизвестное действие: $ACTION" "$RED"
|
||
return 1
|
||
;;
|
||
esac
|
||
}
|
||
|
||
# 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
|
||
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
|
||
echo ""
|
||
}
|
||
|
||
# Функция просмотра логов
|
||
view_logs() {
|
||
while true; do
|
||
clear
|
||
echo -e "${BOLD}${CYAN}Просмотр логов${NC}"
|
||
echo -e "${YELLOW}=============${NC}"
|
||
echo ""
|
||
echo -e "${CYAN}[1]${NC} Логи конкретного туннеля"
|
||
echo -e "${CYAN}[2]${NC} Все логи туннелей (последние 5 строк)"
|
||
echo -e "${CYAN}[3]${NC} Системные логи (journalctl)"
|
||
echo -e "${CYAN}[4]${NC} Лог менеджера"
|
||
echo -e "${CYAN}[5]${NC} Назад"
|
||
echo ""
|
||
|
||
choice=$(read_input "Выберите действие: ")
|
||
|
||
case $choice in
|
||
1)
|
||
echo -e "${CYAN}Доступные логи:${NC}"
|
||
ls -1 "$LOG_DIR"/*.log 2>/dev/null | xargs -n1 basename | grep -v "manager.log" || echo " Логи не найдены"
|
||
echo ""
|
||
log_file=$(read_input "Введите имя лог-файла (например, tunnel-ssh_access.log): ")
|
||
if [ -f "$LOG_DIR/$log_file" ]; then
|
||
less "$LOG_DIR/$log_file"
|
||
else
|
||
log_message "Файл не найден" "$RED"
|
||
sleep 1
|
||
fi
|
||
;;
|
||
2)
|
||
echo -e "${CYAN}Все логи туннелей (последние 5 строк):${NC}"
|
||
for log in "$LOG_DIR"/tunnel-*.log; do
|
||
if [ -f "$log" ]; then
|
||
echo -e "\n${YELLOW}=== $(basename "$log") ===${NC}"
|
||
tail -5 "$log"
|
||
fi
|
||
done
|
||
read_input "Нажмите Enter для продолжения..."
|
||
;;
|
||
3)
|
||
echo -e "${CYAN}Системные логи туннелей (последний час):${NC}"
|
||
journalctl -u 'tunnel-*' --since "1 hour ago" -n 20 2>/dev/null || log_message "Системные логи отсутствуют" "$YELLOW"
|
||
read_input "Нажмите Enter для продолжения..."
|
||
;;
|
||
4)
|
||
echo -e "${CYAN}Лог менеджера:${NC}"
|
||
less "$LOG_DIR/manager.log"
|
||
;;
|
||
5)
|
||
return
|
||
;;
|
||
*)
|
||
log_message "Неверный выбор" "$RED"
|
||
sleep 1
|
||
;;
|
||
esac
|
||
done
|
||
}
|
||
|
||
# Функция тестирования подключений
|
||
test_connections() {
|
||
while true; do
|
||
clear
|
||
echo -e "${BOLD}${CYAN}Тестирование подключений${NC}"
|
||
echo -e "${YELLOW}========================${NC}"
|
||
echo "1) Тест подключения к VPS"
|
||
echo "2) Тест локальных портов туннелей"
|
||
echo "3) Тест SSH ключа"
|
||
echo "4) Тест доступности портов на VPS"
|
||
echo "5) Диагностика всех туннелей"
|
||
echo "6) Назад"
|
||
echo ""
|
||
|
||
test_choice=$(read_input "Выберите тест: ")
|
||
|
||
case $test_choice in
|
||
1)
|
||
check_ssh_connection
|
||
read_input "Нажмите Enter для продолжения..."
|
||
;;
|
||
2)
|
||
echo -e "${CYAN}Тест локальных портов туннелей:${NC}"
|
||
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 is_local_port_listening "$LOCAL_PORT"; then
|
||
echo -e "${GREEN}✓ Открыт${NC}"
|
||
else
|
||
echo -e "${RED}✗ Закрыт${NC}"
|
||
fi
|
||
fi
|
||
done
|
||
read_input "Нажмите Enter для продолжения..."
|
||
;;
|
||
3)
|
||
echo -e "${CYAN}Тест SSH ключа:${NC}"
|
||
if [ -f "$SSH_KEY_PATH" ]; then
|
||
echo -e "${GREEN}✓ Приватный ключ существует${NC}"
|
||
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
|
||
else
|
||
echo -e "${RED}✗ SSH ключ не найден${NC}"
|
||
fi
|
||
read_input "Нажмите Enter для продолжения..."
|
||
;;
|
||
4)
|
||
if load_vps_settings; then
|
||
echo -e "${CYAN}Тест доступности портов на VPS ($VPS_HOST):${NC}"
|
||
|
||
echo -n " SSH порт $VPS_PORT: "
|
||
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 is_vps_reachable "$VPS_HOST" "$REMOTE_PORT"; then
|
||
echo -e "${GREEN}✓ Доступен${NC}"
|
||
else
|
||
echo -e "${RED}✗ Недоступен${NC}"
|
||
fi
|
||
fi
|
||
done
|
||
else
|
||
log_message "Сначала необходимо настроить VPS (опция 1)." "$RED"
|
||
fi
|
||
read_input "Нажмите Enter для продолжения..."
|
||
;;
|
||
5)
|
||
echo -e "${CYAN}Диагностика всех туннелей:${NC}"
|
||
for conf in "$TUNNELS_DIR"/*.conf; do
|
||
if [ -f "$conf" ]; then
|
||
local TUNNEL_ID
|
||
TUNNEL_ID=$(basename "$conf" .conf)
|
||
echo ""
|
||
diagnose_tunnel "$TUNNEL_ID"
|
||
echo ""
|
||
read_input "Нажмите Enter для следующего туннеля..."
|
||
fi
|
||
done
|
||
;;
|
||
6)
|
||
return
|
||
;;
|
||
*)
|
||
log_message "Неверный выбор" "$RED"
|
||
sleep 1
|
||
;;
|
||
esac
|
||
done
|
||
}
|
||
|
||
# Функция настройки маршрутизации (iptables)
|
||
setup_routing() {
|
||
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}"
|
||
echo -e "${YELLOW}==================================${NC}"
|
||
echo "1) Показать текущие правила NAT (PREROUTING)"
|
||
echo "2) Добавить правило перенаправления порта (DNAT)"
|
||
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}"
|
||
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
|
||
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"
|
||
fi
|
||
read_input "Нажмите Enter для продолжения..."
|
||
;;
|
||
3)
|
||
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
|
||
mkdir -p /etc/iptables
|
||
iptables-save > /etc/iptables/rules.v4
|
||
log_message "Правила iptables сохранены в /etc/iptables/rules.v4." "$GREEN"
|
||
else
|
||
log_message "Не найдена утилита для сохранения правил." "$RED"
|
||
fi
|
||
read_input "Нажмите Enter для продолжения..."
|
||
;;
|
||
4)
|
||
return
|
||
;;
|
||
*)
|
||
log_message "Неверный выбор" "$RED"
|
||
sleep 1
|
||
;;
|
||
esac
|
||
done
|
||
}
|
||
|
||
# Функция настройки SSH демона на VPS
|
||
setup_vps_ssh_config() {
|
||
while true; do
|
||
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 "$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}"
|
||
fi
|
||
read_input "Нажмите Enter для продолжения..."
|
||
;;
|
||
2)
|
||
check_ssh_connection
|
||
read_input "Нажмите Enter для продолжения..."
|
||
;;
|
||
3)
|
||
return
|
||
;;
|
||
*)
|
||
log_message "Неверный выбор" "$RED"
|
||
sleep 1
|
||
;;
|
||
esac
|
||
done
|
||
}
|
||
|
||
# Функция настройки локального SSH демона
|
||
setup_local_ssh_config() {
|
||
while true; do
|
||
clear
|
||
echo -e "${BOLD}${CYAN}Настройки локального SSH Демона (sshd)${NC}"
|
||
echo -e "${YELLOW}====================================${NC}"
|
||
echo "1) Показать текущие настройки sshd"
|
||
echo "2) Перезапустить SSH демон"
|
||
echo "3) Проверить конфигурацию sshd"
|
||
echo "4) Установить/переустановить SSH сервер"
|
||
echo "5) Настроить sshd для локальных подключений"
|
||
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 ||
|
||
echo "Настройки не найдены"
|
||
else
|
||
echo "Файл /etc/ssh/sshd_config не найден"
|
||
fi
|
||
read_input "Нажмите Enter для продолжения..."
|
||
;;
|
||
2)
|
||
if systemctl restart sshd 2>/dev/null || service ssh restart 2>/dev/null; then
|
||
log_message "SSH демон перезапущен." "$GREEN"
|
||
else
|
||
log_message "Не удалось перезапустить SSH демон." "$RED"
|
||
fi
|
||
read_input "Нажмите Enter для продолжения..."
|
||
;;
|
||
3)
|
||
if command -v sshd &>/dev/null; then
|
||
if sshd -t 2>/dev/null; then
|
||
log_message "Конфигурация SSH корректна." "$GREEN"
|
||
else
|
||
log_message "Ошибка в конфигурации SSH." "$RED"
|
||
fi
|
||
else
|
||
log_message "SSH сервер не установлен." "$RED"
|
||
fi
|
||
read_input "Нажмите Enter для продолжения..."
|
||
;;
|
||
4)
|
||
install_ssh_server
|
||
read_input "Нажмите Enter для продолжения..."
|
||
;;
|
||
5)
|
||
configure_local_sshd
|
||
read_input "Нажмите Enter для продолжения..."
|
||
;;
|
||
6)
|
||
echo -e "${CYAN}Статус SSH сервера:${NC}"
|
||
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
|
||
echo -e "${GREEN}✓ SSH процесс работает${NC}"
|
||
ps aux | grep sshd | grep -v grep
|
||
else
|
||
echo -e "${RED}✗ SSH сервер не запущен${NC}"
|
||
fi
|
||
read_input "Нажмите Enter для продолжения..."
|
||
;;
|
||
7)
|
||
return
|
||
;;
|
||
*)
|
||
log_message "Неверный выбор" "$RED"
|
||
sleep 1
|
||
;;
|
||
esac
|
||
done
|
||
}
|
||
|
||
# 6. Меню управления туннелями
|
||
manage_tunnels_menu() {
|
||
while true; do
|
||
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
|
||
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}"
|
||
echo -e " Имя: $TUNNEL_NAME"
|
||
echo -e " Локальный порт: $LOCAL_PORT"
|
||
echo -e " Удаленный порт: $REMOTE_PORT"
|
||
echo -e " VPS: $VPS_USER@$VPS_HOST:$VPS_PORT"
|
||
echo -e " Создан: $CREATED"
|
||
echo ""
|
||
fi
|
||
|
||
echo -e "${CYAN}Статус службы:${NC}"
|
||
manage_tunnel "$TUNNEL_ID_MANAGE" "status"
|
||
|
||
echo -e "\n${CYAN}Выберите действие:${NC}"
|
||
echo " s) Статус (обновить)"
|
||
echo " t) Старт"
|
||
echo " p) Стоп"
|
||
echo " r) Перезапуск"
|
||
echo " d) Диагностика"
|
||
echo " e) Включить автозагрузку"
|
||
echo " x) Отключить автозагрузку"
|
||
echo " u) Удалить"
|
||
echo " l) Просмотр логов"
|
||
echo " b) Назад"
|
||
|
||
ACTION_CHOICE=$(read_input "Действие: ")
|
||
|
||
case "$ACTION_CHOICE" in
|
||
s) ;;
|
||
t)
|
||
manage_tunnel "$TUNNEL_ID_MANAGE" "start"
|
||
;;
|
||
p)
|
||
manage_tunnel "$TUNNEL_ID_MANAGE" "stop"
|
||
;;
|
||
r)
|
||
manage_tunnel "$TUNNEL_ID_MANAGE" "restart"
|
||
;;
|
||
d)
|
||
diagnose_tunnel "$TUNNEL_ID_MANAGE"
|
||
read_input "Нажмите Enter для продолжения..."
|
||
;;
|
||
e)
|
||
systemctl enable "tunnel-$TUNNEL_ID_MANAGE.service" 2>/dev/null
|
||
log_message "Автозагрузка включена" "$GREEN"
|
||
sleep 1
|
||
;;
|
||
x)
|
||
systemctl disable "tunnel-$TUNNEL_ID_MANAGE.service" 2>/dev/null
|
||
log_message "Автозагрузка отключена" "$YELLOW"
|
||
sleep 1
|
||
;;
|
||
u)
|
||
CONFIRM_REMOVE=$(read_input "Вы уверены, что хотите удалить туннель $TUNNEL_ID_MANAGE? (y/N): ")
|
||
if [[ "$CONFIRM_REMOVE" =~ ^[Yy]$ ]]; then
|
||
manage_tunnel "$TUNNEL_ID_MANAGE" "remove"
|
||
sleep 2
|
||
return
|
||
fi
|
||
;;
|
||
l)
|
||
if [ -f "$LOG_DIR/$TUNNEL_ID_MANAGE.log" ]; then
|
||
less "$LOG_DIR/$TUNNEL_ID_MANAGE.log"
|
||
else
|
||
log_message "Лог файл не найден" "$RED"
|
||
sleep 1
|
||
fi
|
||
;;
|
||
b) break;;
|
||
*) log_message "Неизвестная опция." "$RED";;
|
||
esac
|
||
read_input "Нажмите Enter для продолжения...";
|
||
done
|
||
else
|
||
log_message "Туннель с ID '$TUNNEL_ID_MANAGE' не найден." "$RED"
|
||
sleep 1
|
||
fi
|
||
done
|
||
}
|
||
|
||
# 7. Главное меню
|
||
main_menu() {
|
||
check_root "menu"
|
||
|
||
while true; do
|
||
clear
|
||
echo -e "${BOLD}${CYAN}========================================================${NC}"
|
||
echo -e "${BOLD}${CYAN} SSH Tunnel Manager v3.0 (Fixed) ${NC}"
|
||
echo -e "${BOLD}${CYAN}========================================================${NC}"
|
||
|
||
if [ ! -f "$SSH_KEY_PATH" ]; then
|
||
echo -e "${RED}⚠ SSH ключ не найден. Выполните установку.${NC}"
|
||
else
|
||
echo -e "${GREEN}✓ SSH ключ настроен${NC}"
|
||
fi
|
||
|
||
if load_vps_settings; then
|
||
echo -e "${GREEN}✓ VPS настроен: $VPS_USER@$VPS_HOST:$VPS_PORT${NC}"
|
||
else
|
||
echo -e "${RED}✗ VPS не настроен. Начните с опции 1.${NC}"
|
||
fi
|
||
|
||
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}"
|
||
else
|
||
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)"
|
||
echo -e "3) ${BOLD}Управление туннелем${NC} (Статус, Старт, Стоп, Перезапуск, Удаление)"
|
||
echo -e "4) ${BOLD}Просмотр логов и мониторинг${NC}"
|
||
echo -e "5) ${BOLD}Тестирование подключений${NC}"
|
||
echo -e "6) ${BOLD}Настройка маршрутизации (iptables)${NC}"
|
||
echo -e "7) ${BOLD}Настройки SSH Демона на VPS${NC}"
|
||
echo -e "8) ${BOLD}Настройки локального SSH Демона${NC}"
|
||
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 ;;
|
||
3) manage_tunnels_menu ;;
|
||
4) view_logs ;;
|
||
5) test_connections ;;
|
||
6) setup_routing ;;
|
||
7) setup_vps_ssh_config ;;
|
||
8) setup_local_ssh_config ;;
|
||
9) show_public_key ;;
|
||
0)
|
||
log_message "Выход из менеджера." "$GREEN"
|
||
exit 0
|
||
;;
|
||
*)
|
||
log_message "Неверный выбор. Попробуйте еще раз." "$RED"
|
||
sleep 1
|
||
;;
|
||
esac
|
||
done
|
||
}
|
||
|
||
# --- Точка входа ---
|
||
if [[ $# -eq 0 ]]; then
|
||
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
|
||
main_menu
|
||
else
|
||
echo -e "${GREEN}Начинаем установку...${NC}"
|
||
echo -e "${YELLOW}Установка займет несколько минут...${NC}"
|
||
sleep 2
|
||
install_manager
|
||
echo -e "${GREEN}Установка завершена. Запуск меню...${NC}"
|
||
sleep 2
|
||
main_menu
|
||
fi
|
||
else
|
||
case "$1" in
|
||
install)
|
||
install_manager
|
||
;;
|
||
menu)
|
||
main_menu
|
||
;;
|
||
*)
|
||
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"
|
||
echo -e " 2. Только установка: sudo bash $0 install"
|
||
echo -e " 3. Только меню: sudo bash $0 menu"
|
||
echo ""
|
||
echo -e "После установки можно использовать: sudo tunnel-manager menu"
|
||
;;
|
||
esac
|
||
fi
|
||
|
||
exit 0
|