rProxy/rproxy

648 lines
22 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.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" <<EOF
# rProxy VPS configuration
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 "Конфигурация сохранена: $CONF_FILE"
header "Готово! Добавьте первый сервис: rproxy add <имя> <хост:порт>"
}
# ─── 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" <<EOF
# rProxy service: $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' добавлен!"
# Auto-start the tunnel
do_start "$name"
}
# ─── REMOVE ───────────────────────────────────────────────────────────
do_remove() {
local name="$1"
[ -z "$name" ] && { err "Использование: rproxy remove <имя>"; 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 <<EOF
${CYAN}${BOLD}rProxy v$VERSION${NC} — Менеджер обратного прокси для Keenetic
${BOLD}Использование:${NC}
rproxy <команда> [аргументы]
${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