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 часто недоступен, поэтому бот живёт на одном из ам. серверов.