#!/bin/sh
# rProxy — Менеджер обратного прокси для роутеров Keenetic
# Публикация локальных сервисов через SSH-туннели + nginx на VPS
# http://5.104.75.50:3000/Petro1990/rProxy

VERSION="1.5.5"
export PATH="/opt/bin:/opt/sbin:$PATH"
CONF_DIR="/opt/etc/rproxy"
CONF_FILE="$CONF_DIR/rproxy.conf"
SERVICES_DIR="$CONF_DIR/services"
VPS_DIR="$CONF_DIR/vps"
PID_DIR="/opt/var/run/rproxy"
SSH_KEY="$CONF_DIR/id_ed25519"
REMOTE_NGINX_DIR="/etc/nginx/sites-enabled"
BASE_TUNNEL_PORT=10000
BASE_EXT_PORT=26000
CERTBOT_EMAIL="" # Будет заполнено из конфига

# ─── Цвета ───────────────────────────────────────────────────────────
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
BOLD='\033[1m'
DIM='\033[2m'
NC='\033[0m'

msg()     { printf "${GREEN}▸${NC} %s\n" "$*"; }
warn()    { printf "${YELLOW}⚠${NC} %s\n" "$*"; }
err()     { printf "${RED}✖${NC} %s\n" "$*" >&2; }
header()  { printf "\n${CYAN}${BOLD}%s${NC}\n" "$*"; }

# ─── Утилиты ─────────────────────────────────────────────────────────
clear_screen() { printf "\033[2J\033[H"; }

draw_box() {
    local title="$1"
    header "$title"
}

draw_separator() {
    printf "${DIM}──────────────────────────────────────────────────${NC}\n"
}

prompt() {
    printf "\n${CYAN}▸${NC} $1"
    read -r REPLY
}

pause() {
    printf "\n${DIM}Нажмите Enter для продолжения...${NC}"
    read -r _
}

check_conf() {
    if [ ! -f "$CONF_FILE" ]; then
        mkdir -p "$CONF_DIR"
        touch "$CONF_FILE"
    fi
    . "$CONF_FILE"
    # Строгая проверка: ищем хотя бы один файл в vps/, в котором заполнена переменная VPS_HOST
    for f in "$VPS_DIR"/*.conf; do
        [ -f "$f" ] || continue
        if grep -qE "VPS_HOST=['\"]?[^'\" ]+" "$f"; then
            return 0
        fi
    done
    return 1
}

load_service() {
    local svc_file="$SERVICES_DIR/$1.conf"
    if [ ! -f "$svc_file" ]; then
        err "Сервис '$1' не найден"
        return 1
    fi
    . "$svc_file"
    # При загрузке сервиса также загружаем его VPS
    if [ -n "$SVC_VPS" ]; then
        load_vps "$SVC_VPS" || return 1
    else
        # Обратная совместимость: если VPS не указан, пробуем default
        load_vps "default" || return 1
    fi
}

load_vps() {
    local vps_id="$1"
    local vps_file="$VPS_DIR/$vps_id.conf"
    if [ ! -f "$vps_file" ]; then
        return 1
    fi
    # Сброс старых переменных перед загрузкой
    VPS_HOST="" VPS_PORT="" VPS_USER="" VPS_AUTH="" VPS_PASS=""
    . "$vps_file"
    CUR_VPS_ID="$vps_id"
    return 0
}

migrate_config() {
    mkdir -p "$VPS_DIR" "$SERVICES_DIR" "$PID_DIR"
    [ ! -f "$CONF_FILE" ] && touch "$CONF_FILE"
    
    # Проверка на "битый" (пустой) конфиг
    local broken=0
    [ -f "$VPS_DIR/default.conf" ] && ! grep -q "VPS_HOST=" "$VPS_DIR/default.conf" && broken=1

    if [ -f "$CONF_FILE" ] && { [ ! -f "$VPS_DIR/default.conf" ] || [ "$broken" -eq 1 ]; }; then
        # Если rproxy.conf ещё содержит данные серверов (не пустой)
        if grep -q "VPS_HOST=" "$CONF_FILE"; then
            msg "Восстановление конфигурации VPS..."
            . "$CONF_FILE"
            cat > "$VPS_DIR/default.conf" <<EOF
VPS_HOST="$VPS_HOST"
VPS_PORT="$VPS_PORT"
VPS_USER="$VPS_USER"
VPS_AUTH="$VPS_AUTH"
VPS_PASS="$VPS_PASS"
EOF
            # Очищаем rproxy.conf от старых системных переменных, НО сохраняем новые
            local tmp_conf="/tmp/rproxy.conf.tmp"
            printf "CERTBOT_EMAIL='%s'\n" "$CERTBOT_EMAIL" > "$tmp_conf"
            [ -n "$DEFAULT_AUTH_USER" ] && printf "DEFAULT_AUTH_USER='%s'\n" "$DEFAULT_AUTH_USER" >> "$tmp_conf"
            [ -n "$DEFAULT_AUTH_PASS" ] && printf "DEFAULT_AUTH_PASS='%s'\n" "$DEFAULT_AUTH_PASS" >> "$tmp_conf"
            mv "$tmp_conf" "$CONF_FILE"
        fi
        [ -f "$VPS_DIR/default.conf" ] && chmod 600 "$VPS_DIR/default.conf"
    fi

    # Если профиль default остался битым и восстановить нечего — удаляем его
    if [ -f "$VPS_DIR/default.conf" ] && ! grep -q "VPS_HOST=" "$VPS_DIR/default.conf"; then
        rm -f "$VPS_DIR/default.conf"
    fi
}

ensure_local_deps() {
    local missing=""
    
    # Внутренняя функция для надежной проверки
    _has_cmd() {
        command -v "$1" >/dev/null 2>&1 || [ -x "/opt/bin/$1" ] || [ -x "/opt/sbin/$1" ]
    }

    # Проверка openssl
    if ! _has_cmd openssl; then
        missing="$missing openssl-util"
    fi
    
    # Проверка sshpass
    if ! _has_cmd sshpass; then
        missing="$missing sshpass"
    fi
    
    # Проверка curl
    if ! _has_cmd curl; then
        missing="$missing curl"
    fi
    
    if [ -n "$missing" ]; then
        msg "Обнаружены недостающие зависимости:$missing"
        msg "Устанавливаю через opkg..."
        opkg update >/dev/null 2>&1
        for pkg in $missing; do
            msg "Установка $pkg..."
            opkg install "$pkg" >/dev/null 2>&1
        done
        hash -r 2>/dev/null
        msg "Все зависимости установлены."
    fi
}

ssh_cmd() {
    local ssh_exec="/opt/bin/ssh"
    [ ! -f "$ssh_exec" ] && ssh_exec="ssh"

    if [ "$VPS_AUTH" = "password" ]; then
        if ! command -v sshpass >/dev/null 2>&1; then
            err "Утилита sshpass не найдена. Авторизация по паролю невозможна."
            err "Пожалуйста, перенастройте rProxy на использование SSH-ключа (rproxy setup)."
            return 1
        fi
        sshpass -p "$VPS_PASS" $ssh_exec -q -o StrictHostKeyChecking=no -o LogLevel=ERROR -p "$VPS_PORT" "$VPS_USER@$VPS_HOST" "$@"
    else
        $ssh_exec -q -o StrictHostKeyChecking=no -o LogLevel=ERROR -i "$SSH_KEY" -p "$VPS_PORT" "$VPS_USER@$VPS_HOST" "$@"
    fi
}

scp_cmd() {
    local scp_exec="/opt/bin/scp"
    [ ! -f "$scp_exec" ] && scp_exec="scp"

    if [ "$VPS_AUTH" = "password" ]; then
        if ! command -v sshpass >/dev/null 2>&1; then
            err "Утилита sshpass не найдена. Копирование по паролю невозможно."
            return 1
        fi
        sshpass -p "$VPS_PASS" $scp_exec -q -o StrictHostKeyChecking=no -o LogLevel=ERROR -P "$VPS_PORT" "$@"
    else
        $scp_exec -q -o StrictHostKeyChecking=no -o LogLevel=ERROR -i "$SSH_KEY" -P "$VPS_PORT" "$@"
    fi
}

next_free_port() {
    local port=$BASE_TUNNEL_PORT
    while true; do
        local used=0
        for f in "$SERVICES_DIR"/*.conf; do
            [ -f "$f" ] || continue
            grep -q "SVC_TUNNEL_PORT=\"$port\"" "$f" && used=1 && break
        done
        [ "$used" -eq 0 ] && echo "$port" && return
        port=$((port + 1))
    done
}

get_router_ip() {
    # Метод 1: Через ndmq (Keenetic Bridge0)
    local ip=$(ndmq -p "show interface Bridge0" -path "address" 2>/dev/null)
    [ -n "$ip" ] && [ "$ip" != "0.0.0.0" ] && echo "$ip" && return

    # Метод 2: Через маршруты (default gateway)
    ip=$(ip route show | grep default | awk '{print $3}' | head -n 1)
    [ -n "$ip" ] && echo "$ip" && return

    # Метод 3: Через ip addr (Entware/Keenetic)
    ip=$(ip addr show br0 2>/dev/null | grep 'inet ' | awk '{print $2}' | cut -d/ -f1 | head -n1)
    [ -n "$ip" ] && echo "$ip" && return

    # Запасной вариант
    echo "192.168.1.1"
}

# Метод 2: Через ndmq (Локальный доступ в Entware)
# Этот метод не требует пароля и работает мгновенно
rci_call() {
    local command="$1"
    ndmq -p "$command" 2>/dev/null
}

enable_router_basic_auth() {
    log_info "Настройка локального доступа через RCI/ndmq..."
    local hostname=$(rci_call "show system" | grep "hostname" | awk '{print $NF}')
    log_info "Хостнейм роутера: $hostname"
    return 0
}

gen_htpasswd() {
    local user="$1"
    local pass="$2"
    # Пытаемся использовать openssl если он есть в Entware
    if command -v openssl >/dev/null 2>&1; then
        local hash=$(openssl passwd -apr1 "$pass")
        echo "$user:$hash"
    else
        warn "Утилита 'openssl' не найдена! Пароль будет сохранен в открытом виде (Nginx может его не принять)."
        warn "Рекомендуется: opkg update && opkg install openssl-util"
        echo "$user:$pass"
    fi
}

next_free_ext_port() {
    local port=$BASE_EXT_PORT
    while true; do
        local used=0
        for f in "$SERVICES_DIR"/*.conf; do
            [ -f "$f" ] || continue
            grep -q "SVC_EXT_PORT=\"$port\"" "$f" && used=1 && break
        done
        [ "$used" -eq 0 ] && echo "$port" && return
        port=$((port + 1))
    done
}

get_pid_file() { echo "$PID_DIR/$1.pid"; }

is_running() {
    local pf
    pf=$(get_pid_file "$1")
    [ -f "$pf" ] && kill -0 "$(cat "$pf")" 2>/dev/null
}

count_services() {
    local count=0
    for f in "$SERVICES_DIR"/*.conf; do
        [ -f "$f" ] && count=$((count + 1))
    done
    echo $count
}

# ══════════════════════════════════════════════════════════════════════
# ГЛАВНОЕ МЕНЮ
# ══════════════════════════════════════════════════════════════════════
main_menu() {
    while true; do
        clear_screen
        draw_box "rProxy v$VERSION"
        printf "\n"

        if ! check_conf; then
            printf "  ${YELLOW}⚠ VPS не настроен${NC}\n\n"
            printf "  ${BOLD}1)${NC} Настроить подключение к VPS\n"
            printf "  ${BOLD}0)${NC} Выход\n"
            prompt "Выберите действие: "
            case "$REPLY" in
                1) do_setup ;;
                0|q) printf "\n"; exit 0 ;;
                *) ;;
            esac
            continue
        fi

        # Подсчёт сервисов и статусов по всем VPS
        local total=0 online=0
        for f in "$SERVICES_DIR"/*.conf; do
            [ -f "$f" ] || continue
            total=$((total + 1))
            (
                . "$f"
                is_running "$SVC_NAME" && exit 0
                exit 1
            ) && online=$((online + 1))
        done

        printf "  ${DIM}Серверов VPS:${NC} ${BOLD}$(ls -A "$VPS_DIR" | wc -l)${NC}  "
        printf "${DIM}Сервисов:${NC} ${BOLD}$total${NC}  "
        printf "${DIM}Онлайн:${NC} ${GREEN}${BOLD}$online${NC}\n"
        draw_separator
        printf "\n"

        printf "  ${BOLD}1)${NC}  📋  Список сервисов и статус\n"
        printf "  ${BOLD}2)${NC}  ➕  Добавить сервис\n"
        printf "  ${BOLD}3)${NC}  📝  Редактировать сервис\n"
        printf "  ${BOLD}4)${NC}  ❌  Удалить сервис\n"
        draw_separator
        printf "  ${BOLD}5)${NC}  ▶️   Запустить туннель\n"
        printf "  ${BOLD}6)${NC}  ⏹️   Остановить туннель\n"
        printf "  ${BOLD}7)${NC}  🔄  Перезапустить туннель\n"
        draw_separator
        draw_separator
        printf "  ${BOLD}8)${NC}  🔒  Получить/Обновить SSL (Certbot)\n"
        printf "  ${BOLD}9)${NC}  ⚙️   Настройки VPS\n"
        printf "  ${BOLD}10)${NC} 🚀  Обновить rProxy\n"
        printf "  ${BOLD}11)${NC} 🏥  Проверка VPS (Health)\n"
        printf "  ${BOLD}0)${NC}  🚪  Выход\n"

        prompt "Выберите действие: "

        case "$REPLY" in
            1) show_status ;;
            2) do_add_interactive ;;
            3) do_edit_interactive ;;
            4) do_remove_interactive ;;
            5) do_start_interactive ;;
            6) do_stop_interactive ;;
            7) do_restart_interactive ;;
            8) do_ssl_interactive ;;
            9) do_setup ;;
            10) do_self_update ;;
            11) do_health_check ;;
            0|q) clear_screen; exit 0 ;;
            *) ;;
        esac
    done
}

# ══════════════════════════════════════════════════════════════════════
# СТАТУС
# ══════════════════════════════════════════════════════════════════════
show_status() {
    clear_screen
    draw_box "Список сервисов"
    printf "\n"

    local has_services=0
    local idx=0

    printf "  ${BOLD}%-4s %-14s %-22s %-7s %-9s %-18s${NC}\n" "№" "ИМЯ" "ЦЕЛЬ" "ПОРТ" "СТАТУС" "ДОМЕН"
    draw_separator

    for f in "$SERVICES_DIR"/*.conf; do
        [ -f "$f" ] || continue
        has_services=1
        idx=$((idx + 1))
        (
            load_service "$(basename "$f" .conf)" >/dev/null 2>&1
            
            local status_text
            if is_running "$SVC_NAME"; then
                status_text="${GREEN}● онлайн${NC}"
            else
                status_text="${RED}○ офлайн${NC}"
            fi

            local domain_text="${SVC_DOMAIN:-—}"
            local auto_text=""
            [ "$SVC_ENABLED" = "yes" ] && auto_text=" ${DIM}[авто]${NC}"

            printf "  %-4s %-14s %-22s %-7s ${status_text}  %-18s${auto_text}\n" \
                "$idx" "$SVC_NAME" "$SVC_TARGET_HOST:$SVC_TARGET_PORT" "$SVC_EXT_PORT" "$domain_text"
        )
    done

    if [ "$has_services" -eq 0 ]; then
        printf "\n  ${YELLOW}Нет добавленных сервисов.${NC}\n"
        printf "  ${DIM}Выберите «Добавить сервис» в главном меню.${NC}\n"
    fi

    printf "\n"
    pause
}

# ══════════════════════════════════════════════════════════════════════
# ══════════════════════════════════════════════════════════════════════
# ПОМОЩНИКИ VPS
# ══════════════════════════════════════════════════════════════════════


find_vps_by_domain() {
    local dom="$1"
    FOUND_VPS_ID=""
    # Пытаемся получить IP через ping (BusyBox стиль)
    local ip=$(ping -c 1 "$dom" 2>/dev/null | grep -o "[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}" | head -n 1)
    # Если не вышло, пробуем nslookup (парсим более надежно через grep -o)
    [ -z "$ip" ] && ip=$(nslookup "$dom" 2>/dev/null | grep "Address" | tail -n 1 | grep -o "[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}" | head -n 1)
    
    [ -z "$ip" ] && return 1

    for f in "$VPS_DIR"/*.conf; do
        [ -f "$f" ] || continue
        # Используем grep для извлечения IP из конфига (быстрее и надежнее источника)
        local v_host=$(grep "VPS_HOST=" "$f" | cut -d'"' -f2)
        if [ "$v_host" = "$ip" ]; then
            FOUND_VPS_ID=$(basename "$f" .conf)
            return 0
        fi
    done
    return 1
}

select_vps_interactive() {
    SELECTED_VPS_ID=""
    draw_box "Выберите VPS для сервиса"
    printf "\n"
    local vps_list=""
    local idx=0
    for f in "$VPS_DIR"/*.conf; do
        [ -f "$f" ] || continue
        idx=$((idx + 1))
        (
            . "$f"
            printf "  ${BOLD}%d)${NC}  %s  (${DIM}%s${NC})\n" "$idx" "$(basename "$f" .conf)" "$VPS_HOST"
        )
        [ -z "$vps_list" ] && vps_list="$(basename "$f" .conf)" || vps_list="$vps_list $(basename "$f" .conf)"
    done
    
    draw_separator
    printf "  ${BOLD}901)${NC} ➕  Добавить новый VPS\n"
    printf "  ${BOLD}0)${NC}   Назад\n"
    prompt "Выберите номер: "
    
    [ "$REPLY" = "0" ] && { SELECTED_VPS_ID=""; return 0; }

    if [ "$REPLY" = "901" ]; then
        do_add_vps
        # Возвращаем последний созданный (упрощение)
        SELECTED_VPS_ID=$(ls -t "$VPS_DIR"/*.conf | head -n 1 | xargs basename | sed 's/\.conf//')
        return 0
    fi
    
    # Валидация ввода для cut
    if echo "$REPLY" | grep -qE '^[0-9]+$'; then
        SELECTED_VPS_ID=$(echo "$vps_list" | cut -d' ' -f"$REPLY")
    fi
    return 0
}

# ══════════════════════════════════════════════════════════════════════
# ДОБАВИТЬ СЕРВИС (интерактивно)
# ══════════════════════════════════════════════════════════════════════
do_add_interactive() {
    clear_screen
    draw_box "Добавить новый сервис"
    printf "\n"

    prompt "Название сервиса (латиницей, без пробелов): "
    local name=$(echo "$REPLY" | tr ' ' '_' | tr -cd '[:alnum:]_-')
    [ -z "$name" ] && { warn "Название не может быть пустым"; pause; return; }

    if [ -f "$SERVICES_DIR/$name.conf" ]; then
        err "Сервис '$name' уже существует"
        pause; return
    fi

    prompt "Адрес локального сервиса (например, 127.0.0.1:8080): "
    local target="$REPLY"
    [ -z "$target" ] && { warn "Адрес не может быть пустым"; pause; return; }

    printf "\n  Привязать к домену?\n"
    printf "  ${BOLD}1)${NC} Да — указать доменное имя (порт 443)\n"
    printf "  ${BOLD}2)${NC} Нет — указать внешний порт\n"
    prompt "Выберите [2]: "
    local mode="${REPLY:-2}"

    local domain="" ext_port="" use_ssl="no" vps_id=""

    if [ "$mode" = "1" ]; then
        prompt "Доменное имя (например, mysite.example.com): "
        domain="$REPLY"
        [ -z "$domain" ] && { warn "Домен не указан"; pause; return; }
        use_ssl="yes"; ext_port=443
        
        if [ -z "$CERTBOT_EMAIL" ]; then
            prompt "Введите Email для Certbot: "
            CERTBOT_EMAIL="$REPLY"
            sed -i "/CERTBOT_EMAIL=/d" "$CONF_FILE"
            echo "CERTBOT_EMAIL=\"$CERTBOT_EMAIL\"" >> "$CONF_FILE"
        fi

        # Автоопределение VPS по домену
        msg "Проверяю куда направлен домен $domain..."
        if find_vps_by_domain "$domain"; then
            vps_id="$FOUND_VPS_ID"
            msg "Домен $domain указывает на ваш VPS '$vps_id'. Выбираю его."
        else
            warn "Не удалось автоматически определить VPS для $domain"
        fi
    else
        ext_port=$(next_free_ext_port)
        prompt "Внешний порт [$ext_port]: "
        ext_port="${REPLY:-$ext_port}"
    fi

    # Если VPS не определен автоматически — выбираем вручную
    if [ -z "$vps_id" ]; then
        select_vps_interactive
        vps_id="$SELECTED_VPS_ID"
        [ -z "$vps_id" ] && { warn "VPS не выбран"; pause; return; }
    fi

    # Загружаем выбранный VPS для деплоя
    load_vps "$vps_id" || { err "Не удалось загрузить VPS '$vps_id'"; pause; return; }

    # Разбор адреса
    local t_host="${target%:*}"
    local t_port="${target#*:}"
    [ "$t_host" = "$t_port" ] && { t_port="$t_host"; t_host="127.0.0.1"; }

    local tunnel_port=$(next_free_port)

    printf "\n"
    draw_separator
    printf "  ${BOLD}Сервис:${NC}        $name\n"
    printf "  ${BOLD}VPS:${NC}           $vps_id ($VPS_HOST)\n"
    printf "  ${BOLD}Цель:${NC}          $t_host:$t_port\n"
    printf "  ${BOLD}Туннель:${NC}       порт $tunnel_port\n"
    [ -n "$domain" ] && printf "  ${BOLD}Домен:${NC}         $domain\n"
    printf "  ${BOLD}Внешний порт:${NC}  $ext_port\n"
    prompt "Защитить сервис паролем? (д/н) [н]: "
    local use_ndm_auth="no"
    local htpasswd_line=""
    case "$REPLY" in
        д|Д|y|Y|да|yes) 
            use_ndm_auth="yes"
            local def_user="${DEFAULT_AUTH_USER:-admin}"
            
            if [ -n "$DEFAULT_AUTH_PASS" ]; then
                printf "  ${DIM}Будут использованы сохраненные данные: $def_user / *****${NC}\n"
                prompt "Использовать их? (д/н) [д]: "
                if [ "${REPLY:-д}" = "д" ] || [ "${REPLY:-д}" = "y" ]; then
                    htpasswd_line=$(gen_htpasswd "$def_user" "$DEFAULT_AUTH_PASS")
                fi
            fi

            if [ -z "$htpasswd_line" ]; then
                prompt "Введите имя пользователя [$def_user]: "
                local user="${REPLY:-$def_user}"
                prompt "Введите пароль для '$user': "
                local pass="$REPLY"
                if [ -z "$pass" ]; then
                     warn "Пароль не может быть пустым. Авторизация будет отключена."
                     use_ndm_auth="no"
                else
                     htpasswd_line=$(gen_htpasswd "$user" "$pass")
                fi
            fi
            ;;
    esac

    draw_separator

    prompt "Всё верно? Добавить сервис? (д/н) [д]: "
    [ "${REPLY:-д}" != "д" ] && [ "${REPLY:-д}" != "y" ] && { msg "Отменено"; pause; return; }

    msg "Добавляю сервис '$name'..."

    # Стелс-режим: прикидываемся локальным запросом
    local stealth_host="$t_host"
    [ "$t_port" != "80" ] && stealth_host="$t_host:$t_port"

    # Конфигурация авторизации
    local auth_config=""
    if [ "$use_ndm_auth" = "yes" ]; then
        auth_config="
    auth_basic \"Restricted Access\";
    auth_basic_user_file /etc/nginx/rproxy_$name.htpasswd;
    "
    fi

    # Генерация конфига nginx
    local tmp="/tmp/rproxy_$name.conf"
    if [ -n "$domain" ]; then
        cat > "$tmp" << NGINXEOF
server {
    listen 80;
    server_name "$domain";

    proxy_buffering off;
    proxy_request_buffering off;
    proxy_buffer_size 128k;
    proxy_buffers 4 256k;
    proxy_busy_buffers_size 256k;

    location / {
        $auth_config
        proxy_pass http://127.0.0.1:$tunnel_port;
        proxy_http_version 1.1;
        proxy_set_header Upgrade \$http_upgrade;
        proxy_set_header Connection "upgrade";
        
        # Стелс-режим: прикидываемся локальным запросом
        proxy_set_header Host "$stealth_host";
        proxy_set_header Origin "http://$stealth_host";
        proxy_set_header Referer "http://$stealth_host/";
        
        proxy_set_header X-Real-IP \$remote_addr;
        proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto \$scheme;
        proxy_set_header X-Forwarded-Host \$http_host;
        proxy_set_header X-Forwarded-Port \$server_port;
        
        # Трансляция куки: меняем локальный IP обратно на домен в браузере
        proxy_cookie_domain "$t_host" "\$host";
        proxy_cookie_path / "/; SameSite=Lax";
        
        proxy_hide_header X-Frame-Options;
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }
    $auth_config
}
NGINXEOF
    else
        cat > "$tmp" << NGINXEOF
server {
    listen $ext_port;

    proxy_buffering off;
    proxy_request_buffering off;
    proxy_buffer_size 128k;
    proxy_buffers 4 256k;
    proxy_busy_buffers_size 256k;

    location / {
        $auth_config
        proxy_pass http://127.0.0.1:$tunnel_port;
        proxy_http_version 1.1;
        proxy_set_header Upgrade \$http_upgrade;
        proxy_set_header Connection "upgrade";
        
        # Стелс-режим: прикидываемся локальным запросом
        proxy_set_header Host "$stealth_host";
        proxy_set_header Origin "http://$stealth_host";
        proxy_set_header Referer "http://$stealth_host/";

        proxy_set_header X-Real-IP \$remote_addr;
        proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto \$scheme;
        proxy_set_header X-Forwarded-Host \$http_host;
        proxy_set_header X-Forwarded-Port \$server_port;
        
        # Трансляция куки: меняем локальный IP обратно на домен в браузере
        proxy_cookie_domain "$t_host" "\$host";
        proxy_cookie_path / "/; SameSite=Lax";
        
        proxy_hide_header X-Frame-Options;
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }
    $auth_config
}
NGINXEOF
    fi

    # Деплой
    # Деплой htpasswd если нужно
    if [ "$use_ndm_auth" = "yes" ]; then
        local ht_tmp="/tmp/rproxy_$name.htpasswd"
        echo "$htpasswd_line" > "$ht_tmp"
        scp_cmd "$ht_tmp" "$VPS_USER@$VPS_HOST:/etc/nginx/rproxy_$name.htpasswd"
        rm -f "$ht_tmp"
    fi

    # Деплой nginx
    scp_cmd "$tmp" "$VPS_USER@$VPS_HOST:$REMOTE_NGINX_DIR/rproxy_$name.conf" || { err "Ошибка деплоя nginx"; rm -f "$tmp"; pause; return; }
    ssh_cmd "nginx -t && systemctl reload nginx" >/dev/null 2>&1

    # SSL если нужно
    if [ "$use_ssl" = "yes" ]; then
        msg "Получаю SSL через Certbot (может занять время)..."
        ssh_cmd "certbot --nginx -d $domain --non-interactive --agree-tos -m $CERTBOT_EMAIL" || warn "Certbot вернул ошибку"
    fi

    # Сохранение конфига сервиса (используем printf для защиты от спецсимволов и одинарные кавычки)
    {
        printf 'SVC_NAME="%s"\n' "$name"
        printf 'SVC_VPS="%s"\n' "$vps_id"
        printf 'SVC_TARGET_HOST="%s"\n' "$t_host"
        printf 'SVC_TARGET_PORT="%s"\n' "$t_port"
        printf 'SVC_TUNNEL_PORT="%s"\n' "$tunnel_port"
        printf 'SVC_EXT_PORT="%s"\n' "$ext_port"
        printf 'SVC_DOMAIN="%s"\n' "$domain"
        printf 'SVC_SSL="%s"\n' "$use_ssl"
        printf 'SVC_NDM_AUTH="%s"\n' "$use_ndm_auth"
        printf "SVC_HTPASSWD='%s'\n" "$htpasswd_line"
        printf 'SVC_ENABLED="yes"\n'
    } > "$SERVICES_DIR/$name.conf"

    msg "Сервис '$name' добавлен на VPS '$vps_id'!"
    do_start_service "$name"
    pause
}

# ══════════════════════════════════════════════════════════════════════
# РЕДАКТИРОВАТЬ СЕРВИС
# ══════════════════════════════════════════════════════════════════════
do_edit_interactive() {
    clear_screen
    draw_box "Редактировать сервис"
    printf "\n"

    select_service "Выберите сервис для редактирования" "all" || { pause; return; }
    local name="$SELECTED_SERVICE"
    [ "$name" = "__ALL__" ] && { warn "Выберите конкретный сервис"; pause; return; }

    load_service "$name" || { pause; return; }

    header "Редактирование: $name"
    printf "  ${DIM}Текущая цель:${NC}  $SVC_TARGET_HOST:$SVC_TARGET_PORT\n\n"

    prompt "Новый IP адрес [$SVC_TARGET_HOST]: "
    local new_host="${REPLY:-$SVC_TARGET_HOST}"
    
    prompt "Новый внутренний порт [$SVC_TARGET_PORT]: "
    local new_port="${REPLY:-$SVC_TARGET_PORT}"

    prompt "Сохранить изменения? (д/н) [д]: "
    [ "${REPLY:-д}" != "д" ] && [ "${REPLY:-д}" != "y" ] && { msg "Отменено"; pause; return; }

    # Обновляем конфиг
    sed -i "s/SVC_TARGET_HOST=.*/SVC_TARGET_HOST=\"$new_host\"/" "$SERVICES_DIR/$name.conf"
    sed -i "s/SVC_TARGET_PORT=.*/SVC_TARGET_PORT=\"$new_port\"/" "$SERVICES_DIR/$name.conf"

    msg "Настройки обновлены. Перезапускаю туннель..."
    if is_running "$name"; then
        do_stop_service "$name"
        sleep 1
        # Обновление htpasswd на VPS если включено
        if [ "$SVC_NDM_AUTH" = "yes" ]; then
             ssh_cmd "printf '%s\n' '$SVC_HTPASSWD' > /etc/nginx/rproxy_$name.htpasswd"
        fi
        do_start_service "$name"
    else
        msg "Сервис был остановлен. Настройки вступят в силу при следующем запуске."
    fi

    msg "Готово!"
    pause
}

# ══════════════════════════════════════════════════════════════════════
# ПОЛУЧИТЬ SSL (Certbot)
# ══════════════════════════════════════════════════════════════════════
do_ssl_interactive() {
    clear_screen
    draw_box "Получить SSL сертификат (Certbot)"
    printf "\n"

    select_service "Выпуститиь SSL для" "all" || { pause; return; }
    local name="$SELECTED_SERVICE"
    [ "$name" = "__ALL__" ] && { warn "Выберите конкретный сервис"; pause; return; }

    load_service "$name" || { pause; return; }

    if [ -z "$SVC_DOMAIN" ]; then
        err "Для получения SSL сервису должен быть назначен домен"
        pause
        return
    fi

    if [ -z "$CERTBOT_EMAIL" ]; then
        prompt "Введите Email для регистрации в Certbot: "
        CERTBOT_EMAIL="$REPLY"
        sed -i "/CERTBOT_EMAIL=/d" "$CONF_FILE"
        echo "CERTBOT_EMAIL=\"$CERTBOT_EMAIL\"" >> "$CONF_FILE"
    fi

    msg "Проверяю наличие Certbot на VPS..."
    ssh_cmd "command -v certbot >/dev/null 2>&1 || (apt-get update -qq && apt-get install -y -qq certbot python3-certbot-nginx || yum install -y certbot python3-certbot-nginx)" || {
        warn "Не удалось автоматически установить Certbot на VPS"
    }

    msg "Запрашиваю сертификат для $SVC_DOMAIN..."
    if ssh_cmd "certbot --nginx -d $SVC_DOMAIN --non-interactive --agree-tos -m $CERTBOT_EMAIL"; then
        msg "SSL сертификат успешно получен!"
        # Обновляем конфиг сервиса
        sed -i "s/SVC_SSL=.*/SVC_SSL=\"yes\"/" "$SERVICES_DIR/$name.conf"
        sed -i "s/SVC_EXT_PORT=.*/SVC_EXT_PORT=\"443\"/" "$SERVICES_DIR/$name.conf"
        
        # Проверка автопродления (добавляем таймер/крон если нет)
        ssh_cmd "systemctl is-active certbot.timer >/dev/null 2>&1 || (crontab -l 2>/dev/null | grep -q certbot || (crontab -l 2>/dev/null; echo \"0 0,12 * * * certbot renew -q\") | crontab -)"
    else
        err "Certbot не смог выпустить сертификат"
        warn "Убедитесь, что домен $SVC_DOMAIN указывает на этот VPS и порт 80 открыт"
    fi
    pause
}

# ══════════════════════════════════════════════════════════════════════
# ОБНОВЛЕНИЕ СКРИПТА
# ══════════════════════════════════════════════════════════════════════
do_self_update() {
    clear_screen
    draw_box "Обновление rProxy"
    printf "\n"
    msg "Проверяю наличие обновлений..."
    
    local url="http://5.104.75.50:3000/Petro1990/rProxy/raw/branch/main/rproxy"
    local tmp_file="/tmp/rproxy_update"
    
    if curl -sSL "$url" -o "$tmp_file"; then
        if [ -s "$tmp_file" ]; then
             mv "$tmp_file" "/opt/bin/rproxy"
             chmod +x "/opt/bin/rproxy"
             msg "Обновление успешно завершено!"
             pause
             exec /opt/bin/rproxy # Перезапуск новой версии
        else
             err "Файл обновления не пуст или не найден"
        fi
    else
        err "Не удалось загрузить обновление"
    fi
    pause
}

# ══════════════════════════════════════════════════════════════════════
# ПРОВЕРКА СОСТОЯНИЯ VPS
# ══════════════════════════════════════════════════════════════════════
do_health_check() {
    clear_screen
    draw_box "Состояние VPS и SSL"
    printf "\n"

    for f in "$VPS_DIR"/*.conf; do
        [ -f "$f" ] || continue
        local vps_id=$(basename "$f" .conf)
        load_vps "$vps_id"
        
        header "Сервер: $vps_id ($VPS_HOST)"
        
        msg "Проверяю Nginx..."
        if ssh_cmd "systemctl is-active nginx >/dev/null 2>&1"; then
            printf "  Nginx:        ${GREEN}${BOLD}Запущен${NC}\n"
        else
            printf "  Nginx:        ${RED}${BOLD}Остановлен${NC}\n"
        fi
        
        msg "Проверяю автопродление SSL (Certbot)..."
        local timer=$(ssh_cmd "systemctl is-active certbot.timer 2>/dev/null | tr -d '\r'")
        if [ "$timer" = "active" ]; then
            printf "  SSL Timer:    ${GREEN}${BOLD}Активен (Systemd)${NC}\n"
            local next_run=$(ssh_cmd "systemctl list-timers certbot.timer --no-legend 2>/dev/null | awk '{print \$1, \$2}'" | head -n 1)
            [ -n "$next_run" ] && printf "  Следующее:    $next_run\n"
        else
            if ssh_cmd "crontab -l 2>/dev/null | grep -q certbot"; then
                printf "  SSL Renewal:  ${GREEN}${BOLD}Настроено (Cron)${NC}\n"
            else
                printf "  SSL Renewal:  ${RED}${BOLD}Не обнаружено${NC}\n"
            fi
        fi
        
        msg "Действующие сертификаты:"
        local certs=$(ssh_cmd "certbot certificates 2>/dev/null | grep -E 'Expiry Date:|Domains:'")
        if [ -n "$certs" ]; then
            echo "$certs" | while read -r line; do
                printf "  %s\n" "$line"
            done
        else
            printf "  ${DIM}Сертификаты не найдены${NC}\n"
        fi
    done

    printf "\n"
    msg "Утилиты на роутере:"
    [ -x "/opt/bin/ssh" ] && printf "  SSH:          ${GREEN}Entware OK${NC}\n" || printf "  SSH:          ${RED}Missing${NC}\n"
    [ -x "/opt/bin/autossh" ] && printf "  AutoSSH:      ${GREEN}OK${NC}\n" || printf "  AutoSSH:      ${RED}Missing${NC}\n"
    
    pause
}

# ══════════════════════════════════════════════════════════════════════
# УДАЛИТЬ СЕРВИС (интерактивно)
# ══════════════════════════════════════════════════════════════════════
do_remove_interactive() {
    clear_screen
    draw_box "Удалить сервис"
    printf "\n"

    select_service "Выберите сервис для удаления" "all" || { pause; return; }
    local name="$SELECTED_SERVICE"
    [ "$name" = "__ALL__" ] && { warn "Для удаления выберите конкретный сервис"; pause; return; }

    prompt "Удалить сервис '$name'? (д/н) [н]: "
    case "${REPLY:-н}" in
        д|Д|y|Y|да|yes) ;;
        *) msg "Отменено"; pause; return ;;
    esac

    load_service "$name" || { pause; return; }
    is_running "$name" && do_stop_service "$name"

    msg "Удаляю конфиг nginx на VPS..."
    ssh_cmd "rm -f $REMOTE_NGINX_DIR/rproxy_$name.conf && nginx -t && systemctl reload nginx" >/dev/null 2>&1 || {
        warn "Не удалось удалить конфиг nginx на VPS (возможно он уже удален)"
    }

    rm -f "$SERVICES_DIR/$name.conf"
    rm -f "$(get_pid_file "$name")"

    msg "Сервис '$name' удалён"
    pause
}

# ══════════════════════════════════════════════════════════════════════
# РЕДАКТИРОВАТЬ СЕРВИС (интерактивно)
# ══════════════════════════════════════════════════════════════════════
do_edit_interactive() {
    clear_screen
    draw_box "Редактировать сервис"
    printf "\n"

    select_service "Выберите сервис для редактирования" "all" || { pause; return; }
    local name="$SELECTED_SERVICE"
    [ "$name" = "__ALL__" ] && { warn "Для редактирования выберите конкретный сервис"; pause; return; }

    load_service "$name" || { pause; return; }

    printf "\n"
    draw_separator
    printf "  ${DIM}Текущие параметры:${NC}\n"
    printf "  Цель:          ${BOLD}$SVC_TARGET_HOST:$SVC_TARGET_PORT${NC}\n"
    printf "  Туннель:       порт ${BOLD}$SVC_TUNNEL_PORT${NC}\n"
    [ -n "$SVC_DOMAIN" ] && printf "  Домен:         ${BOLD}$SVC_DOMAIN${NC}\n"
    printf "  Внешний порт:  ${BOLD}$SVC_EXT_PORT${NC}\n"
    draw_separator

    prompt "Новый адрес (IP:порт) [$SVC_TARGET_HOST:$SVC_TARGET_PORT]: "
    local new_target="${REPLY:-$SVC_TARGET_HOST:$SVC_TARGET_PORT}"

    local new_host="${new_target%:*}"
    local new_port="${new_target#*:}"
    if [ "$new_host" = "$new_port" ]; then
        new_port="$new_host"
        new_host="127.0.0.1"
    fi

    # Если ничего не изменилось
    if [ "$new_host" = "$SVC_TARGET_HOST" ] && [ "$new_port" = "$SVC_TARGET_PORT" ]; then
        msg "Параметры не изменились."
        pause
        return
    fi

    printf "\n"
    draw_separator
    printf "  ${DIM}Было:${NC}   $SVC_TARGET_HOST:$SVC_TARGET_PORT\n"
    printf "  ${GREEN}Стало:${NC}  $new_host:$new_port\n"
    draw_separator

    prompt "Применить изменения? (д/н) [д]: "
    case "${REPLY:-д}" in
        д|Д|y|Y|да|yes) ;;
        *) msg "Отменено"; pause; return ;;
    esac

    printf "\n"
    msg "Применяю изменения..."

    # Остановить туннель если запущен
    local was_running=0
    if is_running "$name"; then
        was_running=1
        do_stop_service "$name"
    fi

    # Обновить конфиг сервиса
    sed -i "s|SVC_TARGET_HOST=\".*\"|SVC_TARGET_HOST=\"$new_host\"|" "$SERVICES_DIR/$name.conf"
    sed -i "s|SVC_TARGET_PORT=\".*\"|SVC_TARGET_PORT=\"$new_port\"|" "$SERVICES_DIR/$name.conf"

    msg "Конфигурация обновлена."

    # Перезапустить туннель если он был запущен
    if [ "$was_running" -eq 1 ]; then
        msg "Перезапускаю туннель..."
        load_service "$name" || { pause; return; }
        do_start_service "$name"
    fi

    msg "Сервис '$name' успешно обновлён!"
    pause
}

# ══════════════════════════════════════════════════════════════════════
# ВЫБОР СЕРВИСА (общий)
# ══════════════════════════════════════════════════════════════════════
select_service() {
    local title="$1"
    local filter="$2"  # all, running, stopped

    local services=""
    local idx=0
    for f in "$SERVICES_DIR"/*.conf; do
        [ -f "$f" ] || continue
        . "$f"

        local running=0
        is_running "$SVC_NAME" && running=1

        # Фильтр
        case "$filter" in
            running) [ "$running" -eq 0 ] && continue ;;
            stopped) [ "$running" -eq 1 ] && continue ;;
        esac

        idx=$((idx + 1))
        local st="${RED}○${NC}"
        [ "$running" -eq 1 ] && st="${GREEN}●${NC}"
        printf "  ${BOLD}%d)${NC}  %b  %-14s  %s:%s" "$idx" "$st" "$SVC_NAME" "$SVC_TARGET_HOST" "$SVC_TARGET_PORT"
        [ -n "$SVC_DOMAIN" ] && printf "  ${DIM}(%s)${NC}" "$SVC_DOMAIN"
        printf "\n"
        [ -z "$services" ] && services="$SVC_NAME" || services="$services $SVC_NAME"
    done

    if [ "$idx" -eq 0 ]; then
        printf "  ${YELLOW}Нет подходящих сервисов.${NC}\n"
        SELECTED_SERVICE=""
        return 1
    fi

    draw_separator
    printf "  ${BOLD}903)${NC} Все сервисы\n"
    printf "  ${BOLD}0)${NC}   Назад\n"
    prompt "Выберите сервис: "

    [ "$REPLY" = "0" ] && { SELECTED_SERVICE=""; return 1; }
    if [ "$REPLY" = "903" ]; then
        SELECTED_SERVICE="__ALL__"
        return 0
    fi

    # Извлечение имени сервиса по индексу (1-based) с валидацией
    if echo "$REPLY" | grep -qE '^[0-9]+$'; then
        SELECTED_SERVICE=$(echo "$services" | cut -d' ' -f"$REPLY")
    else
        SELECTED_SERVICE=""
    fi
    [ -z "$SELECTED_SERVICE" ] && { warn "Неверный выбор"; return 1; }
    return 0
}

# ══════════════════════════════════════════════════════════════════════
# ЗАПУСК / ОСТАНОВ / ПЕРЕЗАПУСК (интерактивно)
# ══════════════════════════════════════════════════════════════════════
do_start_interactive() {
    clear_screen
    draw_box "Запустить туннель"
    printf "\n"
    select_service "Запустить" "stopped" || { pause; return; }

    if [ "$SELECTED_SERVICE" = "__ALL__" ]; then
        do_start_all
    else
        do_start_service "$SELECTED_SERVICE"
    fi
    pause
}

do_stop_interactive() {
    clear_screen
    draw_box "Остановить туннель"
    printf "\n"
    select_service "Остановить" "running" || { pause; return; }

    if [ "$SELECTED_SERVICE" = "__ALL__" ]; then
        do_stop_all
    else
        do_stop_service "$SELECTED_SERVICE"
    fi
    pause
}

do_restart_interactive() {
    clear_screen
    draw_box "Перезапустить туннель"
    printf "\n"
    select_service "Перезапустить" "all" || { pause; return; }

    if [ "$SELECTED_SERVICE" = "__ALL__" ]; then
        for f in "$SERVICES_DIR"/*.conf; do
            [ -f "$f" ] || continue
            . "$f"
            do_stop_service "$SVC_NAME"
            sleep 1
            do_start_service "$SVC_NAME"
        done
    else
        load_service "$SELECTED_SERVICE"
        do_stop_service "$SELECTED_SERVICE"
        sleep 1
        do_start_service "$SELECTED_SERVICE"
    fi
    pause
}

# ══════════════════════════════════════════════════════════════════════
# ЗАПУСК / ОСТАНОВ (одного сервиса)
# ══════════════════════════════════════════════════════════════════════
do_start_service() {
    local name="$1"
    load_service "$name" || return

    if is_running "$name"; then
        warn "Сервис '$name' уже запущен"
        return
    fi

    msg "Запускаю туннель '$name' ($SVC_TARGET_HOST:$SVC_TARGET_PORT → VPS:$SVC_TUNNEL_PORT)..."

    local ssh_exec="/opt/bin/ssh"
    [ ! -f "$ssh_exec" ] && ssh_exec="ssh"
    export AUTOSSH_PATH="$ssh_exec"

    mkdir -p "$PID_DIR"
    local pid_file
    pid_file=$(get_pid_file "$name")

    msg "Синхронизация с VPS (очистка портов)..."
    ssh_cmd "fuser -k $SVC_TUNNEL_PORT/tcp >/dev/null 2>&1 || true"

    local ssh_opts="-o StrictHostKeyChecking=no -o ServerAliveInterval=10 -o ServerAliveCountMax=3 -o ConnectTimeout=10 -o ExitOnForwardFailure=yes"
    local tunnel_args="-R 0.0.0.0:$SVC_TUNNEL_PORT:$SVC_TARGET_HOST:$SVC_TARGET_PORT"

    if [ "$VPS_AUTH" = "password" ]; then
        AUTOSSH_GATETIME=0 sshpass -p "$VPS_PASS" autossh -M 0 -f -N \
            $ssh_opts \
            -p "$VPS_PORT" \
            $tunnel_args \
            "$VPS_USER@$VPS_HOST" &
    else
        AUTOSSH_GATETIME=0 autossh -M 0 -f -N \
            $ssh_opts \
            -i "$SSH_KEY" \
            -p "$VPS_PORT" \
            $tunnel_args \
            "$VPS_USER@$VPS_HOST" &
    fi

    local bg_pid=$!
    sleep 2

    local real_pid
    real_pid=$(pgrep -f "autossh.*$SVC_TUNNEL_PORT:$SVC_TARGET_HOST:$SVC_TARGET_PORT" 2>/dev/null | head -1)
    [ -z "$real_pid" ] && real_pid=$(pgrep -f "ssh.*$SVC_TUNNEL_PORT:$SVC_TARGET_HOST:$SVC_TARGET_PORT" 2>/dev/null | head -1)
    [ -z "$real_pid" ] && real_pid=$bg_pid

    echo "$real_pid" > "$pid_file"
    msg "Туннель '$name' запущен (PID: $real_pid)"
}

do_stop_service() {
    local name="$1"
    load_service "$name" || return # Загружаем VPS для правильной очистки
    local pid_file
    pid_file=$(get_pid_file "$name")

    if [ ! -f "$pid_file" ]; then
        warn "Сервис '$name' не запущен"
        return
    fi

    local pid
    pid=$(cat "$pid_file")
    kill "$pid" 2>/dev/null
    pkill -f "ssh.*:$SVC_TUNNEL_PORT:.*$VPS_HOST" 2>/dev/null

    rm -f "$pid_file"
    msg "Туннель '$name' остановлен"
}

do_start_all() {
    for f in "$SERVICES_DIR"/*.conf; do
        [ -f "$f" ] || continue
        . "$f"
        [ "$SVC_ENABLED" = "yes" ] && do_start_service "$SVC_NAME"
    done
}

do_stop_all() {
    for f in "$SERVICES_DIR"/*.conf; do
        [ -f "$f" ] || continue
        . "$f"
        do_stop_service "$SVC_NAME"
    done
}

# ══════════════════════════════════════════════════════════════════════
# НАСТРОЙКА VPS (Менеджер профилей)
# ══════════════════════════════════════════════════════════════════════
do_setup() {
    while true; do
        clear_screen
        draw_box "Управление VPS серверами"
        printf "\n"

        local vps_list=""
        local idx=0
        for f in "$VPS_DIR"/*.conf; do
            [ -f "$f" ] || continue
            idx=$((idx + 1))
            VPS_HOST="" # Сброс перед загрузкой
            . "$f"
            local v_info="${DIM}${VPS_USER}@${VPS_HOST}:${VPS_PORT}${NC}"
            [ -z "$VPS_HOST" ] && v_info="${RED}Не настроен${NC}"
            printf "  ${BOLD}%d)${NC}  %-14s %b\n" "$idx" "$(basename "$f" .conf)" "$v_info"
            vps_list="$vps_list $(basename "$f" .conf)"
        done

        if [ "$idx" -eq 0 ]; then
            printf "  ${YELLOW}Нет настроенных VPS серверов.${NC}\n"
        fi

        draw_separator
        printf "  ${BOLD}901)${NC} ➕  Добавить новый VPS\n"
        [ "$idx" -gt 0 ] && printf "  ${BOLD}902)${NC} ❌  Удалить VPS\n"
        printf "  ${BOLD}903)${NC} 🔑  Пароль публикации по умолчанию\n"
        printf "  ${BOLD}0)${NC}   Назад\n"
        
        prompt "Выберите действие: "
        case "$REPLY" in
            0) return ;;
            901) do_add_vps ;;
            902) [ "$idx" -gt 0 ] && do_delete_vps_interactive "$vps_list" ;;
            903) do_change_auth_defaults ;;
            [1-9]*)
                local sel_vps=$(echo "$vps_list" | cut -d' ' -f"$REPLY")
                [ -n "$sel_vps" ] && do_add_vps "$sel_vps"
                ;;
        esac
    done
}

do_delete_vps_interactive() {
    local list="$1"
    prompt "Введите номер VPS для удаления: "
    local sel_vps=$(echo "$list" | cut -d' ' -f"$REPLY")
    [ -z "$sel_vps" ] && { warn "Неверный выбор"; pause; return; }

    prompt "Удалить профиль VPS '$sel_vps'? (д/н) [н]: "
    case "$REPLY" in
        д|Д|y|Y) rm -f "$VPS_DIR/$sel_vps.conf"; msg "Удалено" ;;
        *) msg "Отменено" ;;
    esac
    pause
}

do_change_auth_defaults() {
    draw_box "Настройки публикации (Basic Auth)"
    printf "\n"
    printf "  Эти данные будут предлагаться при защите новых сервисов.\n"
    
    prompt "Имя пользователя [${DEFAULT_AUTH_USER:-admin}]: "
    local user="${REPLY:-${DEFAULT_AUTH_USER:-admin}}"
    
    prompt "Пароль (оставьте пустым чтобы не менять): "
    local pass="$REPLY"
    
    if [ -z "$pass" ] && [ -n "$DEFAULT_AUTH_PASS" ]; then
        pass="$DEFAULT_AUTH_PASS"
    fi

    [ -z "$pass" ] && { warn "Пароль не может быть пустым"; pause; return; }

    sed -i "/DEFAULT_AUTH_USER=/d" "$CONF_FILE"
    sed -i "/DEFAULT_AUTH_PASS=/d" "$CONF_FILE"
    printf "DEFAULT_AUTH_USER='%s'\n" "$user" >> "$CONF_FILE"
    printf "DEFAULT_AUTH_PASS='%s'\n" "$pass" >> "$CONF_FILE"
    
    msg "Данные успешно сохранены"
    DEFAULT_AUTH_USER="$user"
    DEFAULT_AUTH_PASS="$pass"
    pause
}

do_add_vps() {
    local vps_id="${1:-}"
    local is_new=0
    [ -z "$vps_id" ] && is_new=1

    if [ "$is_new" -eq 1 ]; then
        prompt "Название сервера (например, amsterdam): "
        vps_id=$(echo "$REPLY" | tr -dc 'a-zA-Z0-9_-')
        [ -z "$vps_id" ] && { warn "Недопустимое название"; pause; return; }
    fi

    local vps_file="$VPS_DIR/$vps_id.conf"
    local v_host="" v_port="22" v_user="root" v_auth="key" v_pass=""

    if [ -f "$vps_file" ]; then
        . "$vps_file"
        v_host="$VPS_HOST"; v_port="$VPS_PORT"; v_user="$VPS_USER"; v_auth="$VPS_AUTH"; v_pass="$VPS_PASS"
    fi

    header "Настройка VPS: $vps_id"
    prompt "IP-адрес VPS [$v_host]: "
    v_host="${REPLY:-$v_host}"
    [ -z "$v_host" ] && { warn "IP обязателен"; pause; return; }

    prompt "SSH порт [$v_port]: "
    v_port="${REPLY:-$v_port}"

    prompt "SSH пользователь [$v_user]: "
    v_user="${REPLY:-$v_user}"

    printf "\n  Метод авторизации:\n"
    printf "  ${BOLD}1)${NC} SSH-ключ (рекомендуется)\n"
    printf "  ${BOLD}2)${NC} Пароль\n"
    prompt "Выберите [${v_auth/key/1}${v_auth/password/2}]: "
    local choice="${REPLY:-${v_auth/key/1}${v_auth/password/2}}"

    if [ "$choice" = "2" ]; then
        v_auth="password"
        prompt "SSH пароль: "
        v_pass="$REPLY"
    else
        v_auth="key"
        # Проверка ключа (код ниже требует наличия SSH_KEY)
        ensure_ssh_key
    fi

    msg "Проверяю подключение к $v_host..."
    local ssh_exec="/opt/bin/ssh"
    [ ! -f "$ssh_exec" ] && ssh_exec="ssh"

    if [ "$v_auth" = "password" ]; then
        if ! command -v sshpass >/dev/null 2>&1; then
            err "Утилита sshpass не найдена."
            pause; return
        fi
        if ! sshpass -p "$v_pass" $ssh_exec -o StrictHostKeyChecking=no -o ConnectTimeout=10 -p "$v_port" "$v_user@$v_host" "echo ok" >/dev/null 2>&1; then
            err "Ошибка подключения."
            pause; return
        fi
    else
        # Копирование ключа
        local copy_id="/opt/bin/ssh-copy-id"
        [ ! -f "$copy_id" ] && copy_id="ssh-copy-id"
        printf "  Введите пароль VPS для копирования ключа:\n"
        $copy_id -i "$SSH_KEY.pub" -p "$v_port" "$v_user@$v_host" 2>/dev/null || {
            cat "$SSH_KEY.pub" | $ssh_exec -o StrictHostKeyChecking=no -p "$v_port" "$v_user@$v_host" \
                "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 700 ~/.ssh && chmod 600 ~/.ssh/authorized_keys"
        }
        if ! $ssh_exec -o StrictHostKeyChecking=no -o ConnectTimeout=10 -i "$SSH_KEY" -p "$v_port" "$v_user@$v_host" "echo ok" >/dev/null 2>&1; then
            err "Ошибка подключения по ключу."
            pause; return
        fi
    fi

    # Сохраняем
    cat > "$vps_file" <<EOF
VPS_HOST="$v_host"
VPS_PORT="$v_port"
VPS_USER="$v_user"
VPS_AUTH="$v_auth"
VPS_PASS="$v_pass"
EOF
    chmod 600 "$vps_file"
    msg "Профиль '$vps_id' сохранён."

    # Настройка Nginx на новом VPS
    msg "Настройка окружения на VPS..."
    VPS_HOST="$v_host" VPS_PORT="$v_port" VPS_USER="$v_user" VPS_AUTH="$v_auth" VPS_PASS="$v_pass"
    ssh_cmd "
        if ! command -v nginx >/dev/null 2>&1; then
            apt-get update -qq && apt-get install -y -qq nginx || yum install -y nginx
        fi
        mkdir -p /etc/nginx/sites-enabled
        grep -q 'sites-enabled' /etc/nginx/nginx.conf || sed -i '/http {/a\    include /etc/nginx/sites-enabled/*.conf;' /etc/nginx/nginx.conf
        command -v certbot >/dev/null 2>&1 || (apt-get update -qq && apt-get install -y -qq certbot python3-certbot-nginx || yum install -y certbot python3-certbot-nginx)
        systemctl enable nginx && systemctl start nginx
        
        # Оптимизация SSH на стороне сервера для туннелей
        grep -q 'ClientAliveInterval' /etc/ssh/sshd_config || echo 'ClientAliveInterval 30' >> /etc/ssh/sshd_config
        grep -q 'ClientAliveCountMax' /etc/ssh/sshd_config || echo 'ClientAliveCountMax 2' >> /etc/ssh/sshd_config
        systemctl restart ssh
    "
    pause
}

ensure_ssh_key() {
    if [ ! -f "$SSH_KEY" ]; then
        msg "Генерирую SSH-ключ (ed25519)..."
        local keygen="/opt/bin/ssh-keygen"
        [ ! -f "$keygen" ] && keygen="ssh-keygen"
        $keygen -t ed25519 -f "$SSH_KEY" -N "" -q
        [ ! -f "$SSH_KEY.pub" ] && $keygen -y -f "$SSH_KEY" > "$SSH_KEY.pub"
        chmod 600 "$SSH_KEY"
    fi
}

# ══════════════════════════════════════════════════════════════════════
# ТОЧКА ВХОДА
# ══════════════════════════════════════════════════════════════════════
migrate_config 2>/dev/null
ensure_local_deps

# Поддержка прямых команд для init-скрипта и автоматизации
case "${1:-}" in
    start)      check_conf && do_start_all ;;
    stop)       check_conf && do_stop_all ;;
    restart)    check_conf && do_stop_all; sleep 1; do_start_all ;;
    status)
        check_conf || { err "VPS не настроен"; exit 1; }
        for f in "$SERVICES_DIR"/*.conf; do
            [ -f "$f" ] || continue
            local name=$(basename "$f" .conf)
            (
                load_service "$name" >/dev/null 2>&1
                local state="остановлен"
                is_running "$SVC_NAME" && state="работает"
                local info="$SVC_NAME  $SVC_TARGET_HOST:$SVC_TARGET_PORT → VPS($CUR_VPS_ID):$SVC_TUNNEL_PORT"
                [ -n "$SVC_DOMAIN" ] && info="$info ($SVC_DOMAIN)"
                echo "$info [$state]"
            )
        done
        ;;
    -v|--version) echo "rProxy v$VERSION" ;;
    "")         main_menu ;;
    *)          main_menu ;;
esac
