montana/_internal-private/NETWORK-STATE-RUNBOOK.md

183 lines
13 KiB
Markdown
Raw Normal View History

2026-05-27 16:23:42 +03:00
# Montana — Network State Snapshot & Recovery Runbook
**Версия слепка:** 1.1.0
**Дата:** 2026-05-27
**Назначение:** зафиксировать рабочее состояние сети + точные процедуры восстановления + все известные причины падений. ВНУТРЕННИЙ документ (содержит IP и ссылки на секреты — НЕ публиковать в efir369999/Montana по правилу no-IP-in-public).
> Версионирование: при любом изменении топологии/ключей/каскадов — bump версии слепка (1.0.0 → 1.1.0) и обновить соответствующий раздел.
---
## 1. Топология (6 узлов)
| alias | IP | хостинг | роль | сервисы |
|-------|----|---------|------|---------|
| moscow | 176.124.208.93 | Timeweb (RU) | genesis, control-plane, VPN own-key | montana-node :8444, xray :443 (Reality masq montana.quest), nginx :8443/:80, orchestrator :5020, sub-gen :5008 |
| frankfurt | 89.19.208.158 | Timeweb (DE) | genesis, **VPN cascade FRONT**, VPN direct | montana-node :8444, xray :443 (Reality universal) + cascade outbounds |
| helsinki | 91.132.142.42 | THE.Hosting (FI) | genesis, VPN exit | montana-node :8444, xray :443 (Reality universal) |
| yerevan | 149.154.184.205 | WorkTitans (AM) | VPN exit (docker) | docker: montana-node :8444, xray :443, nginx-decoy :80 |
| vilnius | 149.154.185.5 | LT | VPN exit (docker) | docker: montana-node :8444, xray :443, nginx-decoy :80 |
| nicosia | 45.9.13.170 | VMmanager (CY) | VPN exit (docker) | docker: montana-node :8444, xray :443, nginx-decoy :80 |
SSH-алиасы: `montana-moscow`/`my-timeweb`, `montana-frankfurt`, `montana-finland`, `montana-armenia`, `montana-lithuania`, `montana-nicosia`.
---
## 2. Архитектура VPN (на момент слепка)
**Достижимые у домашних RU-провайдеров фронты:** Frankfurt (89.19.208.158), Moscow (176.124.208.93) — диапазоны Timeweb не режутся.
**Заблокированные напрямую у домашних RU-ISP:** Helsinki, Yerevan, Vilnius (диапазоны THE.Hosting / 149.154.0.0/16).
**Решение — каскад:** все заблокированные города ходят через достижимый фронт Frankfurt (raw double-crypto в текущей версии), выход — в правильной стране.
```
клиент → de.montana.quest:443 (Frankfurt, Reality) → routing по cascade-UUID → <out> → exit-страна
```
| Город в подписке | Коннект (host) | UUID | flow | Выход (IP / страна) |
|---|---|---|---|---|
| 🇩🇪 Франкфурт | de.montana.quest | e6d355e2 (universal) | vision | 89.19.208.158 / DE (direct) |
| 🇷🇺 Москва | montana.quest | c83d4d13 (own) | vision | 176.124.208.93 / RU (direct) |
| 🇫🇮 Хельсинки | de.montana.quest | 094f9073 (cascade) | none | 91.132.142.42 / FI (via FRA) |
| 🇦🇲 Ереван | de.montana.quest | 43ba0c0e (cascade) | none | 149.154.184.205 / AM (via FRA) |
| 🇱🇹 Вильнюс | de.montana.quest | fc8a174d (cascade) | none | 149.154.185.5 / LT (via FRA) |
| 🇨🇾 Никосия | de.montana.quest | dad79315 (cascade) | none | 45.9.13.170 / CY (via FRA) |
---
## 3. Ключи Reality
**Universal Montana key** (Helsinki/Frankfurt/Yerevan/Vilnius exit + клиентская подписка):
- UUID `e6d355e2-2d79-4c96-a373-3b0e6b6f4b0d`
- PBK `EkTs2aGKnFNgFZ0f7wgft2sJp3VjwFQqIrwkZKM4gD8`
- SID `302805bc0c25e504`
- SNI `www.googletagmanager.com`
- privateKey `cL7D6FCqH5nWcQlHCKH9uNr-RNwCt5peRAqt8tl9mXs` (секрет; на каждом exit-узле в xray config; pre-stage для install: `/etc/montana-vpn/privkey`)
**Moscow own key** (masq под свой сайт montana.quest, т.к. :443 делит с сайтом):
- UUID `c83d4d13-fce9-4c07-85f0-c152d4bda3ee`
- PBK `svxjTnEZxk6aStkaHSYd2b-br3Pe4yqGcNrugokjEgg`
- SID `f976f81b29f78c1f`
- SNI `montana.quest`
- privateKey `iMWS9kMDTBsvRqXMdjXdoRg50DgB3ZRjvJEZ2LxPm3g` (секрет; в xray config Moscow)
**Cascade-UUID** (на Frankfurt-фронте, маршрутизируют к exit-outbound; flow пустой):
- yerevan `43ba0c0e-c1e3-4e30-8ae8-c2e68d24d7c7` → outbound `armenia-out`
- helsinki `094f9073-aff0-4c07-a4af-6ca4c924f6a9` → outbound `helsinki-out`
- vilnius `fc8a174d-f42b-4945-8548-ab5c9f448f81` → outbound `vilnius-out`
- nicosia `dad79315-0b80-5eca-9703-afee839e0131` → outbound `nicosia-out`
Orchestrator admin token: Moscow `/etc/montana/orchestrator-admin-token`. CF API token: Keychain `cloudflare-api-token` / `montana-quest`.
---
## 4. Cloudflare DNS (зона montana.quest, все DNS-only, не proxied)
```
fi.montana.quest → 91.132.142.42 (Helsinki)
de.montana.quest → 89.19.208.158 (Frankfurt — cascade front)
am.montana.quest → 149.154.184.205 (Yerevan)
lt.montana.quest → 149.154.185.5 (Vilnius)
cy.montana.quest → 45.9.13.170 (Nicosia)
cdn.montana.quest → multi-A: 89.19.208.158, 91.132.142.42, 149.154.185.5, 149.154.184.205, 45.9.13.170 (watchdog auto-prune)
montana.quest → 176.124.208.93 (Moscow — сайт + own-key VPN)
```
---
## 5. Control-plane (Moscow)
- **orchestrator** `/opt/montana-orchestrator/server.py`, systemd `montana-orchestrator.service`, :5020. Registry `/var/lib/montana-orchestrator/nodes.json`. API `/register /deregister /nodes /pool` (нужен `secret`=admin token). Watchdog probe Reality :443 каждые 30с, prune dead из cdn multi-A.
- **sub-генератор** `/opt/montana-vpn-balance/app.py`, gunicorn :5008, отдаёт `/vpn/sub`. Карты: `ALIAS_HOST`, `CASCADE` (yerevan/helsinki/vilnius → de.montana.quest), `OWN_KEY` (moscow → montana.quest). Ключи из `/etc/montana-vpn/keys.json` (sync с Helsinki, таймер `montana-vpn-key-sync`).
- **nginx** :443 → SNI-роутинг montana.quest+hub+efir; ВНИМАНИЕ: на Moscow xray держит :443, nginx на 127.0.0.1:8443 (Reality dest=local nginx). Все enabled на boot.
- **сайт-данные**: build-скрипты `montana-net-pull`, `montana-cities-build`, `aggregator.sh`, `collector.sh`, `peer-health.py` (на mos/fra/fin). US удалён отовсюду.
---
## 6. Frankfurt cascade-config
```
inbound: reality-in :443 (universal key)
clients: montana-universal(vision), yerevan-cascade(none), helsinki-cascade(none), vilnius-cascade(none), nicosia-cascade(none)
outbounds: direct, blocked, dns-out, armenia-out(→149.154.184.205:443), helsinki-out(→91.132.142.42:443), vilnius-out(→149.154.185.5:443), nicosia-out(→45.9.13.170:443)
routing: user=<cascade-email> → соответствующий <city>-out (Reality client universal к exit)
```
Бэкапы конфига: `/usr/local/etc/xray/config.json.bak-*` (последний рабочий — без dokodemo).
---
## 7. ВСЕ известные причины падений + восстановление
### 7.1 Frankfurt xray restart-race (СЛУЧАЛОСЬ 2026-05-26)
**Симптом:** xray `failed`, journal `bind: address already in use`, `Start request repeated too quickly`. Все каскады + клиентский VPN вниз.
**Причина:** правка live-конфига (новый inbound/порт) + `Restart=always` → зомби-инстанс держит порт, новый не биндит, systemd крутит рестарты до лимита.
**Восстановление:**
```
ssh montana-frankfurt 'systemctl stop xray; pkill -9 xray; for p in 443 2096 2097 2098; do fuser -k $p/tcp; done; sleep 3; \
cp $(ls -t /usr/local/etc/xray/config.json.bak-* | head -1) /usr/local/etc/xray/config.json; \
xray -test -config /usr/local/etc/xray/config.json; systemctl reset-failed xray; systemctl start xray'
```
**Профилактика:** НЕ править live-xray на проде хирургией; правильный путь load-распределения — deploy mt-egress relay, не dokodemo на живой xray.
### 7.2 ufw блокирует :8444 (узел не пирится входящими)
**Симптом:** montana-node только исходящие ESTAB, входящих нет; не виден peer-ам.
**Причина:** install-vps-full поднимал ufw с 22/80/443, забывал 8444.
**Восстановление:** `ssh <node> 'ufw allow 8444/tcp; ufw reload'`. (install-docker.sh уже открывает 8444.)
### 7.3 Reality-ключи рассинхрон (только Frankfurt работал)
**Симптом:** клиент TLS-handshake проходит, но прокси не работает на части узлов; работает только один город.
**Причина:** privateKey на узле ≠ PBK в подписке.
**Восстановление:** сверить `xray x25519 -i <priv>` каждого узла → PBK должен == подписочный. Universal privkey `cL7D6FCq…` на всех exit. Команда сверки:
```
ssh <node> 'docker exec montana-xray xray x25519 -i $(jq -r .inbounds[0].streamSettings.realitySettings.privateKey /etc/montana-vpn/xray-config.json)'
```
### 7.4 Узел недостижим у конкретного провайдера (IP в РКН-блоке)
**Симптом:** город не открывается у одного ISP, работает у других / из дата-центра.
**Причина:** IP узла в блок-листе оператора (149.154.0.0/16 = легаси-Telegram; THE.Hosting). НЕ баг сервера.
**Восстановление:** завернуть город каскадом через достижимый фронт (Frankfurt/Moscow) — см. §2. Долгосрочно: T1T3 транспорты (spec B1, не готовы) либо чистый IP.
### 7.5 Moscow :443 конфликт (сайт vs VPN)
**Симптом:** упал сайт montana.quest ИЛИ /vpn/sub ИЛИ orchestrator.
**Причина:** xray и nginx делят :443; неверный порядок старта / занятый порт.
**Восстановление:** nginx должен слушать 127.0.0.1:8443 (не :443); xray :443 dest=127.0.0.1:8443. Бэкапы nginx: `/root/nginx-bak-*`. Проверка: `curl -sk --resolve montana.quest:443:127.0.0.1 https://montana.quest/vpn/sub` → 200.
### 7.6 docker build OOM / провал (Armenia/Vilnius при install)
**Симптом:** install-docker.sh падает на cargo build.
**Причины:** (а) RAM < 1.5G без swap OOM; (б) `Montana wordlist.txt` вне build-context (фикс: context = repo root); (в) glibc mismatch (фикс: runtime debian:trixie-slim); (г) volume permission (фикс: chown+runuser); (д) xray x25519 формат вывода (фикс: awk `/Password|ublic/`).
**Восстановление:** все фиксы уже в install-docker.sh main; при OOM добавить swap.
### 7.7 cdn.montana.quest содержит мёртвый/блокируемый IP
**Симптом:** «Auto» попадает на нерабочий узел.
**Причина:** watchdog probe только с Moscow (один vantage) — держит IP, недостижимый у клиента.
**Восстановление:** deregister руками: `curl -X POST -d '{"ip":"<ip>","secret":"<token>"}' https://montana.quest/vpn/node/deregister`. Долгосрочно: reachability-sensing (новый код mt-net, не задеплоен).
---
## 8. Команды проверки здоровья (health-check)
```
# узлы (фаза + пиры)
for n in moscow frankfurt finland armenia lithuania; do ssh montana-$n '...montana-node status...'; done
# подписка (5 городов)
curl -sk https://montana.quest/vpn/sub | base64 -d
# каскады выходят правильной страной (с Moscow)
ssh montana-moscow 'bash /tmp/test-all-cascades.sh' # helsinki→FI, vilnius→LT, yerevan→AM
# Reality :443 фронта извне
echo Q | openssl s_client -connect de.montana.quest:443 -servername www.googletagmanager.com -brief
# orchestrator
curl -sk https://montana.quest/vpn/node/health
```
---
## 9. Что задеплоено vs что в коде (на момент слепка)
- **Прод (живое):** montana-node v1.0.0 (mos/fra/fin/am/lt/cy), статический каскад через Frankfurt, install-docker.sh (авто-join + VPN-ключи).
- **В коде, НЕ задеплоено:** mt-net reachability/steering (M10 A-C), mt-egress (M11 A-B + client + e2e) — юнит-протестировано локально (30+ тестов), вшивание в montana-node (M11 C) pending.
- **Не построено:** T1-T3 транспорты (spec B1), English-перевод Network спеки (B5), M7 fast-sync, M8 multi-node apply, авто-апдейт флота.