From 02a0536b115566953e8245ee796ae276f50cb657 Mon Sep 17 00:00:00 2001 From: Petro1990 Date: Fri, 13 Mar 2026 14:27:35 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=B8=D0=BD=D1=82=D0=B5=D1=80=D0=B0?= =?UTF-8?q?=D0=BA=D1=82=D0=B8=D0=B2=D0=BD=D0=BE=D0=B5=20=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D1=8E=20=E2=80=94=20=D0=BF=D1=80=D0=BE=D1=81=D1=82=D0=BE=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=BF=D1=83=D1=81=D1=82=D0=B8=20rproxy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 117 +++---- rproxy | 922 +++++++++++++++++++++++++++++++----------------------- 2 files changed, 578 insertions(+), 461 deletions(-) diff --git a/README.md b/README.md index fdf3ffa..df8f2b5 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ **Менеджер обратного прокси для роутеров Keenetic** -Публикация локальных сервисов в интернет через VPS с nginx в качестве reverse proxy. +Публикация локальных сервисов в интернет через VPS с nginx в качестве обратного прокси. ``` [LAN сервис] ← [Keenetic] ══SSH туннель══▶ [VPS:nginx] → Интернет @@ -18,64 +18,58 @@ curl -sSL http://5.104.75.50:3000/Petro1990/rProxy/raw/branch/main/install.sh | sh ``` -### 2. Настройка VPS +### 2. Запуск ```bash -rproxy setup +rproxy ``` -Скрипт запросит: -- **IP-адрес VPS** — адрес вашего сервера -- **SSH порт** — порт SSH (по умолчанию 22) -- **SSH пользователь** — логин (по умолчанию root) -- **Метод авторизации** — SSH-ключ или пароль +Откроется интерактивное меню: -При выборе SSH-ключа скрипт автоматически сгенерирует ключ и скопирует его на VPS. Также будет автоматически установлен и настроен nginx. +``` +╔══════════════════════════════════════════════╗ +║ rProxy v1.0.0 ║ +╚══════════════════════════════════════════════╝ -### 3. Публикация сервиса + VPS: 5.104.75.50 Сервисов: 2 Онлайн: 1 +────────────────────────────────────────────────── -**С доменом** (порт 80): -```bash -rproxy add nas 192.168.1.100:5000 --domain nas.example.com + 1) 📋 Список сервисов и статус + 2) ➕ Добавить сервис + 3) ❌ Удалить сервис +────────────────────────────────────────────────── + 4) ▶️ Запустить туннель + 5) ⏹️ Остановить туннель + 6) 🔄 Перезапустить туннель +────────────────────────────────────────────────── + 7) ⚙️ Настройки VPS + 0) 🚪 Выход + +▸ Выберите действие: ``` -**Без домена** (произвольный порт): -```bash -rproxy add camera 192.168.1.50:8080 --port 9090 -``` +### 3. Первый запуск -**С автоподбором порта:** -```bash -rproxy add homeassistant 192.168.1.10:8123 -``` +При первом запуске скрипт предложит настроить подключение к VPS: +- **IP-адрес VPS** +- **SSH порт** (по умолчанию 22) +- **Пользователь** (по умолчанию root) +- **Авторизация** — SSH-ключ или пароль -## 📋 Команды +### 4. Добавление сервиса -| Команда | Описание | -|---------|----------| -| `rproxy setup` | Настроить подключение к VPS | -| `rproxy add <имя> <хост:порт> [опции]` | Добавить сервис | -| `rproxy remove <имя>` | Удалить сервис | -| `rproxy start [имя]` | Запустить туннель(и) | -| `rproxy stop [имя]` | Остановить туннель(и) | -| `rproxy restart [имя]` | Перезапустить туннель(и) | -| `rproxy status` | Статус всех сервисов | -| `rproxy list` | Краткий список | -| `rproxy enable <имя>` | Включить автозапуск | -| `rproxy disable <имя>` | Выключить автозапуск | -| `rproxy logs <имя>` | Логи nginx на VPS | +Выберите пункт **2** в меню. Скрипт пошагово спросит: +- Название сервиса +- Адрес локального сервиса (IP:порт) +- Привязка к домену или внешнему порту -### Опции для `add` - -| Опция | Описание | -|-------|----------| -| `--domain`, `-d` | Привязать к домену (nginx слушает порт 80) | -| `--port`, `-p` | Указать внешний порт (без домена) | +**Если указан домен** — nginx будет слушать порт 80 с этим доменом. +**Если без домена** — nginx будет слушать указанный внешний порт. ## 🏗 Архитектура ### На роутере (Keenetic + Entware) -- **autossh** — устанавливает устойчивые reverse SSH-туннели до VPS +- **autossh** — устанавливает устойчивые обратные SSH-туннели до VPS - Каждый сервис = отдельный туннель - Конфигурация: `/opt/etc/rproxy/` - PID-файлы: `/opt/var/run/rproxy/` @@ -84,62 +78,49 @@ rproxy add homeassistant 192.168.1.10:8123 - **nginx** — принимает входящие запросы и проксирует в SSH-туннели - Конфиги сервисов: `/etc/nginx/sites-enabled/rproxy_*.conf` -### Файлы конфигурации +### Файлы ``` /opt/etc/rproxy/ ├── rproxy.conf # Параметры подключения к VPS +├── id_rsa # SSH-ключ (если выбран) └── services/ - ├── nas.conf # Конфиг сервиса "nas" - ├── camera.conf # Конфиг сервиса "camera" + ├── nas.conf # Конфиг сервиса + ├── camera.conf └── ... ``` ## 🔒 Безопасность -- SSH-ключ хранится в `/opt/etc/rproxy/id_rsa` (права 600) -- Пароль VPS хранится в `/opt/etc/rproxy/rproxy.conf` (права 600) +- SSH-ключ и конфиг хранятся с правами 600 - Все соединения зашифрованы через SSH -> **Рекомендация:** используйте авторизацию по SSH-ключу для лучшей безопасности. +> **Рекомендация:** используйте авторизацию по SSH-ключу. ## 🔧 Решение проблем ### Туннель не подключается ```bash -# Проверьте статус -rproxy status - -# Перезапустите туннель -rproxy restart <имя> - -# Проверьте SSH-подключение вручную -ssh -i /opt/etc/rproxy/id_rsa -p 22 root@ +rproxy # → пункт 1 — проверить статус +rproxy # → пункт 6 — перезапустить ``` ### Сервис не доступен по домену - Убедитесь, что DNS-запись домена указывает на IP VPS -- Проверьте логи: `rproxy logs <имя>` - Проверьте nginx на VPS: `ssh root@VPS 'nginx -t'` -### Автозапуск не работает -```bash -# Проверьте init-скрипт -ls -la /opt/etc/init.d/S98rproxy - -# Проверьте, что сервис enabled -rproxy list -``` +### Автозапуск +Туннели автоматически запускаются при старте роутера через init-скрипт `S98rproxy`. ## 📦 Требования ### Роутер -- Keenetic с установленным [Entware](https://help.keenetic.com/hc/ru/articles/360021214160) -- Пакеты: `openssh-client`, `autossh`, `curl`, `sshpass` +- Keenetic с [Entware](https://help.keenetic.com/hc/ru/articles/360021214160) +- Пакеты: `openssh-client`, `autossh`, `curl`, `sshpass` (ставятся автоматически) ### VPS - Linux (Debian/Ubuntu/CentOS) -- nginx (устанавливается автоматически при `setup`) +- nginx (устанавливается автоматически) - SSH-доступ ## 📝 Лицензия diff --git a/rproxy b/rproxy index 716aa64..757348e 100644 --- a/rproxy +++ b/rproxy @@ -10,15 +10,15 @@ SERVICES_DIR="$CONF_DIR/services" PID_DIR="/opt/var/run/rproxy" SSH_KEY="$CONF_DIR/id_rsa" REMOTE_NGINX_DIR="/etc/nginx/sites-enabled" -REMOTE_NGINX_AVAILABLE="/etc/nginx/sites-available" BASE_TUNNEL_PORT=10000 -# ─── Colors ─────────────────────────────────────────────────────────── +# ─── Цвета ─────────────────────────────────────────────────────────── 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" "$*"; } @@ -26,20 +26,43 @@ warn() { printf "${YELLOW}⚠${NC} %s\n" "$*"; } err() { printf "${RED}✖${NC} %s\n" "$*" >&2; } header() { printf "\n${CYAN}${BOLD}%s${NC}\n" "$*"; } -# ─── Helpers ────────────────────────────────────────────────────────── +# ─── Утилиты ───────────────────────────────────────────────────────── +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 - err "Конфигурация не найдена. Запустите: rproxy setup" - exit 1 + return 1 fi . "$CONF_FILE" + return 0 } load_service() { local svc_file="$SERVICES_DIR/$1.conf" if [ ! -f "$svc_file" ]; then err "Сервис '$1' не найден" - exit 1 + return 1 fi . "$svc_file" } @@ -73,9 +96,7 @@ next_free_port() { done } -get_pid_file() { - echo "$PID_DIR/$1.pid" -} +get_pid_file() { echo "$PID_DIR/$1.pid"; } is_running() { local pf @@ -83,194 +104,196 @@ is_running() { [ -f "$pf" ] && kill -0 "$(cat "$pf")" 2>/dev/null } -# ─── SETUP ──────────────────────────────────────────────────────────── -do_setup() { - header "rProxy — Настройка подключения к VPS" - - printf "IP-адрес VPS: " - read -r vps_host - printf "SSH порт [22]: " - read -r vps_port - vps_port=${vps_port:-22} - printf "SSH пользователь [root]: " - read -r vps_user - vps_user=${vps_user:-root} - - printf "\nМетод авторизации:\n" - printf " 1) SSH-ключ (рекомендуется)\n" - printf " 2) Пароль\n" - printf "Выберите [1]: " - read -r auth_choice - auth_choice=${auth_choice:-1} - - local vps_auth="key" - local vps_pass="" - - if [ "$auth_choice" = "2" ]; then - vps_auth="password" - printf "SSH пароль: " - stty -echo 2>/dev/null - read -r vps_pass - stty echo 2>/dev/null - printf "\n" - - # Check sshpass - if ! command -v sshpass >/dev/null 2>&1; then - msg "Устанавливаю sshpass..." - opkg install sshpass 2>/dev/null || { - err "Не удалось установить sshpass. Установите вручную: opkg install sshpass" - exit 1 - } - fi - else - # Generate SSH key if not exists - if [ ! -f "$SSH_KEY" ]; then - msg "Генерирую SSH-ключ..." - mkdir -p "$CONF_DIR" - ssh-keygen -t rsa -b 4096 -f "$SSH_KEY" -N "" -q - msg "Ключ создан: $SSH_KEY" - fi - fi - - # Test connection - msg "Проверяю подключение к $vps_host..." - if [ "$vps_auth" = "password" ]; then - if ! sshpass -p "$vps_pass" ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 \ - -p "$vps_port" "$vps_user@$vps_host" "echo ok" >/dev/null 2>&1; then - err "Не удалось подключиться к VPS. Проверьте параметры." - exit 1 - fi - else - # Copy key to VPS - msg "Копирую SSH-ключ на VPS..." - printf "Введите пароль VPS для копирования ключа:\n" - ssh-copy-id -i "$SSH_KEY.pub" -p "$vps_port" "$vps_user@$vps_host" 2>/dev/null || { - # Fallback: manual copy - cat "$SSH_KEY.pub" | ssh -p "$vps_port" "$vps_user@$vps_host" \ - "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys" - } - - if ! ssh -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 по ключу." - exit 1 - fi - fi - msg "Подключение успешно!" - - # Setup nginx on VPS - msg "Настраиваю nginx на VPS..." - ssh_cmd_raw() { - if [ "$vps_auth" = "password" ]; then - sshpass -p "$vps_pass" ssh -o StrictHostKeyChecking=no -p "$vps_port" "$vps_user@$vps_host" "$@" - else - ssh -o StrictHostKeyChecking=no -i "$SSH_KEY" -p "$vps_port" "$vps_user@$vps_host" "$@" - fi - } - - ssh_cmd_raw " - 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 $REMOTE_NGINX_DIR - mkdir -p /etc/nginx/rproxy - # Ensure sites-enabled is included - 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 - # Enable and start nginx - systemctl enable nginx 2>/dev/null - systemctl start nginx 2>/dev/null - echo 'OK' - " || { - warn "Не удалось автоматически настроить nginx. Убедитесь, что nginx установлен на VPS." - } - - # Save config - mkdir -p "$CONF_DIR" "$SERVICES_DIR" "$PID_DIR" - cat > "$CONF_FILE" < <хост:порт>" +count_services() { + local count=0 + for f in "$SERVICES_DIR"/*.conf 2>/dev/null; do + [ -f "$f" ] && count=$((count + 1)) + done + echo $count } -# ─── ADD ────────────────────────────────────────────────────────────── -do_add() { - local name="$1" - local target="$2" - shift 2 +# ══════════════════════════════════════════════════════════════════════ +# ГЛАВНОЕ МЕНЮ +# ══════════════════════════════════════════════════════════════════════ +main_menu() { + while true; do + clear_screen + draw_box "rProxy v$VERSION" + printf "\n" - if [ -z "$name" ] || [ -z "$target" ]; then - err "Использование: rproxy add <имя> <хост:порт> [--domain example.com] [--port 8080]" - exit 1 + 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 2>/dev/null; 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 " ${BOLD}6)${NC} 🔄 Перезапустить туннель\n" + draw_separator + printf " ${BOLD}7)${NC} ⚙️ Настройки VPS\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_setup ;; + 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 2>/dev/null; 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 - check_conf + printf "\n" + pause +} + +# ══════════════════════════════════════════════════════════════════════ +# ДОБАВИТЬ СЕРВИС (интерактивно) +# ══════════════════════════════════════════════════════════════════════ +do_add_interactive() { + clear_screen + draw_box "Добавить новый сервис" + printf "\n" + + prompt "Название сервиса (латиницей, без пробелов): " + local name="$REPLY" + [ -z "$name" ] && { warn "Название не может быть пустым"; pause; return; } if [ -f "$SERVICES_DIR/$name.conf" ]; then - err "Сервис '$name' уже существует. Удалите его сначала: rproxy remove $name" - exit 1 + 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="" - while [ $# -gt 0 ]; do - case "$1" in - --domain|-d) - domain="$2" - shift 2 - ;; - --port|-p) - ext_port="$2" - shift 2 - ;; - *) - shift - ;; - esac - done - - local tunnel_port - tunnel_port=$(next_free_port) - - # Determine external port - if [ -n "$domain" ]; then + if [ "$mode" = "1" ]; then + prompt "Доменное имя (например, mysite.example.com): " + domain="$REPLY" + [ -z "$domain" ] && { warn "Домен не указан"; pause; return; } ext_port=80 - elif [ -z "$ext_port" ]; then - ext_port=$tunnel_port + else + local suggested + suggested=$(next_free_port) + prompt "Внешний порт [$suggested]: " + ext_port="${REPLY:-$suggested}" fi + # Разбор адреса local target_host="${target%:*}" local target_port="${target#*:}" - - # Validate target if [ "$target_host" = "$target_port" ]; then - # No colon — assume localhost target_port="$target_host" target_host="127.0.0.1" fi - msg "Добавляю сервис '$name'..." - msg " Цель: $target_host:$target_port" - msg " Туннель порт: $tunnel_port" - [ -n "$domain" ] && msg " Домен: $domain" - msg " Внешний порт: $ext_port" + local tunnel_port + tunnel_port=$(next_free_port) - # Generate nginx config + 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" + 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 nginx_conf="# rProxy: $name @@ -315,26 +338,28 @@ server { " fi - # Deploy nginx config to VPS + # Деплой nginx конфига на VPS 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" - exit 1 + pause + return } rm -f "$tmp_file" - # Reload nginx + # Перезагрузка nginx ssh_cmd "nginx -t && systemctl reload nginx" || { err "Ошибка конфигурации nginx на VPS" ssh_cmd "rm -f $REMOTE_NGINX_DIR/rproxy_$name.conf" - exit 1 + pause + return } - # Save service config + # Сохранение конфига сервиса cat > "$SERVICES_DIR/$name.conf" <"; exit 1; } +# ══════════════════════════════════════════════════════════════════════ +# УДАЛИТЬ СЕРВИС (интерактивно) +# ══════════════════════════════════════════════════════════════════════ +do_remove_interactive() { + clear_screen + draw_box "Удалить сервис" + printf "\n" - check_conf - load_service "$name" + local services="" + local idx=0 + for f in "$SERVICES_DIR"/*.conf 2>/dev/null; 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 - # Stop tunnel if running - is_running "$name" && do_stop "$name" - - # Remove nginx config from VPS - msg "Удаляю конфиг nginx на VPS..." - ssh_cmd "rm -f $REMOTE_NGINX_DIR/rproxy_$name.conf && nginx -t && systemctl reload nginx" || { - warn "Не удалось удалить конфиг nginx на VPS" - } - - # Remove local config - rm -f "$SERVICES_DIR/$name.conf" - rm -f "$(get_pid_file "$name")" - - msg "Сервис '$name' удалён" -} - -# ─── START ──────────────────────────────────────────────────────────── -do_start() { - local name="$1" - - check_conf - - if [ -z "$name" ]; then - # Start all enabled services - local count=0 - for f in "$SERVICES_DIR"/*.conf; do - [ -f "$f" ] || continue - local svc_name - svc_name=$(basename "$f" .conf) - . "$f" - if [ "$SVC_ENABLED" = "yes" ]; then - do_start "$svc_name" - count=$((count + 1)) - fi - done - [ "$count" -eq 0 ] && warn "Нет сервисов для запуска" + if [ "$idx" -eq 0 ]; then + printf " ${YELLOW}Нет сервисов для удаления.${NC}\n" + pause return fi - load_service "$name" + 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 2>/dev/null; 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 2>/dev/null; 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' уже запущен" @@ -413,14 +561,12 @@ do_start() { local ssh_opts="-o StrictHostKeyChecking=no -o ServerAliveInterval=30 -o ServerAliveCountMax=3 -o ExitOnForwardFailure=yes" if [ "$VPS_AUTH" = "password" ]; then - # With sshpass 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 - # With key AUTOSSH_GATETIME=0 autossh -M 0 -f -N \ $ssh_opts \ -i "$SSH_KEY" \ @@ -432,37 +578,17 @@ do_start() { local bg_pid=$! sleep 2 - # Find the actual autossh/ssh process local real_pid real_pid=$(pgrep -f "autossh.*$SVC_TUNNEL_PORT:$SVC_TARGET_HOST:$SVC_TARGET_PORT" 2>/dev/null | head -1) - if [ -z "$real_pid" ]; then - real_pid=$(pgrep -f "ssh.*$SVC_TUNNEL_PORT:$SVC_TARGET_HOST:$SVC_TARGET_PORT" 2>/dev/null | head -1) - fi + [ -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 - if [ -n "$real_pid" ]; then - echo "$real_pid" > "$pid_file" - msg "Туннель '$name' запущен (PID: $real_pid)" - else - echo "$bg_pid" > "$pid_file" - msg "Туннель '$name' запущен (PID: $bg_pid)" - fi + echo "$real_pid" > "$pid_file" + msg "Туннель '$name' запущен (PID: $real_pid)" } -# ─── STOP ───────────────────────────────────────────────────────────── -do_stop() { +do_stop_service() { local name="$1" - - check_conf - - if [ -z "$name" ]; then - # Stop all - for f in "$SERVICES_DIR"/*.conf; do - [ -f "$f" ] || continue - do_stop "$(basename "$f" .conf)" - done - return - fi - local pid_file pid_file=$(get_pid_file "$name") @@ -473,175 +599,185 @@ do_stop() { local pid pid=$(cat "$pid_file") - - # Kill autossh and child ssh processes kill "$pid" 2>/dev/null - # Also kill any related ssh tunnels - pkill -f "ssh.*$SVC_TUNNEL_PORT:.*:$SVC_TARGET_PORT" 2>/dev/null + pkill -f "ssh.*:$name.*$VPS_HOST" 2>/dev/null rm -f "$pid_file" msg "Туннель '$name' остановлен" } -# ─── RESTART ────────────────────────────────────────────────────────── -do_restart() { - local name="$1" - check_conf - - if [ -z "$name" ]; then - for f in "$SERVICES_DIR"/*.conf; do - [ -f "$f" ] || continue - do_restart "$(basename "$f" .conf)" - done - return - fi - - load_service "$name" - do_stop "$name" - sleep 1 - do_start "$name" +do_start_all() { + for f in "$SERVICES_DIR"/*.conf 2>/dev/null; do + [ -f "$f" ] || continue + . "$f" + [ "$SVC_ENABLED" = "yes" ] && do_start_service "$SVC_NAME" + done } -# ─── STATUS ─────────────────────────────────────────────────────────── -do_status() { - check_conf +do_stop_all() { + for f in "$SERVICES_DIR"/*.conf 2>/dev/null; do + [ -f "$f" ] || continue + . "$f" + do_stop_service "$SVC_NAME" + done +} - header "rProxy v$VERSION — Статус сервисов" +# ══════════════════════════════════════════════════════════════════════ +# НАСТРОЙКА VPS +# ══════════════════════════════════════════════════════════════════════ +do_setup() { + clear_screen + draw_box "Настройка подключения к VPS" printf "\n" - printf "${BOLD}%-15s %-25s %-8s %-10s %-20s${NC}\n" "ИМЯ" "ЦЕЛЬ" "ПОРТ" "СТАТУС" "ДОМЕН" - printf "%-15s %-25s %-8s %-10s %-20s\n" "───────────────" "─────────────────────────" "────────" "──────────" "────────────────────" - local has_services=0 - for f in "$SERVICES_DIR"/*.conf; do - [ -f "$f" ] || continue - has_services=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:-—}" - - printf "%-15s %-25s %-8s ${status_text} %-20s\n" \ - "$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 " Добавьте сервис: ${CYAN}rproxy add <имя> <хост:порт>${NC}\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 - printf "\n${BOLD}VPS:${NC} $VPS_HOST:$VPS_PORT (${VPS_AUTH})\n\n" -} + prompt "IP-адрес VPS: " + local vps_host="$REPLY" + [ -z "$vps_host" ] && { warn "IP-адрес обязателен"; pause; return; } -# ─── LIST ───────────────────────────────────────────────────────────── -do_list() { - check_conf + prompt "SSH порт [22]: " + local vps_port="${REPLY:-22}" - for f in "$SERVICES_DIR"/*.conf; do - [ -f "$f" ] || continue - . "$f" - local state="остановлен" - is_running "$SVC_NAME" && state="работает" + prompt "SSH пользователь [root]: " + local vps_user="${REPLY:-root}" - local info="$SVC_NAME $SVC_TARGET_HOST:$SVC_TARGET_PORT → :$SVC_EXT_PORT" - [ -n "$SVC_DOMAIN" ] && info="$info ($SVC_DOMAIN)" - info="$info [$state]" - echo "$info" - done -} + printf "\n Метод авторизации:\n" + printf " ${BOLD}1)${NC} SSH-ключ (рекомендуется)\n" + printf " ${BOLD}2)${NC} Пароль\n" + prompt "Выберите [1]: " + local auth_choice="${REPLY:-1}" -# ─── ENABLE / DISABLE ──────────────────────────────────────────────── -do_enable() { - local name="$1" - [ -z "$name" ] && { err "Использование: rproxy enable <имя>"; exit 1; } + local vps_auth="key" + local vps_pass="" - check_conf - load_service "$name" + if [ "$auth_choice" = "2" ]; then + vps_auth="password" + printf " SSH пароль: " + stty -echo 2>/dev/null + read -r vps_pass + stty echo 2>/dev/null + printf "\n" - sed -i "s/SVC_ENABLED=.*/SVC_ENABLED=\"yes\"/" "$SERVICES_DIR/$name.conf" - msg "Сервис '$name' добавлен в автозапуск" -} + if ! command -v sshpass >/dev/null 2>&1; then + msg "Устанавливаю sshpass..." + opkg install sshpass 2>/dev/null || { + err "Не удалось установить sshpass. Установите вручную: opkg install sshpass" + pause + return + } + fi + else + if [ ! -f "$SSH_KEY" ]; then + msg "Генерирую SSH-ключ..." + mkdir -p "$CONF_DIR" + ssh-keygen -t rsa -b 4096 -f "$SSH_KEY" -N "" -q + msg "Ключ создан: $SSH_KEY" + fi + fi -do_disable() { - local name="$1" - [ -z "$name" ] && { err "Использование: rproxy disable <имя>"; exit 1; } + # Проверка подключения + printf "\n" + msg "Проверяю подключение к $vps_host..." + if [ "$vps_auth" = "password" ]; then + if ! sshpass -p "$vps_pass" ssh -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..." + printf " Введите пароль VPS для копирования ключа:\n" + ssh-copy-id -i "$SSH_KEY.pub" -p "$vps_port" "$vps_user@$vps_host" 2>/dev/null || { + cat "$SSH_KEY.pub" | ssh -p "$vps_port" "$vps_user@$vps_host" \ + "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys" + } - check_conf - load_service "$name" + if ! ssh -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 "Подключение успешно!" - sed -i "s/SVC_ENABLED=.*/SVC_ENABLED=\"no\"/" "$SERVICES_DIR/$name.conf" - msg "Сервис '$name' убран из автозапуска" -} + # Настройка 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 -# ─── LOGS ───────────────────────────────────────────────────────────── -do_logs() { - local name="$1" - [ -z "$name" ] && { err "Использование: rproxy logs <имя>"; exit 1; } - - check_conf - - msg "Логи nginx на VPS для '$name':" - ssh_cmd "grep -i 'rproxy_$name\\|$name' /var/log/nginx/access.log /var/log/nginx/error.log 2>/dev/null | tail -50" -} - -# ─── USAGE ──────────────────────────────────────────────────────────── -usage() { - cat < [аргументы] - -${BOLD}Команды:${NC} - ${GREEN}setup${NC} Настроить подключение к VPS - ${GREEN}add${NC} <имя> <хост:порт> [опции] Добавить сервис - ${GREEN}remove${NC} <имя> Удалить сервис - ${GREEN}start${NC} [имя] Запустить туннель (все/один) - ${GREEN}stop${NC} [имя] Остановить туннель (все/один) - ${GREEN}restart${NC} [имя] Перезапустить туннель - ${GREEN}status${NC} Показать статус сервисов - ${GREEN}list${NC} Список сервисов (краткий) - ${GREEN}enable${NC} <имя> Включить автозапуск - ${GREEN}disable${NC} <имя> Выключить автозапуск - ${GREEN}logs${NC} <имя> Показать логи nginx - -${BOLD}Опции для add:${NC} - --domain, -d <домен> Привязать к домену (порт 80) - --port, -p <порт> Внешний порт (без домена) - -${BOLD}Примеры:${NC} - rproxy setup - rproxy add nas 192.168.1.100:5000 --domain nas.example.com - rproxy add camera 192.168.1.50:8080 --port 9090 - rproxy add homeassistant 192.168.1.10:8123 - rproxy remove nas - rproxy status + 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 + 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 - setup) do_setup ;; - add) shift; do_add "$@" ;; - remove|rm|del) shift; do_remove "$@" ;; - start) shift; do_start "$@" ;; - stop) shift; do_stop "$@" ;; - restart) shift; do_restart "$@" ;; - status|st) do_status ;; - list|ls) do_list ;; - enable) shift; do_enable "$@" ;; - disable) shift; do_disable "$@" ;; - logs|log) shift; do_logs "$@" ;; - -v|--version) echo "rProxy v$VERSION" ;; - -h|--help|"") usage ;; - *) err "Неизвестная команда: $1"; usage; exit 1 ;; + 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 2>/dev/null; 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