Files
vps_ssh_tunel/install_ssh_tunnel.sh
Deploy Bot a4a627fdbc fix: исправлены критические ошибки SSH туннеля
- 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
2026-07-29 20:26:41 +01:00

1754 lines
70 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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