Initial release of Kaskad

Каскадная маршрутизация русских сайтов через свой набор RU-серверов
с failover, Telegram-ботом и веб-интерфейсом.

- bin/        — failover-script, route/domain helpers, ams/ru bootstrap
- bot/        — Telegram-бот (Python, long-poll)
- webui/      — Flask single-page dashboard
- docs/       — установка, архитектура, API бота и WebUI
- examples/   — шаблоны конфигов
This commit is contained in:
andrey271192
2026-05-02 09:13:10 +03:00
parent 7a2a41510c
commit 209caa81a8
22 changed files with 2802 additions and 0 deletions

106
docs/architecture.md Normal file
View File

@@ -0,0 +1,106 @@
# Архитектура
## Обзор
Каждый ам. сервер — это **WG-клиент**, держит ОДИН активный туннель к одному из RU-серверов. RU-серверы — это **WG-серверы**, у каждого N пиров (по числу ам.).
```
┌─────────────┐
│ RU primary│
│ (priority 1)│
└──┬──┬──┬──┬──┘
│ │ │ │ WG туннели
┌─────┘ │ │ └─────┐
▼ ▼ ▼ ▼
┌──────┐ ┌──────┐ ┌──────┐
│ ams1 │ │ ams2 │ │ ams3 │
│X-ray │ │X-ray │ │X-ray │
└──────┘ └──────┘ └──────┘
▲ ▲ ▲
│ │ │ failover при падении primary
│ ┌────┘ │
│ │ │
┌─▼─────────────▼──┐
│ RU backup │
│ (priority 2) │
└──────────────────┘
```
## Компоненты
### На RU-серверах
- `wg_ru` интерфейс, listen UDP/1939, peers = все ам. серверы (`tunnel_ip``pubkey`)
- `iptables -t nat -A POSTROUTING -o <wan_iface> -j MASQUERADE` — выходящий трафик от ам. серверов наружу
- `iptables -I FORWARD -i wg_ru -j ACCEPT`, `-o wg_ru -j ACCEPT` — пропуск форварда
### На ам. серверах
- `ru` интерфейс, peer = один из RU-серверов (тот что активен сейчас)
- `/etc/wireguard/ru.conf` — текущая конфигурация туннеля
- `/etc/wireguard/ru-servers.json` — общий список всех RU и ам. серверов (синхронизируется ботом/WebUI)
- `/etc/wireguard/ru-base.aips` — базовый набор подсетей в `AllowedIPs` (создаётся при первой установке)
- `/etc/wireguard/ru-extra.list` — дополнительные IP/CIDR, добавленные через бот
- `/etc/wireguard/ru-domains.json` — карта домен → список IP (резолвится через `dig`)
### Скрипты на ам. серверах (`/usr/local/bin/`)
| Скрипт | Что делает |
|---|---|
| `ru-failover.py` | Cron каждую минуту. Проверяет handshake/probe текущего peer'а, переключает на другой при падении. Trial-failback при возврате primary. |
| `ru-set.sh <id>` | Принудительная установка peer'а из JSON по `id` |
| `ru-routes.sh add\|remove\|clear\|list\|apply` | Управление `ru-extra.list` + live `ip route` + iptables mangle + sync allowed-ips через `wg syncconf` |
| `ru-domains.py add\|remove\|list\|show\|refresh` | Резолв доменов и проксирование результатов в `ru-routes.sh` |
## Маршрутизация трафика
```
Запрос с телефона на gosuslugi.ru:
1. X-ray на ам. сервере получает пакет на amn0 интерфейсе
2. iptables -t mangle -A PREROUTING -i amn0 -d 95.163.0.0/16 -j MARK --set-mark 100
3. ip rule fwmark 100 lookup 200
4. table 200: default dev ru
5. WG проверяет AllowedIPs пира (95.163.0.0/16 ∈ allowed) → шифрует
6. Пакет идёт в туннель к RU-серверу
7. На RU: pакет приходит на wg_ru, MASQUERADE на wan, идёт в интернет с RU IP
8. Ответ возвращается обратно через NAT → wg_ru → ам. сервер → телефон
```
Для НЕ-российских сайтов: пакет в mangle не получает MARK 100, идёт по обычному маршруту через провайдера ам. сервера.
## Failover
`ru-failover.py` запускается из cron каждую минуту:
```
1. Прочитать /etc/wireguard/ru-servers.json (отсортированный по priority)
2. Определить current = peer чей endpoint в /etc/wireguard/ru.conf
3. age = now - last_handshake (от wg show)
4. Если current dead (age > 180 && TCP probe не отвечает && cooldown 5min прошёл):
→ переключиться на ближайший живой
5. Если current не highest-priority И есть более приоритетный живой И cooldown && fail_backoff прошли:
→ переключиться на него (mode = failback test)
6. Если в режиме failback test:
- handshake появился за <60s → success
- не появился → откат на запасной + 30min backoff на повторный failback
```
Переключение через `wg syncconf` — без рестарта интерфейса. Маршруты, mangle, расширенные allowed-ips восстанавливаются через `ru-routes.sh apply` (вызывается из PostUp и сразу после switch).
## Синхронизация конфига
`ru-servers.json` — единый источник правды. Хранится на каждом ам. сервере. Изменения вносятся ТОЛЬКО через бот/WebUI на «локальном» ам. сервере (где бот живёт), затем `save_and_distribute()` SCP-ит файл на остальные через root SSH-ключ.
Конфиг WG-серверов (`/etc/wireguard/wg_ru.conf` на RU) бот редактирует напрямую при `/ams-add` / `/ams-remove` — добавляет/удаляет [Peer] секции и делает `wg syncconf wg_ru`.
## SSH между серверами
Бот (на одном из ам. серверов) генерирует ed25519 ключ при первом запуске. Этот ключ:
- автоматически прописывается в `/root/.ssh/authorized_keys` на всех остальных ам. серверах при `/ams-add` (или вручную при онбординге)
- автоматически прописывается на новых RU при `/server-add` (через add-ru-helper.sh)
- для существующих RU — добавляется один раз вручную при первичной настройке
После этого бот работает только по ключу, никаких сохранённых паролей.
## Уведомления
`ru-failover.py` каждый switch/success/fail логирует через `logger -t ru-failover` И шлёт в Telegram если есть `/etc/wireguard/notify.env` с `TG_BOT_TOKEN` и `TG_CHAT_ID`.
С RU-серверов в России TG API часто недоступен, поэтому бот живёт на одном из ам. серверов.

88
docs/bot.md Normal file
View File

@@ -0,0 +1,88 @@
# Telegram-бот
Бот живёт на одном из ам. серверов (там, где есть доступ к api.telegram.org). Управляет всеми остальными по SSH через свой ed25519 ключ.
## Доступ
Бот реагирует ТОЛЬКО на сообщения из чата с `chat_id == TG_CHAT_ID` (см. `/etc/wireguard/notify.env`). Остальные сообщения игнорирует.
## Команды
### Туннели
| Команда | Описание |
|---|---|
| `/status` | Endpoint и handshake age для каждого ам. сервера |
| `/use <id>` | Принудительно переключить все ам. на RU-сервер с этим id |
| `/primary` | Алиас `/use primary` |
| `/backup` | Алиас `/use backup` |
| `/failover` | Алиас `/use backup` |
| `/failback` | Алиас `/use primary` |
### RU-серверы
| Команда | Описание |
|---|---|
| `/server-list` | Список RU-серверов с приоритетами |
| `/server-add <host> <user> <ssh_port> <id> [prio] [password=PW] [label=...] [listen_port=1939] [probe_port=ssh_port]` | Бот SSHит на новый RU, ставит wireguard, генерит ключ, добавляет 4 ам. как peer'ы, открывает 1939/UDP, кладёт свой ключ для будущего управления |
| `/server-remove <id\|host>` | Удалить RU из ротации (сам сервер не выключается, просто перестаёт использоваться) |
| `/bot-key` | Показать публичный SSH-ключ бота — для добавления в `authorized_keys` на новом сервере (если не хочется передавать пароль через TG) |
**Безопасность**: при `password=PW` бот после обработки автоматически удаляет своё сообщение через TG API. Но в моменте оно всё равно попадает в логи TG, поэтому надёжнее — использовать ключевую авторизацию (см. `/bot-key`).
### Ам. серверы
| Команда | Описание |
|---|---|
| `/ams-list` | Список ам. серверов |
| `/ams-add <host> <user> <ssh_port> <id> [tunnel_ip=auto] [xray_iface=amn0] [password=PW]` | Бот: ставит wireguard, кладёт свой ключ, генерит WG-ключ ам., выдаёт следующий свободный 10.0.0.X, копирует все скрипты и шаблон ru.conf со sga1, поднимает туннель на текущий primary, ставит cron, и добавляет [Peer] на ВСЕХ RU-серверах |
| `/ams-remove <id\|host>` | Снять [Peer] со всех RU; обновить JSON. Сам сервер остаётся работать (X-ray, OS), просто перестаёт быть в туннеле |
X-ray на новый ам. сервер бот НЕ ставит — это вне его компетенции.
### Маршруты (IP/CIDR)
| Команда | Описание |
|---|---|
| `/ips` | Все маршруты сразу (база + доп.) |
| `/list` | Только дополнительные (добавленные через бот) |
| `/add <IP/CIDR ...>` | Добавить (через пробел или с новой строки; `/32` дописывается сам) |
| `/remove <IP/CIDR ...>` | Удалить |
| `/clear` | Удалить ВСЕ доп. маршруты |
Базовые подсети (10 крупных сервисов: Яндекс, ВК, Mail.ru, Госуслуги, Ozon) трогать через бот нельзя — они в `/etc/wireguard/ru-base.aips`. Если нужно изменить — править вручную и `/usr/local/bin/ru-routes.sh apply`.
### Домены
| Команда | Описание |
|---|---|
| `/list-domains` | Список доменов с количеством IP |
| `/add-domain <domain ...>` | Резолвит каждый домен через `dig +short A`, добавляет полученные IP как /32 в маршруты на всех ам. серверах. Формат: пробел или с новой строки. |
| `/remove-domain <domain ...>` | Убирает домен и его IP (если их не использует другой домен). |
| `/show-domain <domain>` | Текущие IP конкретного домена |
| `/refresh-domains` | Принудительно перерезолвить все домены |
Cron `17 */6 * * * /usr/local/bin/ru-domains.py refresh` — автоматический refresh каждые 6 часов. Лог: `/var/log/ru-domains.log`.
### Прочее
| Команда | Описание |
|---|---|
| `/bot-key` | Публичный SSH-ключ бота |
| `/help` | Полная справка |
## Уведомления
При каждом автоматическом переключении (failover/failback успех/failback провал) бот шлёт сообщение в `TG_CHAT_ID` с тэгом `[ru-failover @ <hostname>]`. См. `journalctl -t ru-failover` для истории.
## Логи
```bash
# логи бота
journalctl -u ru-tg-bot.service -f
# логи failover
journalctl -t ru-failover -f
# или
grep ru-failover /var/log/syslog | tail
```

216
docs/install.md Normal file
View File

@@ -0,0 +1,216 @@
# Установка
## Требования
- Минимум 1 RU-сервер (Ubuntu 20.04+) с публичным IP или пробросом UDP/1939 на роутере
- Минимум 1 ам. сервер (Ubuntu 20.04+) с установленной X-ray панелью (3x-ui)
- Telegram-бот (создать в @BotFather) и chat ID для уведомлений (узнать у @userinfobot)
- Root доступ на оба сервера
## Шаг 1. Первый RU-сервер
На RU-сервере под root:
```bash
apt update && apt install -y wireguard iptables-persistent
# 1. Генерация ключа
cd /etc/wireguard
umask 077
wg genkey | tee ru_private.key | wg pubkey > ru_public.key
PRIVKEY=$(cat ru_private.key)
# 2. ip_forward
sysctl -w net.ipv4.ip_forward=1
echo 'net.ipv4.ip_forward=1' >> /etc/sysctl.conf
# 3. Конфиг — пока без peer'ов, добавятся когда подключим первый ам.
IFACE=$(ip route | awk '/^default/{print $5; exit}')
cat > /etc/wireguard/wg_ru.conf <<EOF
[Interface]
Address = 10.0.0.1/24
PrivateKey = $PRIVKEY
ListenPort = 1939
PostUp = iptables -t nat -A POSTROUTING -o $IFACE -j MASQUERADE; iptables -I FORWARD 1 -i wg_ru -j ACCEPT; iptables -I FORWARD 1 -o wg_ru -j ACCEPT; iptables -I INPUT -i wg_ru -j ACCEPT
PostDown = iptables -t nat -D POSTROUTING -o $IFACE -j MASQUERADE; iptables -D FORWARD -i wg_ru -j ACCEPT; iptables -D FORWARD -o wg_ru -j ACCEPT; iptables -D INPUT -i wg_ru -j ACCEPT
EOF
chmod 600 /etc/wireguard/wg_ru.conf
# 4. Открыть порт 1939/UDP
iptables -A INPUT -p udp --dport 1939 -j ACCEPT
netfilter-persistent save
# 5. Запуск + автостарт
wg-quick up wg_ru
systemctl enable wg-quick@wg_ru
# 6. Сохранить публичный ключ — пригодится
cat ru_public.key
```
Если RU за NAT (Keenetic и т.п.) — пробрось UDP/1939 на роутере на этот сервер.
## Шаг 2. Первый ам. сервер
На ам. сервере под root:
```bash
apt update && apt install -y wireguard iptables-persistent dnsutils
# 1. Ключ
cd /etc/wireguard
umask 077
wg genkey | tee ru_private.key | wg pubkey > ru_public.key
cat ru_public.key # записать — добавим в peer'ы RU
# 2. Узнать имя интерфейса X-ray
ip a | grep -E 'amn|tun' | grep -v '@'
# обычно amn0 — используем дальше
# 3. Конфиг ru.conf — IP в туннеле = 10.0.0.2 (первый ам.)
PRIVKEY=$(cat ru_private.key)
cat > /etc/wireguard/ru.conf <<EOF
[Interface]
Address = 10.0.0.2/32
PrivateKey = $PRIVKEY
Table = off
PostUp = ip route add 95.163.0.0/16 dev ru; ip route add 185.73.192.0/22 dev ru; ip route add 213.59.0.0/16 dev ru; ip route add 77.88.0.0/18 dev ru; ip route add 93.158.128.0/18 dev ru; ip route add 188.40.167.0/24 dev ru; ip route add 176.114.120.0/22 dev ru; ip route add 178.248.232.0/22 dev ru; ip route add 213.180.192.0/20 dev ru; ip route add 87.240.128.0/18 dev ru; ip rule add fwmark 100 table 200; ip route add default dev ru table 200; iptables -t nat -A POSTROUTING -o ru -j MASQUERADE
PostDown = ip rule del fwmark 100 table 200; ip route flush table 200; iptables -t nat -D POSTROUTING -o ru -j MASQUERADE
[Peer]
PublicKey = ПУБЛИЧНЫЙ_КЛЮЧ_RU_СЕРВЕРА
Endpoint = ВНЕШНИЙ_IP_RU:1939
AllowedIPs = 10.0.0.0/24, 95.163.0.0/16, 185.73.192.0/22, 213.59.0.0/16, 77.88.0.0/18, 93.158.128.0/18, 188.40.167.0/24, 176.114.120.0/22, 178.248.232.0/22, 213.180.192.0/20, 87.240.128.0/18
PersistentKeepalive = 25
EOF
chmod 600 /etc/wireguard/ru.conf
# 4. Mangle для X-ray трафика — за каждой подсетью
for net in 95.163.0.0/16 185.73.192.0/22 213.59.0.0/16 77.88.0.0/18 93.158.128.0/18 188.40.167.0/24 176.114.120.0/22 178.248.232.0/22 213.180.192.0/20 87.240.128.0/18; do
iptables -t mangle -A PREROUTING -i amn0 -d $net -j MARK --set-mark 100
done
netfilter-persistent save
# 5. Запуск
wg-quick up ru
systemctl enable wg-quick@ru
wg show ru # должен быть handshake
```
На RU-сервере добавить этот ам. как peer:
```bash
wg set wg_ru peer ПУБЛИЧНЫЙ_КЛЮЧ_АМ allowed-ips 10.0.0.2/32
cat >> /etc/wireguard/wg_ru.conf <<EOF
[Peer]
PublicKey = ПУБЛИЧНЫЙ_КЛЮЧ_АМ
AllowedIPs = 10.0.0.2/32
EOF
```
Проверить что трафик идёт:
```bash
# на ам. сервере
curl --interface ru https://gosuslugi.ru -I
# должен ответить HTTP/... 200
```
## Шаг 3. Установка скриптов и бота
На ам. сервере где будет жить бот (в Амстердаме, Telegram должен быть доступен!):
```bash
git clone https://github.com/andrey271192/kaskad.git /opt/kaskad
cd /opt/kaskad
# 1. Скопировать скрипты
install -m 755 bin/ru-failover.py /usr/local/bin/
install -m 755 bin/ru-set.sh /usr/local/bin/
install -m 755 bin/ru-routes.sh /usr/local/bin/
install -m 755 bin/ru-domains.py /usr/local/bin/
install -m 755 bin/add-ru-helper.sh /usr/local/bin/
install -m 755 bin/add-ams-helper.sh /usr/local/bin/
# 2. Создать notify.env
cp examples/notify.env.example /etc/wireguard/notify.env
chmod 600 /etc/wireguard/notify.env
# ВПИСАТЬ TG_BOT_TOKEN и TG_CHAT_ID
# 3. Создать ru-servers.json
cp examples/ru-servers.example.json /etc/wireguard/ru-servers.json
chmod 600 /etc/wireguard/ru-servers.json
# ОТРЕДАКТИРОВАТЬ — вписать host, pubkey, ssh_user/port для каждого RU; для каждого ам. — host, pubkey, tunnel_ip
# 4. Базовый список allowed-ips (берётся из ru.conf при первом apply)
# создаст ru-base.aips автоматически
/usr/local/bin/ru-routes.sh apply
# 5. Cron на failover и refresh доменов
( crontab -l 2>/dev/null; \
echo '* * * * * /usr/local/bin/ru-failover.py'; \
echo '17 */6 * * * /usr/local/bin/ru-domains.py refresh >> /var/log/ru-domains.log 2>&1' \
) | crontab -
# 6. SSH-ключ бота (нужен для управления остальными серверами через ssh-key auth)
[ -f /root/.ssh/id_ed25519 ] || ssh-keygen -t ed25519 -N '' -f /root/.ssh/id_ed25519
cat /root/.ssh/id_ed25519.pub
# скопировать в /root/.ssh/authorized_keys на всех остальных ам. серверах
# и на всех RU-серверах
# 7. Установить бот
install -m 755 bot/ru-tg-bot.py /usr/local/bin/
install -m 644 bot/ru-tg-bot.service /etc/systemd/system/
apt install -y python3 sshpass
systemctl daemon-reload
systemctl enable --now ru-tg-bot.service
journalctl -u ru-tg-bot.service -f
```
Послать боту в Telegram `/status` — должно прийти текущее состояние.
## Шаг 4. Веб-интерфейс (опционально)
На том же ам. сервере, где бот:
```bash
cd /opt/kaskad
apt install -y python3-flask
install -m 755 webui/app.py /usr/local/bin/ru-webui.py
mkdir -p /usr/local/share/kaskad
cp -r webui/templates webui/static /usr/local/share/kaskad/
# в app.py указать template_folder и static_folder если нужно;
# по умолчанию работает из текущей директории, поэтому проще:
ln -s /usr/local/share/kaskad/templates /usr/local/bin/templates
ln -s /usr/local/share/kaskad/static /usr/local/bin/static
mkdir -p /etc/kaskad
cp webui/webui.env.example /etc/kaskad/webui.env
chmod 600 /etc/kaskad/webui.env
# ВПИСАТЬ KASKAD_WEB_USER и KASKAD_WEB_PASS
install -m 644 webui/ru-webui.service /etc/systemd/system/
systemctl daemon-reload
systemctl enable --now ru-webui.service
# Открыть порт 8088 (по желанию)
iptables -I INPUT -p tcp --dport 8088 -j ACCEPT
netfilter-persistent save
# Открыть в браузере: http://AMS-IP:8088
```
Рекомендуется поставить за HTTPS reverse-proxy (nginx, caddy, traefik) с Let's Encrypt.
## Шаг 5. Дальше
Через бот или WebUI:
- `/server-add` — добавить новый RU-сервер
- `/ams-add` — добавить новый ам. сервер
- `/add-domain vk.com ozon.ru` — добавить русские сайты по доменам
- `/add 5.45.192.1/32` — добавить конкретные IP/CIDR
Failover-скрипт каждую минуту проверяет здоровье текущего peer'а и переключает при необходимости.
## Если что-то не работает
См. [docs/troubleshooting.md](troubleshooting.md).

104
docs/troubleshooting.md Normal file
View File

@@ -0,0 +1,104 @@
# Troubleshooting
## Туннель не устанавливается (нет handshake)
```bash
# на ам. сервере
wg show ru
# если нет endpoint вообще — что-то с конфигом
# если есть, но нет handshake:
nc -uvz <RU_HOST> 1939 # проверить что 1939/UDP проброшен
journalctl -u wg-quick@ru -n 30
```
На RU:
```bash
iptables -L INPUT -n | grep 1939 # должно быть ACCEPT
ss -ulnp | grep 1939 # WG слушает
wg show wg_ru # должны быть peer'ы
```
Если за NAT (Keenetic) — пробрось 1939/UDP на роутере.
## Handshake есть, curl через туннель не идёт (HTTP 000)
Скорее всего `FORWARD` policy = DROP на RU (часто из-за установленного Docker):
```bash
iptables -L FORWARD -n | head -1
# если DROP — добавить:
iptables -I FORWARD -i wg_ru -j ACCEPT
iptables -I FORWARD -o wg_ru -j ACCEPT
iptables -I INPUT -i wg_ru -j ACCEPT
netfilter-persistent save
```
И прописать в `PostUp` `wg_ru.conf` чтобы пережили рестарт.
Также проверить `ip_forward`:
```bash
sysctl net.ipv4.ip_forward # должно быть = 1
```
## Failover не срабатывает / срабатывает зря
Логи:
```bash
journalctl -t ru-failover -n 50
cat /var/lib/ru-failover/last_switch # timestamp последнего switch
cat /var/lib/ru-failover/test_started # 0 если не в режиме failback test
cat /var/lib/ru-failover/last_fail # timestamp последнего failed failback
```
Health-check использует TCP probe (по `probe_port` из JSON). Если хост жив, но probe_port закрыт — будет ложное срабатывание. Проверь:
```bash
nc -zv <RU_HOST> <probe_port>
```
Если ICMP блокируется на RU — это нормально, мы не используем ping.
## /add-domain ничего не добавляет
```bash
# на ам. сервере
which dig # если нет — apt install dnsutils
dig +short A vk.com # должны быть IP
/usr/local/bin/ru-domains.py add vk.com # ручной тест
```
## Бот не отвечает
```bash
systemctl status ru-tg-bot.service
journalctl -u ru-tg-bot.service -n 30
# проверка доступности TG:
curl -s -m 5 -o /dev/null -w '%{http_code}\n' https://api.telegram.org
# должно быть 302; 000 = заблокирован, бота нужно перенести на сервер вне РФ
```
Проверь, что `TG_CHAT_ID` в `notify.env` совпадает с твоим chat_id (узнать у `@userinfobot` или просто ничего боту не пиши — он молчит для всех кроме разрешённого chat_id).
## WebUI не открывается
```bash
systemctl status ru-webui.service
journalctl -u ru-webui.service -n 30
ss -tlnp | grep 8088
iptables -L INPUT -n | grep 8088 # порт должен быть ACCEPT
```
## Сменился pubkey ам. сервера, но JSON не обновился
Если ты руками регенерил ключи WG на ам. сервере — нужно:
1. Обновить `pubkey` этого ам. в `ru-servers.json` через бот: `/ams-remove <id>` + `/ams-add ...`
2. Или вручную в JSON и `wg syncconf wg_ru` на каждом RU
## Конфиги разъехались между серверами
Бот считает `ru-servers.json` на «локальном» ам. сервере источником правды и синхронит на остальные при каждом изменении. Если ты вручную менял JSON на не-локальном — изменения потеряются.
Принудительная синхронизация: на локальном ам.
```bash
/usr/local/bin/ru-tg-bot.py --resync # (если нет — через любую команду /add /remove /server-add т.п.)
```
Или просто пересохрани JSON через WebUI (любая операция запишет и распространит).

80
docs/webui.md Normal file
View File

@@ -0,0 +1,80 @@
# Web UI
Flask-приложение, бежит рядом с TG-ботом (на одном из ам. серверов). Использует **те же** скрипты и тот же `ru-servers.json`, что и бот.
## Возможности
- **Дашборд** — состояние туннелей всех ам. серверов: куда подключены, возраст handshake
- **Force-переключение** на любой RU-сервер одной кнопкой
- **CRUD RU-серверов** — добавление/удаление с веб-формы (бот сам пробрасывает SSH ключи и поднимает WG)
- **CRUD ам. серверов** — то же
- **CRUD доменов** — добавить/удалить с автоматическим резолвом
- **CRUD IP/CIDR** — добавить/удалить любые подсети
- **Просмотр базовых подсетей** — read-only
- **Фильтры по доменам и IP** — для удобства поиска
## API
Все endpoint'ы под `/api/`, все требуют HTTP basic auth.
| Метод | Путь | Тело | Описание |
|---|---|---|---|
| GET | `/api/state` | — | Полное состояние JSON |
| POST | `/api/use` | `{"id":"primary"}` | Force-переключить все ам. на этот RU |
| POST | `/api/server` | `{host,id,user,ssh_port,priority,...}` | Добавить новый RU |
| DELETE | `/api/server/<id>` | — | Удалить RU |
| POST | `/api/ams` | `{host,id,user,ssh_port,xray_iface,...}` | Добавить новый ам. |
| DELETE | `/api/ams/<id>` | — | Удалить ам. |
| POST | `/api/domains` | `{"domains":["vk.com",...]}` | Добавить домены |
| DELETE | `/api/domains` | `{"domains":[...]}` | Удалить домены |
| POST | `/api/domains/refresh` | — | Перерезолвить |
| POST | `/api/ips` | `{"ips":["1.2.3.4/32",...]}` или `{"ips":"text with IPs"}` | Добавить |
| DELETE | `/api/ips` | то же | Удалить |
| POST | `/api/ips/clear` | — | Очистить все доп. IP |
Пример:
```bash
curl -u admin:PASS https://your-host/api/state | jq
curl -u admin:PASS -X POST https://your-host/api/use -d '{"id":"primary"}' -H 'Content-Type: application/json'
```
## Безопасность
- Basic auth обязателен. `KASKAD_WEB_PASS` должен быть длинным и случайным (см. `webui.env.example`)
- По умолчанию слушает на `0.0.0.0:8088`. **Рекомендуется** поставить за HTTPS reverse-proxy (nginx/caddy) с Let's Encrypt
- Если хочется привязать только к localhost — `KASKAD_HOST=127.0.0.1` и пользоваться через SSH-туннель: `ssh -L 8088:localhost:8088 root@ams1`
- Пароли SSH (`password=` при `/server-add`, `/ams-add`) передаются по HTTPS только если веб за reverse-proxy. Без HTTPS не передавай пароли через WebUI — используй ключи (см. `/bot-key`)
## Конфиг (env-переменные)
| Переменная | Дефолт | Описание |
|---|---|---|
| `KASKAD_WEB_USER` | `admin` | Логин для basic auth |
| `KASKAD_WEB_PASS` | (нет) | Пароль; ОБЯЗАТЕЛЬНО задать |
| `LOCAL_HOST` | `ams1` | Имя локального ам. сервера |
| `LOCAL_IP` | `127.0.0.1` | Локальный IP — определяет, какой ам. читать локально без SSH |
| `KASKAD_HOST` | `0.0.0.0` | Адрес для bind |
| `KASKAD_PORT` | `8088` | Порт |
| `KASKAD_SSH_KEY` | `/root/.ssh/id_ed25519` | SSH-ключ для управления другими серверами |
| `KASKAD_SERVERS_JSON` | `/etc/wireguard/ru-servers.json` | Путь к конфигу |
## nginx reverse-proxy (пример)
```nginx
server {
listen 443 ssl http2;
server_name kaskad.example.com;
ssl_certificate /etc/letsencrypt/live/kaskad.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/kaskad.example.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8088;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 600s; # для долгих /add-domain пачкой
}
}
```
И в WebUI поставить `KASKAD_HOST=127.0.0.1`, не открывать 8088 наружу.