aiogram 3 · Remnawave · SSH

Автоматизация Remnawave

Ноды, домены, сертификаты, файрвол, БС-релеи — из чата. Бот показывает план, делает по шагам и проверяет результат. Дальше по пунктам меню: как включить и обо что легко споткнуться.

Разработка Quas1k FraG_mmM Claude

Что делает

  • Ставит ноду с нуля — от чистого VPS до работающей remnanode: docker, /opt/remnanode/, регистрация в панели и проверка, что контейнер жив, а ключ на месте.
  • Ведёт домены и DNS — A-записи в Cloudflare, свободный номер подбирает сам, умеет переселить базовый домен всего флота на новую зону.
  • Держит два типа конфига — Reality или TLS-сертификат Let's Encrypt, выбор на уровне локации.
  • Готовит сервер — apt, отключение IPv6, ufw, accelerator, traffic-guard: по одной ноде или сразу по всему флоту.
  • Поднимает БС-серверы — HAProxy-релеи белого списка, со своим планом развёртывания и повтором с того же места.
  • Управляет через API панели — список нод, вкл/выкл, рестарт xray.
  • Знает структуру флота — локации, шаблоны имён и подписей правятся прямо из бота, без правки файлов на сервере.

Из чего состоит

  • Бот — Python и aiogram 3, long polling. Входящие порты не нужны, поэтому ботов на одном сервере может жить сколько угодно.
  • Слой операций — план, шаги и проверка результата: каждый шаг сначала смотрит, не сделан ли он уже.
  • SSH — один вход на все команды, успех судится по коду возврата, а не по факту «команда доехала до конца».
  • HTTP-клиенты — панель Remnawave и Cloudflare.
  • Каталог data/ — настройки, структура флота и SSH-ключи. Лежит на хосте, переживает пересборку, в git не едет.

Что нужно

Сервер бота Linux + Docker compose v2; установщик поставит сам
Без Docker Python 3.10+ venv и requirements.txt
Нода Чистый VPS root по SSH — ключом или паролем
Доступы Remnawave + Cloudflare URL панели, токены, Zone ID
Telegram BOT_TOKEN и ADMIN_ID @BotFather и @userinfobot

С чего начать

Запуск

git clone <репозиторий> && cd RWT-Automation/bot
./install.sh   # спросит BOT_TOKEN и ADMIN_ID, поднимет контейнер

Репозиторий монтируется внутрь контейнера, поэтому кнопка «Обновить бота» делает git pull прямо в этот каталог, а рестарт подхватывает новый код.

Несколько ботов на одном сервере: каждому нужен свой клон, свой data/, свой .env и свой COMPOSE_PROJECT_NAME — иначе docker сочтёт их одним проектом.

Доступ

Бот отвечает только администратору (ADMIN_ID плюс добавленные в настройках). Остальным молчит — других уровней доступа нет.

Настройка

Для новой установки — четыре шага:

  1. Remnawave и Cloudflare — URL, токены, Zone ID. Кнопки «Проверить» делают живой запрос.
  2. Локации — достаточно кода (NL): имя, подпись с флагом и префикс поддомена подставятся сами.
  3. Схемы имён — если у вас другие: имя ноды, подпись host'а, поддомен, тег инбаунда.
  4. Настройки БС — профиль белого списка и шаблон имени хостов.

{num} — номер, который бот подбирает сам (первый свободный по A-записям, хостам и нодам). Поэтому он обязателен во всех шаблонах имён.

Почему настройки в data/

Бот обновляется через git, а data/ не в репозитории. Пока конфиг флота лежал в файле репозитория, обновление его затирало.

Доступ к серверам

Бот заходит по ключу (по умолчанию) или по паролю. Режим — свойство ноды: одни серверы могут быть на ключе, другие на пароле.

Ключи по умолчанию

Делать ничего не нужно: у каждого сервера свой ключ, его выдаёт bootstrap при добавлении. Утечка одного не открывает остальные.

Общий ключ — наследие старых версий: бот его больше не создаёт, только использует, пока файл есть. Экран 🔑 Ключи доступа показывает, кто ещё на нём, и раздаёт оставшимся свои.

Вход по паролю новое

Для готовых серверов, где ключа бота нет, а парольный вход работает. Бот запоминает пароль и ходит по нему: ключ не ставит, парольный вход не трогает.

КогдаГде включить
При заведении ➕ Добавить ноду🔐 По паролю
У существующей Карточка → 🔐 Доступ и ключи🔐 Вход по паролю
Вернуть ключ Там же → 🔑 Вернуть вход по ключу

Пароль проверяется реальным заходом до сохранения: не пускает — режим не меняется, причина показывается. В карточке появляется строка «Вход: 🔒 по паролю».

Ротация безопасна

Ключ или порт меняются только после того, как новый доказанно работает. Кнопка удаления общего ключа появляется, лишь когда он никому не нужен.

Ноды и установка

Список тянется из панели со статусом: 🟢 подключена · 🔴 нет · ⚪ выключена. Тап открывает карточку.

Меню карточки

ГруппаЧто внутри
🔐 Доступ и ключипоказать/скачать ключ, смена ключа и SSH-порта, вход по паролю
🌍 Домен / Realityсмена домена, замена privateKey, переименование инбаунда
🧰 Обслуживаниеустановка ноды, евро-нода, обновление remnanode
🛠 Утилитыapt, autoremove, IPv6 off, accelerator, traffic-guard

Что делает установка

Выбираешь локацию, бот подбирает домен и показывает план — ничего не начнётся, пока не подтвердишь. Дальше по порядку:

  1. A-запись в Cloudflare (рано — чтобы DNS успел разойтись)
  2. подготовка сервера — только отмеченные галочками утилиты
  3. сертификат — только для cert-локаций, до remnanode
  4. remnanode: docker → compose up → проверка, что контейнер жив
  5. профиль ноды в панели и привязка к сквадам
  6. selfsteal — для Reality-локаций
  7. регистрация ноды и host в панели, затем перезагрузка

Галочки подготовки: 📦 apt · 🚫 IPv6 off · 🛡 файрвол · 🚀 accelerator · 🧿 traffic-guard. По умолчанию отмечено всё. Порядок задан в коде: traffic-guard идёт после ufw, поэтому его отметка сама включает файрвол.

Повтор безопасен

Установка идемпотентна: каждый шаг сначала смотрит, не сделан ли он уже. Упало посередине — запускай заново.

Смена SSH-порта

Идёт в две фазы: сервер слушает старый и новый порт сразу, бот проверяет вход по новому — и только потом закрывает старый. Не сработало — откатывается сам.

Тип конфига локации новое

Тип решает, какой профиль бот сгенерирует ноде и что подготовит на сервере. Переключается в карточке локации.

ТипКонфигУстановка
🕵️ Reality дефолт VLESS Reality, домен ноды в serverNames ставит сайт-прикрытие (caddy/nginx)
🔐 TLS-сертификат VLESS + настоящий TLS, Let's Encrypt выпускает сертификат, монтирует его в контейнер, selfsteal не ставит

Reality — значение по умолчанию: у существующих локаций поля нет, поэтому они работают как раньше.

Что делает cert-локация

Сертификат выпускается до remnanode — xray читает его на старте:

certbot certonly --standalone --keep-until-expiring -d <домен>

Затем профиль генерится с путями /etc/letsencrypt/live/<домен>/, а контейнер поднимается с volume /etc/letsencrypt:ro. Флаг --keep-until-expiring делает шаг идемпотентным: живой сертификат не перевыпускается. Продление — таймером certbot.

Требования

Порт 80 свободен и открыт (правило ufw бот добавит сам), A-запись — DNS-only, без оранжевого облака: через прокси ACME не достучится. Порт 443 занимает xray, поэтому selfsteal здесь несовместим.

Почта для certbot — ⚙️ Настройки → Ноды → Email для Let's Encrypt (пусто — регистрация без почты).

Xray-шаблоны

Каждая нода ставится на свой профиль — он создаётся при установке из шаблона. Шаблоны правятся в 🧬 Xray → Шаблоны: пришли JSON файлом или текстом, бот проверит рендеринг и сохранит. Пусто — встроенный дефолт.

Обычный конфиг — Reality-локации

{{domain}}{{tag}}{{privateKey}} {{publicKey}}{{shortId}}{{fingerprint}} {{dest}}{{xver}}{{port}}

TLS-сертификат — cert-локации новое

{{domain}}{{tag}}{{port}} {{fallbackPort}}{{certDir}}{{keyFile}} {{certFile}}{{fingerprint}}

Мост — цепочка из двух нод

{{entry_domain}}{{exit_address}} {{client_id}}{{outbound_tag}} {{flow}}

Строковые значения берутся в кавычки в самом шаблоне, числовые — без:

"serverName": "{{domain}}",
"port": {{port}}

Встроенный cert-дефолт даёт один VLESS-TCP-TLS инбаунд. Свой шаблон нужен, если инбаундов должно быть несколько (например ещё grpc).

Генератор шаблона прямо здесь

Переменные в шаблон бот сам не подставит: он делает текстовую замену {{domain}} на значение, а догадаться, что yt1.example.com в вашем конфиге — это «домен ноды», не может. Загрузите конфиг как есть — домен останется константой, и все ноды получат один и тот же.

Инструмент ниже делает это за вас: вставьте рабочий конфиг, он найдёт места, которые меняются от ноды к ноде, и заменит их плейсхолдерами. Всё считается прямо в браузере, никуда не отправляется.

ТИП ШАБЛОНА:
Несколько инбаундов с разными доменами

Профиль создаётся на каждую ноду свой, и домен у ноды один. Если в конфиге инбаунды на yt1, yt2, yt3 — подставлять {{domain}} во все три нельзя: получится три одинаковых инбаунда на одном порту. Оставьте в шаблоне один инбаунд (или несколько, но на разных портах и с разными тегами).

Файрвол и массовые действия

Файрвол — единый поток и на одной ноде, и массово: цель (веб · SSH · порт панели · свой порт) → протокол (TCP / UDP / оба). ufw ставится автоматически, если его нет.

Включение по политике «минимум»: всё входящее закрыто, кроме текущего SSH-порта. Порт панели по возможности открывается только для её IP.

Массовые действия — те же операции по всем нодам с отчётом по строке на ноду. Разрушающие — с подтверждением. Вкл/выкл ноды только индивидуально.

Отдельно — перевод на ключ: бот идёт по серверам, заходит по паролю, ставит каждому свой ключ и отключает парольный вход. Для каждого можно ввести пароль, повторить прошлый (.) или пропустить (-).

БС-серверы

БС — не нода Remnawave, а прозрачный TCP-релей: HAProxy слушает порты инбаундов и по SNI пробрасывает трафик на евро-ноды, где терминируется Reality.

клиент → [ БС: HAProxy, SNI ] → [ евро-нода: Reality ] → интернет

Поэтому у БС нет xray и «включить в панели», а DNS для него не создаётся — клиенты ходят на IP. Конфиг не хардкодится: бот читает профиль из панели и строит из него frontend'ы, список SNI и backend.

Добавили евро-ноду в профиль — 🔁 Пересобрать балансир подхватит её. Сама евро-нода заводится кнопкой 🛡 Евро-нода: без DNS и host, чтобы не светить её в обход белого списка.

Обновление бота

Бот обновляет себя сам: git pull --ff-only и перезапуск. Источник — ветка или latest (последний релизный тег), задаётся в 🤖 Бот → Репозиторий вместе с URL и токеном.

Раз в 3 минуты бот проверяет новую версию: если автообновление выключено — пришлёт уведомление с кнопкой и списком изменений, если включено — обновится сам и напишет об этом.

Токен чужого репозитория

Если репозиторий принадлежит другому пользователю, а вы соавтор, нужен classic PAT: fine-grained его не видит, и git падает с 403.

Данные и секреты

ПутьЧто внутриВ git
bot/.envтокен бота, ID админанет
data/settings.jsonтокены панели и Cloudflare, схемы имённет
data/locations.jsonструктура флотанет
data/nodes.jsonноды и БС, пути к ключам, паролинет
data/keys/приватные SSH-ключи серверовнет
node/docker-compose.ymlшаблон, который льётся на нодуда
Перед git add

Весь data/ и .env — секреты: токены, SECRET_KEY нод, приватные ключи, пароли серверов. Они gitignored, но проверяйте индекс.

Каталоги bot/ и node/ нельзя двигать друг относительно друга: шаблон compose читается по относительному пути.

Проблемы

apt вернул код 100

На свежем VPS unattended-upgrades держит dpkg-лок первые минуты. Бот кладёт на сервер конфиг с таймаутом, и apt начинает ждать лок вместо падения. Если всё равно упало — просто повторите шаг.

«Нет своего ключа, а общего больше нет»

Доступа к серверу у бота нет вообще. Варианты: завести ключ по паролю, включить вход по паролю или указать свой ключ в настройках.

Certbot не выпустил сертификат

Проверьте: A-запись указывает на этот сервер, она DNS-only, порт 80 свободен и доступен снаружи.

git pull не удался: diverged

Локальная история разошлась с удалённой. Сначала бэкап, потом сброс:

git branch backup/local
git reset --hard origin/main

Панель не видит ноду

Смотрите порт: он задаётся в настройках и подставляется и в compose ноды, и в правило ufw. Если порт открыт только для IP панели — проверьте, что IP актуален.

Внутри

Точка входа — bot.py. Операции (nodeops.py, bsops.py) стоят поверх двух обёрток: SSH-слоя и HTTP-клиентов панели и Cloudflare. Мимо них ходить нельзя.

Принципы

  • Проверяем результат, а не факт выполнения — перечитываем файл, а не верим коду возврата.
  • Идемпотентность — каждый шаг сначала смотрит, не сделан ли он уже.
  • Опасное — после доказательства — старый ключ снимается, когда новый уже работает.
  • Одно место правды — меню строятся из реестра действий, порт живёт в настройках, выбор «ключ или пароль» решается в одной функции.