NaïveProxy: установка полной каскадной цепочки: различия между версиями

Материал из wolfram
Перейти к навигации Перейти к поиску
Новая страница: « = NaïveProxy: установка полной каскадной цепочки = Данный гайд описывает пошаговое развёртывание каскадного прокси-сервера на базе протокола NaïveProxy. Трафик проходит полную цепочку:<pre> Устройства → OpenWrt роутер (naive) → RU сервер (Caddy + naive) → EU сервер (Caddy) → WARP →...»
 
мНет описания правки
 
(не показано 5 промежуточных версий этого же участника)
Строка 1: Строка 1:
= NaïveProxy: установка полной каскадной цепочки =


= NaïveProxy: установка полной каскадной цепочки =
== Что такое NaïveProxy и зачем он нужен ==
Данный гайд описывает пошаговое развёртывание каскадного прокси-сервера на базе протокола NaïveProxy. Трафик проходит полную цепочку:<pre>
 
=== Проблема: как работают системы блокировок ===
 
Современные системы глубокой инспекции пакетов (DPI — Deep Packet Inspection), которые применяются интернет-провайдерами и регуляторами по всему миру, умеют анализировать не только адреса, но и '''характер трафика'''. Даже если трафик зашифрован, по поведению соединения можно определить, что это не обычный браузер, а программа для обхода ограничений (VPN, Shadowsocks, обычный SOCKS5-прокси и т.д.).
 
Принцип прост: DPI «смотрит» на то, как именно клиент общается с сервером:
* сколько байт летит в каждую сторону,
* с какими временны́ми интервалами,
* какой у соединения «fingerprint» (цифровой отпечаток),
* как выглядит TLS-рукопожатие.
 
Обычный HTTPS-прокси легко выявляется, потому что программа-клиент общается иначе, чем реальный браузер.
 
=== Решение: маскировка под браузер Chrome ===
 
NaïveProxy решает эту проблему '''принципиально''': клиентская часть (naive binary) притворяется настоящим браузером Chrome, который делает обычные HTTPS-запросы. Серверная часть (Caddy с плагином forwardproxy) притворяется обычным веб-сервером.
 
Ключевая особенность — NaïveProxy использует '''встроенный сетевой стек Chromium'''. Это значит, что трафик неотличим от реального Chrome не только по содержимому, но и по поведению на уровне TCP/TLS: те же задержки, те же размеры пакетов, тот же TLS fingerprint.
 
{| class="wikitable"
|-
! Протокол !! Как выглядит для DPI !! Устойчивость к блокировке
|-
| Обычный VPN (WireGuard, OpenVPN) || Характерный шаблон UDP/TCP || Низкая — легко заблокировать по fingerprint
|-
| Shadowsocks || Случайный зашифрованный поток || Средняя — выявляется по энтропии и поведению
|-
| VLESS / VMess (без маскировки) || TLS, но нестандартный fingerprint || Средняя — детектируется active probing
|-
| VLESS + REALITY || TLS с реальным сертификатом стороннего сайта || Высокая — но заметен при активном зондировании
|-
| '''NaïveProxy''' || '''Неотличим от Chrome HTTPS''' || '''Очень высокая — устойчив к DPI и активному зондированию'''
|}
 
=== Как работает NaïveProxy технически ===
 
NaïveProxy использует протокол HTTP/2 CONNECT для туннелирования трафика. Упрощённо:
 
# Клиент (naive binary на вашем устройстве) открывает HTTPS-соединение с сервером — точь-в-точь как Chrome.
# Внутри этого HTTPS-соединения по протоколу HTTP/2 CONNECT прокидывается туннель для вашего трафика.
# Сервер (Caddy + forwardproxy) обрабатывает запросы как обычный веб-сервер, а туннельные запросы перенаправляет дальше.
# Если кто-то «постучится» на сервер без правильных credentials — сервер ответит как обычный сайт ('''probe_resistance''' — защита от зондирования).
 
=== Каскадная цепочка: зачем два сервера ===
 
В данном решении используется '''каскад из двух серверов''': Entry Node (точка входа, ближайший к клиентам) и Exit Node (точка выхода в интернет). Это сделано намеренно:
 
* '''Снижение риска для клиента:''' клиенты подключаются только к ближайшему серверу-входу — подключение к локальному узлу менее заметно, чем прямое соединение с зарубежным сервером.
* '''Гибкость маршрутизации:''' Entry Node может обслуживать разных клиентов по разным протоколам (VLESS, Trojan, NaïveProxy) и перенаправлять их на Exit Node.
* '''Дополнительный слой анонимности:''' Exit Node выходит в интернет через Cloudflare WARP — конечный сайт видит IP Cloudflare, а не IP вашего сервера.
 
----
 
== Схема конкретного решения ==
 
=== Компоненты инфраструктуры ===
 
{| class="wikitable"
|-
! Компонент !! Роль !! IP / Адрес !! ОС
|-
| Устройства домашней сети || Конечные пользователи || — || Любые
|-
| Роутер Xiaomi Redmi AX6000 || Прозрачный прокси для всей сети || — || OpenWrt 24.10 (aarch64)
|-
| RU сервер ('''Entry Node''') || Точка входа (Entry Node) || <code>YOUR_RU_SERVER_IP</code> / <code>your-naive-ru-domain.example.com</code> || Ubuntu 24.04
|-
| EU сервер ('''Exit Node''') || Точка выхода (Exit Node) || <code>YOUR_EU_SERVER_IP</code> / <code>your-naive-eu-domain.example.com</code> || Ubuntu 24.04
|-
| Cloudflare WARP || Финальный выход в интернет || IP Cloudflare || —
|}
 
=== Полная схема прохождения трафика ===
[[Файл:Generated image NaïveProxy Полная схема прохождения трафика.png|безрамки|739x739пкс]]
 
=== Протоколы на каждом участке цепочки ===
 
{| class="wikitable"
|-
! Участок !! Протокол !! Шифрование !! Что видит наблюдатель
|-
| Устройство → Роутер || Обычный TCP/UDP || Нет (локальная сеть) || Локальный трафик
|-
| Роутер → RU Caddy || HTTPS HTTP/2 (NaïveProxy) || TLS 1.3 (Let's Encrypt) || Браузер Chrome обращается к сайту
|-
| RU naive → EU Caddy || HTTPS HTTP/2 (NaïveProxy) || TLS 1.3 (Let's Encrypt) || Браузер Chrome обращается к сайту
|-
| EU Caddy → 3x-ui inbound || SOCKS5 (localhost) || — (локально) || Только на EU сервере
|-
| EU 3x-ui → WARP || WireGuard (gVisor) || WireGuard || Зашифрованный WireGuard к Cloudflare
|-
| WARP → Интернет || Обычный TCP/UDP || — || IP Cloudflare
|}
 
=== Дополнительные каналы (параллельно NaïveProxy) ===
 
{| class="wikitable"
|-
! Канал !! Протокол !! Маршрут !! Статус
|-
| 3x-ui каскад (основной) || VLESS / REALITY || Клиенты → RU 3x-ui → EU 3x-ui → Интернет || ✅ Активен
|-
| NaïveProxy каскад (этот гайд) || HTTP/2 naïve || Роутер/ПК → RU Caddy → EU Caddy → WARP || ✅ Активен
|-
| MTProto (Telegram) || MTProto || Клиенты Telegram → EU :8443, :2443 || ✅ Активен
|-
| Hysteria2 || QUIC/UDP || → EU || ⏸ Конфиг есть, сервис не запущен
|}
 
----
 
Данный гайд описывает пошаговое развёртывание каскадного прокси-сервера на базе протокола NaïveProxy.
Трафик проходит полную цепочку:
 
<pre>
Устройства → OpenWrt роутер (naive) → RU сервер (Caddy + naive) → EU сервер (Caddy) → WARP → Интернет
Устройства → OpenWrt роутер (naive) → RU сервер (Caddy + naive) → EU сервер (Caddy) → WARP → Интернет
</pre>На серверах предполагается, что '''3x-ui уже установлен и работает'''.
</pre>
 
На серверах предполагается, что '''3x-ui уже установлен и работает'''.


== Предварительные требования ==
== Предварительные требования ==
Строка 76: Строка 193:


== Часть 1: Настройка EU сервера (Exit Node) ==
== Часть 1: Настройка EU сервера (Exit Node) ==
Цель: развернуть Caddy с плагином forwardproxy (NaïveProxy) в Docker. Caddy будет принимать зашифрованные соединения от RU сервера и выпускать трафик через WARP.
 
Цель: развернуть Caddy с плагином forwardproxy (NaïveProxy) в Docker.
Caddy будет принимать зашифрованные соединения от RU сервера и выпускать трафик через WARP.


=== 1.1 Установка Docker ===
=== 1.1 Установка Docker ===
Выполни на EU сервере:<syntaxhighlight lang="bash">
 
Выполни на EU сервере:
 
<syntaxhighlight lang="bash">
# Обновить пакеты
# Обновить пакеты
apt-get update && apt-get upgrade -y
apt-get update && apt-get upgrade -y
Строка 106: Строка 228:
docker --version
docker --version
docker compose version
docker compose version
</syntaxhighlight>Ожидаемый вывод:<pre>
</syntaxhighlight>
 
Ожидаемый вывод:
<pre>
Docker version 27.x.x, build ...
Docker version 27.x.x, build ...
Docker Compose version v2.x.x
Docker Compose version v2.x.x
Строка 112: Строка 237:


=== 1.2 Создание структуры каталогов ===
=== 1.2 Создание структуры каталогов ===
<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
mkdir -p /opt/caddy-naive/www
mkdir -p /opt/caddy-naive/www
Строка 118: Строка 244:


=== 1.3 Создание Dockerfile ===
=== 1.3 Создание Dockerfile ===
Caddy собирается с нестандартным плагином <code>forwardproxy</code> (форк klzgrad/forwardproxy@naive). Используется Go 1.25+ — обязательное требование для текущей версии Caddy.<syntaxhighlight lang="bash">
 
Caddy собирается с нестандартным плагином <code>forwardproxy</code> (форк klzgrad/forwardproxy@naive).
Используется Go 1.25+ — обязательное требование для текущей версии Caddy.
 
<syntaxhighlight lang="bash">
cat > /opt/caddy-naive/Dockerfile << 'EOF'
cat > /opt/caddy-naive/Dockerfile << 'EOF'
FROM golang:1.25-alpine AS builder
FROM golang:1.25-alpine AS builder
Строка 138: Строка 268:


=== 1.4 Создание docker-compose.yml ===
=== 1.4 Создание docker-compose.yml ===
<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
cat > /opt/caddy-naive/docker-compose.yml << 'EOF'
cat > /opt/caddy-naive/docker-compose.yml << 'EOF'
Строка 146: Строка 277:
     restart: unless-stopped
     restart: unless-stopped
     network_mode: host
     network_mode: host
    environment:
      - XDG_DATA_HOME=/data
     volumes:
     volumes:
       - ./Caddyfile:/etc/caddy/Caddyfile:ro
       - ./Caddyfile:/etc/caddy/Caddyfile:ro
Строка 158: Строка 291:
</syntaxhighlight>
</syntaxhighlight>


; Почему network_mode
; Почему XDG_DATA_HOME=/data?
: host?
: Кастомный образ Caddy (собранный из <code>debian:bookworm-slim</code>) не наследует переменные официального Docker образа Caddy. Без этой переменной Caddy хранит сертификаты и служебные данные в <code>/root/.local/share/caddy/</code> — внутри writable layer контейнера. При пересборке или пересоздании контейнера все сертификаты теряются, и Caddy запрашивает новые у Let's Encrypt (лимит: 5 сертификатов на домен в неделю). С <code>XDG_DATA_HOME=/data</code> данные пишутся в <code>/data/caddy/</code>, который примонтирован как named volume — и переживают любые пересборки.
 
; Почему network_mode: host?
: Контейнер использует сетевой стек хоста напрямую. Это необходимо чтобы:
: Контейнер использует сетевой стек хоста напрямую. Это необходимо чтобы:
:* Caddy получал реальные клиентские IP для корректной работы probe_resistance
:* Caddy получал реальные клиентские IP для корректной работы probe_resistance
Строка 165: Строка 300:


=== 1.5 Создание заглушки для probe_resistance ===
=== 1.5 Создание заглушки для probe_resistance ===
<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
cat > /opt/caddy-naive/www/index.html << 'EOF'
cat > /opt/caddy-naive/www/index.html << 'EOF'
Строка 177: Строка 313:
: Функция <code>probe_resistance</code> в Caddy скрывает факт наличия прокси.
: Функция <code>probe_resistance</code> в Caddy скрывает факт наличия прокси.
: При обращении без авторизации сервер возвращает эту обычную HTML-страницу вместо ответа прокси.
: При обращении без авторизации сервер возвращает эту обычную HTML-страницу вместо ответа прокси.
: Это защищает от автоматических сканеров и активных проверок (ТСПУ, DPI).
: Это защищает от автоматических сканеров и активных DPI-проверок.


=== 1.6 Создание Caddyfile ===
=== 1.6 Создание Caddyfile ===
<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
cat > /opt/caddy-naive/Caddyfile << 'EOF'
cat > /opt/caddy-naive/Caddyfile << 'EOF'
Строка 202: Строка 339:
</syntaxhighlight>
</syntaxhighlight>


; Важные нюансы синтаксиса
; Важные нюансы синтаксиса:
:
: * <code>:443, your-naive-eu-domain.example.com</code> — запятая и пробел обязательны. Именно такой синтаксис активирует HTTP/2 для CONNECT-туннелей, без которого NaïveProxy не работает
: * <code>:443, your-naive-eu-domain.example.com</code> — запятая и пробел обязательны. Именно такой синтаксис активирует HTTP/2 для CONNECT-туннелей, без которого NaïveProxy не работает
: * <code>upstream socks5://127.0.0.1:24363</code> — перенаправляет исходящий трафик в Xray inbound (WARP). Если WARP ещё не настроен, эту строку можно убрать — трафик пойдёт напрямую в интернет
: * <code>upstream socks5://127.0.0.1:24363</code> — перенаправляет исходящий трафик в Xray inbound (WARP). Если WARP ещё не настроен, эту строку можно убрать — трафик пойдёт напрямую в интернет
Строка 209: Строка 345:


=== 1.7 Настройка Xray inbound для WARP (в 3x-ui) ===
=== 1.7 Настройка Xray inbound для WARP (в 3x-ui) ===
Данный шаг необходим чтобы выходящий трафик от Caddy уходил через Cloudflare WARP, а не напрямую в интернет.
Данный шаг необходим чтобы выходящий трафик от Caddy уходил через Cloudflare WARP, а не напрямую в интернет.


Строка 229: Строка 366:
# Это направит весь трафик от Caddy через Cloudflare WARP
# Это направит весь трафик от Caddy через Cloudflare WARP


; Проверка WARP inbound
; Проверка WARP inbound:
:
<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
curl -s -x socks5h://127.0.0.1:24363 https://ifconfig.me
curl -s -x socks5h://127.0.0.1:24363 https://ifconfig.me
Строка 237: Строка 373:


=== 1.8 Сборка и запуск ===
=== 1.8 Сборка и запуск ===
<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
cd /opt/caddy-naive
cd /opt/caddy-naive
Строка 248: Строка 385:
# Наблюдение за логами (получение сертификата занимает ~30 секунд)
# Наблюдение за логами (получение сертификата занимает ~30 секунд)
docker compose logs -f
docker compose logs -f
</syntaxhighlight>Ожидаемый вывод в логах:<pre>
</syntaxhighlight>
 
Ожидаемый вывод в логах:
<pre>
...
...
{"level":"info","msg":"certificate obtained successfully","identifier":"your-naive-eu-domain.example.com"}
{"level":"info","msg":"certificate obtained successfully","identifier":"your-naive-eu-domain.example.com"}
Строка 255: Строка 395:


=== 1.9 Проверка EU сервера ===
=== 1.9 Проверка EU сервера ===
<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
# Проверка probe_resistance — должна вернуть HTML страницу (не ошибку прокси)
# Проверка probe_resistance — должна вернуть HTML страницу (не ошибку прокси)
Строка 262: Строка 403:
curl -s -x socks5h://127.0.0.1:24363 https://ifconfig.me
curl -s -x socks5h://127.0.0.1:24363 https://ifconfig.me
</syntaxhighlight>
</syntaxhighlight>
----
----


== Часть 2: Настройка RU сервера (Entry Node) ==
== Часть 2: Настройка RU сервера (Entry Node) ==
Цель: на RU сервере поднять два компонента:
Цель: на RU сервере поднять два компонента:
# '''Caddy''' (Docker) — принимает соединения от клиентов (роутеров), пробрасывает через локальный naive daemon
# '''Caddy''' (Docker) — принимает соединения от клиентов (роутеров), пробрасывает через локальный naive daemon
# '''naive daemon''' (systemd) — клиент, который соединяется с EU Caddy по протоколу NaïveProxy
# '''naive daemon''' (systemd) — клиент, который соединяется с EU Caddy по протоколу NaïveProxy


=== 2.1 Установка Docker ===
=== 2.1 Установка Docker ===
Те же команды, что и для EU сервера:<syntaxhighlight lang="bash">
 
Те же команды, что и для EU сервера:
 
<syntaxhighlight lang="bash">
apt-get update && apt-get upgrade -y
apt-get update && apt-get upgrade -y
apt-get install -y ca-certificates curl gnupg lsb-release
apt-get install -y ca-certificates curl gnupg lsb-release
Строка 293: Строка 438:


=== 2.2 Структура каталогов и файлы Docker ===
=== 2.2 Структура каталогов и файлы Docker ===
<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
mkdir -p /opt/caddy-naive/www
mkdir -p /opt/caddy-naive/www
cd /opt/caddy-naive
cd /opt/caddy-naive
</syntaxhighlight>Dockerfile идентичен EU серверу:<syntaxhighlight lang="bash">
</syntaxhighlight>
 
Dockerfile идентичен EU серверу:
 
<syntaxhighlight lang="bash">
cat > /opt/caddy-naive/Dockerfile << 'EOF'
cat > /opt/caddy-naive/Dockerfile << 'EOF'
FROM golang:1.25-alpine AS builder
FROM golang:1.25-alpine AS builder
Строка 310: Строка 460:
CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"]
CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"]
EOF
EOF
</syntaxhighlight><syntaxhighlight lang="bash">
</syntaxhighlight>
 
<syntaxhighlight lang="bash">
cat > /opt/caddy-naive/docker-compose.yml << 'EOF'
cat > /opt/caddy-naive/docker-compose.yml << 'EOF'
services:
services:
Строка 318: Строка 470:
     restart: unless-stopped
     restart: unless-stopped
     network_mode: host
     network_mode: host
    environment:
      - XDG_DATA_HOME=/data
     volumes:
     volumes:
       - ./Caddyfile:/etc/caddy/Caddyfile:ro
       - ./Caddyfile:/etc/caddy/Caddyfile:ro
Строка 328: Строка 482:
   caddy_logs:
   caddy_logs:
EOF
EOF
</syntaxhighlight><syntaxhighlight lang="bash">
</syntaxhighlight>
 
<syntaxhighlight lang="bash">
cat > /opt/caddy-naive/www/index.html << 'EOF'
cat > /opt/caddy-naive/www/index.html << 'EOF'
<!DOCTYPE html>
<!DOCTYPE html>
Строка 338: Строка 494:


=== 2.3 Создание Caddyfile RU сервера ===
=== 2.3 Создание Caddyfile RU сервера ===
Ключевое отличие от EU: директива <code>upstream socks5://127.0.0.1:10808</code>, которая перенаправляет входящие CONNECT-запросы в локальный naive daemon.<syntaxhighlight lang="bash">
 
Ключевое отличие от EU: директива <code>upstream socks5://127.0.0.1:10808</code>, которая перенаправляет входящие CONNECT-запросы в локальный naive daemon.
 
<syntaxhighlight lang="bash">
cat > /opt/caddy-naive/Caddyfile << 'EOF'
cat > /opt/caddy-naive/Caddyfile << 'EOF'
{
{
Строка 364: Строка 523:


=== 2.4 Сборка и запуск Caddy ===
=== 2.4 Сборка и запуск Caddy ===
<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
cd /opt/caddy-naive
cd /opt/caddy-naive
Строка 369: Строка 529:
docker compose up -d
docker compose up -d
docker compose logs -f
docker compose logs -f
</syntaxhighlight>Ожидаемый вывод:<pre>
</syntaxhighlight>
 
Ожидаемый вывод:
<pre>
{"level":"info","msg":"certificate obtained successfully","identifier":"your-naive-ru-domain.example.com"}
{"level":"info","msg":"certificate obtained successfully","identifier":"your-naive-ru-domain.example.com"}
</pre>
</pre>
Строка 376: Строка 539:


==== 2.5.1 Определение архитектуры сервера ====
==== 2.5.1 Определение архитектуры сервера ====
<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
uname -m
uname -m
Строка 382: Строка 546:


==== 2.5.2 Скачивание бинарника naive ====
==== 2.5.2 Скачивание бинарника naive ====
Перейди на страницу релизов: https://github.com/klzgrad/naiveproxy/releases
Перейди на страницу релизов: https://github.com/klzgrad/naiveproxy/releases


Найди архив для своей архитектуры. Для Ubuntu x86_64:<pre>
Найди архив для своей архитектуры. Для Ubuntu x86_64:
<pre>
naiveproxy-vXXX-linux-x86_64.tar.xz
naiveproxy-vXXX-linux-x86_64.tar.xz
</pre><syntaxhighlight lang="bash">
</pre>
 
<syntaxhighlight lang="bash">
# Скачать (замени URL на актуальный из Releases)
# Скачать (замени URL на актуальный из Releases)
cd /tmp
cd /tmp
Строка 405: Строка 573:


==== 2.5.3 Создание конфигурации naive daemon ====
==== 2.5.3 Создание конфигурации naive daemon ====
<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
mkdir -p /etc/naive
mkdir -p /etc/naive
Строка 417: Строка 586:
</syntaxhighlight>
</syntaxhighlight>


; Что происходит
; Что происходит:
:* <code>listen</code> — naive слушает SOCKS5 на localhost:10808 (именно сюда смотрит upstream в Caddyfile RU)
:* <code>listen</code> — naive слушает SOCKS5 на localhost:10808 (именно сюда смотрит upstream в Caddyfile RU)
:* <code>proxy</code> — подключается к EU Caddy по протоколу NaïveProxy (HTTP/2 CONNECT over TLS, трафик неотличим от обычного Chrome HTTPS)
:* <code>proxy</code> — подключается к EU Caddy по протоколу NaïveProxy (HTTP/2 CONNECT over TLS, трафик неотличим от обычного Chrome HTTPS)


==== 2.5.4 Создание systemd-сервиса ====
==== 2.5.4 Создание systemd-сервиса ====
<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
cat > /etc/systemd/system/naive.service << 'EOF'
cat > /etc/systemd/system/naive.service << 'EOF'
Строка 437: Строка 607:
WantedBy=multi-user.target
WantedBy=multi-user.target
EOF
EOF
</syntaxhighlight><syntaxhighlight lang="bash">
</syntaxhighlight>
 
<syntaxhighlight lang="bash">
# Активировать и запустить
# Активировать и запустить
systemctl daemon-reload
systemctl daemon-reload
Строка 445: Строка 617:
# Проверить статус
# Проверить статус
systemctl status naive
systemctl status naive
</syntaxhighlight>Ожидаемый вывод:<pre>
</syntaxhighlight>
 
Ожидаемый вывод:
<pre>
● naive.service - NaïveProxy client daemon
● naive.service - NaïveProxy client daemon
     Loaded: loaded (/etc/systemd/system/naive.service; enabled)
     Loaded: loaded (/etc/systemd/system/naive.service; enabled)
Строка 452: Строка 627:


=== 2.6 Проверка цепочки с RU сервера ===
=== 2.6 Проверка цепочки с RU сервера ===
<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
# Тест HTTP через naive daemon → EU → WARP
# Тест HTTP через naive daemon → EU → WARP
Строка 461: Строка 637:
# Должен вернуть тот же результат
# Должен вернуть тот же результат
</syntaxhighlight>
</syntaxhighlight>
----
----


== Часть 3: Настройка OpenWrt роутера ==
== Часть 3: Настройка OpenWrt роутера ==
Цель: запустить naive как клиент на роутере, подключить podkop для избирательной маршрутизации через RU Caddy.
Цель: запустить naive как клиент на роутере, подключить podkop для избирательной маршрутизации через RU Caddy.


=== 3.1 Определение архитектуры роутера ===
=== 3.1 Определение архитектуры роутера ===
<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
uname -m
uname -m
# aarch64 → нужен пакет openwrt-aarch64_cortex-a53-static
# aarch64 → нужен пакет openwrt-aarch64_cortex-a53-static
# или x86_64, mipsel, armv7l — зависит от модели роутера
# или x86_64, mipsel, armv7l — зависит от модели роутера
</syntaxhighlight><syntaxhighlight lang="bash">
</syntaxhighlight>
 
<syntaxhighlight lang="bash">
# Подробнее об архитектуре
# Подробнее об архитектуре
cat /etc/openwrt_release | grep ARCH
cat /etc/openwrt_release | grep ARCH
Строка 477: Строка 658:


=== 3.2 Скачивание бинарника naive для OpenWrt ===
=== 3.2 Скачивание бинарника naive для OpenWrt ===
На странице релизов https://github.com/klzgrad/naiveproxy/releases найди архив:<pre>
 
На странице релизов https://github.com/klzgrad/naiveproxy/releases найди архив:
<pre>
naiveproxy-vXXX-openwrt-aarch64_cortex-a53.tar.xz
naiveproxy-vXXX-openwrt-aarch64_cortex-a53.tar.xz
</pre>(замени архитектуру на свою)<syntaxhighlight lang="bash">
</pre>
(замени архитектуру на свою)
 
<syntaxhighlight lang="bash">
# Создать каталог
# Создать каталог
mkdir -p /etc/naive
mkdir -p /etc/naive
Строка 488: Строка 674:
</syntaxhighlight>
</syntaxhighlight>


; Важно
; Важно: OpenWrt использует BusyBox tar, который не поддерживает .xz напрямую.
: OpenWrt использует BusyBox tar, который не поддерживает .xz напрямую.
Используй pipe с xzcat:


Используй pipe с xzcat:<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
# Если xz не установлен:
# Если xz не установлен:
opkg update && opkg install xz
opkg update && opkg install xz
Строка 507: Строка 693:


=== 3.3 Создание конфигурации ===
=== 3.3 Создание конфигурации ===
<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
cat > /etc/naive/config.json << 'EOF'
cat > /etc/naive/config.json << 'EOF'
Строка 517: Строка 704:
</syntaxhighlight>
</syntaxhighlight>


; Чем этот конфиг отличается от конфига на RU сервере
; Чем этот конфиг отличается от конфига на RU сервере:
:* Роутер подключается к RU Caddy (не к EU напрямую) — это первое звено каскада
:* Роутер подключается к RU Caddy (не к EU напрямую) — это первое звено каскада
:* Слушает на порту 1080 (стандарт для SOCKS5, podkop по умолчанию ожидает его)
:* Слушает на порту 1080 (стандарт для SOCKS5, podkop по умолчанию ожидает его)


=== 3.4 Создание init-скрипта procd ===
=== 3.4 Создание init-скрипта procd ===
<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
cat > /etc/init.d/naive << 'EOF'
cat > /etc/init.d/naive << 'EOF'
Строка 547: Строка 735:


=== 3.5 Запуск и автозапуск ===
=== 3.5 Запуск и автозапуск ===
<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
# Включить автозапуск
# Включить автозапуск
Строка 559: Строка 748:


=== 3.6 Проверка ===
=== 3.6 Проверка ===
<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
# Тест подключения через наивный прокси
# Тест подключения через наивный прокси
Строка 566: Строка 756:


=== 3.7 Настройка podkop ===
=== 3.7 Настройка podkop ===
В интерфейсе podkop (LuCI или CLI):
В интерфейсе podkop (LuCI или CLI):


Строка 575: Строка 766:


=== 3.8 Управление сервисом на роутере ===
=== 3.8 Управление сервисом на роутере ===
{| class="wikitable"
{| class="wikitable"
!Команда
!Действие
|-
|-
|<code>/etc/init.d/naive start</code>
! Команда !! Действие
|Запустить
|-
| <code>/etc/init.d/naive start</code> || Запустить
|-
|-
|<code>/etc/init.d/naive stop</code>
| <code>/etc/init.d/naive stop</code> || Остановить
|Остановить
|-
|-
|<code>/etc/init.d/naive restart</code>
| <code>/etc/init.d/naive restart</code> || Перезапустить
|Перезапустить
|-
|-
|<code>/etc/init.d/naive status</code>
| <code>/etc/init.d/naive status</code> || Статус
|Статус
|-
|-
|<code>/etc/init.d/naive enable</code>
| <code>/etc/init.d/naive enable</code> || Добавить в автозапуск
|Добавить в автозапуск
|-
|-
|<code>/etc/init.d/naive disable</code>
| <code>/etc/init.d/naive disable</code> || Убрать из автозапуска
|Убрать из автозапуска
|}
|}
----
----


== Часть 4: Управление пользователями (утилита naive-users) ==
== Часть 4: Управление пользователями (утилита naive-users) ==
На RU сервере доступна CLI-утилита для управления пользователями Caddy без ручного редактирования Caddyfile.


=== 4.1 Установка утилиты ===
=== Что это и зачем ===
Скопируй файл <code>naive-users.sh</code> с рабочей машины на сервер:<syntaxhighlight lang="bash">
 
В рамках данного сетапа была написана специализированная CLI-утилита <code>naive-users.sh</code> — интерактивное меню для управления клиентами NaïveProxy непосредственно на Entry Node (RU сервере).
 
Проблема, которую она решает: Caddy хранит учётные данные клиентов (логин + пароль) в виде plain-text строк <code>basic_auth</code> внутри <code>Caddyfile</code>. Добавление, удаление или просмотр пользователей вручную требует:
# зайти на сервер,
# открыть <code>/opt/caddy-naive/Caddyfile</code> в редакторе,
# найти нужные строки,
# не ошибиться в формате,
# перезапустить Caddy через <code>docker compose restart</code>.
 
Утилита автоматизирует весь этот процесс. Она работает непосредственно с <code>Caddyfile</code>, перезапускает Caddy после каждого изменения и сразу выводит готовый <code>config.json</code> для нового клиента.
 
'''Ключевые возможности:'''
 
{| class="wikitable"
|-
! Функция !! Описание
|-
| Список клиентов || Показывает всех существующих пользователей из Caddyfile
|-
| Добавить клиента || Запрашивает имя; пароль вводится вручную или генерируется автоматически (20 символов, <code>openssl rand</code>)
|-
| Удалить клиента || Выбор из списка + подтверждение, после — автоперезапуск Caddy
|-
| Показать конфиг || Читает логин/пароль из Caddyfile и выводит готовый <code>config.json</code> для ПК или роутера
|}
 
Caddy поддерживает произвольное количество <code>basic_auth</code> записей — каждый клиент имеет собственные логин и пароль, но все используют одну и ту же цепочку:
<pre>
Entry Node Caddy → naive daemon → Exit Node Caddy → WARP → Интернет
</pre>
 
; Важно: отзыв доступа происходит мгновенно — удалённый пользователь получит ошибку аутентификации при следующей попытке соединения, без перезапуска клиентского устройства.
 
=== 4.1 Расположение файла ===
 
Скрипт хранится в репозитории по пути <code>scripts/naive-users.sh</code>. Файл изначально сохранён с Unix-окончаниями строк (LF) — дополнительная конвертация не требуется.
 
=== 4.2 Установка на сервер ===
 
Скопируй файл с рабочей машины на Entry Node сервер:
 
<syntaxhighlight lang="bash">
# На рабочей машине (Windows PowerShell):
# На рабочей машине (Windows PowerShell):
scp naive-users.sh root@YOUR_RU_SERVER_IP:/usr/local/bin/naive-users.sh
scp scripts/naive-users.sh root@YOUR_RU_SERVER_IP:/usr/local/bin/naive-users.sh
</syntaxhighlight>На RU сервере:<syntaxhighlight lang="bash">
</syntaxhighlight>
# Убрать Windows-окончания строк (CRLF → LF) если файл создан на Windows
 
sed -i 's/\r//' /usr/local/bin/naive-users.sh
На сервере — сделать исполняемым и (опционально) создать короткий алиас:


# Сделать исполняемым
<syntaxhighlight lang="bash">
chmod +x /usr/local/bin/naive-users.sh
chmod +x /usr/local/bin/naive-users.sh


# Создать короткий алиас (опционально)
# Опционально: алиас для быстрого запуска
echo 'alias naive-users="sudo bash /usr/local/bin/naive-users.sh"' >> ~/.bashrc
echo 'alias naive-users="sudo bash /usr/local/bin/naive-users.sh"' >> ~/.bashrc
source ~/.bashrc
source ~/.bashrc
</syntaxhighlight>
</syntaxhighlight>


=== 4.2 Запуск ===
; Внимание: перед запуском отредактируй переменную <code>DOMAIN</code> внутри скрипта — укажи свой реальный домен Entry Node Caddy (строка <code>DOMAIN="..."</code> в начале файла).
 
=== 4.3 Запуск ===
 
<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
sudo bash /usr/local/bin/naive-users.sh
sudo bash /usr/local/bin/naive-users.sh
# или после создания алиаса:
# или, если создан алиас:
naive-users
naive-users
</syntaxhighlight>Главное меню:<pre>
</syntaxhighlight>
 
Утилита требует прав root: она читает и изменяет <code>/opt/caddy-naive/Caddyfile</code> и перезапускает Docker-контейнер.
 
Главное меню:
<pre>
╔══════════════════════════════════════╗
╔══════════════════════════════════════╗
║  NaïveProxy User Manager (RU)      ║
║  NaïveProxy User Manager (RU)      ║
Строка 638: Строка 874:
</pre>
</pre>


=== 4.3 Функции утилиты ===
=== 4.4 Примеры работы ===


==== Список клиентов ====
==== Список клиентов ====
Показывает нумерованный список всех <code>basic_auth</code> записей из Caddyfile.<pre>
 
Показывает нумерованный список всех <code>basic_auth</code>-записей из Caddyfile.
 
<pre>
=== Список клиентов ===
=== Список клиентов ===
   1. alice
   1. alice
Строка 651: Строка 890:


==== Добавить клиента ====
==== Добавить клиента ====
Запрашивает имя. Пароль можно ввести вручную или нажать Enter — пароль сгенерируется автоматически (20 символов, криптостойкий). После добавления Caddy автоматически перезапускается и сразу выводится готовый <code>config.json</code> для клиента.<pre>
 
Запрашивает имя. Пароль можно ввести вручную или нажать Enter — пароль сгенерируется автоматически (20 символов, криптостойкий, на базе <code>openssl rand</code>). После добавления Caddy автоматически перезапускается и сразу выводится готовый <code>config.json</code> для клиента.
 
<pre>
=== Добавить клиента ===
=== Добавить клиента ===
Имя пользователя: alice
Имя пользователя: alice
Строка 676: Строка 918:


==== Удалить клиента ====
==== Удалить клиента ====
Показывает список, просит указать номер, требует подтверждение (y/N), затем удаляет строку из Caddyfile и перезапускает Caddy.
 
Показывает список, просит указать номер, требует подтверждение <code>(y/N)</code>, затем удаляет строку <code>basic_auth</code> из Caddyfile и перезапускает Caddy.


==== Показать конфиг клиента ====
==== Показать конфиг клиента ====
Выбрать существующего пользователя из списка — утилита прочитает логин и пароль из Caddyfile и выведет готовый <code>config.json</code>, который нужно скопировать на клиентское устройство.
Выбрать существующего пользователя из списка — утилита прочитает логин и пароль из Caddyfile и выведет готовый <code>config.json</code>, который нужно скопировать на клиентское устройство.


=== 4.4 Как добавить нескольких пользователей ===
=== 4.5 Исходный код ===
Caddy поддерживает произвольное количество <code>basic_auth</code> записей. Каждый пользователь имеет уникальные логин и пароль, но все они используют одну и ту же цепочку:<pre>
 
RU Caddy → naive daemon → EU Caddy → WARP → Интернет
<div class="toccolours mw-collapsible mw-collapsed" style="width:100%">
</pre>
<div style="font-weight:bold;line-height:1.6;padding:4px 0">▶ naive-users.sh — показать исходный код</div>
<div class="mw-collapsible-content">
 
<syntaxhighlight lang="bash">
#!/bin/bash
# =============================================================
# naive-users — управление пользователями NaïveProxy на RU сервере
# Caddyfile: /opt/caddy-naive/Caddyfile
# =============================================================
 
CADDYFILE="/opt/caddy-naive/Caddyfile"
CADDY_DIR="/opt/caddy-naive"
# Подставьте свой домен (не коммитьте реальные значения в публичный репозиторий)
DOMAIN="your-naive-ru-domain.example.com"
 
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m'
 
# --- Вспомогательные функции ---
 
check_root() {
    if [[ $EUID -ne 0 ]]; then
        echo -e "${RED}Ошибка: запусти скрипт от root (sudo bash naive-users.sh)${NC}"
        exit 1
    fi
}
 
check_caddyfile() {
    if [[ ! -f "$CADDYFILE" ]]; then
        echo -e "${RED}Ошибка: Caddyfile не найден: $CADDYFILE${NC}"
        exit 1
    fi
}
 
# Возвращает массив пользователей из Caddyfile
get_users() {
    grep -oP '(?<=basic_auth )\S+' "$CADDYFILE"
}
 
# Возвращает пароль пользователя
get_password() {
    local user="$1"
    grep "basic_auth $user " "$CADDYFILE" | awk '{print $3}'
}
 
restart_caddy() {
    echo -e "${YELLOW}Перезапуск Caddy...${NC}"
    cd "$CADDY_DIR" && docker compose restart caddy-naive > /dev/null 2>&1
    if [[ $? -eq 0 ]]; then
        echo -e "${GREEN}Caddy перезапущен успешно.${NC}"
    else
        echo -e "${RED}Ошибка при перезапуске Caddy. Проверь: cd $CADDY_DIR && docker compose logs${NC}"
    fi
}
 
press_enter() {
    echo ""
    read -rp "Нажми Enter для возврата в меню..."
}
 
# --- Функции меню ---
 
list_users() {
    echo ""
    echo -e "${BOLD}${CYAN}=== Список клиентов ===${NC}"
    mapfile -t users < <(get_users)
    if [[ ${#users[@]} -eq 0 ]]; then
        echo -e "${YELLOW}Клиентов нет.${NC}"
    else
        for i in "${!users[@]}"; do
            echo -e "  ${BOLD}$((i+1)).${NC} ${users[$i]}"
        done
        echo ""
        echo -e "  Всего: ${#users[@]} клиент(ов)"
    fi
    press_enter
}
 
add_user() {
    echo ""
    echo -e "${BOLD}${CYAN}=== Добавить клиента ===${NC}"
 
    read -rp "Имя пользователя: " username
    username=$(echo "$username" | tr -d ' ')
 
    if [[ -z "$username" ]]; then
        echo -e "${RED}Имя не может быть пустым.${NC}"
        press_enter
        return
    fi
 
    # Проверка — уже существует?
    if grep -q "basic_auth $username " "$CADDYFILE"; then
        echo -e "${RED}Пользователь '$username' уже существует.${NC}"
        press_enter
        return
    fi
 
    read -rp "Пароль (Enter = сгенерировать автоматически): " password
    if [[ -z "$password" ]]; then
        password=$(openssl rand -base64 16 | tr -d '=/+' | head -c 20)
        echo -e "  Сгенерирован пароль: ${BOLD}${password}${NC}"
    fi
 
    # Вставить строку basic_auth перед первой строкой upstream или probe_resistance
    sed -i "/probe_resistance/i\\    basic_auth $username $password" "$CADDYFILE"
 
    echo -e "${GREEN}Пользователь '${username}' добавлен.${NC}"
    restart_caddy
 
    echo ""
    echo -e "${BOLD}Конфиг для клиента:${NC}"
    print_config_for "$username" "$password"
    press_enter
}
 
delete_user() {
    echo ""
    echo -e "${BOLD}${CYAN}=== Удалить клиента ===${NC}"
    mapfile -t users < <(get_users)
 
    if [[ ${#users[@]} -eq 0 ]]; then
        echo -e "${YELLOW}Нет клиентов для удаления.${NC}"
        press_enter
        return
    fi
 
    for i in "${!users[@]}"; do
        echo -e "  ${BOLD}$((i+1)).${NC} ${users[$i]}"
    done
    echo "  0. Отмена"
    echo ""
    read -rp "Выбери номер для удаления: " choice
 
    if [[ "$choice" == "0" || -z "$choice" ]]; then
        return
    fi
 
    if ! [[ "$choice" =~ ^[0-9]+$ ]] || (( choice < 1 || choice > ${#users[@]} )); then
        echo -e "${RED}Неверный номер.${NC}"
        press_enter
        return
    fi
 
    target="${users[$((choice-1))]}"
    read -rp "Удалить пользователя '${target}'? (y/N): " confirm
    if [[ "$confirm" =~ ^[Yy]$ ]]; then
        sed -i "/basic_auth $target /d" "$CADDYFILE"
        echo -e "${GREEN}Пользователь '${target}' удалён.${NC}"
        restart_caddy
    else
        echo "Отменено."
    fi
    press_enter
}
 
print_config_for() {
    local user="$1"
    local pass="$2"
    echo ""
    echo -e "${BOLD}┌─────────────────────────────────────────────┐${NC}"
    echo -e "${BOLD}│  config.json для naive (ПК / роутер)        │${NC}"
    echo -e "${BOLD}└─────────────────────────────────────────────┘${NC}"
    echo '{
  "listen": "socks://127.0.0.1:1080",
  "proxy": "https://'"$user"':'"$pass"'@'"$DOMAIN"'",
  "log": ""
}'
    echo ""
    echo -e "${CYAN}Путь на роутере OpenWrt:${NC}  /etc/naive/config.json"
    echo -e "${CYAN}Путь на Windows ПК:${NC}      рядом с naive.exe"
    echo -e "${CYAN}SOCKS5 адрес для podkop:${NC}  socks5://127.0.0.1:1080"
}
 
show_config() {
    echo ""
    echo -e "${BOLD}${CYAN}=== Показать конфиг клиента ===${NC}"
    mapfile -t users < <(get_users)
 
    if [[ ${#users[@]} -eq 0 ]]; then
        echo -e "${YELLOW}Нет клиентов.${NC}"
        press_enter
        return
    fi
 
    for i in "${!users[@]}"; do
        echo -e "  ${BOLD}$((i+1)).${NC} ${users[$i]}"
    done
    echo "  0. Отмена"
    echo ""
    read -rp "Выбери номер клиента: " choice
 
    if [[ "$choice" == "0" || -z "$choice" ]]; then
        return
    fi
 
    if ! [[ "$choice" =~ ^[0-9]+$ ]] || (( choice < 1 || choice > ${#users[@]} )); then
        echo -e "${RED}Неверный номер.${NC}"
        press_enter
        return
    fi
 
    local user="${users[$((choice-1))]}"
    local pass
    pass=$(get_password "$user")
    echo -e "Клиент: ${BOLD}${user}${NC}"
    print_config_for "$user" "$pass"
    press_enter
}
 
# --- Главное меню ---
 
main_menu() {
    check_root
    check_caddyfile
 
    while true; do
        clear
        echo -e "${BOLD}${CYAN}"
        echo "╔══════════════════════════════════════╗"
        echo "║  NaïveProxy User Manager (RU)      ║"
        echo "║  Домен: $DOMAIN  ║"
        echo "╚══════════════════════════════════════╝"
        echo -e "${NC}"
 
        mapfile -t users < <(get_users)
        echo -e "  Активных клиентов: ${BOLD}${#users[@]}${NC}"
        echo ""
        echo -e "  ${BOLD}1.${NC} Список клиентов"
        echo -e "  ${BOLD}2.${NC} Добавить клиента"
        echo -e "  ${BOLD}3.${NC} Удалить клиента"
        echo -e "  ${BOLD}4.${NC} Показать конфиг клиента"
        echo -e "  ${BOLD}0.${NC} Выход"
        echo ""
        read -rp "Выбор: " option
 
        case "$option" in
            1) list_users ;;
            2) add_user ;;
            3) delete_user ;;
            4) show_config ;;
            0) echo "Выход."; exit 0 ;;
            *) echo -e "${RED}Неверный выбор.${NC}"; sleep 1 ;;
        esac
    done
}
 
main_menu
</syntaxhighlight>


; Важно
</div>
: отзыв доступа происходит мгновенно — достаточно удалить пользователя через утилиту и Caddy перезапустится. Клиент получит ошибку аутентификации при следующей попытке соединения.
</div>


----
----
Строка 694: Строка 1190:


=== 5.1 Пошаговая проверка ===
=== 5.1 Пошаговая проверка ===
{| class="wikitable"
{| class="wikitable"
!Шаг
!Команда
!Где выполнять
!Ожидаемый результат
|-
|-
|1
! Шаг !! Команда !! Где выполнять !! Ожидаемый результат
|<code>curl -s -x socks5h://127.0.0.1:24363 https://ifconfig.me</code>
|-
|EU сервер
| 1 || <code>curl -s -x socks5h://127.0.0.1:24363 https://ifconfig.me</code> || EU сервер || Cloudflare WARP IP (104.x.x.x)
|Cloudflare WARP IP (104.x.x.x)
|-
|-
|2
| 2 || <code>curl -s -x socks5h://127.0.0.1:10808 https://ifconfig.me</code> || RU сервер || Тот же WARP IP
|<code>curl -s -x socks5h://127.0.0.1:10808 https://ifconfig.me</code>
|RU сервер
|Тот же WARP IP
|-
|-
|3
| 3 || <code>curl -s -x socks5h://127.0.0.1:1080 https://ifconfig.me</code> || OpenWrt роутер || Тот же WARP IP
|<code>curl -s -x socks5h://127.0.0.1:1080 https://ifconfig.me</code>
|OpenWrt роутер
|Тот же WARP IP
|-
|-
|4
| 4 || Открыть браузер через podkop || Устройство в сети || IP отличается от домашнего
|Открыть браузер через podkop
|Устройство в сети
|IP отличается от домашнего
|}
|}


=== 5.2 Проверка probe_resistance ===
=== 5.2 Проверка probe_resistance ===
<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
# Запрос без авторизации — должна вернуться HTML-страница, а не ошибка прокси
# Запрос без авторизации — должна вернуться HTML-страница, а не ошибка прокси
Строка 729: Строка 1213:


=== 5.3 Просмотр логов ===
=== 5.3 Просмотр логов ===
<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
# Caddy (EU или RU сервер)
# Caddy (EU или RU сервер)
Строка 741: Строка 1226:


=== 5.4 Перезапуск компонентов ===
=== 5.4 Перезапуск компонентов ===
{| class="wikitable"
{| class="wikitable"
!Компонент
!Команда
|-
|-
|Caddy EU
! Компонент !! Команда
|<code>cd /opt/caddy-naive && docker compose restart</code>
|-
| Caddy EU || <code>cd /opt/caddy-naive && docker compose restart</code>
|-
|-
|Caddy RU
| Caddy RU || <code>cd /opt/caddy-naive && docker compose restart</code>
|<code>cd /opt/caddy-naive && docker compose restart</code>
|-
|-
|naive daemon RU
| naive daemon RU || <code>systemctl restart naive</code>
|<code>systemctl restart naive</code>
|-
|-
|naive daemon OpenWrt
| naive daemon OpenWrt || <code>/etc/init.d/naive restart</code>
|<code>/etc/init.d/naive restart</code>
|}
|}
----
----


Строка 762: Строка 1245:


=== EU сервер ===
=== EU сервер ===
<pre>
<pre>
/opt/caddy-naive/
/opt/caddy-naive/
Строка 772: Строка 1256:


=== RU сервер ===
=== RU сервер ===
<pre>
<pre>
/opt/caddy-naive/
/opt/caddy-naive/
Строка 787: Строка 1272:


=== OpenWrt роутер ===
=== OpenWrt роутер ===
<pre>
<pre>
/usr/bin/naive              ← бинарник naive (архитектура роутера)
/usr/bin/naive              ← бинарник naive (архитектура роутера)
Строка 792: Строка 1278:
/etc/init.d/naive          ← автозапуск через procd (START=95)
/etc/init.d/naive          ← автозапуск через procd (START=95)
</pre>
</pre>
----
----


== Приложение: Частые ошибки ==
== Приложение: Частые ошибки ==
{| class="wikitable"
{| class="wikitable"
!Ошибка
!Причина
!Решение
|-
|-
|<code>requires go >= 1.25.0</code> при сборке
! Ошибка !! Причина !! Решение
|Системный Go устарел
|-
|Dockerfile уже использует <code>golang:1.25-alpine</code> — ошибка не возникнет
| <code>requires go >= 1.25.0</code> при сборке || Системный Go устарел || Dockerfile уже использует <code>golang:1.25-alpine</code> — ошибка не возникнет
|-
|-
|<code>wrong version number</code> в curl через Caddy
| <code>wrong version number</code> в curl через Caddy || RU Caddy использует <code>upstream https://</code> вместо SOCKS5 || Заменить на <code>upstream socks5://127.0.0.1:10808</code>
|RU Caddy использует <code>upstream https://</code> вместо SOCKS5
|Заменить на <code>upstream socks5://127.0.0.1:10808</code>
|-
|-
|<code>Content-Length: 0</code> для HTTP GET через Caddy
| <code>Content-Length: 0</code> для HTTP GET через Caddy || Нормальное поведение forwardproxy для не-CONNECT запросов || Не является ошибкой
|Нормальное поведение forwardproxy для не-CONNECT запросов
|Не является ошибкой
|-
|-
|<code>tar: invalid tar magic</code> на OpenWrt
| <code>tar: invalid tar magic</code> на OpenWrt || BusyBox tar не поддерживает .xz || Использовать <code>xzcat file.tar.xz | tar -xf -</code>
|BusyBox tar не поддерживает .xz
|tar -xf -
|-
|-
|<code>$'\r': command not found</code> в bash-скрипте
| <code>$'\r': command not found</code> в bash-скрипте || Windows CRLF окончания строк || Выполнить <code>sed -i 's/\r//' /путь/к/скрипту.sh</code>
|Windows CRLF окончания строк
|Выполнить <code>sed -i 's/\r//' /путь/к/скрипту.sh</code>
|-
|-
|Caddy не получает сертификат
| Caddy не получает сертификат || Порт 80 занят другим сервисом || Проверить <code>ss -tlnp | grep :80</code>, освободить порт
|Порт 80 занят другим сервисом
|grep :80, освободить порт
|-
|-
|naive daemon не подключается к EU
| naive daemon не подключается к EU || Неверные креды или домен в config.json || Проверить <code>journalctl -u naive</code>, перепроверить Caddyfile EU
|Неверные креды или домен в config.json
|Проверить <code>journalctl -u naive</code>, перепроверить Caddyfile EU
|-
|-
|probe_resistance возвращает ошибку прокси
| probe_resistance возвращает ошибку прокси || Отсутствует <code>file_server</code> блок в Caddyfile || Добавить <code>file_server { root /var/www/html }</code>
|Отсутствует <code>file_server</code> блок в Caddyfile
|Добавить <code>file_server { root /var/www/html }</code>
|}
|}

Текущая версия от 08:05, 26 апреля 2026

NaïveProxy: установка полной каскадной цепочки

Что такое NaïveProxy и зачем он нужен

Проблема: как работают системы блокировок

Современные системы глубокой инспекции пакетов (DPI — Deep Packet Inspection), которые применяются интернет-провайдерами и регуляторами по всему миру, умеют анализировать не только адреса, но и характер трафика. Даже если трафик зашифрован, по поведению соединения можно определить, что это не обычный браузер, а программа для обхода ограничений (VPN, Shadowsocks, обычный SOCKS5-прокси и т.д.).

Принцип прост: DPI «смотрит» на то, как именно клиент общается с сервером:

  • сколько байт летит в каждую сторону,
  • с какими временны́ми интервалами,
  • какой у соединения «fingerprint» (цифровой отпечаток),
  • как выглядит TLS-рукопожатие.

Обычный HTTPS-прокси легко выявляется, потому что программа-клиент общается иначе, чем реальный браузер.

Решение: маскировка под браузер Chrome

NaïveProxy решает эту проблему принципиально: клиентская часть (naive binary) притворяется настоящим браузером Chrome, который делает обычные HTTPS-запросы. Серверная часть (Caddy с плагином forwardproxy) притворяется обычным веб-сервером.

Ключевая особенность — NaïveProxy использует встроенный сетевой стек Chromium. Это значит, что трафик неотличим от реального Chrome не только по содержимому, но и по поведению на уровне TCP/TLS: те же задержки, те же размеры пакетов, тот же TLS fingerprint.

Протокол Как выглядит для DPI Устойчивость к блокировке
Обычный VPN (WireGuard, OpenVPN) Характерный шаблон UDP/TCP Низкая — легко заблокировать по fingerprint
Shadowsocks Случайный зашифрованный поток Средняя — выявляется по энтропии и поведению
VLESS / VMess (без маскировки) TLS, но нестандартный fingerprint Средняя — детектируется active probing
VLESS + REALITY TLS с реальным сертификатом стороннего сайта Высокая — но заметен при активном зондировании
NaïveProxy Неотличим от Chrome HTTPS Очень высокая — устойчив к DPI и активному зондированию

Как работает NaïveProxy технически

NaïveProxy использует протокол HTTP/2 CONNECT для туннелирования трафика. Упрощённо:

  1. Клиент (naive binary на вашем устройстве) открывает HTTPS-соединение с сервером — точь-в-точь как Chrome.
  2. Внутри этого HTTPS-соединения по протоколу HTTP/2 CONNECT прокидывается туннель для вашего трафика.
  3. Сервер (Caddy + forwardproxy) обрабатывает запросы как обычный веб-сервер, а туннельные запросы перенаправляет дальше.
  4. Если кто-то «постучится» на сервер без правильных credentials — сервер ответит как обычный сайт (probe_resistance — защита от зондирования).

Каскадная цепочка: зачем два сервера

В данном решении используется каскад из двух серверов: Entry Node (точка входа, ближайший к клиентам) и Exit Node (точка выхода в интернет). Это сделано намеренно:

  • Снижение риска для клиента: клиенты подключаются только к ближайшему серверу-входу — подключение к локальному узлу менее заметно, чем прямое соединение с зарубежным сервером.
  • Гибкость маршрутизации: Entry Node может обслуживать разных клиентов по разным протоколам (VLESS, Trojan, NaïveProxy) и перенаправлять их на Exit Node.
  • Дополнительный слой анонимности: Exit Node выходит в интернет через Cloudflare WARP — конечный сайт видит IP Cloudflare, а не IP вашего сервера.

Схема конкретного решения

Компоненты инфраструктуры

Компонент Роль IP / Адрес ОС
Устройства домашней сети Конечные пользователи Любые
Роутер Xiaomi Redmi AX6000 Прозрачный прокси для всей сети OpenWrt 24.10 (aarch64)
RU сервер (Entry Node) Точка входа (Entry Node) YOUR_RU_SERVER_IP / your-naive-ru-domain.example.com Ubuntu 24.04
EU сервер (Exit Node) Точка выхода (Exit Node) YOUR_EU_SERVER_IP / your-naive-eu-domain.example.com Ubuntu 24.04
Cloudflare WARP Финальный выход в интернет IP Cloudflare

Полная схема прохождения трафика

Протоколы на каждом участке цепочки

Участок Протокол Шифрование Что видит наблюдатель
Устройство → Роутер Обычный TCP/UDP Нет (локальная сеть) Локальный трафик
Роутер → RU Caddy HTTPS HTTP/2 (NaïveProxy) TLS 1.3 (Let's Encrypt) Браузер Chrome обращается к сайту
RU naive → EU Caddy HTTPS HTTP/2 (NaïveProxy) TLS 1.3 (Let's Encrypt) Браузер Chrome обращается к сайту
EU Caddy → 3x-ui inbound SOCKS5 (localhost) — (локально) Только на EU сервере
EU 3x-ui → WARP WireGuard (gVisor) WireGuard Зашифрованный WireGuard к Cloudflare
WARP → Интернет Обычный TCP/UDP IP Cloudflare

Дополнительные каналы (параллельно NaïveProxy)

Канал Протокол Маршрут Статус
3x-ui каскад (основной) VLESS / REALITY Клиенты → RU 3x-ui → EU 3x-ui → Интернет ✅ Активен
NaïveProxy каскад (этот гайд) HTTP/2 naïve Роутер/ПК → RU Caddy → EU Caddy → WARP ✅ Активен
MTProto (Telegram) MTProto Клиенты Telegram → EU :8443, :2443 ✅ Активен
Hysteria2 QUIC/UDP → EU ⏸ Конфиг есть, сервис не запущен

Данный гайд описывает пошаговое развёртывание каскадного прокси-сервера на базе протокола NaïveProxy. Трафик проходит полную цепочку:

Устройства → OpenWrt роутер (naive) → RU сервер (Caddy + naive) → EU сервер (Caddy) → WARP → Интернет

На серверах предполагается, что 3x-ui уже установлен и работает.

Предварительные требования

Инфраструктура

Компонент Требования
EU сервер Ubuntu 24.04, публичный IP, домен с A-записью → IP сервера
RU сервер Ubuntu 24.04, публичный IP, домен с A-записью → IP сервера
OpenWrt роутер OpenWrt 22.03+, архитектура aarch64_cortex-a53 или другая
Порт 80 и 443 Должны быть свободны на обоих серверах (3x-ui не занимает эти порты)

DNS-записи (создать до начала)

Запись Тип Значение
your-naive-ru-domain.example.com A IP RU сервера
your-naive-eu-domain.example.com A IP EU сервера

Используемые заглушки

В данном руководстве применяются следующие заглушки. Замени их реальными значениями:

Заглушка Описание
YOUR_RU_SERVER_IP Публичный IP RU сервера
YOUR_EU_SERVER_IP Публичный IP EU сервера
your-naive-ru-domain.example.com Домен для RU сервера (Caddy)
your-naive-eu-domain.example.com Домен для EU сервера (Caddy)
YOUR_EMAIL@example.com Email для Let's Encrypt (уведомления об истечении сертификата)
RU_PROXY_USER Логин пользователя для RU Caddy
RU_PROXY_PASSWORD Пароль пользователя для RU Caddy
EU_PROXY_USER Логин пользователя для EU Caddy
EU_PROXY_PASSWORD Пароль пользователя для EU Caddy

Часть 1: Настройка EU сервера (Exit Node)

Цель: развернуть Caddy с плагином forwardproxy (NaïveProxy) в Docker. Caddy будет принимать зашифрованные соединения от RU сервера и выпускать трафик через WARP.

1.1 Установка Docker

Выполни на EU сервере:

# Обновить пакеты
apt-get update && apt-get upgrade -y

# Установить зависимости
apt-get install -y ca-certificates curl gnupg lsb-release

# Добавить GPG-ключ Docker
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
  -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc

# Добавить репозиторий
echo "deb [arch=$(dpkg --print-architecture) \
  signed-by=/etc/apt/keyrings/docker.asc] \
  https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
  > /etc/apt/sources.list.d/docker.list

# Установить Docker
apt-get update
apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin

# Проверить
docker --version
docker compose version

Ожидаемый вывод:

Docker version 27.x.x, build ...
Docker Compose version v2.x.x

1.2 Создание структуры каталогов

mkdir -p /opt/caddy-naive/www
cd /opt/caddy-naive

1.3 Создание Dockerfile

Caddy собирается с нестандартным плагином forwardproxy (форк klzgrad/forwardproxy@naive). Используется Go 1.25+ — обязательное требование для текущей версии Caddy.

cat > /opt/caddy-naive/Dockerfile << 'EOF'
FROM golang:1.25-alpine AS builder
RUN apk add --no-cache git
RUN go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
RUN xcaddy build \
    --with github.com/caddyserver/forwardproxy=github.com/klzgrad/forwardproxy@naive

FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
COPY --from=builder /go/caddy /usr/local/bin/caddy
EXPOSE 80 443
CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"]
EOF
Почему Go 1.25-alpine?
Текущая версия Caddy требует Go >= 1.25. Системный Go в Ubuntu 24.04 — версия 1.22, которая не подходит. Поэтому сборка происходит внутри Docker-контейнера с нужной версией.

1.4 Создание docker-compose.yml

cat > /opt/caddy-naive/docker-compose.yml << 'EOF'
services:
  caddy-naive:
    build: .
    container_name: caddy-naive
    restart: unless-stopped
    network_mode: host
    environment:
      - XDG_DATA_HOME=/data
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - ./www:/var/www/html:ro
      - caddy_data:/data
      - caddy_logs:/var/log/caddy

volumes:
  caddy_data:
  caddy_logs:
EOF
Почему XDG_DATA_HOME=/data?
Кастомный образ Caddy (собранный из debian:bookworm-slim) не наследует переменные официального Docker образа Caddy. Без этой переменной Caddy хранит сертификаты и служебные данные в /root/.local/share/caddy/ — внутри writable layer контейнера. При пересборке или пересоздании контейнера все сертификаты теряются, и Caddy запрашивает новые у Let's Encrypt (лимит: 5 сертификатов на домен в неделю). С XDG_DATA_HOME=/data данные пишутся в /data/caddy/, который примонтирован как named volume — и переживают любые пересборки.
Почему network_mode
host?
Контейнер использует сетевой стек хоста напрямую. Это необходимо чтобы:
  • Caddy получал реальные клиентские IP для корректной работы probe_resistance
  • Контейнер мог обращаться к 127.0.0.1:24363 (Xray inbound WARP) — локальному сервису хоста

1.5 Создание заглушки для probe_resistance

cat > /opt/caddy-naive/www/index.html << 'EOF'
<!DOCTYPE html>
<html><head><title>Welcome</title></head>
<body><h1>Welcome</h1><p>Nothing to see here.</p></body>
</html>
EOF
Зачем это нужно?
Функция probe_resistance в Caddy скрывает факт наличия прокси.
При обращении без авторизации сервер возвращает эту обычную HTML-страницу вместо ответа прокси.
Это защищает от автоматических сканеров и активных DPI-проверок.

1.6 Создание Caddyfile

cat > /opt/caddy-naive/Caddyfile << 'EOF'
{
  order forward_proxy before file_server
}

:443, your-naive-eu-domain.example.com {
  tls YOUR_EMAIL@example.com
  forward_proxy {
    basic_auth EU_PROXY_USER EU_PROXY_PASSWORD
    upstream socks5://127.0.0.1:24363
    hide_ip
    hide_via
    probe_resistance
  }
  file_server {
    root /var/www/html
  }
}
EOF
Важные нюансы синтаксиса
* :443, your-naive-eu-domain.example.com — запятая и пробел обязательны. Именно такой синтаксис активирует HTTP/2 для CONNECT-туннелей, без которого NaïveProxy не работает
* upstream socks5://127.0.0.1:24363 — перенаправляет исходящий трафик в Xray inbound (WARP). Если WARP ещё не настроен, эту строку можно убрать — трафик пойдёт напрямую в интернет
* probe_resistance — без параметра означает ответ HTML-заглушкой на неавторизованные запросы

1.7 Настройка Xray inbound для WARP (в 3x-ui)

Данный шаг необходим чтобы выходящий трафик от Caddy уходил через Cloudflare WARP, а не напрямую в интернет.

В панели 3x-ui на EU сервере:

  1. Перейди в раздел "Подключения" → "Создать подключение"
  2. Заполни параметры:
    • Протокол: mixed
    • Мониторинг IP: 127.0.0.1
    • IP: 127.0.0.1
    • Порт: 24363 (или любой свободный)
    • Пароль: выключен
    • UDP: включён
    • Примечание: naive-Caddy
  3. Сохрани подключение

Создать routing rule (Xray настройки → Маршрутизация):

  1. Добавь правило: inbound tag naive-Caddy → outbound WARP
  2. Это направит весь трафик от Caddy через Cloudflare WARP
Проверка WARP inbound
curl -s -x socks5h://127.0.0.1:24363 https://ifconfig.me
# Должен вернуть IP из диапазона Cloudflare: 104.x.x.x или 162.159.x.x

1.8 Сборка и запуск

cd /opt/caddy-naive

# Сборка образа (занимает 3-5 минут — компилируется Go)
docker compose build

# Запуск в фоне
docker compose up -d

# Наблюдение за логами (получение сертификата занимает ~30 секунд)
docker compose logs -f

Ожидаемый вывод в логах:

...
{"level":"info","msg":"certificate obtained successfully","identifier":"your-naive-eu-domain.example.com"}
...

1.9 Проверка EU сервера

# Проверка probe_resistance — должна вернуть HTML страницу (не ошибку прокси)
curl -sv https://your-naive-eu-domain.example.com 2>&1 | grep -E "< HTTP|title"

# Проверка прокси-функциональности через SOCKS5 inbound
curl -s -x socks5h://127.0.0.1:24363 https://ifconfig.me

Часть 2: Настройка RU сервера (Entry Node)

Цель: на RU сервере поднять два компонента:

  1. Caddy (Docker) — принимает соединения от клиентов (роутеров), пробрасывает через локальный naive daemon
  2. naive daemon (systemd) — клиент, который соединяется с EU Caddy по протоколу NaïveProxy

2.1 Установка Docker

Те же команды, что и для EU сервера:

apt-get update && apt-get upgrade -y
apt-get install -y ca-certificates curl gnupg lsb-release

install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
  -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc

echo "deb [arch=$(dpkg --print-architecture) \
  signed-by=/etc/apt/keyrings/docker.asc] \
  https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
  > /etc/apt/sources.list.d/docker.list

apt-get update
apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin

docker --version && docker compose version

2.2 Структура каталогов и файлы Docker

mkdir -p /opt/caddy-naive/www
cd /opt/caddy-naive

Dockerfile идентичен EU серверу:

cat > /opt/caddy-naive/Dockerfile << 'EOF'
FROM golang:1.25-alpine AS builder
RUN apk add --no-cache git
RUN go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
RUN xcaddy build \
    --with github.com/caddyserver/forwardproxy=github.com/klzgrad/forwardproxy@naive

FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
COPY --from=builder /go/caddy /usr/local/bin/caddy
EXPOSE 80 443
CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"]
EOF
cat > /opt/caddy-naive/docker-compose.yml << 'EOF'
services:
  caddy-naive:
    build: .
    container_name: caddy-naive
    restart: unless-stopped
    network_mode: host
    environment:
      - XDG_DATA_HOME=/data
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - ./www:/var/www/html:ro
      - caddy_data:/data
      - caddy_logs:/var/log/caddy

volumes:
  caddy_data:
  caddy_logs:
EOF
cat > /opt/caddy-naive/www/index.html << 'EOF'
<!DOCTYPE html>
<html><head><title>Welcome</title></head>
<body><h1>Welcome</h1><p>Nothing to see here.</p></body>
</html>
EOF

2.3 Создание Caddyfile RU сервера

Ключевое отличие от EU: директива upstream socks5://127.0.0.1:10808, которая перенаправляет входящие CONNECT-запросы в локальный naive daemon.

cat > /opt/caddy-naive/Caddyfile << 'EOF'
{
  order forward_proxy before file_server
}

:443, your-naive-ru-domain.example.com {
  tls YOUR_EMAIL@example.com
  forward_proxy {
    basic_auth RU_PROXY_USER RU_PROXY_PASSWORD
    upstream socks5://127.0.0.1:10808
    hide_ip
    hide_via
    probe_resistance
  }
  file_server {
    root /var/www/html
  }
}
EOF
Почему upstream socks5, а не https?
Вариант upstream https://EU_CADDY не работает в данной схеме — Caddy при таком подходе "протекает" заголовки HTTP-ответа в TLS-туннель, что ломает рукопожатие. Правильное решение — цепочка через SOCKS5 к бинарнику naive, который уже корректно реализует протокол NaïveProxy.

2.4 Сборка и запуск Caddy

cd /opt/caddy-naive
docker compose build
docker compose up -d
docker compose logs -f

Ожидаемый вывод:

{"level":"info","msg":"certificate obtained successfully","identifier":"your-naive-ru-domain.example.com"}

2.5 Установка naive daemon (клиент для EU)

2.5.1 Определение архитектуры сервера

uname -m
# x86_64 → нужен пакет для linux-x86_64-static

2.5.2 Скачивание бинарника naive

Перейди на страницу релизов: https://github.com/klzgrad/naiveproxy/releases

Найди архив для своей архитектуры. Для Ubuntu x86_64:

naiveproxy-vXXX-linux-x86_64.tar.xz
# Скачать (замени URL на актуальный из Releases)
cd /tmp
wget https://github.com/klzgrad/naiveproxy/releases/download/vXXX/naiveproxy-vXXX-linux-x86_64.tar.xz

# Распаковать (xzcat нужен если tar не поддерживает .xz напрямую)
tar -xf naiveproxy-vXXX-linux-x86_64.tar.xz
# или
xzcat naiveproxy-vXXX-linux-x86_64.tar.xz | tar -xf -

# Установить бинарник
cp naiveproxy-vXXX-linux-x86_64/naive /usr/bin/naive
chmod +x /usr/bin/naive

# Проверить
naive --version

2.5.3 Создание конфигурации naive daemon

mkdir -p /etc/naive

cat > /etc/naive/config.json << 'EOF'
{
  "listen": "socks://127.0.0.1:10808",
  "proxy": "https://EU_PROXY_USER:EU_PROXY_PASSWORD@your-naive-eu-domain.example.com",
  "log": ""
}
EOF
Что происходит
  • listen — naive слушает SOCKS5 на localhost:10808 (именно сюда смотрит upstream в Caddyfile RU)
  • proxy — подключается к EU Caddy по протоколу NaïveProxy (HTTP/2 CONNECT over TLS, трафик неотличим от обычного Chrome HTTPS)

2.5.4 Создание systemd-сервиса

cat > /etc/systemd/system/naive.service << 'EOF'
[Unit]
Description=NaïveProxy client daemon
After=network.target

[Service]
ExecStart=/usr/bin/naive /etc/naive/config.json
Restart=always
RestartSec=5
User=nobody

[Install]
WantedBy=multi-user.target
EOF
# Активировать и запустить
systemctl daemon-reload
systemctl enable naive
systemctl start naive

# Проверить статус
systemctl status naive

Ожидаемый вывод:

● naive.service - NaïveProxy client daemon
     Loaded: loaded (/etc/systemd/system/naive.service; enabled)
     Active: active (running)

2.6 Проверка цепочки с RU сервера

# Тест HTTP через naive daemon → EU → WARP
curl -s -x socks5h://127.0.0.1:10808 http://ifconfig.me
# Должен вернуть IP EU сервера или Cloudflare WARP IP

# Тест HTTPS
curl -s -x socks5h://127.0.0.1:10808 https://ifconfig.me
# Должен вернуть тот же результат

Часть 3: Настройка OpenWrt роутера

Цель: запустить naive как клиент на роутере, подключить podkop для избирательной маршрутизации через RU Caddy.

3.1 Определение архитектуры роутера

uname -m
# aarch64 → нужен пакет openwrt-aarch64_cortex-a53-static
# или x86_64, mipsel, armv7l — зависит от модели роутера
# Подробнее об архитектуре
cat /etc/openwrt_release | grep ARCH

3.2 Скачивание бинарника naive для OpenWrt

На странице релизов https://github.com/klzgrad/naiveproxy/releases найди архив:

naiveproxy-vXXX-openwrt-aarch64_cortex-a53.tar.xz

(замени архитектуру на свою)

# Создать каталог
mkdir -p /etc/naive

# Скачать (замени URL)
cd /tmp
wget https://github.com/klzgrad/naiveproxy/releases/download/vXXX/naiveproxy-vXXX-openwrt-aarch64_cortex-a53.tar.xz
Важно
OpenWrt использует BusyBox tar, который не поддерживает .xz напрямую.

Используй pipe с xzcat:

# Если xz не установлен:
opkg update && opkg install xz

# Распаковать через pipe
xzcat naiveproxy-vXXX-openwrt-aarch64_cortex-a53.tar.xz | tar -xf -

# Установить бинарник
cp naiveproxy-vXXX-openwrt-aarch64_cortex-a53/naive /usr/bin/naive
chmod +x /usr/bin/naive

# Проверить
naive --version

3.3 Создание конфигурации

cat > /etc/naive/config.json << 'EOF'
{
  "listen": "socks://127.0.0.1:1080",
  "proxy": "https://RU_PROXY_USER:RU_PROXY_PASSWORD@your-naive-ru-domain.example.com",
  "log": ""
}
EOF
Чем этот конфиг отличается от конфига на RU сервере
  • Роутер подключается к RU Caddy (не к EU напрямую) — это первое звено каскада
  • Слушает на порту 1080 (стандарт для SOCKS5, podkop по умолчанию ожидает его)

3.4 Создание init-скрипта procd

cat > /etc/init.d/naive << 'EOF'
#!/bin/sh /etc/rc.common

START=95
STOP=01
USE_PROCD=1

start_service() {
    procd_open_instance
    procd_set_param command /usr/bin/naive /etc/naive/config.json
    procd_set_param respawn
    procd_set_param stdout 1
    procd_set_param stderr 1
    procd_close_instance
}
EOF

chmod +x /etc/init.d/naive
Почему START=95?
Сервис должен запускаться после сети (START=20) и других зависимостей. 95 — безопасное значение.

3.5 Запуск и автозапуск

# Включить автозапуск
/etc/init.d/naive enable

# Запустить сейчас
/etc/init.d/naive start

# Проверить статус
/etc/init.d/naive status

3.6 Проверка

# Тест подключения через наивный прокси
curl -s -x socks5h://127.0.0.1:1080 http://ifconfig.me
# Должен вернуть IP WARP или EU сервера (НЕ IP роутера или RU сервера)

3.7 Настройка podkop

В интерфейсе podkop (LuCI или CLI):

  1. Тип прокси: SOCKS5
  2. Адрес: socks5://127.0.0.1:1080
  3. Сохрани и применй настройки

После этого все устройства домашней сети, трафик которых podkop перенаправляет, будут автоматически идти через полную цепочку: Роутер → RU Caddy → EU Caddy → WARP → Интернет.

3.8 Управление сервисом на роутере

Команда Действие
/etc/init.d/naive start Запустить
/etc/init.d/naive stop Остановить
/etc/init.d/naive restart Перезапустить
/etc/init.d/naive status Статус
/etc/init.d/naive enable Добавить в автозапуск
/etc/init.d/naive disable Убрать из автозапуска

Часть 4: Управление пользователями (утилита naive-users)

Что это и зачем

В рамках данного сетапа была написана специализированная CLI-утилита naive-users.sh — интерактивное меню для управления клиентами NaïveProxy непосредственно на Entry Node (RU сервере).

Проблема, которую она решает: Caddy хранит учётные данные клиентов (логин + пароль) в виде plain-text строк basic_auth внутри Caddyfile. Добавление, удаление или просмотр пользователей вручную требует:

  1. зайти на сервер,
  2. открыть /opt/caddy-naive/Caddyfile в редакторе,
  3. найти нужные строки,
  4. не ошибиться в формате,
  5. перезапустить Caddy через docker compose restart.

Утилита автоматизирует весь этот процесс. Она работает непосредственно с Caddyfile, перезапускает Caddy после каждого изменения и сразу выводит готовый config.json для нового клиента.

Ключевые возможности:

Функция Описание
Список клиентов Показывает всех существующих пользователей из Caddyfile
Добавить клиента Запрашивает имя; пароль вводится вручную или генерируется автоматически (20 символов, openssl rand)
Удалить клиента Выбор из списка + подтверждение, после — автоперезапуск Caddy
Показать конфиг Читает логин/пароль из Caddyfile и выводит готовый config.json для ПК или роутера

Caddy поддерживает произвольное количество basic_auth записей — каждый клиент имеет собственные логин и пароль, но все используют одну и ту же цепочку:

Entry Node Caddy → naive daemon → Exit Node Caddy → WARP → Интернет
Важно
отзыв доступа происходит мгновенно — удалённый пользователь получит ошибку аутентификации при следующей попытке соединения, без перезапуска клиентского устройства.

4.1 Расположение файла

Скрипт хранится в репозитории по пути scripts/naive-users.sh. Файл изначально сохранён с Unix-окончаниями строк (LF) — дополнительная конвертация не требуется.

4.2 Установка на сервер

Скопируй файл с рабочей машины на Entry Node сервер:

# На рабочей машине (Windows PowerShell):
scp scripts/naive-users.sh root@YOUR_RU_SERVER_IP:/usr/local/bin/naive-users.sh

На сервере — сделать исполняемым и (опционально) создать короткий алиас:

chmod +x /usr/local/bin/naive-users.sh

# Опционально: алиас для быстрого запуска
echo 'alias naive-users="sudo bash /usr/local/bin/naive-users.sh"' >> ~/.bashrc
source ~/.bashrc
Внимание
перед запуском отредактируй переменную DOMAIN внутри скрипта — укажи свой реальный домен Entry Node Caddy (строка DOMAIN="..." в начале файла).

4.3 Запуск

sudo bash /usr/local/bin/naive-users.sh
# или, если создан алиас:
naive-users

Утилита требует прав root: она читает и изменяет /opt/caddy-naive/Caddyfile и перезапускает Docker-контейнер.

Главное меню:

╔══════════════════════════════════════╗
║   NaïveProxy User Manager (RU)       ║
║   Домен: your-naive-ru-domain...     ║
╚══════════════════════════════════════╝

  Активных клиентов: 2

  1. Список клиентов
  2. Добавить клиента
  3. Удалить клиента
  4. Показать конфиг клиента
  0. Выход

4.4 Примеры работы

Список клиентов

Показывает нумерованный список всех basic_auth-записей из Caddyfile.

=== Список клиентов ===
  1. alice
  2. bob
  3. router-home

  Всего: 3 клиент(ов)

Добавить клиента

Запрашивает имя. Пароль можно ввести вручную или нажать Enter — пароль сгенерируется автоматически (20 символов, криптостойкий, на базе openssl rand). После добавления Caddy автоматически перезапускается и сразу выводится готовый config.json для клиента.

=== Добавить клиента ===
Имя пользователя: alice
Пароль (Enter = сгенерировать автоматически): [Enter]
  Сгенерирован пароль: xK9mPqR2nLvT8hQw3Yz5

Пользователь 'alice' добавлен.
Перезапуск Caddy...
Caddy перезапущен успешно.

┌─────────────────────────────────────────────┐
│  config.json для naive (ПК / роутер)        │
└─────────────────────────────────────────────┘
{
  "listen": "socks://127.0.0.1:1080",
  "proxy": "https://alice:xK9mPqR2nLvT8hQw3Yz5@your-naive-ru-domain.example.com",
  "log": ""
}

Путь на роутере OpenWrt:  /etc/naive/config.json
Путь на Windows ПК:       рядом с naive.exe
SOCKS5 адрес для podkop:  socks5://127.0.0.1:1080

Удалить клиента

Показывает список, просит указать номер, требует подтверждение (y/N), затем удаляет строку basic_auth из Caddyfile и перезапускает Caddy.

Показать конфиг клиента

Выбрать существующего пользователя из списка — утилита прочитает логин и пароль из Caddyfile и выведет готовый config.json, который нужно скопировать на клиентское устройство.

4.5 Исходный код

▶ naive-users.sh — показать исходный код
#!/bin/bash
# =============================================================
# naive-users — управление пользователями NaïveProxy на RU сервере
# Caddyfile: /opt/caddy-naive/Caddyfile
# =============================================================

CADDYFILE="/opt/caddy-naive/Caddyfile"
CADDY_DIR="/opt/caddy-naive"
# Подставьте свой домен (не коммитьте реальные значения в публичный репозиторий)
DOMAIN="your-naive-ru-domain.example.com"

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m'

# --- Вспомогательные функции ---

check_root() {
    if [[ $EUID -ne 0 ]]; then
        echo -e "${RED}Ошибка: запусти скрипт от root (sudo bash naive-users.sh)${NC}"
        exit 1
    fi
}

check_caddyfile() {
    if [[ ! -f "$CADDYFILE" ]]; then
        echo -e "${RED}Ошибка: Caddyfile не найден: $CADDYFILE${NC}"
        exit 1
    fi
}

# Возвращает массив пользователей из Caddyfile
get_users() {
    grep -oP '(?<=basic_auth )\S+' "$CADDYFILE"
}

# Возвращает пароль пользователя
get_password() {
    local user="$1"
    grep "basic_auth $user " "$CADDYFILE" | awk '{print $3}'
}

restart_caddy() {
    echo -e "${YELLOW}Перезапуск Caddy...${NC}"
    cd "$CADDY_DIR" && docker compose restart caddy-naive > /dev/null 2>&1
    if [[ $? -eq 0 ]]; then
        echo -e "${GREEN}Caddy перезапущен успешно.${NC}"
    else
        echo -e "${RED}Ошибка при перезапуске Caddy. Проверь: cd $CADDY_DIR && docker compose logs${NC}"
    fi
}

press_enter() {
    echo ""
    read -rp "Нажми Enter для возврата в меню..."
}

# --- Функции меню ---

list_users() {
    echo ""
    echo -e "${BOLD}${CYAN}=== Список клиентов ===${NC}"
    mapfile -t users < <(get_users)
    if [[ ${#users[@]} -eq 0 ]]; then
        echo -e "${YELLOW}Клиентов нет.${NC}"
    else
        for i in "${!users[@]}"; do
            echo -e "  ${BOLD}$((i+1)).${NC} ${users[$i]}"
        done
        echo ""
        echo -e "  Всего: ${#users[@]} клиент(ов)"
    fi
    press_enter
}

add_user() {
    echo ""
    echo -e "${BOLD}${CYAN}=== Добавить клиента ===${NC}"

    read -rp "Имя пользователя: " username
    username=$(echo "$username" | tr -d ' ')

    if [[ -z "$username" ]]; then
        echo -e "${RED}Имя не может быть пустым.${NC}"
        press_enter
        return
    fi

    # Проверка — уже существует?
    if grep -q "basic_auth $username " "$CADDYFILE"; then
        echo -e "${RED}Пользователь '$username' уже существует.${NC}"
        press_enter
        return
    fi

    read -rp "Пароль (Enter = сгенерировать автоматически): " password
    if [[ -z "$password" ]]; then
        password=$(openssl rand -base64 16 | tr -d '=/+' | head -c 20)
        echo -e "  Сгенерирован пароль: ${BOLD}${password}${NC}"
    fi

    # Вставить строку basic_auth перед первой строкой upstream или probe_resistance
    sed -i "/probe_resistance/i\\    basic_auth $username $password" "$CADDYFILE"

    echo -e "${GREEN}Пользователь '${username}' добавлен.${NC}"
    restart_caddy

    echo ""
    echo -e "${BOLD}Конфиг для клиента:${NC}"
    print_config_for "$username" "$password"
    press_enter
}

delete_user() {
    echo ""
    echo -e "${BOLD}${CYAN}=== Удалить клиента ===${NC}"
    mapfile -t users < <(get_users)

    if [[ ${#users[@]} -eq 0 ]]; then
        echo -e "${YELLOW}Нет клиентов для удаления.${NC}"
        press_enter
        return
    fi

    for i in "${!users[@]}"; do
        echo -e "  ${BOLD}$((i+1)).${NC} ${users[$i]}"
    done
    echo "  0. Отмена"
    echo ""
    read -rp "Выбери номер для удаления: " choice

    if [[ "$choice" == "0" || -z "$choice" ]]; then
        return
    fi

    if ! [[ "$choice" =~ ^[0-9]+$ ]] || (( choice < 1 || choice > ${#users[@]} )); then
        echo -e "${RED}Неверный номер.${NC}"
        press_enter
        return
    fi

    target="${users[$((choice-1))]}"
    read -rp "Удалить пользователя '${target}'? (y/N): " confirm
    if [[ "$confirm" =~ ^[Yy]$ ]]; then
        sed -i "/basic_auth $target /d" "$CADDYFILE"
        echo -e "${GREEN}Пользователь '${target}' удалён.${NC}"
        restart_caddy
    else
        echo "Отменено."
    fi
    press_enter
}

print_config_for() {
    local user="$1"
    local pass="$2"
    echo ""
    echo -e "${BOLD}┌─────────────────────────────────────────────┐${NC}"
    echo -e "${BOLD}│  config.json для naive (ПК / роутер)        │${NC}"
    echo -e "${BOLD}└─────────────────────────────────────────────┘${NC}"
    echo '{
  "listen": "socks://127.0.0.1:1080",
  "proxy": "https://'"$user"':'"$pass"'@'"$DOMAIN"'",
  "log": ""
}'
    echo ""
    echo -e "${CYAN}Путь на роутере OpenWrt:${NC}  /etc/naive/config.json"
    echo -e "${CYAN}Путь на Windows ПК:${NC}       рядом с naive.exe"
    echo -e "${CYAN}SOCKS5 адрес для podkop:${NC}  socks5://127.0.0.1:1080"
}

show_config() {
    echo ""
    echo -e "${BOLD}${CYAN}=== Показать конфиг клиента ===${NC}"
    mapfile -t users < <(get_users)

    if [[ ${#users[@]} -eq 0 ]]; then
        echo -e "${YELLOW}Нет клиентов.${NC}"
        press_enter
        return
    fi

    for i in "${!users[@]}"; do
        echo -e "  ${BOLD}$((i+1)).${NC} ${users[$i]}"
    done
    echo "  0. Отмена"
    echo ""
    read -rp "Выбери номер клиента: " choice

    if [[ "$choice" == "0" || -z "$choice" ]]; then
        return
    fi

    if ! [[ "$choice" =~ ^[0-9]+$ ]] || (( choice < 1 || choice > ${#users[@]} )); then
        echo -e "${RED}Неверный номер.${NC}"
        press_enter
        return
    fi

    local user="${users[$((choice-1))]}"
    local pass
    pass=$(get_password "$user")
    echo -e "Клиент: ${BOLD}${user}${NC}"
    print_config_for "$user" "$pass"
    press_enter
}

# --- Главное меню ---

main_menu() {
    check_root
    check_caddyfile

    while true; do
        clear
        echo -e "${BOLD}${CYAN}"
        echo "╔══════════════════════════════════════╗"
        echo "║   NaïveProxy User Manager (RU)       ║"
        echo "║   Домен: $DOMAIN   ║"
        echo "╚══════════════════════════════════════╝"
        echo -e "${NC}"

        mapfile -t users < <(get_users)
        echo -e "  Активных клиентов: ${BOLD}${#users[@]}${NC}"
        echo ""
        echo -e "  ${BOLD}1.${NC} Список клиентов"
        echo -e "  ${BOLD}2.${NC} Добавить клиента"
        echo -e "  ${BOLD}3.${NC} Удалить клиента"
        echo -e "  ${BOLD}4.${NC} Показать конфиг клиента"
        echo -e "  ${BOLD}0.${NC} Выход"
        echo ""
        read -rp "Выбор: " option

        case "$option" in
            1) list_users ;;
            2) add_user ;;
            3) delete_user ;;
            4) show_config ;;
            0) echo "Выход."; exit 0 ;;
            *) echo -e "${RED}Неверный выбор.${NC}"; sleep 1 ;;
        esac
    done
}

main_menu

Часть 5: Проверка полной цепочки

5.1 Пошаговая проверка

Шаг Команда Где выполнять Ожидаемый результат
1 curl -s -x socks5h://127.0.0.1:24363 https://ifconfig.me EU сервер Cloudflare WARP IP (104.x.x.x)
2 curl -s -x socks5h://127.0.0.1:10808 https://ifconfig.me RU сервер Тот же WARP IP
3 curl -s -x socks5h://127.0.0.1:1080 https://ifconfig.me OpenWrt роутер Тот же WARP IP
4 Открыть браузер через podkop Устройство в сети IP отличается от домашнего

5.2 Проверка probe_resistance

# Запрос без авторизации — должна вернуться HTML-страница, а не ошибка прокси
curl -sv https://your-naive-ru-domain.example.com 2>&1 | grep -E "< HTTP|<title"
# Ожидается: HTTP/2 200 и <title>Welcome</title>

5.3 Просмотр логов

# Caddy (EU или RU сервер)
cd /opt/caddy-naive && docker compose logs -f

# naive daemon (RU сервер)
journalctl -u naive -f

# naive daemon (OpenWrt роутер)
logread -f | grep naive

5.4 Перезапуск компонентов

Компонент Команда
Caddy EU cd /opt/caddy-naive && docker compose restart
Caddy RU cd /opt/caddy-naive && docker compose restart
naive daemon RU systemctl restart naive
naive daemon OpenWrt /etc/init.d/naive restart

Приложение: Структура файлов

EU сервер

/opt/caddy-naive/
├── Dockerfile          ← сборка Caddy с плагином forwardproxy
├── docker-compose.yml  ← network_mode: host, монтирование томов
├── Caddyfile           ← домен, TLS, basic_auth, upstream WARP
└── www/
    └── index.html      ← заглушка для probe_resistance

RU сервер

/opt/caddy-naive/
├── Dockerfile
├── docker-compose.yml
├── Caddyfile           ← upstream socks5://127.0.0.1:10808
└── www/
    └── index.html

/usr/bin/naive                      ← бинарник naive daemon
/etc/naive/config.json              ← конфиг: listen 10808, proxy → EU Caddy
/etc/systemd/system/naive.service   ← автозапуск через systemd
/usr/local/bin/naive-users.sh       ← утилита управления пользователями

OpenWrt роутер

/usr/bin/naive              ← бинарник naive (архитектура роутера)
/etc/naive/config.json      ← конфиг: listen 1080, proxy → RU Caddy
/etc/init.d/naive           ← автозапуск через procd (START=95)

Приложение: Частые ошибки

Ошибка Причина Решение
requires go >= 1.25.0 при сборке Системный Go устарел Dockerfile уже использует golang:1.25-alpine — ошибка не возникнет
wrong version number в curl через Caddy RU Caddy использует upstream https:// вместо SOCKS5 Заменить на upstream socks5://127.0.0.1:10808
Content-Length: 0 для HTTP GET через Caddy Нормальное поведение forwardproxy для не-CONNECT запросов Не является ошибкой
tar: invalid tar magic на OpenWrt BusyBox tar не поддерживает .xz tar -xf -
$'\r': command not found в bash-скрипте Windows CRLF окончания строк Выполнить sed -i 's/\r//' /путь/к/скрипту.sh
Caddy не получает сертификат Порт 80 занят другим сервисом grep :80, освободить порт
naive daemon не подключается к EU Неверные креды или домен в config.json Проверить journalctl -u naive, перепроверить Caddyfile EU
probe_resistance возвращает ошибку прокси Отсутствует file_server блок в Caddyfile Добавить file_server { root /var/www/html }