rProxy/rproxy

1011 lines
40 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/sh
# rProxy — Менеджер обратного прокси для роутеров Keenetic
# Публикация локальных сервисов через SSH-туннели + nginx на VPS
# http://5.104.75.50:3000/Petro1990/rProxy
VERSION="1.0.4"
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 " ${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}10)${NC} 🏥 Проверка VPS (Health)\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 ;;
10) 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))
. "$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} Да — указать доменное имя (порт 443)\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; }
# SSL теперь всегда включен для доменов по запросу пользователя
use_ssl="yes"
ext_port=443
if [ -z "$CERTBOT_EMAIL" ]; then
prompt "Введите Email для регистрации сертификатов (Certbot): "
CERTBOT_EMAIL="$REPLY"
# Сохраняем email в конфиг
sed -i "/CERTBOT_EMAIL=/d" "$CONF_FILE"
echo "CERTBOT_EMAIL=\"$CERTBOT_EMAIL\"" >> "$CONF_FILE"
fi
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" <<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_SSL="$use_ssl"
SVC_ENABLED="yes"
EOF
msg "Сервис '$name' добавлен!"
# Автозапуск туннеля
do_start_service "$name"
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"
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
# Проверка cron
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 "Действующие сертификаты:"
ssh_cmd "certbot certificates 2>/dev/null | grep -E 'Expiry Date:|Domains:'" | while read -r line; do
printf " %s\n" "$line"
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"
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"
[ -z "$services" ] && services="$SVC_NAME" || 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" | cut -d' ' -f"$REPLY")
[ -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"
[ -z "$services" ] && services="$SVC_NAME" || 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
# Извлечение имени сервиса по индексу (1-based)
SELECTED_SERVICE=$(echo "$services" | cut -d' ' -f"$REPLY")
[ -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" <<EOF
# rProxy — настройки VPS
VPS_HOST="$vps_host"
VPS_PORT="$vps_port"
VPS_USER="$vps_user"
VPS_AUTH="$vps_auth"
VPS_PASS="$vps_pass"
CERTBOT_EMAIL="$CERTBOT_EMAIL"
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