feat: rProxy — менеджер обратного прокси для Keenetic
This commit is contained in:
commit
f336aec6d5
|
|
@ -0,0 +1,147 @@
|
||||||
|
# 🔄 rProxy
|
||||||
|
|
||||||
|
**Менеджер обратного прокси для роутеров Keenetic**
|
||||||
|
|
||||||
|
Публикация локальных сервисов в интернет через VPS с nginx в качестве reverse proxy.
|
||||||
|
|
||||||
|
```
|
||||||
|
[LAN сервис] ← [Keenetic] ══SSH туннель══▶ [VPS:nginx] → Интернет
|
||||||
|
```
|
||||||
|
|
||||||
|
## ⚡ Быстрый старт
|
||||||
|
|
||||||
|
### 1. Установка (на роутере Keenetic)
|
||||||
|
|
||||||
|
> Требуется [Entware](https://help.keenetic.com/hc/ru/articles/360021214160)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sSL http://5.104.75.50:3000/Petro1990/rProxy/raw/branch/main/install.sh | sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Настройка VPS
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rproxy setup
|
||||||
|
```
|
||||||
|
|
||||||
|
Скрипт запросит:
|
||||||
|
- **IP-адрес VPS** — адрес вашего сервера
|
||||||
|
- **SSH порт** — порт SSH (по умолчанию 22)
|
||||||
|
- **SSH пользователь** — логин (по умолчанию root)
|
||||||
|
- **Метод авторизации** — SSH-ключ или пароль
|
||||||
|
|
||||||
|
При выборе SSH-ключа скрипт автоматически сгенерирует ключ и скопирует его на VPS. Также будет автоматически установлен и настроен nginx.
|
||||||
|
|
||||||
|
### 3. Публикация сервиса
|
||||||
|
|
||||||
|
**С доменом** (порт 80):
|
||||||
|
```bash
|
||||||
|
rproxy add nas 192.168.1.100:5000 --domain nas.example.com
|
||||||
|
```
|
||||||
|
|
||||||
|
**Без домена** (произвольный порт):
|
||||||
|
```bash
|
||||||
|
rproxy add camera 192.168.1.50:8080 --port 9090
|
||||||
|
```
|
||||||
|
|
||||||
|
**С автоподбором порта:**
|
||||||
|
```bash
|
||||||
|
rproxy add homeassistant 192.168.1.10:8123
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📋 Команды
|
||||||
|
|
||||||
|
| Команда | Описание |
|
||||||
|
|---------|----------|
|
||||||
|
| `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 |
|
||||||
|
|
||||||
|
### Опции для `add`
|
||||||
|
|
||||||
|
| Опция | Описание |
|
||||||
|
|-------|----------|
|
||||||
|
| `--domain`, `-d` | Привязать к домену (nginx слушает порт 80) |
|
||||||
|
| `--port`, `-p` | Указать внешний порт (без домена) |
|
||||||
|
|
||||||
|
## 🏗 Архитектура
|
||||||
|
|
||||||
|
### На роутере (Keenetic + Entware)
|
||||||
|
- **autossh** — устанавливает устойчивые reverse SSH-туннели до VPS
|
||||||
|
- Каждый сервис = отдельный туннель
|
||||||
|
- Конфигурация: `/opt/etc/rproxy/`
|
||||||
|
- PID-файлы: `/opt/var/run/rproxy/`
|
||||||
|
|
||||||
|
### На VPS
|
||||||
|
- **nginx** — принимает входящие запросы и проксирует в SSH-туннели
|
||||||
|
- Конфиги сервисов: `/etc/nginx/sites-enabled/rproxy_*.conf`
|
||||||
|
|
||||||
|
### Файлы конфигурации
|
||||||
|
|
||||||
|
```
|
||||||
|
/opt/etc/rproxy/
|
||||||
|
├── rproxy.conf # Параметры подключения к VPS
|
||||||
|
└── services/
|
||||||
|
├── nas.conf # Конфиг сервиса "nas"
|
||||||
|
├── camera.conf # Конфиг сервиса "camera"
|
||||||
|
└── ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔒 Безопасность
|
||||||
|
|
||||||
|
- SSH-ключ хранится в `/opt/etc/rproxy/id_rsa` (права 600)
|
||||||
|
- Пароль VPS хранится в `/opt/etc/rproxy/rproxy.conf` (права 600)
|
||||||
|
- Все соединения зашифрованы через SSH
|
||||||
|
|
||||||
|
> **Рекомендация:** используйте авторизацию по SSH-ключу для лучшей безопасности.
|
||||||
|
|
||||||
|
## 🔧 Решение проблем
|
||||||
|
|
||||||
|
### Туннель не подключается
|
||||||
|
```bash
|
||||||
|
# Проверьте статус
|
||||||
|
rproxy status
|
||||||
|
|
||||||
|
# Перезапустите туннель
|
||||||
|
rproxy restart <имя>
|
||||||
|
|
||||||
|
# Проверьте SSH-подключение вручную
|
||||||
|
ssh -i /opt/etc/rproxy/id_rsa -p 22 root@<VPS_IP>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Сервис не доступен по домену
|
||||||
|
- Убедитесь, что DNS-запись домена указывает на IP VPS
|
||||||
|
- Проверьте логи: `rproxy logs <имя>`
|
||||||
|
- Проверьте nginx на VPS: `ssh root@VPS 'nginx -t'`
|
||||||
|
|
||||||
|
### Автозапуск не работает
|
||||||
|
```bash
|
||||||
|
# Проверьте init-скрипт
|
||||||
|
ls -la /opt/etc/init.d/S98rproxy
|
||||||
|
|
||||||
|
# Проверьте, что сервис enabled
|
||||||
|
rproxy list
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📦 Требования
|
||||||
|
|
||||||
|
### Роутер
|
||||||
|
- Keenetic с установленным [Entware](https://help.keenetic.com/hc/ru/articles/360021214160)
|
||||||
|
- Пакеты: `openssh-client`, `autossh`, `curl`, `sshpass`
|
||||||
|
|
||||||
|
### VPS
|
||||||
|
- Linux (Debian/Ubuntu/CentOS)
|
||||||
|
- nginx (устанавливается автоматически при `setup`)
|
||||||
|
- SSH-доступ
|
||||||
|
|
||||||
|
## 📝 Лицензия
|
||||||
|
|
||||||
|
MIT
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# rProxy init script for Entware (Keenetic)
|
||||||
|
# Autostart/stop SSH tunnels on boot/shutdown
|
||||||
|
|
||||||
|
ENABLED=yes
|
||||||
|
PROCS="rproxy"
|
||||||
|
DESC="rProxy reverse proxy tunnels"
|
||||||
|
|
||||||
|
start() {
|
||||||
|
echo "Starting $DESC..."
|
||||||
|
/opt/bin/rproxy start
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
echo "Stopping $DESC..."
|
||||||
|
/opt/bin/rproxy stop
|
||||||
|
}
|
||||||
|
|
||||||
|
restart() {
|
||||||
|
stop
|
||||||
|
sleep 2
|
||||||
|
start
|
||||||
|
}
|
||||||
|
|
||||||
|
status() {
|
||||||
|
/opt/bin/rproxy status
|
||||||
|
}
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
start) start ;;
|
||||||
|
stop) stop ;;
|
||||||
|
restart) restart ;;
|
||||||
|
status) status ;;
|
||||||
|
*)
|
||||||
|
echo "Usage: $0 {start|stop|restart|status}"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
exit 0
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# rProxy Installer for Keenetic Routers (Entware)
|
||||||
|
# Usage: curl -sSL http://5.104.75.50:3000/Petro1990/rProxy/raw/branch/main/install.sh | sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
CYAN='\033[0;36m'
|
||||||
|
BOLD='\033[1m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
REPO_URL="http://5.104.75.50:3000/Petro1990/rProxy/raw/branch/main"
|
||||||
|
INSTALL_DIR="/opt/bin"
|
||||||
|
CONF_DIR="/opt/etc/rproxy"
|
||||||
|
INIT_DIR="/opt/etc/init.d"
|
||||||
|
|
||||||
|
msg() { printf "${GREEN}▸${NC} %s\n" "$*"; }
|
||||||
|
err() { printf "${RED}✖${NC} %s\n" "$*" >&2; }
|
||||||
|
|
||||||
|
header() {
|
||||||
|
printf "\n"
|
||||||
|
printf "${CYAN}${BOLD}╔══════════════════════════════════════════╗${NC}\n"
|
||||||
|
printf "${CYAN}${BOLD}║ rProxy — Installer for Keenetic ║${NC}\n"
|
||||||
|
printf "${CYAN}${BOLD}╚══════════════════════════════════════════╝${NC}\n"
|
||||||
|
printf "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
header
|
||||||
|
|
||||||
|
# ─── Check Entware ───────────────────────────────────────────────────
|
||||||
|
if [ ! -d "/opt/bin" ] || ! command -v opkg >/dev/null 2>&1; then
|
||||||
|
err "Entware не найден!"
|
||||||
|
err "Установите Entware на роутер: https://help.keenetic.com/hc/ru/articles/360021214160"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
msg "Entware обнаружен ✓"
|
||||||
|
|
||||||
|
# ─── Install dependencies ───────────────────────────────────────────
|
||||||
|
msg "Проверяю зависимости..."
|
||||||
|
|
||||||
|
install_pkg() {
|
||||||
|
local pkg="$1"
|
||||||
|
if ! opkg list-installed 2>/dev/null | grep -q "^$pkg "; then
|
||||||
|
msg "Устанавливаю $pkg..."
|
||||||
|
opkg update >/dev/null 2>&1
|
||||||
|
opkg install "$pkg" || {
|
||||||
|
err "Не удалось установить $pkg"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
else
|
||||||
|
msg "$pkg уже установлен ✓"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
install_pkg "openssh-client"
|
||||||
|
install_pkg "autossh"
|
||||||
|
install_pkg "curl"
|
||||||
|
install_pkg "sshpass"
|
||||||
|
|
||||||
|
# ─── Download rproxy script ─────────────────────────────────────────
|
||||||
|
msg "Скачиваю rproxy..."
|
||||||
|
curl -sSL "$REPO_URL/rproxy" -o "$INSTALL_DIR/rproxy" || {
|
||||||
|
err "Не удалось скачать rproxy"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
chmod +x "$INSTALL_DIR/rproxy"
|
||||||
|
msg "rproxy установлен в $INSTALL_DIR/rproxy ✓"
|
||||||
|
|
||||||
|
# ─── Download init script ───────────────────────────────────────────
|
||||||
|
msg "Устанавливаю init-скрипт для автозапуска..."
|
||||||
|
curl -sSL "$REPO_URL/S98rproxy" -o "$INIT_DIR/S98rproxy" || {
|
||||||
|
err "Не удалось скачать init-скрипт"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
chmod +x "$INIT_DIR/S98rproxy"
|
||||||
|
msg "Init-скрипт установлен ✓"
|
||||||
|
|
||||||
|
# ─── Create directories ─────────────────────────────────────────────
|
||||||
|
mkdir -p "$CONF_DIR/services"
|
||||||
|
mkdir -p "/opt/var/run/rproxy"
|
||||||
|
|
||||||
|
# ─── Done ────────────────────────────────────────────────────────────
|
||||||
|
printf "\n"
|
||||||
|
printf "${GREEN}${BOLD}══════════════════════════════════════════${NC}\n"
|
||||||
|
printf "${GREEN}${BOLD} rProxy успешно установлен!${NC}\n"
|
||||||
|
printf "${GREEN}${BOLD}══════════════════════════════════════════${NC}\n"
|
||||||
|
printf "\n"
|
||||||
|
printf " Следующий шаг — настройте подключение к VPS:\n"
|
||||||
|
printf "\n"
|
||||||
|
printf " ${CYAN}rproxy setup${NC}\n"
|
||||||
|
printf "\n"
|
||||||
|
printf " Затем добавьте сервис:\n"
|
||||||
|
printf "\n"
|
||||||
|
printf " ${CYAN}rproxy add myservice 192.168.1.100:8080 --domain my.domain.com${NC}\n"
|
||||||
|
printf " ${CYAN}rproxy add camera 192.168.1.50:554 --port 9554${NC}\n"
|
||||||
|
printf "\n"
|
||||||
|
printf " Полная справка: ${CYAN}rproxy --help${NC}\n"
|
||||||
|
printf "\n"
|
||||||
|
|
@ -0,0 +1,647 @@
|
||||||
|
#!/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
|
||||||
Loading…
Reference in New Issue