NaïveProxy: установка полной каскадной цепочки
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 для туннелирования трафика. Упрощённо:
- Клиент (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 вашего сервера.
Схема конкретного решения
Компоненты инфраструктуры
| Компонент | Роль | 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 сервере:
- Перейди в раздел "Подключения" → "Создать подключение"
- Заполни параметры:
- Протокол:
mixed - Мониторинг IP:
127.0.0.1 - IP:
127.0.0.1 - Порт:
24363(или любой свободный) - Пароль: выключен
- UDP: включён
- Примечание:
naive-Caddy
- Протокол:
- Сохрани подключение
Создать routing rule (Xray настройки → Маршрутизация):
- Добавь правило: inbound tag
naive-Caddy→ outbound WARP - Это направит весь трафик от 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 сервере поднять два компонента:
- Caddy (Docker) — принимает соединения от клиентов (роутеров), пробрасывает через локальный naive daemon
- 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):
- Тип прокси: SOCKS5
- Адрес:
socks5://127.0.0.1:1080 - Сохрани и применй настройки
После этого все устройства домашней сети, трафик которых 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. Добавление, удаление или просмотр пользователей вручную требует:
- зайти на сервер,
- открыть
/opt/caddy-naive/Caddyfileв редакторе, - найти нужные строки,
- не ошибиться в формате,
- перезапустить 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 Исходный код
#!/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 }
|