846 lines
32 KiB
Bash
846 lines
32 KiB
Bash
#!/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_rsa"
|
||
REMOTE_NGINX_DIR="/etc/nginx/sites-enabled"
|
||
BASE_TUNNEL_PORT=10000
|
||
|
||
# ─── Цвета ───────────────────────────────────────────────────────────
|
||
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 -o StrictHostKeyChecking=no -p "$VPS_PORT" "$VPS_USER@$VPS_HOST" "$@"
|
||
else
|
||
$ssh_exec -o StrictHostKeyChecking=no -i "$SSH_KEY" -p "$VPS_PORT" "$@"
|
||
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 -o StrictHostKeyChecking=no -P "$VPS_PORT" "$@"
|
||
else
|
||
scp -o StrictHostKeyChecking=no -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 " ${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; 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="$REPLY"
|
||
[ -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=""
|
||
|
||
if [ "$mode" = "1" ]; then
|
||
prompt "Доменное имя (например, mysite.example.com): "
|
||
domain="$REPLY"
|
||
[ -z "$domain" ] && { warn "Домен не указан"; pause; return; }
|
||
ext_port=80
|
||
else
|
||
local suggested
|
||
suggested=$(next_free_port)
|
||
prompt "Внешний порт [$suggested]: "
|
||
ext_port="${REPLY:-$suggested}"
|
||
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"
|
||
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
|
||
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
|
||
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
|
||
ssh_cmd "nginx -t && systemctl reload nginx" || {
|
||
err "Ошибка конфигурации nginx на VPS"
|
||
ssh_cmd "rm -f $REMOTE_NGINX_DIR/rproxy_$name.conf"
|
||
pause
|
||
return
|
||
}
|
||
|
||
# Сохранение конфига сервиса
|
||
cat > "$SERVICES_DIR/$name.conf" <<EOF
|
||
# rProxy сервис: $name
|
||
SVC_NAME="$name"
|
||
SVC_TARGET_HOST="$target_host"
|
||
SVC_TARGET_PORT="$target_port"
|
||
SVC_TUNNEL_PORT="$tunnel_port"
|
||
SVC_EXT_PORT="$ext_port"
|
||
SVC_DOMAIN="$domain"
|
||
SVC_ENABLED="yes"
|
||
EOF
|
||
|
||
msg "Сервис '$name' добавлен!"
|
||
|
||
# Автозапуск туннеля
|
||
do_start_service "$name"
|
||
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-ключ..."
|
||
mkdir -p "$CONF_DIR"
|
||
|
||
# Предпочтительно используем полный путь к OpenSSH из Entware
|
||
local keygen_cmd="/opt/bin/ssh-keygen"
|
||
[ ! -f "$keygen_cmd" ] && keygen_cmd="ssh-keygen"
|
||
|
||
# 1. Попытка создания OpenSSH ключа (современный формат)
|
||
if ! $keygen_cmd -t rsa -b 2048 -f "$SSH_KEY" -N "" -q 2>/dev/null; then
|
||
# 2. Попытка для Dropbear (флаг -s вместо -b)
|
||
if ! $keygen_cmd -t rsa -s 2048 -f "$SSH_KEY" -N "" 2>/dev/null; then
|
||
# 3. Минимально возможный набор флагов
|
||
$keygen_cmd -t rsa -f "$SSH_KEY" -N ""
|
||
fi
|
||
fi
|
||
|
||
# Извлекаем публичный ключ, если он не создался (особенности Dropbear)
|
||
if [ ! -f "$SSH_KEY.pub" ] && [ -f "$SSH_KEY" ]; then
|
||
# Dropbear -y может выводить лишний текст, берем только строку начинающуюся с ssh-rsa
|
||
$keygen_cmd -y -f "$SSH_KEY" 2>/dev/null | grep "^ssh-rsa" > "$SSH_KEY.pub"
|
||
fi
|
||
|
||
# Проверка формата: если ключ Dropbear, а клиент OpenSSH — может потребоваться конвертация
|
||
# Но обычно если мы в Entware, /opt/bin/ssh должен понимать PEM
|
||
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
|
||
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" <<EOF
|
||
# rProxy — настройки VPS
|
||
VPS_HOST="$vps_host"
|
||
VPS_PORT="$vps_port"
|
||
VPS_USER="$vps_user"
|
||
VPS_AUTH="$vps_auth"
|
||
VPS_PASS="$vps_pass"
|
||
EOF
|
||
chmod 600 "$CONF_FILE"
|
||
|
||
msg "Конфигурация сохранена!"
|
||
printf "\n ${GREEN}${BOLD}VPS настроен и готов к работе.${NC}\n"
|
||
pause
|
||
}
|
||
|
||
# ══════════════════════════════════════════════════════════════════════
|
||
# ТОЧКА ВХОДА
|
||
# ══════════════════════════════════════════════════════════════════════
|
||
mkdir -p "$CONF_DIR" "$SERVICES_DIR" "$PID_DIR" 2>/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
|