Compare commits
2 Commits
80a93d6355
...
12705f148f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
12705f148f | ||
|
|
14249577bc |
@@ -65,6 +65,16 @@ tags: [decision, ai, hermes, telegram, lxc, assistant]
|
|||||||
- **Enroll с `--disable-dns`**: `netbird up --setup-key <KEY> --disable-dns --hostname german`. Это сознательно — на LXC 132/137 NetBird-DNS (resolv.conf через wt0) уже ломал связность; German держит свой `1.1.1.1`/`8.8.8.8` и ходит по IP. Magic-DNS `*.netbird.cloud` в контейнере НЕ резолвится (`search dttb.ru` хайджачит) — **только по IP 100.70.x**.
|
- **Enroll с `--disable-dns`**: `netbird up --setup-key <KEY> --disable-dns --hostname german`. Это сознательно — на LXC 132/137 NetBird-DNS (resolv.conf через wt0) уже ломал связность; German держит свой `1.1.1.1`/`8.8.8.8` и ходит по IP. Magic-DNS `*.netbird.cloud` в контейнере НЕ резолвится (`search dttb.ru` хайджачит) — **только по IP 100.70.x**.
|
||||||
- Проверено: openclaw `100.70.167.54:18789` → 200; github/telegram/OmniRoute/DNS целы; `netbird` autostart enabled; hermes-german не пострадал.
|
- Проверено: openclaw `100.70.167.54:18789` → 200; github/telegram/OmniRoute/DNS целы; `netbird` autostart enabled; hermes-german не пострадал.
|
||||||
|
|
||||||
|
## Веб-дашборд `german.dttb.ru` (добавлено 2026-06-26)
|
||||||
|
Родная веб-панель Hermes (`hermes dashboard`: чат + config + sessions + встроенный PTY-терминал), не путать с Open WebUI.
|
||||||
|
- **systemd `hermes-dashboard.service`** (рядом с gateway, не вместо): `hermes dashboard --host 0.0.0.0 --port 9119 --skip-build --no-open`, Restart=always, бинд `0.0.0.0:9119`.
|
||||||
|
- **Фронт пришлось собрать**: `web/dist` отсутствовал. `cd /usr/local/lib/hermes-agent/web && npm install && npm run build` → vite кладёт в `../hermes_cli/web_dist` (outDir в vite.config, НЕ `web/dist`), оттуда `--skip-build` и отдаёт. node v22 (warning EBADENGINE про node≥24 — не критично).
|
||||||
|
- **NPM** #40 (10.0.0.195) `german.dttb.ru` → `10.0.0.141:9119`, force-SSL+WSS+HTTP2, block-exploits. LE-серт **id130** (HTTP-01, до 24.09.2026). NPM v2.14: cert-create только `meta:{}` (email/agree/dns_challenge = «additional properties»).
|
||||||
|
- **DNS**: публичный A `german.dttb.ru`→`176.62.183.186` добавлен через Spaceweb API (`editMain` `action:add`, один вызов — циклом ломает зону). Локально wildcard `*.dttb.ru`→`10.0.0.195` уже резолвил. Проверять пропагацию только DoH (8.8.8.8/resolve), `dig` хайджачится локальным DNS.
|
||||||
|
- **Аутентификация — `dashboard.basic_auth`** в `/root/.hermes/config.yaml`: `username: oleg`, `password_hash` (scrypt, через `plugins.dashboard_auth.basic.hash_password`), `secret` (32 байта hex), `session_ttl_seconds: 43200`, `public_url: https://german.dttb.ru`. Плейнтекст-пароль не хранится. Креды в [[../projects/dttb/credentials.md]]. Бэкап `config.yaml.bak-dashboard-20260626`.
|
||||||
|
- ⚠️ **ГЛАВНАЯ ГРАБЛЯ — `--insecure` отключает gate.** `should_require_auth(host, allow_public)`: non-loopback + `--insecure` → `auth_required=False` → активен легаси `_SESSION_TOKEN`-middleware (ephemeral токен, инжектится в loopback-HTML), basic-cookie-gate `gated_auth_middleware` становится no-op. Симптом: `/auth/password-login`→`{"ok":true}` (кука минтится), но `/api/auth/me` + все данные→401. Провайдер/секрет/токен/куки исправны (доказано: офлайн `_unsign(live_token, _resolve_secret(cfg))`→payload ок; round-trip провайдера→ok). **Фикс = убрать `--insecure`** (бинд `0.0.0.0` без него: `auth_required=True`, провайдер `basic`, и `0.0.0.0` принимает любой Host — иначе явный бинд на IP даёт 400 «Invalid Host header» на NPM-домен). Hermes сам это документирует: `dashboard_register.py:218` «To require login (LAN/public): hermes dashboard --host 0.0.0.0». Лог-маркер: `Dashboard binding to 0.0.0.0 with OAuth auth gate enabled. Providers: basic`.
|
||||||
|
- Проверено end-to-end через NPM: `https://german.dttb.ru` login `oleg` → `/api/auth/me`→200, `/api/sessions`→200; http→301; серт CN german.dttb.ru. Telegram-gateway не задет (оба сервиса active+enabled).
|
||||||
|
|
||||||
## TODO / на будущее
|
## TODO / на будущее
|
||||||
- Fallback-цепочка (cc/sonnet-4-6, kr/sonnet-4.5, cx/gpt-5.4) — формат `fallback_providers` с `api_key_env` (НЕ `${VAR}` — путь резолва фолбэков не делает env-подстановку). Пока не ставил (primary надёжен).
|
- Fallback-цепочка (cc/sonnet-4-6, kr/sonnet-4.5, cx/gpt-5.4) — формат `fallback_providers` с `api_key_env` (НЕ `${VAR}` — путь резолва фолбэков не делает env-подстановку). Пока не ставил (primary надёжен).
|
||||||
- Настоящий web-search через self-hosted SearXNG (`SEARXNG_URL`) или ключ Tavily/Firecrawl — если ddgs будет мало.
|
- Настоящий web-search через self-hosted SearXNG (`SEARXNG_URL`) или ключ Tavily/Firecrawl — если ddgs будет мало.
|
||||||
|
|||||||
58
decisions/2026-06-26-benelux-podkop-recovery-watchdog.md
Normal file
58
decisions/2026-06-26-benelux-podkop-recovery-watchdog.md
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
---
|
||||||
|
date: 2026-06-26
|
||||||
|
type: decision
|
||||||
|
tags: [benelux, podkop, watchdog, monitoring, self-heal, openwrt, amneziawg, failover]
|
||||||
|
status: stage1-done
|
||||||
|
---
|
||||||
|
|
||||||
|
# Бенелюкс — инструмент восстановления обхода (мониторинг + автолечение)
|
||||||
|
|
||||||
|
## Задача
|
||||||
|
Олег: «инструмент восстановления обхода блокировок, мониторинг + автоисправление, должно работать на 100%, наверное на внешних ресурсах от Бенелюкса».
|
||||||
|
|
||||||
|
## Калибровка «100%»
|
||||||
|
Буквальные 100% одним туннелём недостижимы (ISP/нода/NetBird могут лечь — удалённо не починить). Реальная цель: **обход никогда не остаётся сломанным незаметно, чинится сам за 1–3 мин, иначе алерт раньше клиента.** Достигается тремя слоями.
|
||||||
|
|
||||||
|
## Решения Олега (AskUserQuestion)
|
||||||
|
- Наблюдатель — **дома, LXC** (клон antoshka-watch-self, алерт через бота).
|
||||||
|
- Автофикс — **до ребута включительно**, с гистерезисом.
|
||||||
|
- Резерв выхода — **второй AWG-сервер** (не VLESS) → Finland-хаб.
|
||||||
|
|
||||||
|
## Архитектура (3 слоя)
|
||||||
|
1. **Резерв выхода:** awg0 Singapore (primary) + awg1 Finland (secondary). Этап 2.
|
||||||
|
2. **Лёгкое самолечение на роутере:** podkop `enable_badwan_interface_monitoring=1` (следит за wan, перезагружает sing-box) — уже было.
|
||||||
|
3. **Внешний сторож (главный):** `benelux-podkop-watchdog.sh` на LXC 137, cron `*/5`, проверки УДАЛЁННО по SSH через NetBird.
|
||||||
|
|
||||||
|
## Ключевой принцип — анти-flapping
|
||||||
|
Грабли OpenWrt_4/Оливье: собственный watchdog.sh агрессивно рестартовал sing-box и сам создавал обрывы. Поэтому: лечим только после **2 подряд провалов**, cooldown 5 мин между шагами, лимит **2 ребута/сутки**.
|
||||||
|
|
||||||
|
## Что проверяет сторож (изнутри роутера)
|
||||||
|
- sing-box жив + Clash API (`192.168.1.1:9090`) отвечает
|
||||||
|
- handshake текущего выхода < 200с + `ping -I awgN 1.1.1.1` (транзит, ловит rp_filter-ловушку)
|
||||||
|
- FakeIP: youtube → `198.18.x` (заворачивается)
|
||||||
|
- **анти-утечка:** ozon.ru НЕ `198.18.x` (страж рецидива `russia_outside`) — только алерт, не автофикс
|
||||||
|
- достижимость: SSH нет → различает «роутер пингуется, SSH моргнул» vs «Бенелюкс лёг»
|
||||||
|
|
||||||
|
## Лестница лечения (с гистерезисом)
|
||||||
|
0. `podkop restart`
|
||||||
|
1. флип `podkop.main.interface` awg0→awg1 (если awg1 поднят и здоров), иначе `ifdown/ifup` + restart
|
||||||
|
2. `reboot` (лимит 2/сутки)
|
||||||
|
3. сдаёмся → алерт «нужно руками»
|
||||||
|
Каждое срабатывание + восстановление (✅) → Telegram Олегу (1292155421) через токен бота из `/root/.openclaw/openclaw.json` (тот же тракт, что antoshka-watch-self).
|
||||||
|
|
||||||
|
## Деплой Этапа 1 (2026-06-26) — СДЕЛАНО
|
||||||
|
- Ключ LXC137 `root@openclaw` добавлен в `/etc/dropbear/authorized_keys` Бенелюкса.
|
||||||
|
- `/root/benelux-podkop-watchdog.sh` на LXC 137, cron `*/5`. Исходник в vault `snippets/benelux/benelux-podkop-watchdog.sh`.
|
||||||
|
- **Боевой тест пройден:** `podkop stop` → прогон1 DEGRADED 1/2 (без действий) → прогон2 2/2 → Шаг1 restart → sing-box поднят, FakeIP вернулся → прогон3 OK + ✅-отбой + сброс счётчика. ⚠️/✅ алерты дошли.
|
||||||
|
- Грабли при написании: busybox `pgrep -x` не работает (→ `pgrep`); `read < нет_файла` течёт ошибкой (→ guard `[ -f ]`).
|
||||||
|
|
||||||
|
## Этап 2 (TODO) — резервный выход Finland + failover
|
||||||
|
- Через Amnezia Web Panel (LXC 143 `10.0.0.143:5000`, admin/AmnPanel!2026-fi) нарезать пир Бенелюкса на Finland-хаб `151.241.234.241:41624` (AmneziaWG 2.0, subnet `10.8.1.0/24`).
|
||||||
|
- Поднять `awg1` на роутере (UCI-референс — HomeLab awg2, [[../projects/dttb/openwrt-router]]); awg1 в firewall WAN-зону; per-iface `rp_filter=2`.
|
||||||
|
- Failover уже заложен в watchdog (Шаг1 флипает на awg1) — активируется автоматически, как только `network.awg1` появится.
|
||||||
|
- Закрывает TODO из [[2026-06-23-amnezia-web-panel-lxc143]] «failover (AWG сам не переключается)».
|
||||||
|
|
||||||
|
## Опционально (Этап 3)
|
||||||
|
- Второй независимый сторож на Finland VPS (на случай падения дома) — взаимный догляд ближе к «100%».
|
||||||
|
|
||||||
|
См. [[../snippets/podkop-reference]], [[../claude-memory/benelux]].
|
||||||
@@ -146,6 +146,16 @@ cd /var/lib/rustdesk-api && /usr/bin/rustdesk-api reset-admin-pwd <new-pw>
|
|||||||
| Email | `it5870@yandex.ru` |
|
| Email | `it5870@yandex.ru` |
|
||||||
| Пароль | `1qaz!QAZ` |
|
| Пароль | `1qaz!QAZ` |
|
||||||
|
|
||||||
|
## German Hermes Dashboard (LXC 141)
|
||||||
|
|
||||||
|
| Параметр | Значение |
|
||||||
|
|----------|----------|
|
||||||
|
| URL | https://german.dttb.ru (NPM #40 → 10.0.0.141:9119) |
|
||||||
|
| Логин | `oleg` / `German-ecddc1edea-2026` |
|
||||||
|
| Механизм | `dashboard.basic_auth` в `/root/.hermes/config.yaml` (scrypt `password_hash` + `secret`, plaintext не хранится) |
|
||||||
|
| systemd | `hermes-dashboard.service` = `hermes dashboard --host 0.0.0.0 --port 9119 --skip-build --no-open` |
|
||||||
|
| ⚠️ Грабля | **БЕЗ `--insecure`!** `--insecure` отключает cookie-gate (включает легаси `_SESSION_TOKEN`) → login проходит, но всё внутри 401. Бинд `0.0.0.0` без `--insecure` = `auth_required=True` + принимает любой Host (для NPM-домена). См. [[../../decisions/2026-06-18-german-hermes-agent-deploy]] |
|
||||||
|
|
||||||
## SSH-ключи и доступы
|
## SSH-ключи и доступы
|
||||||
|
|
||||||
| Хост | Порт | Метод |
|
| Хост | Порт | Метод |
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ tags: [dttb, npm]
|
|||||||
> **NPM LXC:** 103 (10.0.0.195)
|
> **NPM LXC:** 103 (10.0.0.195)
|
||||||
> **Панель:** https://npm.dttb.ru
|
> **Панель:** https://npm.dttb.ru
|
||||||
>
|
>
|
||||||
> Последнее обновление: 2026-06-15 (добавлен unifi.dttb.ru → UniFi LXC 140)
|
> Последнее обновление: 2026-06-26 (добавлен german.dttb.ru → Hermes Dashboard LXC 141)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ tags: [dttb, npm]
|
|||||||
|
|
||||||
| Всего хостов | С SSL | Без SSL | Активных |
|
| Всего хостов | С SSL | Без SSL | Активных |
|
||||||
|--------------|-------|---------|----------|
|
|--------------|-------|---------|----------|
|
||||||
| 22 | 18 | 4 | 22 |
|
| 23 | 19 | 4 | 23 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -53,6 +53,7 @@ tags: [dttb, npm]
|
|||||||
| 34 | `rustdesk.umnybot.ru` | 10.0.0.190:3005 | ✅ Let's Encrypt | ✅ | ✅ | ❌ | RustDesk client web (KasmVNC, ZimaOS) — Basic Auth ACL `umnybot-kasm` |
|
| 34 | `rustdesk.umnybot.ru` | 10.0.0.190:3005 | ✅ Let's Encrypt | ✅ | ✅ | ❌ | RustDesk client web (KasmVNC, ZimaOS) — Basic Auth ACL `umnybot-kasm` |
|
||||||
| 36 | `unifi.dttb.ru` | 10.0.0.196:8443 (HTTPS) | ✅ Let's Encrypt | ✅ | ✅ | ✅ | **UniFi Network Application** (LXC 140) |
|
| 36 | `unifi.dttb.ru` | 10.0.0.196:8443 (HTTPS) | ✅ Let's Encrypt | ✅ | ✅ | ✅ | **UniFi Network Application** (LXC 140) |
|
||||||
| 39 | `chat.dttb.ru` | 10.0.0.142:3000 | ✅ Let's Encrypt (id129) | ✅ | ✅ | ✅ | **Open WebUI** (LXC 142) — веб-клиент поверх OmniRoute |
|
| 39 | `chat.dttb.ru` | 10.0.0.142:3000 | ✅ Let's Encrypt (id129) | ✅ | ✅ | ✅ | **Open WebUI** (LXC 142) — веб-клиент поверх OmniRoute |
|
||||||
|
| 40 | `german.dttb.ru` | 10.0.0.141:9119 | ✅ Let's Encrypt (id130) | ✅ | ✅ | ✅ | **Hermes Dashboard** (LXC 141) — веб-панель German; basic_auth `oleg`, см. credentials |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -91,6 +92,7 @@ location /.well-known/matrix/client {
|
|||||||
| 10.0.0.10 | 9443 | porteiner.dttb.ru |
|
| 10.0.0.10 | 9443 | porteiner.dttb.ru |
|
||||||
| 10.0.0.43 | 21114 | remot.dttb.ru |
|
| 10.0.0.43 | 21114 | remot.dttb.ru |
|
||||||
| 10.0.0.112 | 8840 | ip.dttb.ru |
|
| 10.0.0.112 | 8840 | ip.dttb.ru |
|
||||||
|
| 10.0.0.141 | 9119 | german.dttb.ru (Hermes Dashboard) |
|
||||||
| 10.0.0.142 | 3000 | chat.dttb.ru (Open WebUI) |
|
| 10.0.0.142 | 3000 | chat.dttb.ru (Open WebUI) |
|
||||||
| 10.0.0.155 | 8123 | home.dttb.ru |
|
| 10.0.0.155 | 8123 | home.dttb.ru |
|
||||||
| 10.0.0.169 | 8080 | office.dttb.ru |
|
| 10.0.0.169 | 8080 | office.dttb.ru |
|
||||||
|
|||||||
@@ -224,6 +224,7 @@ tags: [dttb, proxmox]
|
|||||||
| ОС/рантайм | Debian 12, unprivileged + nesting, Hermes Agent v0.16.0 (Python, `/usr/local/lib/hermes-agent`, данные `/root/.hermes`) |
|
| ОС/рантайм | Debian 12, unprivileged + nesting, Hermes Agent v0.16.0 (Python, `/usr/local/lib/hermes-agent`, данные `/root/.hermes`) |
|
||||||
| Ресурсы | 2 vCPU / 3 GB / 12 GB (rootfs на local-lvm) |
|
| Ресурсы | 2 vCPU / 3 GB / 12 GB (rootfs на local-lvm) |
|
||||||
| Telegram | бот **«Герман Непомнящий»** @german_dttb_bot — заперт на Олега (`TELEGRAM_ALLOWED_USERS=1292155421`) |
|
| Telegram | бот **«Герман Непомнящий»** @german_dttb_bot — заперт на Олега (`TELEGRAM_ALLOWED_USERS=1292155421`) |
|
||||||
|
| Веб-дашборд | **https://german.dttb.ru** (NPM #40 → `:9119`, родная панель Hermes: чат+config+sessions+PTY). systemd `hermes-dashboard.service` (`--host 0.0.0.0` БЕЗ `--insecure`!). basic_auth `oleg`, креды в credentials. См. decision 2026-06-18 (раздел дашборд) |
|
||||||
| Модель | **`cc/claude-opus-4-8`** (Opus 4.8 via Max) через OmniRoute (`http://10.0.0.179:20128/v1`), fallback `kr/claude-sonnet-4.5` (free), auxiliary→main. ⚠️ 400 «out of extra usage» бывает **транзиентным** (краткий 5-час кап Max при общей нагрузке me/German/openclaw/swarmclaw/code-server) — само отпускает; проверка `curl cc/claude-opus-4-8` (small+8KB) = 200. Если 400 участятся — включить overflow на claude.ai/settings/usage |
|
| Модель | **`cc/claude-opus-4-8`** (Opus 4.8 via Max) через OmniRoute (`http://10.0.0.179:20128/v1`), fallback `kr/claude-sonnet-4.5` (free), auxiliary→main. ⚠️ 400 «out of extra usage» бывает **транзиентным** (краткий 5-час кап Max при общей нагрузке me/German/openclaw/swarmclaw/code-server) — само отпускает; проверка `curl cc/claude-opus-4-8` (small+8KB) = 200. Если 400 участятся — включить overflow на claude.ai/settings/usage |
|
||||||
| Workspace | `/root/german` (KB-зеркало `knowledge-base/` RO, cron `*/15` pull; `notes/` writable) |
|
| Workspace | `/root/german` (KB-зеркало `knowledge-base/` RO, cron `*/15` pull; `notes/` writable) |
|
||||||
| Сервис | systemd `hermes-german.service` (`hermes gateway run --replace`) |
|
| Сервис | systemd `hermes-german.service` (`hermes gateway run --replace`) |
|
||||||
|
|||||||
156
snippets/benelux/benelux-podkop-watchdog.sh
Normal file
156
snippets/benelux/benelux-podkop-watchdog.sh
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# benelux-podkop-watchdog.sh — внешний сторож обхода РКН на Бенелюксе (Cudy TR3000).
|
||||||
|
# Живёт на LXC 137 (Антошка), cron */5. Проверки делает УДАЛЁННО по SSH через NetBird,
|
||||||
|
# лечит лестницей с гистерезисом, алертит Олегу через токен бота (как antoshka-watch-self.sh).
|
||||||
|
#
|
||||||
|
# Принцип против flapping (грабли OpenWrt_4/Оливье): лечим только после 2 подряд провалов,
|
||||||
|
# cooldown между шагами, лимит ребутов в сутки. Тупой рестарт по таймеру — хуже чем ничего.
|
||||||
|
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||||
|
|
||||||
|
# ── конфиг ───────────────────────────────────────────────────────────────────
|
||||||
|
ROUTER=100.70.207.97
|
||||||
|
SSH="ssh -i /root/.ssh/id_ed25519 -o StrictHostKeyChecking=no -o ConnectTimeout=8 -o BatchMode=yes root@$ROUTER"
|
||||||
|
CFG=/root/.openclaw/openclaw.json # источник токена бота
|
||||||
|
CHAT=1292155421 # Олег @it5870
|
||||||
|
HS_MAX=200 # макс. возраст awg-handshake, сек
|
||||||
|
PRIMARY=awg0 # основной выход (Singapore)
|
||||||
|
SECONDARY=awg1 # резервный выход (Finland) — если поднят
|
||||||
|
|
||||||
|
STATE=/root/.benelux-wd.state # счётчик подряд-провалов + шаг лестницы
|
||||||
|
ALERT=/root/.benelux-wd.alert # md5 последнего алерта (антиспам)
|
||||||
|
REBOOTS=/root/.benelux-wd.reboots # "YYYY-MM-DD count" — лимит ребутов/сутки
|
||||||
|
COOLDOWN=/run/benelux-wd.cooldown # ts последнего лечащего действия
|
||||||
|
COOLDOWN_SEC=300 # не лечить чаще раза в 5 мин
|
||||||
|
REBOOT_CAP=2 # максимум ребутов в сутки
|
||||||
|
|
||||||
|
TOKEN=$(grep -oE '[0-9]{8,}:[A-Za-z0-9_-]{30,}' "$CFG" 2>/dev/null | head -1)
|
||||||
|
send(){ [ -n "$TOKEN" ] && curl -s --max-time 20 "https://api.telegram.org/bot${TOKEN}/sendMessage" \
|
||||||
|
--data-urlencode "chat_id=$CHAT" --data-urlencode "text=$1" >/dev/null 2>&1; }
|
||||||
|
NOW=$(date +%s); TODAY=$(date +%F)
|
||||||
|
|
||||||
|
issues=""; heals=""
|
||||||
|
|
||||||
|
# ── 0. достижимость роутера ───────────────────────────────────────────────────
|
||||||
|
PROBE=$($SSH '
|
||||||
|
echo "SINGBOX=$(pgrep sing-box >/dev/null && echo 1 || echo 0)"
|
||||||
|
echo "IFACE=$(uci -q get podkop.main.interface)"
|
||||||
|
echo "HS0=$(awg show '"$PRIMARY"' latest-handshakes 2>/dev/null | awk "{print \$2}" | sort -rn | head -1)"
|
||||||
|
echo "HS1=$(awg show '"$SECONDARY"' latest-handshakes 2>/dev/null | awk "{print \$2}" | sort -rn | head -1)"
|
||||||
|
echo "AWG1=$(uci -q get network.'"$SECONDARY"' >/dev/null 2>&1 && echo 1 || echo 0)"
|
||||||
|
echo "PING0=$(ping -I '"$PRIMARY"' -c1 -W2 1.1.1.1 >/dev/null 2>&1 && echo 1 || echo 0)"
|
||||||
|
echo "PING1=$(ping -I '"$SECONDARY"' -c1 -W2 1.1.1.1 >/dev/null 2>&1 && echo 1 || echo 0)"
|
||||||
|
echo "CLASH=$(wget -qO- http://192.168.1.1:9090/version 2>/dev/null | grep -q sing-box && echo 1 || echo 0)"
|
||||||
|
echo "YT=$(nslookup youtube.com 127.0.0.42 2>/dev/null | grep -c "198.1[89].")"
|
||||||
|
echo "OZ=$(nslookup ozon.ru 127.0.0.42 2>/dev/null | grep -c "198.1[89].")"
|
||||||
|
' 2>/dev/null)
|
||||||
|
|
||||||
|
if [ -z "$PROBE" ]; then
|
||||||
|
# SSH не прошёл — отличаем «роутер лёг» от «SSH/NetBird моргнул»
|
||||||
|
if ping -c2 -W2 "$ROUTER" >/dev/null 2>&1; then
|
||||||
|
issues="• Роутер пингуется, но SSH не отвечает (NetBird/dropbear моргнул).\n"
|
||||||
|
else
|
||||||
|
issues="• Бенелюкс НЕДОСТУПЕН — не пингуется по NetBird (роутер/WAN/туннель лёг).\n"
|
||||||
|
fi
|
||||||
|
# удалённо чинить нечем → только алерт (антиспам ниже)
|
||||||
|
h=$(printf '%s' "$issues" | md5sum | cut -d' ' -f1)
|
||||||
|
echo "UNREACHABLE"; printf '%b' "$issues"
|
||||||
|
[ "$(cat "$ALERT" 2>/dev/null)" = "$h" ] && exit 0
|
||||||
|
echo "$h" > "$ALERT"; send "$(printf '⚠️ Бенелюкс-обход:\n%b' "$issues")"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
eval "$PROBE" # SINGBOX IFACE HS0 HS1 AWG1 PING0 PING1 CLASH YT OZ
|
||||||
|
AGE0=999999; [ -n "$HS0" ] && AGE0=$((NOW - HS0))
|
||||||
|
AGE1=999999; [ -n "$HS1" ] && AGE1=$((NOW - HS1))
|
||||||
|
|
||||||
|
# текущий выход здоров? (handshake свежий + транзит идёт)
|
||||||
|
cur_ok(){
|
||||||
|
case "$IFACE" in
|
||||||
|
"$PRIMARY") [ "$AGE0" -lt "$HS_MAX" ] && [ "$PING0" = 1 ] ;;
|
||||||
|
"$SECONDARY") [ "$AGE1" -lt "$HS_MAX" ] && [ "$PING1" = 1 ] ;;
|
||||||
|
*) return 1 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
HEALTHY=1
|
||||||
|
[ "$SINGBOX" = 1 ] || { HEALTHY=0; issues="${issues}• sing-box не запущен.\n"; }
|
||||||
|
[ "$CLASH" = 1 ] || { HEALTHY=0; issues="${issues}• Clash API ($IFACE) не отвечает.\n"; }
|
||||||
|
cur_ok || { HEALTHY=0; issues="${issues}• Выход $IFACE мёртв (handshake ${AGE0}/${AGE1}s, ping0=$PING0 ping1=$PING1).\n"; }
|
||||||
|
[ "${YT:-0}" -ge 1 ] || { HEALTHY=0; issues="${issues}• FakeIP не работает (youtube не заворачивается).\n"; }
|
||||||
|
|
||||||
|
# анти-утечка: РФ-сайт НЕ должен фейкапиться (рецидив russia_outside) — алерт, но НЕ автофикс
|
||||||
|
LEAK=""
|
||||||
|
[ "${OZ:-0}" -ge 1 ] && LEAK="• ⚠️ Утечка: ozon.ru уходит в туннель (вернулся russia_outside?). Проверь списки podkop.\n"
|
||||||
|
|
||||||
|
# ── здоров ────────────────────────────────────────────────────────────────────
|
||||||
|
if [ "$HEALTHY" = 1 ] && [ -z "$LEAK" ]; then
|
||||||
|
echo "OK: Бенелюкс-обход здоров (выход $IFACE, hs ${AGE0}s)."
|
||||||
|
rm -f "$STATE"
|
||||||
|
if [ -f "$ALERT" ]; then send "✅ Бенелюкс-обход снова в норме (выход $IFACE)."; rm -f "$ALERT"; fi
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── гистерезис: считаем подряд-провалы ────────────────────────────────────────
|
||||||
|
FAILS=0; STEP=0; [ -f "$STATE" ] && read -r FAILS STEP < "$STATE"; FAILS=${FAILS:-0}; STEP=${STEP:-0}
|
||||||
|
FAILS=$((FAILS + 1))
|
||||||
|
|
||||||
|
# утечка списков — только сигнал, не повод лечить сервис
|
||||||
|
if [ -n "$LEAK" ] && [ "$HEALTHY" = 1 ]; then
|
||||||
|
echo "$FAILS $STEP" > "$STATE"
|
||||||
|
h=$(printf '%s' "$LEAK" | md5sum | cut -d' ' -f1)
|
||||||
|
[ "$(cat "$ALERT" 2>/dev/null)" = "$h" ] && exit 0
|
||||||
|
echo "$h" > "$ALERT"; send "$(printf '⚠️ Бенелюкс-обход:\n%b' "$LEAK")"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$FAILS" -lt 2 ]; then
|
||||||
|
echo "$FAILS $STEP" > "$STATE"
|
||||||
|
echo "DEGRADED (провал $FAILS/2, жду подтверждения перед лечением):"; printf '%b' "$issues"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# cooldown между лечащими действиями
|
||||||
|
if [ -f "$COOLDOWN" ] && [ $((NOW - $(cat "$COOLDOWN"))) -lt "$COOLDOWN_SEC" ]; then
|
||||||
|
echo "cooldown активен — жду"; echo "$FAILS $STEP" > "$STATE"; exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── лестница лечения ──────────────────────────────────────────────────────────
|
||||||
|
do_heal(){ echo "$NOW" > "$COOLDOWN"; }
|
||||||
|
|
||||||
|
case "$STEP" in
|
||||||
|
0) # мягкий рестарт podkop
|
||||||
|
$SSH '/etc/init.d/podkop restart' >/dev/null 2>&1
|
||||||
|
heals="🔧 Шаг1: перезапустил podkop.\n"; STEP=1; do_heal ;;
|
||||||
|
1) # переподнять интерфейс / переключить на резерв
|
||||||
|
if [ "$AWG1" = 1 ] && [ "$IFACE" = "$PRIMARY" ] && [ "$AGE1" -lt "$HS_MAX" ] && [ "$PING1" = 1 ]; then
|
||||||
|
$SSH "uci set podkop.main.interface=$SECONDARY; uci commit podkop; /etc/init.d/podkop restart" >/dev/null 2>&1
|
||||||
|
heals="🔧 Шаг2: основной выход ($PRIMARY/Singapore) мёртв → переключил на резерв $SECONDARY (Finland).\n"
|
||||||
|
else
|
||||||
|
$SSH "ifdown $IFACE; sleep 2; ifup $IFACE; sleep 3; /etc/init.d/podkop restart" >/dev/null 2>&1
|
||||||
|
heals="🔧 Шаг2: переподнял интерфейс $IFACE + рестарт podkop.\n"
|
||||||
|
fi
|
||||||
|
STEP=2; do_heal ;;
|
||||||
|
2) # ребут (с лимитом в сутки)
|
||||||
|
RDAY=""; RCNT=0; [ -f "$REBOOTS" ] && read -r RDAY RCNT < "$REBOOTS"
|
||||||
|
[ "$RDAY" != "$TODAY" ] && { RDAY=$TODAY; RCNT=0; }
|
||||||
|
if [ "${RCNT:-0}" -lt "$REBOOT_CAP" ]; then
|
||||||
|
$SSH 'reboot' >/dev/null 2>&1
|
||||||
|
echo "$TODAY $((RCNT + 1))" > "$REBOOTS"
|
||||||
|
heals="🔧 Шаг3: рестарты не помогли — перезагружаю роутер (ребут $((RCNT+1))/$REBOOT_CAP сегодня).\n"
|
||||||
|
STEP=3; do_heal
|
||||||
|
else
|
||||||
|
issues="${issues}• Лимит ребутов на сегодня исчерпан ($REBOOT_CAP).\n"
|
||||||
|
STEP=3
|
||||||
|
fi ;;
|
||||||
|
*) # сдаёмся — нужно руками
|
||||||
|
issues="${issues}• Автолечение не помогло (пройдены все шаги). Нужно вмешательство.\n" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo "$FAILS $STEP" > "$STATE"
|
||||||
|
|
||||||
|
# ── алерт (антиспам по содержимому) ───────────────────────────────────────────
|
||||||
|
echo "ISSUES:"; printf '%b%b' "$issues" "$heals"
|
||||||
|
h=$(printf '%s%s' "$issues" "$heals" | md5sum | cut -d' ' -f1)
|
||||||
|
[ "$(cat "$ALERT" 2>/dev/null)" = "$h" ] && exit 0
|
||||||
|
echo "$h" > "$ALERT"
|
||||||
|
send "$(printf '⚠️ Бенелюкс-обход — сбой:\n%b\n%b' "$issues" "$heals")"
|
||||||
Reference in New Issue
Block a user