#!/bin/sh # rProxy — Менеджер обратного прокси для роутеров Keenetic # Публикация локальных сервисов через SSH-туннели + nginx на VPS # http://5.104.75.50:3000/Petro1990/rProxy VERSION="1.0.0" CONF_DIR="/opt/etc/rproxy" CONF_FILE="$CONF_DIR/rproxy.conf" SERVICES_DIR="$CONF_DIR/services" PID_DIR="/opt/var/run/rproxy" SSH_KEY="$CONF_DIR/id_ed25519" REMOTE_NGINX_DIR="/etc/nginx/sites-enabled" BASE_TUNNEL_PORT=10000 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" printf "${CYAN}╔══════════════════════════════════════════════╗${NC}\n" printf "${CYAN}║${NC} ${BOLD}%-44s${NC}${CYAN}║${NC}\n" "$title" printf "${CYAN}╚══════════════════════════════════════════════╝${NC}\n" } 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 return 1 fi . "$CONF_FILE" return 0 } load_service() { local svc_file="$SERVICES_DIR/$1.conf" if [ ! -f "$svc_file" ]; then err "Сервис '$1' не найден" return 1 fi . "$svc_file" } 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 "TUNNEL_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 # Подсчёт сервисов и статусов local total=0 online=0 for f in "$SERVICES_DIR"/*.conf; do [ -f "$f" ] || continue total=$((total + 1)) . "$f" is_running "$SVC_NAME" && online=$((online + 1)) done printf " ${DIM}VPS:${NC} ${BOLD}$VPS_HOST${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" draw_separator printf " ${BOLD}4)${NC} ▶️ Запустить туннель\n" printf " ${BOLD}5)${NC} ⏹️ Остановить туннель\n" printf " ${CYAN}${BOLD}6)${NC} 🔄 Перезапустить туннель\n" draw_separator printf " ${BOLD}7)${NC} 🔒 Получить/Обновить SSL (Certbot)\n" printf " ${BOLD}8)${NC} ⚙️ Настройки VPS\n" printf " ${BOLD}9)${NC} 🚀 Обновить rProxy\n" printf " ${BOLD}0)${NC} 🚪 Выход\n" prompt "Выберите действие: " case "$REPLY" in 1) show_status ;; 2) do_add_interactive ;; 3) do_remove_interactive ;; 4) do_start_interactive ;; 5) do_stop_interactive ;; 6) do_restart_interactive ;; 7) do_ssl_interactive ;; 8) do_setup ;; 9) do_self_update ;; 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)) . "$f" 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 } # ══════════════════════════════════════════════════════════════════════ # ДОБАВИТЬ СЕРВИС (интерактивно) # ══════════════════════════════════════════════════════════════════════ 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 "Адрес локального сервиса (например, 192.168.1.100:8080): " local target="$REPLY" [ -z "$target" ] && { warn "Адрес не может быть пустым"; pause; return; } printf "\n Привязать к домену?\n" printf " ${BOLD}1)${NC} Да — указать доменное имя (порт 80)\n" printf " ${BOLD}2)${NC} Нет — указать внешний порт\n" prompt "Выберите [2]: " local mode="${REPLY:-2}" local domain="" local ext_port="" local use_ssl="no" if [ "$mode" = "1" ]; then prompt "Доменное имя (например, mysite.example.com): " domain="$REPLY" [ -z "$domain" ] && { warn "Домен не указан"; pause; return; } ext_port=80 printf "\n Включить SSL (HTTPS) через Certbot? (д/н) [н]: " read -r use_ssl_ans case "$use_ssl_ans" in д|Д|y|Y|да|yes) use_ssl="yes" ext_port=443 if [ -z "$CERTBOT_EMAIL" ]; then prompt "Введите Email для регистрации сертификатов: " CERTBOT_EMAIL="$REPLY" # Сохраняем email в конфиг sed -i "/CERTBOT_EMAIL=/d" "$CONF_FILE" echo "CERTBOT_EMAIL=\"$CERTBOT_EMAIL\"" >> "$CONF_FILE" fi ;; *) use_ssl="no" ;; esac else local suggested suggested=$(next_free_port) prompt "Внешний порт [$suggested]: " ext_port="${REPLY:-$suggested}" use_ssl="no" fi # Разбор адреса local target_host="${target%:*}" local target_port="${target#*:}" if [ "$target_host" = "$target_port" ]; then target_port="$target_host" target_host="127.0.0.1" fi local tunnel_port tunnel_port=$(next_free_port) printf "\n" draw_separator printf " ${BOLD}Сервис:${NC} $name\n" printf " ${BOLD}Цель:${NC} $target_host:$target_port\n" printf " ${BOLD}Туннель:${NC} порт $tunnel_port\n" [ -n "$domain" ] && printf " ${BOLD}Домен:${NC} $domain\n" printf " ${BOLD}Внешний порт:${NC} $ext_port\n" [ "$use_ssl" = "yes" ] && printf " ${BOLD}SSL:${NC} Включен (Certbot)\n" draw_separator prompt "Всё верно? Добавить сервис? (д/н) [д]: " local confirm="${REPLY:-д}" case "$confirm" in д|Д|y|Y|да|yes) ;; *) msg "Отменено"; pause; return ;; esac printf "\n" msg "Добавляю сервис '$name'..." # Генерация конфига nginx local nginx_conf="" if [ -n "$domain" ]; then # Для домена всегда сначала создаем конфиг на 80 порт # Если нужен SSL, Certbot сам его проапгрейдит nginx_conf="# rProxy: $name server { listen 80; server_name $domain; location / { 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 \$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_connect_timeout 60s; proxy_send_timeout 60s; proxy_read_timeout 60s; } } " else nginx_conf="# rProxy: $name server { listen $ext_port; location / { 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 \$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_connect_timeout 60s; proxy_send_timeout 60s; proxy_read_timeout 60s; } } " fi # Деплой nginx конфига на VPS msg "Загружаю конфигурацию nginx..." local tmp_file="/tmp/rproxy_nginx_$name.conf" printf '%s' "$nginx_conf" > "$tmp_file" scp_cmd "$tmp_file" "$VPS_USER@$VPS_HOST:$REMOTE_NGINX_DIR/rproxy_$name.conf" || { err "Не удалось загрузить конфиг nginx на VPS" rm -f "$tmp_file" pause return } rm -f "$tmp_file" # Проверка и перезагрузка nginx msg "Проверяю конфигурацию nginx на VPS..." local nginx_test nginx_test=$(ssh_cmd "nginx -t 2>&1") if [ $? -ne 0 ]; then err "Ошибка в конфигурации nginx на VPS:" printf " ${DIM}%s${NC}\n" "$nginx_test" ssh_cmd "rm -f $REMOTE_NGINX_DIR/rproxy_$name.conf" pause return fi ssh_cmd "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 не смог получить сертификат. Проверьте DNS и доступность порта 80." use_ssl="no" ext_port=80 } fi # Сохранение конфига сервиса cat > "$SERVICES_DIR/$name.conf" <> "$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" 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 } # ══════════════════════════════════════════════════════════════════════ # УДАЛИТЬ СЕРВИС (интерактивно) # ══════════════════════════════════════════════════════════════════════ do_remove_interactive() { clear_screen draw_box "Удалить сервис" printf "\n" local services="" local idx=0 for f in "$SERVICES_DIR"/*.conf; do [ -f "$f" ] || continue idx=$((idx + 1)) . "$f" local st="○" is_running "$SVC_NAME" && st="●" printf " ${BOLD}%d)${NC} %s %s (%s:%s)\n" "$idx" "$st" "$SVC_NAME" "$SVC_TARGET_HOST" "$SVC_TARGET_PORT" services="$services $SVC_NAME" done if [ "$idx" -eq 0 ]; then printf " ${YELLOW}Нет сервисов для удаления.${NC}\n" pause return fi printf " ${BOLD}0)${NC} Назад\n" prompt "Выберите сервис для удаления: " [ "$REPLY" = "0" ] && return [ -z "$REPLY" ] && return local sel_name sel_name=$(echo "$services" | tr ' ' '\n' | sed -n "${REPLY}p") [ -z "$sel_name" ] && { warn "Неверный выбор"; pause; return; } prompt "Удалить сервис '$sel_name'? (д/н) [н]: " local confirm="${REPLY:-н}" case "$confirm" in д|Д|y|Y|да|yes) ;; *) msg "Отменено"; pause; return ;; esac load_service "$sel_name" || { pause; return; } is_running "$sel_name" && do_stop_service "$sel_name" msg "Удаляю конфиг nginx на VPS..." ssh_cmd "rm -f $REMOTE_NGINX_DIR/rproxy_$sel_name.conf && nginx -t && systemctl reload nginx" || { warn "Не удалось удалить конфиг nginx на VPS" } rm -f "$SERVICES_DIR/$sel_name.conf" rm -f "$(get_pid_file "$sel_name")" msg "Сервис '$sel_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" services="$services $SVC_NAME" done if [ "$idx" -eq 0 ]; then printf " ${YELLOW}Нет подходящих сервисов.${NC}\n" SELECTED_SERVICE="" return 1 fi printf " ${BOLD}a)${NC} Все сервисы\n" printf " ${BOLD}0)${NC} Назад\n" prompt "Выберите сервис: " [ "$REPLY" = "0" ] && { SELECTED_SERVICE=""; return 1; } if [ "$REPLY" = "a" ] || [ "$REPLY" = "A" ] || [ "$REPLY" = "а" ] || [ "$REPLY" = "А" ]; then SELECTED_SERVICE="__ALL__" return 0 fi SELECTED_SERVICE=$(echo "$services" | tr ' ' '\n' | sed -n "${REPLY}p") [ -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") local ssh_opts="-o StrictHostKeyChecking=no -o ServerAliveInterval=30 -o ServerAliveCountMax=3 -o ExitOnForwardFailure=yes" if [ "$VPS_AUTH" = "password" ]; then AUTOSSH_GATETIME=0 sshpass -p "$VPS_PASS" autossh -M 0 -f -N \ $ssh_opts \ -p "$VPS_PORT" \ -R "0.0.0.0:$SVC_TUNNEL_PORT:$SVC_TARGET_HOST:$SVC_TARGET_PORT" \ "$VPS_USER@$VPS_HOST" & else AUTOSSH_GATETIME=0 autossh -M 0 -f -N \ $ssh_opts \ -i "$SSH_KEY" \ -p "$VPS_PORT" \ -R "0.0.0.0:$SVC_TUNNEL_PORT:$SVC_TARGET_HOST:$SVC_TARGET_PORT" \ "$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" 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.*:$name.*$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() { clear_screen draw_box "Настройка подключения к VPS" printf "\n" # Показать текущие настройки, если есть if check_conf; then printf " ${DIM}Текущие настройки:${NC}\n" printf " Хост: ${BOLD}$VPS_HOST${NC} Порт: ${BOLD}$VPS_PORT${NC} Пользователь: ${BOLD}$VPS_USER${NC} Авторизация: ${BOLD}$VPS_AUTH${NC}\n" draw_separator printf "\n" fi prompt "IP-адрес VPS: " local vps_host="$REPLY" [ -z "$vps_host" ] && { warn "IP-адрес обязателен"; pause; return; } prompt "SSH порт [22]: " local vps_port="${REPLY:-22}" prompt "SSH пользователь [root]: " local vps_user="${REPLY:-root}" local ssh_exec="/opt/bin/ssh" [ ! -f "$ssh_exec" ] && ssh_exec="ssh" printf "\n Метод авторизации:\n" printf " ${BOLD}1)${NC} SSH-ключ (рекомендуется)\n" printf " ${BOLD}2)${NC} Пароль\n" prompt "Выберите [1]: " local auth_choice="${REPLY:-1}" local vps_auth="key" local vps_pass="" if [ "$auth_choice" = "2" ]; then if command -v sshpass >/dev/null 2>&1; then vps_auth="password" printf " SSH пароль: " stty -echo 2>/dev/null read -r vps_pass stty echo 2>/dev/null printf "\n" else printf "\n ${YELLOW}⚠ Утилита sshpass не найдена.${NC}\n" printf " Для фоновой работы сервисов (туннелей) нужен либо пакет sshpass,\n" printf " либо использование SSH-ключа (рекомендуется).\n\n" printf " ${CYAN}Решение (Обход):${NC}\n" printf " Используйте вариант ${BOLD}1 (SSH-ключ)${NC}. Скрипт сам создаст ключ,\n" printf " попросит ваш пароль один раз, чтобы скопировать его на VPS,\n" printf " и дальше всё будет работать автоматически и БЕЗОПАСНО.\n" pause return fi else if [ ! -f "$SSH_KEY" ]; then msg "Генерирую SSH-ключ (ed25519)..." mkdir -p "$CONF_DIR" # Удаляем старые RSA ключи, если они есть (избегаем путаницы) rm -f "$CONF_DIR/id_rsa" "$CONF_DIR/id_rsa.pub" local keygen_cmd="/opt/bin/ssh-keygen" [ ! -f "$keygen_cmd" ] && keygen_cmd="ssh-keygen" # Создаем современный и компактный ed25519 ключ # Он лучше всего совместим между OpenSSH и Dropbear if ! $keygen_cmd -t ed25519 -f "$SSH_KEY" -N "" -q 2>/dev/null; then # Танцы с бубном для старых версий $keygen_cmd -t ed25519 -f "$SSH_KEY" -N "" fi # Извлекаем публичный ключ, если он не создался if [ ! -f "$SSH_KEY.pub" ] && [ -f "$SSH_KEY" ]; then $keygen_cmd -y -f "$SSH_KEY" 2>/dev/null | grep "^ssh-ed25519" > "$SSH_KEY.pub" fi if [ -f "$SSH_KEY" ]; then chmod 600 "$SSH_KEY" msg "Ключ создан: $SSH_KEY" else err "Не удалось создать SSH-ключ" pause return fi fi fi # Проверка подключения printf "\n" msg "Проверяю подключение к $vps_host..." if [ "$vps_auth" = "password" ]; then if ! sshpass -p "$vps_pass" $ssh_exec -o StrictHostKeyChecking=no -o ConnectTimeout=10 \ -p "$vps_port" "$vps_user@$vps_host" "echo ok" >/dev/null 2>&1; then err "Не удалось подключиться к VPS. Проверьте параметры." pause return fi else msg "Копирую SSH-ключ на VPS..." if [ ! -f "$SSH_KEY.pub" ]; then err "Публичный ключ не найден: $SSH_KEY.pub" pause return fi local copy_id_exec="/opt/bin/ssh-copy-id" [ ! -f "$copy_id_exec" ] && copy_id_exec="ssh-copy-id" printf " Введите пароль VPS для копирования ключа:\n" $copy_id_exec -i "$SSH_KEY.pub" -p "$vps_port" "$vps_user@$vps_host" 2>/dev/null || { cat "$SSH_KEY.pub" | $ssh_exec -o StrictHostKeyChecking=no -p "$vps_port" "$vps_user@$vps_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 "$vps_port" "$vps_user@$vps_host" "echo ok" >/dev/null 2>&1; then err "Не удалось подключиться к VPS по ключу." pause return fi fi msg "Подключение успешно!" # Настройка nginx на VPS msg "Настраиваю nginx на VPS..." local ssh_setup_cmd if [ "$vps_auth" = "password" ]; then ssh_setup_cmd="sshpass -p \"$vps_pass\" ssh -o StrictHostKeyChecking=no -p $vps_port $vps_user@$vps_host" else ssh_setup_cmd="ssh -o StrictHostKeyChecking=no -i $SSH_KEY -p $vps_port $vps_user@$vps_host" fi eval $ssh_setup_cmd "' if ! command -v nginx >/dev/null 2>&1; then echo \"Устанавливаю nginx...\" apt-get update -qq && apt-get install -y -qq nginx >/dev/null 2>&1 || \ yum install -y nginx >/dev/null 2>&1 || { echo \"FAIL: не удалось установить nginx\" exit 1 } fi mkdir -p /etc/nginx/sites-enabled if ! grep -q \"include.*sites-enabled\" /etc/nginx/nginx.conf 2>/dev/null; then sed -i \"/http {/a\\\\ include /etc/nginx/sites-enabled/*.conf;\" /etc/nginx/nginx.conf 2>/dev/null fi if ! command -v certbot >/dev/null 2>&1; then echo \"Устанавливаю Certbot...\" apt-get update -qq && apt-get install -y -qq certbot python3-certbot-nginx >/dev/null 2>&1 || \ yum install -y certbot python3-certbot-nginx >/dev/null 2>&1 fi systemctl enable nginx 2>/dev/null systemctl start nginx 2>/dev/null echo OK '" || { warn "Не удалось автоматически настроить nginx. Убедитесь, что nginx установлен на VPS." } # Сохранение конфига mkdir -p "$CONF_DIR" "$SERVICES_DIR" "$PID_DIR" cat > "$CONF_FILE" </dev/null # Поддержка прямых команд для 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 . "$f" local state="остановлен" is_running "$SVC_NAME" && state="работает" local info="$SVC_NAME $SVC_TARGET_HOST:$SVC_TARGET_PORT → :$SVC_EXT_PORT" [ -n "$SVC_DOMAIN" ] && info="$info ($SVC_DOMAIN)" echo "$info [$state]" done ;; -v|--version) echo "rProxy v$VERSION" ;; "") main_menu ;; *) main_menu ;; esac