#!/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" 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' 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" "$*"; } # ─── Helpers ────────────────────────────────────────────────────────── check_conf() { if [ ! -f "$CONF_FILE" ]; then err "Конфигурация не найдена. Запустите: rproxy setup" exit 1 fi . "$CONF_FILE" } load_service() { local svc_file="$SERVICES_DIR/$1.conf" if [ ! -f "$svc_file" ]; then err "Сервис '$1' не найден" exit 1 fi . "$svc_file" } ssh_cmd() { 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 } scp_cmd() { if [ "$VPS_AUTH" = "password" ]; then 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 2>/dev/null; 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 } # ─── 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" < <хост:порт>" } # ─── ADD ────────────────────────────────────────────────────────────── do_add() { local name="$1" local target="$2" shift 2 if [ -z "$name" ] || [ -z "$target" ]; then err "Использование: rproxy add <имя> <хост:порт> [--domain example.com] [--port 8080]" exit 1 fi check_conf if [ -f "$SERVICES_DIR/$name.conf" ]; then err "Сервис '$name' уже существует. Удалите его сначала: rproxy remove $name" exit 1 fi 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 ext_port=80 elif [ -z "$ext_port" ]; then ext_port=$tunnel_port 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" # Generate nginx config 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 # Deploy nginx config to 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 } rm -f "$tmp_file" # Reload nginx ssh_cmd "nginx -t && systemctl reload nginx" || { err "Ошибка конфигурации nginx на VPS" ssh_cmd "rm -f $REMOTE_NGINX_DIR/rproxy_$name.conf" exit 1 } # Save service config cat > "$SERVICES_DIR/$name.conf" <"; exit 1; } check_conf load_service "$name" # 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 "Нет сервисов для запуска" return fi load_service "$name" if is_running "$name"; then warn "Сервис '$name' уже запущен" return fi msg "Запускаю туннель '$name' ($SVC_TARGET_HOST:$SVC_TARGET_PORT → VPS:$SVC_TUNNEL_PORT)..." 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 # 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" \ -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 # 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 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 } # ─── STOP ───────────────────────────────────────────────────────────── do_stop() { 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") if [ ! -f "$pid_file" ]; then warn "Сервис '$name' не запущен" return fi 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 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" } # ─── STATUS ─────────────────────────────────────────────────────────── do_status() { check_conf header "rProxy v$VERSION — Статус сервисов" 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" fi printf "\n${BOLD}VPS:${NC} $VPS_HOST:$VPS_PORT (${VPS_AUTH})\n\n" } # ─── LIST ───────────────────────────────────────────────────────────── do_list() { check_conf 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)" info="$info [$state]" echo "$info" done } # ─── ENABLE / DISABLE ──────────────────────────────────────────────── do_enable() { local name="$1" [ -z "$name" ] && { err "Использование: rproxy enable <имя>"; exit 1; } check_conf load_service "$name" sed -i "s/SVC_ENABLED=.*/SVC_ENABLED=\"yes\"/" "$SERVICES_DIR/$name.conf" msg "Сервис '$name' добавлен в автозапуск" } do_disable() { local name="$1" [ -z "$name" ] && { err "Использование: rproxy disable <имя>"; exit 1; } check_conf load_service "$name" sed -i "s/SVC_ENABLED=.*/SVC_ENABLED=\"no\"/" "$SERVICES_DIR/$name.conf" msg "Сервис '$name' убран из автозапуска" } # ─── 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 EOF } # ─── MAIN ───────────────────────────────────────────────────────────── mkdir -p "$CONF_DIR" "$SERVICES_DIR" "$PID_DIR" 2>/dev/null 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 ;; esac