### 2026-07-19 — создан CT 480 (petrosvyaz.dgw.etersoft.ru)
-**Что:** Клонирован CT 694 (dgw.etersoft.ru) → CT 480. IP 91.232.225.250, routing через Петровсвязь: `ip rule from 91.232.225.250 lookup petrosvyaz` (persistent в `/etc/net/ifaces/ether3/ipv4rule`). PTR добавлен в обратную зону. На egw route-update.sh обработал petrosvyaz группу: google-dns маршруты (8.8.8.8, 8.8.4.4) через 91.232.225.250. Masquerade НЕ нужен — Петровсвязь маршрутизирует ответы до .250 обратно через priv.
-**Зачем:** Изоляция трафика через Петровсвязь — вместо кастомных маршрутов на priv, отдельный контейнер с policy routing. [ses_08605dc7cffeh, ses_077433517ffe]
### 2026-07-19 — eterweb.ru: истёк→освобождён→ПЕРЕРЕГИСТРИРОВАН обратно + фикс сертификата gnucheva
**Инцидент: `eterweb.ru` истёк и был освобождён из реестра `.ru`.**
- whois (TCI) первое время: `No entries found` — домен **полностью отсутствовал** (истёк, освобождён). TLD `.ru` отдавал NXDOMAIN+SOA зоны `.ru`, публичные резолверы — NXDOMAIN на все `*.eterweb.ru`. Наши ns1/ns4 хранили зону, но без делегирования мир её не видел.
-**Восстановлено пользователем в тот же день** (~19:36Z): перерегистрация через регистратора `DOMAINSHOP-RU`, `paid-till: 2027-07-19`. Делегирование `state: REGISTERED, DELEGATED, UNVERIFIED`.
-**Набор NS изменился** при перерегистрации: теперь `ns1/ns2.etersoft.ru` + `ns3/ns4.etersoft.com` (раньше было `ns1/ns4.etersoft.ru`). Все 4 синхронно отдают зону (SOA serial `2018011404`, знают `vgnucheva.eterweb.ru`→.9). ТLD `.ru` подхватывает делегирование с задержкой (минуты–десятки минут) — публичные резолверы сразу после регистрации ещё NXDOMAIN.
-**Влияние было:** все `*.eterweb.ru` публично мертвы ~7 недель; сертификаты LE с SAN `*.eterweb.ru` протухли (renew падал на DNS NXDOMAIN): `eterweb.ru` (16.05), `vgucheva.eterweb.ru` (09.05), `anastasia-nikolaeva.eterweb.ru` (09.05), `vgnucheva.eterweb.ru` (30.05).
-**После возврата домена:** renew этих сертификатов снова заработает автоматически, как только TLD распространит делегирование. Ничего удалять не нужно.
-**2026-07-20:** как только DNS `*.eterweb.ru` ожил публично (TLD `.ru` разнёс NS-делегирование), вручную перевыпущены `certbot renew --cert-name {eterweb.ru, vgucheva.eterweb.ru, anastasia-nikolaeva.eterweb.ru}` — все три до 17.10.2026, HTTPS ожил (anastasia-nikolaeva 200, остальные 302-forward). **Грабля:** certbot 0.31 `renew` ставит `random delay` до ~480 c перед каждым cert → пакетный renew 3 сертификатов занял ~10 мин; для скорости можно `--no-random-sleep-on-renew` (но лишает anti-load jitter).
**Фикс: сертификат `vgnucheva.eterweb.ru` (varvara.gnucheva.com и др.).**
- Root cause: в SAN было `vgnucheva.eterweb.ru` (NXDOMAIN публично из-за потери зоны) → роняло весь сертификат → истёк 30.05.2026.
- Перевыпустил с 6 доменами **без**`vgnucheva.eterweb.ru`: `certbot certonly --webroot -w /var/spool/nginx/tmp/client -d gnucheva.com -d gnucheva.ru -d varvara.gnucheva.com -d varvara.gnucheva.ru -d varya.gnucheva.com -d varya.gnucheva.ru --cert-name vgnucheva.eterweb.ru` (cert-name оставлен для сохранения путей).
- Новый cert: `notAfter=2026-10-17`. `--dry-run` auto-renew прошёл.
- certbot при `certonly`**стёр `renew_hook`** из renewal-conf → восстановил вручную (`renew_hook = serv nginx reload`), бэкап `.bak-hook-*`. **Грабли:** всегда проверять renew_hook после ручного certonly.
- Тестовый challenge-файл `z-claude-test` в webroot создан для диагностики и **удалён**.
**Прочее из mass-renew-failure (04:49, 23 серта):** у каждого своя причина (не только eterweb). Например `ximper.ru` — домен указывает на .173 (другой хост), `www.ximper.ru` NXDOMAIN. Это отдельная задача массового разбора.
### 2026-06-27 — массовые 503 на soulibre.ru (ДОКАЗАННАЯ причина)
-**Симптом:** soulibre.ru плавающе отдаёт 503, реально **1 685 200 ответов 503/сутки** (~60% запросов). Даже 1 запрос в 6 сек ловит 503; wikilivres.ru (тот же движок/хост) и etersoft.ru — чисто.
-**503 рисует nginx, НЕ бэкенд.** Apache в CT 167 за сутки отдал **0 ответов 503** (200/404/302/301/304), mod_status Load 0.5 — перегрузки нет. Бэкенд ни при чём (важно: первая гипотеза про «маленький CT 167 захлёбывается» оказалась неверной).
- Модули `nf_conntrack_pptp`+`nf_nat_pptp` в `/etc/modules-load.d/pptp-nat.conf`.
- FORWARD policy ACCEPT, без state/INVALID-правил (GRE не дропается файрволом — проблема была именно в хелпере).
- Можно откатить: убрать `*raw` секцию (или восстановить из .pre-pptp-helper), удалить modules-load файл.
## BIRD (маршрутизация, AS198324)
**Текущее состояние (с 2026-07-05): BIRD2 2.15.1-alt2** — единый dual-stack демон `bird` (пакет `bird2`), работает от **`_bird`**. bird1 (bird+bird6 1.6.8) снят.
- BGP-пиры: **Petrosvyaz AS50538 — активный** (`petrosvyaz4`+`petrosvyaz6`, два отдельных сеанса на разных адресах, НЕ слиты в MP-BGP), ~1.09M v4 + ~250K v6; Prometey/Ekvant/Antifilter — `disabled` в include (failover).
- bird экспортирует master4/master6 в **kernel table 5** (iproute2 `common`). `ip rule``10000: from all lookup common` — весь трафик по table 5. Source-policy (rule 1000 prometey/petrosvyaz) — статика, bird не трогает.
- Caps: bird от `_bird` удерживает CAP_NET_ADMIN через `-u` (юнит стартует от root, дропает привилегии, CapEff=0x3c00). file-caps/AmbientCapabilities не нужны.
- В `.claude/skills/network/SKILL.md` priv помечен «BIRD2» — теперь корректно. (iBGP/AS65000+`route-update.sh` — это igw, другая машина.)
- Cutover: `epm remove bird bird6` → `epm install bird2` (bird2 `Conflicts: bird`), уложить конфиг, parse-check, `systemctl enable --now bird`. Маршруты в table 5 на время свопа держит `persist`.
-**Cleanup при переводе**: выкинуты мёртвые таблицы (testable/maintable/antifilter — kernel-таблиц 6/8 в ядре не существовало) и неиспользуемые фильтры; пиры переименованы.
-**Грабли BIRD1→BIRD2**: (1) `preference N;` нельзя на уровне protocol → `preference = N;` внутри import-фильтра; (2) `include` — только ПОСЛЕ определений функций/фильтров (символы top-down).
-**Логирование → syslog** (не файл): bird2 от `_bird`**падает в цикле**, если `/var/log/bird.log` не доступен на запись (а от bird1 он root-owned). С syslog этого режима отказа нет.
-**Урок: `learn;` в kernel-протоколе при `import none` — БЕССМЫСЛЕНЕН и вреден.** Чужих маршрутов в table 5 нет (0), но `learn` заставляет bird2 вычитывать ВСЮ kernel table 5 каждый scan. После cutover из-за `persist` table 5 оказалась удвоенной (bird1 metric0 + bird2 metric32 ≈ 2.18M) → `learn` вычитывал 2M → переполнение netlink (`Kernel dropped some netlink messages`) → `I/O loop ~8с` → спам `Netlink: No route to host`. **Фикс: убрать `learn;` из k4/k6** → спам прекратился сразу.
-**Удвоение v4 в table 5** (persist-артефакт cutover) — очищается само при ребуте (kernel-таблицы сбрасываются, bird2 стартует fresh). Flush 2.18M iproute2 за проход не осиливает.
- v6 под bird1 в kernel table 5 застревал на ~5342 (bird6 со своим `learn` тоже страдал netlink'ом); под bird2 v6 корректно наполняется до ~250K.
-**ip rule персистентны через etcnet**: `/etc/net/ifaces/{vmbr0,ether3,ether1}/{ipv4rule,ipv6rule}` (главное `from all lookup common pref 10000` на vmbr0 + source-policy).
- rt_tables в `/etc/iproute2/rt_tables` (common=5, prometey=2, petrosvyaz=4).
-**main-default персистентен**: `ether3/ipv4route: default via 109.235.223.52` — страхует паузу сходимости BGP после загрузки (~30–60с, после неё table 5 перепишется clean).
## 2026-07-07 — 3-й канал enp8s0f0: GlobalNet (DATA-IX пиинг + транзит) — бага #19190
Канал через GlobalNet — **НЕ обычный аплинк**, а tagged trunk с ДВУМЯ VLAN (бага Etersoft#19190 — туда пишем всё). Провайдер: Антон Корбин (GlobalNet). Наш MAC `8c:dc:d4:b5:ff:90` (enp8s0f0, sub-if наследуют). Физически — Intel 82599ES SFP+, модуль FIBO FT-S10-W2720LD (10G BiDi 1270/1330); **порт 23 (10G) — завтра**.
- Свой блок `91.232.225.0/24` (AS198324) анонсим на RS + GlobalNet → SNAT офиса `.1` (не меняется).
-**etcnet применён** (2026-07-07): старый Prometey (85.235.192.190/30) снесён (бэкап `/root/etcnet-backup-enp8s0f0-20260707`), source-policy удалён. VLAN'ы `.3210`/`.3211` подняты. Source-policy ip rules: `from 178.18.227.15 lookup main pref 500`, `from 94.124.183.151 lookup main pref 500` (persistent в etcnet ipv4rule) — без них table 5 BGP-маршруты через Petrosvyaz перебивали прямые scope-link.
-**BIRD2** (2026-07-07): `/etc/bird/bird.d/globalnet.conf` загружен (`birdc configure`, не рестарт). Все 6 сессий Established: `dix_rs1/2_v4/v6` (DATA-IX, BFD Up, 1000×5) + `gbl_v4/v6` (GlobalNet, полная таблица ~1M v4 + ~250K v6). IX local_pref=150, transit local_pref=100. Export: только `91.232.225.0/24` + `2a03:5a00:c::/48`.
-**iptables**: BGP peer-ACCEPT :179 + BFD :3784 на sub-if — **live** (инкрементально), generic REJECT :179 на .3210/.3211. ⚠ НЕ persist в `/etc/sysconfig/iptables` — после ребута потеряются.
-**Открыто**: persist iptables; сверить local_pref Petrosvyaz vs GlobalNet; мониторинг BGP в InfluxDB.
### firewall pre-stage (enp8s0f0, до пиинга)
Завтра к priv подключают 3-й внешний канал через доп. NIC `enp8s0f0` (сейчас DOWN, MAC 8c:dc:d4:b5:ff:90; enp8s0f1 — близнец). Распространил на него все WAN-правила ether1/ether3, иначе канал = дыра (:1080 к защищённым прокси .120/.139/.140, BGP-сканы, сервисные порты, обход eterban/anti-spoof). Источник истины файрвола — `/etc/sysconfig/iptables`.
-**15 статических правил** добавлены в `/etc/sysconfig/iptables` (twin к ether3, без ipset-зависимости → безопасно в файле): nat POSTROUTING SNAT (.1), INPUT REJECT :179+8006+3128+5900:5999+5404,5405+5876+19999+46555, FORWARD :1080 DROP + 5 anti-spoof (10/8,172.16/12,192.168/16,100.64/10,169.254/16). `iptables-restore --test` = OK.
- Live: те же 15 + **4 eterban-правила enp8s0f0** (FORWARD eterban_1 REJECT @top, nat PREROUTING eterban_white/eterban_1/firehol DNAT→.67) — применены инкрементально (без flush, eterban-динамика ether1/ether3/vmbr0 не тронута). Live=19 enp8s0f0, файл=15.
-**ВНИМАНИЕ — eterban enp8s0f0 НЕ персистентен через ребут.** eterban знает только 2 WAN (`i_interface`/`i_interface2` в `/etc/eterban/settings.ini`, switcher.py хардкодит 2 слота), его правила dynamic и НЕ лежат в файле (ipset-зависимость → `iptables.service` упал бы при загрузке). См. [[lesson_eterban_priv_two_wan_slots]]. После ребута enp8s0f0 потеряет 4 eterban-правила (останутся 15 статических). Фикс = расширить eterban на 3-й интерфейс (build-запрос через Kanban → alt-packaging-agents) ИЛИ sidecar systemd-сервис `After=eterban.service`.
- Live-позиции статических правил = appended (мои скрипт twjn-lookup споткнулся: `iptables -S` не умеет `--line-numbers`), но **функционально эквивалентно** twin-позициям в файле (ESTABLISHED не матчит NEW; .68/.69 :1080 ACCEPT интерфейс-агностичны и стоят раньше). Файл = source of truth, при ребуте встанут twin-позиции.
**⚠ Открыто (после поднятия канала):**
1.**BGP peer-ACCEPT :179 для enp8s0f0** — нужен IP пира + IP priv на линке. Пока только generic REJECT :179. Добавить `-I INPUT -s <peer> -d <priv_ip> -i enp8s0f0 -p tcp --dport 179 -j ACCEPT` ПЕРЕД REJECT, + BIRD-пир.
2.**SNAT source** — стоит `91.232.225.1` (как ether1/ether3). Если у канала свой публичный IP и .1 не анонсится — поменять `--to-source`.
3. Поднять enp8s0f0 в etcnet (IP/маршрут) — отдельная задача; потом проверить счётчики `iptables -L FORWARD -n -v | grep enp8s0f0` и внешний тест :1080/8006 через новый путь (tcpdump на .140 при стуке через новый канал).
4. Решить персистентность eterban (см. выше) ДО ребута.
**Статика**: `public_html/` содержит только index.php, static.php, installer.php, .htaccess. Вся статика (JS, CSS, шрифты) отдаётся через `static.php`. Прямых файлов для nginx нет. Кеш: `cache-control: public, max-age=604800` (7 дней).
**Плагины**: contextmenu оставлен (встроенное меню 1.7.0 — только список писем). Дубликат image_paster убран. zipdownload отключён (500 на AJAX).
**Безопасность**: directory listing отключён, внутренние файлы не доступны, installer.php заблокирован nginx.
## 2026-07-09 — проверка исходящей связности
- По запросу проверен `ping -c 4 ya.ru` от `root@10.20.30.66`.
Связано: [[lesson-telemt-middle-proxy-egress-match]] (egress=registered IP для ME), [[reference-remote-ssh-access]], [[lesson-anyssh-ru-in-91232225]], [[lesson-etcnet-hooks]].
Персистентность 2026-06-06 оказалась **недостаточной**: `start_action=start` срабатывает ТОЛЬКО при загрузке конфига в charon, а **после провала rekey SA умирает и не переинициируется**. strongSwan на parentglobal крутился с 2026-05-26 без рестарта; последний CHILD_SA `div{278}` — 14 июля 06:48, в 12:33 «restarting CHILD_SA div» провалился → тишина до ручного initiate. telemt висел в `No ME connections` (~3 суток ретраев).
**Runtime-фикс 2026-07-15:**
1.`ssh -p10337 etersoft@anyssh.ru 'sudo swanctl --initiate --ike divserver --child div'` (child = **`div`**, НЕ `s2s`) → SA up.
2. divserver responder был полностью здоров (conn `parentglobal` загружен, ipsec1.service active, policy-route table 201 на месте) — проблема была только в отсутствии инициации с parentglobal.
**Self-heal (настоящий фикс) — watchdog-timer на parentglobal** (как autoheal у fr-туннеля, но systemd, не docker):
-`/usr/local/bin/ikev2-keepalive.sh` — каждые 2 мин (timer) проверяет `swanctl --list-sas | grep -q '^<ike>:.*ESTABLISHED'`; если 0 → `swanctl --initiate --ike <ike> --child <child>`. Логирует в syslog тегом `ikev2-keepalive`.
- Покрывает **оба** туннеля parentglobal: `divserver`/child `div` (этот telemt-эгресс) и `site-to-site`/child `s2s` (gvpn Greek-эгресс, [[reference_ikev2_gr_tunnel]] — у него 2026-07-15 был тот же гэп).
Доступ к parentglobal: `ssh -p10337 etersoft@anyssh.ru` (sudo). divserver: `ssh root@div.eterhost.ru`.
## Побочное (отдельно)
Сертификат `/etc/ssl/chat.eterfund.ru/fullchain.pem` на divserver (и на .43/.140) имеет SAN только `chat.eterfund.ru`, без `d.chat.eterfund.ru` → в браузере ошибка имени. Для telemt fake-TLS неважно. Cert раздаётся централизованно на все 3 узла (одинаковый, LE, выпуск 2026-05-08). nginx vhost (`server_name chat.eterfund.ru d.chat.eterfund.ru`) проксирует на web.matrix.etersoft.ru — это камуфляж-бэкенд.
- Worker-клиент встроен в Bugzilla: шаблон `template/en/default/etersoft/bugswatcher.html.tmpl` PROCESS'ится в `bug/edit.html.tmpl:115`; JS — `/var/www/bugs.etersoft.ru/js/etersoft/bugsWatcher.js` (подключает socket.io 2.3.0 с cdnjs).
## Архитектура
socket.io **v2.3.0** (Engine.IO v3) и на клиенте, и на сервере. События: клиент Bugzilla шлёт `addUser {bugID,email,workTime}` → сервер хранит состояние в памяти (`workers`/`bugs`) → дашборд читает `getActiveBugsList` → `showActiveBugsList`. Состояние **только в памяти** (при рестарте очищается; клиенты переотправляют только при перезагрузке страницы, не на реконнекте).
## Репозиторий
`git@gitlab.eterfund.ru:etersoft/bugs-watcher.git` (проект перенесён из `kantegory/bugs-watcher`; у lav push-доступ). **Локальный чекаут:**`/home/lav/Projects/git-eter/bugs-watcher` (с 2026-07-27; до этого был во временном `/home/lav/tmp/claude/bw-git`). Пуш — обычный `git push origin master` (gitlab, НЕ gpush). **2026-07-25 синхронирован:** была дивержденция (развёрнуто на старом `ff30da7`, origin/master `7b6996b` с workTime); local master сброшен на upstream + коммиты. deployed==committed==origin/master. Backup-tag локально: `backup/pre-sync-ff30da7`.
**master HEAD `e9afef1`** (на 2026-07-27). Фиксы:
-`e7446ec` server: ping cadence 25s→15s (fallback для close-detection).
-`40f5b4d` fix: dashboard live-update (Fix 2 — см. ниже).
-`2966698` graph: summary + workers на узлах, направленные рёбра.
-`b64b7c6` dashboard: auto light/dark theme (`prefers-color-scheme`).
-**Дашборд 2026-07-26 (issue-фиксы):**`1c270a2` authedFetch (token+cookie fallback, первая версия — ловил только пустой массив); `7ce87f2` убран дублирующий столбец #; `9790b86` cache-busting `?v=` на js/css; `d988113` скрыт 00:00:00 workTime для свежих задач.
-`97f823c` (затем снят) — попытка cookie-fallback: ловил и error `{code:32000}`, и пустой массив → ретраил same-origin без токена. **Основан на неверной посылке** (REST не читает cookies) — бесполезен.
-**`08a881a` (рабочий фикс summary/рёбер):** cookie-fallback убран; при REST-ошибке `32000` (протухший токен) дашборд чистит токен из localStorage и делает `location.reload()` → после релоада `isLogged()=false` → `showLogin()` → юзер перезаходит → `/rest/login` → свежий токен → `successLogin` → `initDashboard()` наполняет summary/рёбра. Паттерн как у kanban (`signOut()` на 32000). Пустой массив (restricted-баг без прав) НЕ считается auth-failure.
-**`232530f` (стабильность графа + колонка Summary):** (1) граф раньше на КАЖДОМ store-update делал `destroy()`+`grid`-relayout → узлы/стрелки скакали. Теперь `cyInstance` строится один раз (grid), дальше `renderGraph()` синхронизирует на месте (`startBatch`: add/remove узлов — новые на спирали Фибоначчи; обновление label; rebuild рёбер) **без relayout** → позиции стабильны. Серия enrichment-апдейтов коалесится через `requestAnimationFrame` (`scheduleRender` на `bugsFullfilled`). Защита от отсоединённого `#cy` после logout/login. (2) Таблица Active bugs: добавлена колонка **Summary**; `.bug`-ссылка держит `#id`, summary пишется в отдельную ячейку `.summary` (bug-объект `tag:'.summary'`, не `'a.bug'`). Cache-bust `?v=20260726d`.
-**`b35e7d0` (ghost-зависимости + фикс мигания/стрелок/зума):** (1) граф показывает **зависимые/блокируемые баги, над которыми сейчас НЕ работают** — «ghost»-узлы, приглушённым цветом (серый фон `#3a3a40`/`#f4f4f4` + пунктирная рамка), селектор `node[ghost=1]`. Видно связь, но сразу понятно «не в работе». (2) Метаданные багов (`window.bwMeta`: `{summary, depends_on, blocks}`) — глобальный кэш, **переживает wipe store на каждом showActiveBugsList emit**, шарится между bugsWatcher.js (наполняет из REST) и graph.js (читает узлы/рёбра). Ghost-метаданные тянутся **один хоп** (нет транзитивного раскрытия — `enqueueGhostFetches` только для активных багов, по `.bug[data-id]`). (3) Стрелки больше не «пляшут»: ребро одно на пару — depends_on даёт `prereq→bug`, blocks только к ghost (`bug→dependent`); если dependent активен, reverse depends_on уже нарисовал стрелку → blocks пропускаем; дедуп по unordered-паре (`collectEdges`). (4) Summary больше не мигает пустым — ячейка `.summary` сидируется из кэша при перестроении; `userNameCache` (email→real_name) чтобы не рефетчить имена. (5) `wheelSensitivity:0.3` — мельче шаг зума колесом. Cache-bust `?v=20260726e`. Развёрнуто на 91.232.225.24.
-**`63e8301` (плотность графа + статусы + шрифт + живое время):** (1) **Расстояние:** новые узлы ставятся рядом с уже размещённым соседом по зависимости (`neighborIds` + anchor), а не на глобальной раскручивающейся спирали; ghost-баги одним уровнем **НИЖЕ** активного родителя (`down=130`, `spread=70`) — иерархия «в работе сверху, простаивает снизу»; существующие узлы не двигаются → граф не прыгает. Initial grid ужат (`condense:true`). (2) **Цвет узла = статус Bugzilla** (`statusColor`: NEW/REOPENED красный `#c0392b`, ASSIGNED синий `#2980b9`, RESOLVED зелёный `#27ae60`, VERIFIED бирюза `#16a085`, CLOSED серый `#7f8c8d`); статус тянется в `bwMeta.status` (`include_fields=summary,status,depends_on,blocks`); ghost'ы тот же статус-цвет, но `opacity:0.5` + пунктир (не серый фон). (3) **Шрифт подписей 10px**. (4) **Work-time ЖИВОЙ**: сервер шлёт снапшот один раз → дашборд тикает на клиенте каждую секунду (`liveTimers[bug|email]={src,base,since}`, `startLiveTicker``setInterval(1000)` обновляет все `.livetime`-спаны; `noteWorkTime` НЕ реcидит при том же снапшоте → таймер не сбрасывается на перестроениях; чистится по `activeKeys` при rebuild). **Без правок сервера.** Оговорка: тикает «по умолчанию активно» (нет реального сигнала активности — отошёл, но баг открыт = идёт). Cache-bust `?v=20260726f`.
-**`fcd5478` (легенда + 2 хопа + grace 10м + no-overlap + шрифт по таблице):** только фронт, без сервера. (1) **Легенда** цвет-ключ под графом (`#graphLegend` в index.html, стили `.legend/.lg/.sw` в dashboard.css) — статусы + «idle dependency» (пунктир) + «recently left <10m»(серый).`renderLegend()`перерисовываетсяв`createGraph`и`applyGraphTheme`.(2)**2хопазависимостей:**`collectGhostIds`(graph.js)—двапрохода`addDepsOf`;`fetchBugMeta(id,hop)`+`MAX_DEP_HOP=2`(bugsWatcher.js)—активныеhop0,ихdepshop1,deps-of-depshop2,дальшестоп(нетбезконечноготранзитивногораскрытия).Рёбрапоdepends_onтолько(blocks =обратное)—`collectEdges`,дедупunordered-пары.(3)**Grace10минпризакрытиивкладки:**багнепропадаетсразу—сереетна10мин(снапшотворкеров+замороженныйwork-time),потомуходит.Считаетсянаклиентеdiff'омактивногоспискамеждуапдейтами:`prevActiveBugs`/`lastWorkers`/`graceLeaving`(bug→{workers:[{email,secs}],removedAt}),`GRACE_MS=10*60*1000`;`renderGracedBug`→`<trclass="leaving">` с `.bug[data-leaving=1]` → граф красит узел серым (`node[leaving=1]`, стоит после status-селекторов → перекрывает). `ensureGraceTimer` реэмитит `getActiveBugsList` раз в 60с (чистит истёкшее, обновляет «left Nm ago»), сам-стоп когда `graceLeaving` пуст. `deleteUser` теперь просто `socket.emit('getActiveBugsList')` (diff сам кладёт баг в grace). (4) **No-overlap:** cose-layout запускается ТОЛЬКО при изменении набора узлов/рёбер (`structureKey`, `lastStructureKey`), не на заливке лейблов — узлы не перекрывают друг друга, но граф не скачет на каждом апдейте. (5) **Шрифт = таблица:** `tableFontSize()` читает computed font-size `#workers` (≈14px), не хардкод 10px. (6) `escapeHtml` для summary/real_name в innerHTML (XSS-гигиена). Cache-bust `?v=20260727a` (js) / `?v=20260727` (css). Развёрнуто на 91.232.225.24.
- **`bc3681b` (ОТКАТ 2-хопа → 1 хоп + шрифт 11px):** фидбэк пользователя. (1) **Зависимости — только 1 хоп:** показывать только активные баги и их ПРЯМЫЕ blockers/зависимости (`depends_on`+`blocks` активного), НЕ рекурсивно раскрывая deps самих idle-ghost'ов (2-й хоп тянул off-chain баги). `collectGhostIds` — убран hop-2 проход; `MAX_DEP_HOP` 2→1. Рёбра по depends_on только — без изменений. (2) **Шрифт 14→11px:** «как в списке» (=14px) оказалось слишком крупным на компактных узлах → `GRAPH_FONT_SIZE=11` (хардкод), `tableFontSize()` убран. Cache-bust `?v=20260727b`. **Важно:** 2-й хоп был сделан по прямому запросу «ещё на шаг глубже», но пользователь откатил — держать 1 хоп.
- **`db4556a` (направленный layered-граф + список сверху + авторазмер canvas):** фидбэк «prereqs сверху, dependents снизу» + «сначала список, потом граф, без ограничения поля» + «используй библиотечный алгоритм, не hand-roll». (1) **Раскладка:** убрал cose (force-directed, не чувствителен к направлению) и весь hand-rolled positioning (`layeredPositions`/`neighborIds`/`nextNodePosition`/`layoutOpts`) → **cytoscape built-in `breadthfirst` с `directed:true`**. Рёбра и так указывают prereq→dependent (`collectEdges`: для X depends_on Y ребро Y→X), значит направленный top-down layout САМ кладёт prereqs сверху, dependents снизу — без ручных координат, без перекрытий. Roots = узлы без входящих рёбер (чистые prereqs), `opts.roots='#id1, #id2'`; fallback на дефолт если цикл. `fit:false` + `spacingFactor:1.1`. (2) **Порядок:** в index.html «Active bugs» таблица ТЕПЕРЬ ВЫШЕ секции графа. (3) **Без ограничения поля:** `.graph` больше не 50vh — `sizeGraphToContent()` после layout меряет `nodes.boundingBox({includeLabels:true})`, ставит `.graph.style.height` = контент+80 (min 420), `cy.resize()`, потом `cy.fit(undefined,30)`. CSS дефолт `height:60vh; min-height:320px` (только первичная отрисовка до JS). Структура-меняется → re-layout+resize+fit (gate по `structureKey`, label-fill не триггерит). **Библиотека:** cytoscape.js (локально `scripts/graph/cytoscape.min.js`); built-in layouts: cose/breadthfirst/grid/circle/concentric/preset; canonical DAG-алгоритм = dagre/Sugiyama (extension `cytoscape-dagre`, НЕ подключён — добавляется при необходимости). Cache-bust `?v=20260727c`.
- **`e9afef1` (форматирование index.html, ОТДЕЛЬНЫЙ коммит):** tabs→2-space, whitespace-only (`git diff --ignore-all-space` пуст). Пользователь просил форматирование отдельным коммитом, не смешивать с фичей. Больше файлов с tab-смесью нет (graph.js/bugsWatcher.js/dashboard.css уже на space).
## Worker-клиент (Bugzilla) — ВНЕ репозитория
`/var/www/bugs.etersoft.ru/js/etersoft/bugsWatcher.js` (Bugzilla-кастомизация, НЕ часть `etersoft/bugs-watcher`). Шлёт `addUser {bugID,email,workTime}` на `socket.io ${window.location.origin}`; слушает `showAllUsers` (`for...in` по `data[bugID]` — object-shape, работает) и `deleteUser`. **2026-07-26: добавлен `pagehide`/`beforeunload` → `socket.disconnect()`** — раньше закрытие вкладки не давало чистого disconnect, сервер ловил уход только по ping-timeout (~30s) → «при закрытии не обновляется». Backup: `*.bak-20260725`. Права `0640 root:apache2`.
## Фиксы архитектуры (2026-07-26)
1. **Close-delay (Fix 1):** worker-клиент теперь шлёт чистый disconnect на `pagehide`/`beforeunload`; сервер `pingInterval` 25s→15s как fallback. Закрытие бага теперь сразу убирает воркера (а не через ~30s).
2. **Dashboard live-update (Fix 2):** `showActiveBugsList` раньше эмитился в **двух несовместимых форматах** — `getActiveBugsList` слал `{bug:[{socketId,workerEmail,workTime}]}` (array), а `addUser` слал сырой `{bug:{socketId:email}}` (object, ещё и с мусорными пустыми `{}` от disconnect). Обработчик дашборда `for (let worker of activeBugsList[bug])` падал на object → дашборд НЕ live-обновлялся (только initial snapshot). Фикс: единые `buildActiveBugsList()`/`emitActiveBugsList()` (array, все воркеры, stale-пропуск) в `addUser`+`getActiveBugsList`+`disconnect`; `disconnect` удаляет пустые `bugs[bugID]`; дашборд `showActiveBugsList` делает clear (`workers.replaceChildren()` + `store.object.storeData.length=0`) + rebuild + `initGraph()`.
3. **Graph:** узел = `#id` + summary (truncate 42) + воркеры (из DOM `#workers{id} a.worker`); рёбра depends_on/blocks направленные; `cyInstance.destroy()` перед redraw (не плодить canvas'ы).
4. **Theme:** `prefers-color-scheme` CSS-переменные (`--bw-bg/fg/surface/border/muted`) в `dashboard.css` (body/table/modal/form/borders); graph.js `graphColors()` через `matchMedia` + re-render на смену темы.
## Доп.
- Дашборд **требует логин** (auth-модалка, `auth.js`) — без него граф/таблица не строятся. `/bugs-watcher/` статика через nginx alias.
- **REST auth (root cause отсутствия summary/рёбер, подтверждено 2026-07-26):** REST **НЕ читает session-cookies** — `WebService/Server/REST.pm:203` ставит `auth_no_automatic_login=1` → `Auth::Login::Cookie.pm:38` пропускает cookies. Поэтому «залогинен на сайте» для `/rest/` бесполезно. Работает только валидный `token` (logincookie, 30 дней, `MAX_LOGINCOOKIE_AGE`) или `api_key`. Дашбордный токен в localStorage протух → `/rest/bug?token=STALE` → `{"code":32000,...}` → enrichment падает. Без auth restricted-баг → `{"bugs":[]}` (проверено повторно 2026-07-26). **Решение (`08a881a`, deployed):** re-login на 32000 (kanban-паттерн) — токен чистится, страница релоадится, юзер перезаходит, свежий токен возвращает summary + `depends_on`/`blocks`. Готового серверного api_key-proxy пока НЕТ (вариант A отложен; `server/index.js`-proxy был написан локально, НЕ deployed). Полная картина инстанса — [.claude/docs/bugzilla.md](../../docs/bugzilla.md).
- Большинство багов с пустыми depends_on/blocks → рёбра на графе бывают редко.
## Краш-баги (починены 2026-07-25, запушено в master)
Сервис падал и рестартился **>104 раз** (`systemctl show -p NRestarts`), симптомы: intermittent **502 Bad Gateway** на `/socket.io/?...&sid=...` (handshake без sid при этом 200). Node умирал → sid инвалидировался → 502. Два bug'а в `server/index.js`:
1.`addUser`: `bugID.split('#')` / `email.length` на undefined (Bugzilla-клиент шлёт addUser на странице без `?id=`, напр. `process_bug.cgi`). Фикс: guard `if (!data || data.bugID==null || data.email==null) return;` + `String()` coercion + bail если пусто.
2.`getActiveBugsList`: `workers[socketId].workTime` где `socketId=undefined` для пустой записи `{}` (накапливаются в `bugs` после дисконнекта — disconnect ставит `bugs[bugID]={}` вместо delete). Любой запуск дашборда ронял сервер, если есть хоть одна пустая запись. Фикс: `if (!socketId || !workers[socketId]) continue;` в цикле.
Коммиты в `etersoft/bugs-watcher` master: `5e27d2b` (краш-фикс), `4c81c08` (deploy: `0.0.0.0`→`127.0.0.1` + URL дашборда `${window.location.origin}:17003`→`${window.location.origin}` — upstream-версии были сломаны для деплоя за nginx). Проверено end-to-end через публичный nginx-эндпоинт, NRestarts=0.
## Тикет разработки
**Баг #12105** «Хранение статуса задачи "в работе" на лету в сервере на nodejs» (создан lav 2017-10-29, на kantegory, ASSIGNED, component bugs.etersoft.ru) — на него ссылаются upstream-коммиты `Fix #12105#cNN`. Связанные: #4529 «Поддержка bugs.etersoft.ru» (на lav), #14524 «граф активных задач» (на kantegory).
**eterventcontrol** — сервис управления вентиляцией офиса (Modbus TCP → приточка Zentec 192.168.8.217:502, PID по CO2/температуре, FastAPI/uvicorn API).
## Где код
- Репозиторий: `/srv/lav/Projects/git-eter/eterventcontrol` (Python, отдельный git-проект, НЕ в etersoft-admin-essential)
- Запуск: `python3 -m eterventcontrol.main loop` (он же `/usr/bin/eterventcontrol loop`)
- Есть `services/eterventcontrol.service` (ExecStart=/usr/bin/eterventcontrol loop), но на server он НЕ установлен как сервис
## Где запущен
- Машина **server** = `192.168.0.1`
-**API: http://192.168.0.1:8000** (uvicorn/FastAPI; в коде дефолт `127.0.0.1:8000`, host переопределён на LAN-адрес 192.168.0.1)
- Крутится как обычный процесс, НЕ через systemd: `serv eterventcontrol status` → `not-found`, автозапуск выключен (запущен вручную/из другого места — родитель не уточнён)
## Порты (дефолты в коде)
- API: **8000** (`main.py` api_cfg)
- Modbus TCP к устройству вентиляции: **502** (`vent_device.py`, `modbus_client.py`)
-**API: http://vent.office.etersoft.ru:8000** — UI `/`, API под `/api/v1/` (`/api/v1/health`, `/api/v1/rooms`, `/api/v1/status`…). Auth: api_key (`X-API-Key`) ИЛИ Kerberos SPNEGO.
- DNS: A+PTR `vent.office.etersoft.ru`→192.168.0.67 на dhcp (зона office.etersoft.ru внутренняя, на ns1 нет).
## Сеть в контейнере (грабли шаблона 720)
- Шаблон 720 — etcnet, настроен на `breth0` (10.20.30.246), а PVE создаёт `eth0` и пишет мёртвый `/etc/systemd/network/eth0.network` (networkd **не установлен**).
- Фикс: настроить `/etc/net/ifaces/eth0/` (ipv4address 192.168.0.67/24, ipv4route `default via 192.168.0.1`, options BOOTPROTO=static TYPE=eth ONBOOT=yes), убрать `breth0` и мёртвые networkd .network, **`systemctl enable network`** (в шаблоне active-но-disabled → без enable eth0 не поднимется после ребута). См. [[lesson_lxc_etcnet_to_systemd_networkd]].
- Modbus 192.168.8.217:502 достижим через server (192.168.0.1) как gw.
-`python3-module-pylibmodbus`**нет в регулярном p11/branch** (только girar-task 402985). Ставят локальным rpm: на server кеширован `python3-module-pylibmodbus_0.6.2-alt1_noarch.rpm` (p11+402985). noarch, зависит только от `python3(cffi)`.
## Kerberos SSO
- AD-аккаунт **`http-vent`** (CN=Users, UAC 512, как `http-builder`), SPN `HTTP/vent.office.etersoft.ru`.
- Тест acceptor: `gssapi.Credentials(name=HTTP@vent.office.etersoft.ru, usage=accept, store={keytab:...})` — грузится OK. `kinit -k HTTP/...` падает «not found» (SPN-as-client не работает в этом Samba) — это нормально, акцептор-роль работает; реальный тест — `curl --negotiate` с клиентского TGT.
- Сенсоры (читаются из InfluxDB): `fenix.office.etersoft.ru`, `lav.office.etersoft.ru`
## Важно
-**Два инстанса одновременно запускать нельзя** — оба пишут Modbus control-регистры (конфликт управления, износ EEPROM). Перед миграцией — cutover-свап (stop старого → start нового).
-`cleanup()` восстанавливает initial fan/supply (пишет, только если значение успело измениться).
## История
- До 2026-07-21: крутился как `systemctl --user` сервис у `lav` на `server` (192.168.0.1, шлюз, не PVE). После ребута server 02:49 был down ~12ч (unit с `ConditionHost=server.office.etersoft.ru`). Перенесён в контейнер vent (CT 762 на border), server-side user-service disabled.
Обход `claude.ai` и Anthropic-доменов через французский egress. Вся система = 3 части: группа `fr` в route-update (на igw/egw) → IPsec-туннель `.140` ↔ rpi(Франция). Зафиксировано после инцидента «egress = .140 вместо Франции» (2026-07-05).
## Data path (как идёт трафик)
```
клиент/сервис
│ (claude.ai резолвится в 160.79.104.10 / 2607:6bc0::10)
│ ip route get claude.ai → via 10.10.10.6 dev ipsec0
│ XFRM if_id=42 → шифрует (ESP) → NAT-T :4500
▼
rpi (Франция, public 78.193.2.190, Orange/AS3215) — контейнер ikev2-fr (INITIATOR)
│ ipsec0 = 10.10.10.6/30, MASQUERADE → eth0
▼
claude.ai (видит src 78.193.2.190 — Франция, проходит геоблок)
```
## Часть 1. Группа `fr` в route-update (igw + egw)
`routes.d/fr/` (v4) и `routes6.d/fr/` (v6) на обоих шлюзах:
-`gateway` (v4) = `91.232.225.140 metric 50`
-`gateway` (v6) = `unreachable` (route-type keyword, см. [[reference_route_update_sh]])
-`options` = `pref 900` (явный override, **перебивает**`egw/ai` на pref 1210)
-`claude.ai.list` → симлинк на `/root/egw-route/claude.ai.list` (Anthropic-домены)
- таблица `claude.ai` (per-list, №223 на igw)
Симметрично на igw и egw. Подробно механика route-update — [[reference_route_update_sh]]. Сами списки живут в git `eterfund/egw-route.git` (`/root/egw-route/` на хостах).
- Соединение `site-to-site`: `local_addrs=91.232.225.140:4500`, `remote_addrs=%any` (**responder**, ждёт инициатора). local id `ikev2.fr.egw.etersoft.ru`, remote id `fr.egw.etersoft.ru`. PSK в `secrets`.
-`ipsec0` XFRM if_id=42, `10.10.10.5/30`.
- Перезагрузка конфига: `swanctl --load-all` (или `systemctl restart strongswan`).
## Часть 3. rpi (initiator, Франция)
-**Raspberry Pi** во Франции, public `78.193.2.190` (Orange).
- Доступ через reverse-SSH: `ssh -p 10338 root@anyssh.eterhost.ru` (hostname `rpi`). Reverse-SSH поднят контейнером `tunnel-fr` (autossh `-R 10338:localhost:22`, проект `/root/tunnel-fr/`, пользователь `a338`, ключ `/root/tunnel-fr/etersoft.key` → COPY в образ как `/root/.ssh/etersoft.key`).
-**Альтернативный доступ** (когда reverse-SSH упал): с .140 через IPsec → Docker bridge. `ikev2-fr` = host networking, rpi-хост = `172.17.0.1` (Docker bridge): `ssh root@91.232.225.140 'ssh root@172.17.0.1 "hostname"'`. Примечание: `10.10.10.6` — это контейнер `ikev2-fr`, НЕ rpi-хост.
-`swanctl.conf` в образе: `remote_addrs=91.232.225.140:4500`, local id `fr.egw.etersoft.ru`. PSK = тот же, что на .140. `start_action=start`, `close_action=start`, `dpd_action=restart`.
-`entrypoint.sh`: создаёт `ipsec0` (10.10.10.6/30, if_id 42), MASQUERADE на eth0, `exec charon-systemd` (PID 1).
## Ключевая диагностика: «egress = .140 вместо Франции»
= **IPsec SA не установлен**. Тогда .140 маршрутизирует claude.ai через свой default (Etersoft uplink 91.232.225.1) и egress'ит как `91.232.225.140` (Anthropic отдаёт 403 на curl).
Проверки:
```bash
# на .140:
ssh root@91.232.225.140 'swanctl --list-sas | grep -i ESTABLISHED'# пусто → SA down
ssh root@91.232.225.140 'ip route get 160.79.104.10'# via ipsec0=OK, via eth0=down
# на rpi:
docker exec ikev2-fr swanctl --list-sas# SA со стороны инициатора
docker ps | grep ikev2-fr # Up (unhealthy)? → SA down
```
AUTH_FAILED в логе .140 от IP типа 164.90.141.172 / 205.210.31.215 — **сканеры**, не наш пир (они до IKE_AUTH не доходят или фейлятся раньше). Реальный пир = rpi (78.193.2.190).
После этого `swanctl --list-sas` на .140 покажет `ESTABLISHED ... remote 'fr.egw.etersoft.ru' @ 78.193.2.190`, `ip route get 160.79.104.10` → `via 10.10.10.6 dev ipsec0`, egress claude.ai = 78.193.2.190.
## Persistence (self-healing)
`ikev2-fr` имеет label `autoheal=true` (в `docker-compose.yml`). Контейнер `autoheal` (`willfarrell/autoheal`, проект `/root/tunnel-fr/`) рестартит unhealthy-контейнеры с этой меткой:
```
SA down → healthcheck (swanctl --list-sas | grep ESTABLISHED) fail → unhealthy
~60-90с восстановление. На свежем старте SA поднимается сам. **Именно отсутствие autoheal-label** было причиной 7-дневного дауна (контейнер висел unhealthy, никто не рестартил).
Все настройки ikev2-fr в образе `ikev2-fr-client` (== `/opt/ikev2-fr-docker/*`, `docker diff` чист) — пересоздание через `docker compose up -d` ничего не теряет.
## Мониторинг (route-health)
`route-health.sh` (timer на igw/egw) + Telegraf → InfluxDB (`gateways`): по шлюзам собирает ping/iperf3/vpn/proxy, пишет `health.json` (кормит route-web UI).
**ikev2.fr и ikev2.gr — ping-only** (`is_pingonly_gw` в route-health.sh): у rpi-пира нет iperf3-сервера и VPN-status-feed, поэтому судят **только по ping** (loss≥50 или no-data = dead). Раньше они проверялись по iperf3 → success=0 → перманентный false-`down`, что маскировало реальные обрывы (7-дневный даун не заметили). Теперь: туннель up → ping через него идёт → healthy; упал → ping до 10.10.10.6 не идёт → dead (реальная детекция).
Health виден в route-web UI (`health.json`: `groups[].gateways[]` для fr → ikev2.fr). **Push-алертинга пока нет** — состояние видно в UI + rpi восстанавливается через autoheal, но уведомления (Telegram/почта) не настроены.
## Грабли / нюансы
-`claude.ai` на plain-`curl` = **403** (anti-bot по User-Agent). С браузерным UA = **302** (норма). 403 ≠ геоблок.
- Туннель **IPv4-only** → v6 claude.ai = unreachable (fast reject), не пытаться чинить v6 через rpi.
-`docker logs ikev2-fr` пустой (charon-systemd пишет в journald, которого нет в контейнере) — диагностика через `swanctl --list-sas` / `--initiate`.
- PSK хранится в swanctl.conf с обеих сторон (в secrets); при смене — одинаково в `/opt/ikev2-fr-docker/swanctl.conf` (rpi) и `/etc/strongswan/swanctl/conf.d/site-to-site.conf` (.140), потом rebuild/reload.
Greek egress для **gvpn** (CT 752 на enceladus). Полный аналог французского туннеля ([[reference_ikev2_fr_tunnel]]), только страна/хосты другие. Зафиксировано 2026-07-15 после инцидента «gvpn offline».
## Data path
```
gvpn (CT 752, enceladus) default via 91.232.225.139
| PSK | 7f02acba… (id-server ikev2.gr.egw… / id-client gr.egw…) | тот же |
PSK и конфиг — близнецы fr.egw/CT704, только `.139`/`gr` вместо `.140`/`fr`.
## Доступ к parentglobal (за NAT)
-**Reverse-SSH:**`ssh -p 10337 etersoft@anyssh.ru` (anyssh.ru=91.232.225.8), потом sudo. См. [[reference_remote_ssh_access]].
-**Через IPsec (когда SA up):**`ssh -J root@91.232.225.139 root@10.10.10.6`.
## Ключевая диагностика: «gvpn offline»
gvpn (CT 752) имеет `default via 91.232.225.139 dev breth0`. Если **CT 706 остановлен** → у gvpn нет шлюза → offline. Симптом: `ping 91.232.225.139` из gvpn = 100% loss, `lxc-attach -n 752 -- curl` виснет.
```bash
ssh root@border 'pct status 706'# stopped → причина
CT 706 = responder (`remote_addrs=%any`, `start_action=none`) — сам SA не инициирует. Если parentglobal не переподключился сам (редко), инициировать с его стороны:
После ручного initiate дальше self-heal (`close_action=start`).
## Различие с fr
- fr.egw (CT 704, .140) обслуживает **обход claude.ai/Anthropic** через igw/egw (группа `fr` в route-update, таблица claude.ai). См. [[reference_ikev2_fr_tunnel]].
- gr.egw (CT 706, .139) обслуживает **только gvpn** (default route). Группы `gr` в route-update НЕТ, route-health ikev2.gr не мониторит (только ikev2.fr/ikev2.gr формально ping-only, но routes.d/gr пуст).
## Смежный туннель на parentglobal: `divserver`
У parentglobal в swanctl **две** conn: `site-to-site` (→.139, этот документ) и `divserver` (→divserver, telemt/Telegram MTProxy, ipsec1 10.43.0.0/30). На 2026-07-15 `divserver` SA **down** — это отдельная deferred-проблема ([[project_dchat_telemt_greek_tunnel_down]]), к gvpn-эгрессу отношения не имеет.
## Грабли
-[[lesson_anyssh_ru_in_91232225]] — НЕ добавлять на parentglobal маршрут `91.232.225.0/24 via ipsec0` до установки SA: перехватит трафик к anyssh.ru (.8), умрёт reverse-SSH, parentglobal недоступен извне.
- Туннель **IPv4-only** (как и fr). gvpn без IPv6 специально ([[lesson_clone_container_iptables]] контекст, fvpn/gvpn v6 убран 2026-07-15).
-`pct start` пишет «failed to connect to monitor socket: Connection refused» — безвредно (QEMU monitor ещё не готов), старт идёт.
Версии сняты **pre-auth handshake-баннером** (без credов) — Python `socket`→ HandshakeV10, версия идёт до авторизации. См. `~/tmp/claude/mysql_banner.py`.
## Прод-серверы (4 выделенных, port 3306 открыт в сети)
| **mysql.office**.etersoft.ru | 10.20.30.191 | MySQL **8.0.16**-alt1 | почти наверняка shared-БД офиса (zabbix CT132, taiga CT152, wiki CT235, collectd CT213, noc CT225). ⚠️ у имени **НЕТ A-записи** (forward не резолвится), только stale PTR. SSH-ключа нет. |
| **mariadb.dmz**.etersoft.ru | 10.20.30.195 | MariaDB **11.8.8** | ✅ **NEW (CT 369, border, 2026-07-16)** — выделенный RT-хост. Full-clone шаблона 720 → etcnet убран, **systemd-networkd**. utf8mb4/utf8mb4_unicode_ci, `innodb_buffer_pool_size=4G`, `max_allowed_packet=64M`, bind **10.20.30.195:3306** (skip-networking OFF). Зарегистрирован в phpMyAdmin (mysql.eterhost.ru, CT 240; в массиве `$hosts` в `/var/www/webapps/phpMyAdmin/config.inc.php`). **Юзеры созданы** (пароли = как на mysql.auth): `root`@`10.20.30.%`+`91.232.225.23` (=`6f1XQPLsfd`, pma ходит с **10.20.30.99**), `eter_rt`@`%` (=`Veegi9sh`, боевой — RT переподключится без смены пароля при смене DatabaseHost), `phpmyadmin` controluser (=`CHwJTSr58wwBTYBS`, фикс. в config.inc.php) + 19 `pma__` таблиц. Готов к заливке дампа `eter_rt`. [[plans-rt-update]] |
Все 4 старых — на DHCP (без static ip= в PVE-конфиге). **mariadb.dmz — статический** (10.20.30.195). SSH-ключа к office/fond/host03 нет — к auth (CT 219) и mariadb.dmz (CT 369) есть.
## Тест-стенды
-**rt-test** (CT 183, gefest, 192.168.0.191) — локальная **MariaDB 11.8.8** (дамп боевой `eter_rt`), RT 4.4.7 с ней работает. [[plans/rt-update]]
-**test-mail** (CT 943, border, 10.20.30.246) — MariaDB **bound на 127.0.0.1:3306** (по сети недоступен), копия таблиц `mail`. [[plans/mail-etersoft-ru]]
## Совместимость с RT (для миграции [plans/rt-update])
RT 4.4.x работает с **MySQL 5.5 / 5.7** и **MariaDB 10 / 11**, но **НЕ с MySQL 8** (RT не использует quoted identifiers → `SQL syntax error` на `Groups`/`Principals`, баг 7693#c51-53). Поэтому:
- ✅ RT-совместимые прод-хосты: только **auth (5.5)** и **fond (5.7)**
- ❌ office (8.0) и host03 (8.0) — MySQL 8, RT не заведётся
- Решение по БД для RT: **новый выделенный MariaDB 11 на p11** — ✅ ВЫПОЛНЕНО, это **mariadb.dmz.etersoft.ru (CT 369, 10.20.30.195, MariaDB 11.8.8)**. (office/host03 отлетают по версии, auth — EOL-p8 shared с которого уходим, fond — чужой). Открытый вопрос про БД-хост в [[plans/rt-update]] закрыт.
## ⚠️ Документация
`plans/mail-etersoft-ru.md` (стр. 17) ошибочно пишет `mysql.auth.dmz.etersoft.ru (10.20.30.191)` — mysql.auth реально на **.202**, а .191 = mysql.office (MySQL 8). При ротации/миграции не перепутать.
## 🔜 Будущее: mysql.auth сам требует обновления (отдельная задача, «потом»)
`mysql.auth` (CT 219) = **ALT p8 + MySQL 5.5.43** — глубоко EOL, и это **shared-хост** (`eter_rt` + `mail` + `eter_mnogosearch` + `eter_sales`). Его собственная миграция (p8→p11, MySQL 5.5→MariaDB 10/11) — крупная и рискованная: `mail` критична и тесно связана (Cyrus/Postfix-мапы, cyradm, sec — см. [[lesson-mail-mysql-credentials]]). **Вывод для RT:** не привязывать RT к mysql.auth — иначе его БД-коннекшен переедет ещё раз при будущем апгрейде auth, плюс RT окажется завязан на чужую рискованную миграцию. Дать RT свой выделенный MariaDB-хост сейчас → RT независим.
Причина: Chromium крашился с `pthread_create: Resource temporarily unavailable` (bug https://bugs.etersoft.ru/show_bug.cgi?id=19109). ИИ-агенты (codex+claude+mimo) занимают ~770 потоков, Chromium с 50 вкладками — ещё ~1500-2000. Лимит4096 не хватало.
При обновлении пакета roundcube миграции БД НЕ выполняются автоматически.
**Проверить текущую версию:**
```
mysql -h mysql.dmz.etersoft.ru -u eter_roundcube -p4h7dGS2Sp3ruw5Vd eter_roundcube -e "SELECT * FROM \`system\`;"
```
**Запустить миграции:**
```
cd /usr/share/roundcube && php bin/updatedb.sh --dir SQL --package roundcube
```
**Если миграция падает** (таблица/колонка уже существует — бывает от плагинов):
1. Посмотреть что делает миграция: `cat /usr/share/roundcube/SQL/mysql/<VERSION>.sql`
2. Пропустить проблемную миграцию: `UPDATE \`system\` SET value='<VERSION>' WHERE name='roundcube-version';`
3. Запустить updatedb.sh снова
**Важно:**`--dir SQL` (не `--dir SQL/mysql`!) — скрипт сам добавляет `/mysql` к пути.
### Симптом: вложения не прикладываются к письмам
Если при прикреплении файлов к письму Roundcube молча не загружает вложение — проверить наличие таблицы `uploads` в БД. Если таблицы нет — миграции не выполнялись после обновления пакета. Пример: БД на версии 2016112200 (Roundcube ~1.2), пакет — 1.7.2. После `updatedb.sh` создаётся таблица `uploads` и вложения работают. [ses_077433517ffe]
`router/route-update.sh` на igw.etersoft.ru — декларативная policy-маршрутизация. Каталоги `routes.d/` (v4) и `routes6.d/` (v6), site-specific (в `.gitignore`), сам скрипт — в git.
**Архитектура**
-**Таблица = per-list**, не per-group. Каждому `.list` — своя таблица 200-250 (автовыделение в `/etc/iproute2/rt_tables`, напр. `ai`=201, `claude.ai`=223). Несколько `.list` в каталоге группы = несколько таблиц, шарящих `gateway`.
-**`ip rule` pref = порядок обработки, НЕ имя/номер таблицы.** Группы обрабатываются **по алфавиту** (`for d in routes*.d/*/`, локаль `ru_RU.UTF-8` — **цифры сортируются ПОСЛЕ букв**, так что `0-fr` встанет последним, не первым!), каждой — блок в 100 с 1000, внутри группы `.list` += 10. По умолчанию `egw` (ai=1210) перебивает `fr` (claude.ai=1300).
-**Override приоритета**: `options` файл в группе со строкой `pref N` → `process_routes` использует N как pref_base вместо алфавитного. `option_value()` в `functions`. Применено: `routes.d/fr/options` + `routes6.d/fr/options` = `pref 900` → claude.ai (fr) перебивает ai. Грабля (fixed): переезд правила на новый pref не происходил при skip-ветке «resolved unchanged» — `_fixup_rule_pref` теперь вызывается и перед этим skip.
- gateway: IP/hostname/`default`, метрики (`metric N`) = failover, multipath если несколько строк без метрики.
-**gateway route-type keywords** (added 2026-07-05): `blackhole`/`unreachable`/`prohibit`/`throw` — скрипт ставит маршруты этого типа (без `via`), `ip route replace <kw> <dst> table N`. `unreachable`/`prohibit` = ICMP назад → мгновенный отказ (curl падает за ~2мс). Применено на `routes6.d/fr` → claude.ai v6 reject (50 unreachable-маршрутов, вкл. covering-подсети). Keyword первым в `gateway` → вся группаrejected. **Почему v6=unreachable**: fr-узел `ikev2.fr` (.140, v4-only gateway группы fr) **не имеет IPv6** — v6-пути во Францию нет, поэтому reject fast вместо попытки шлюза.
**Слои персистентности IP** (для DNS round-robin tolerance)
1.`resolved` — текущий набор IP списка.
2.`resolve_history/{1..20}` — union из `HISTORY_SIZE=20` snapshot'ов, мерджится в `resolved` каждый запуск. Самоочищается за N запусков **только если IP перестаёт поступать**.
3.`volatile_ips/<домен>` (только IPv6) — для volatile-доменов (TTL≤120 / diff-resolvers); `expand_volatile_subnets` собирает IP с нескольких резолверов и добавляет covering-подсети. **Имена файлов = домены.**
**Баг (fixed 2026-07-05):**`expand_volatile_subnets` читал весь `volatile_ips/` как список доменов к ре-резолву, **не удаляя осиротевшие** → удалённый из `.list` домен пожизненно перевпрыскивал свои IP в таблицу. Фикс: prune `volatile_ips/<домен>` если домена нет в текущем `.list` (передан `$_f`). Побочно вычистило кучи стаи в web-bypass (~40 доменов), blocked и др.
**Грабли при очистке state:** после удаления IP из `resolved`+history перезагрузка может пройти мимо stale-removal — если `resolved` совпал сам с собой, load идёт по короткому пути «без изменений» и kernel stale-маршруты не трогает. Добивать вручную: `while ip -6 route del <dst> table <T>; do :; done`.
**Триггеры на igw:** inotify-наблюдатель (PID ~962337) — `route-update.sh` при изменении `/root/egw-route/`, `/root/antifilter/`, `/root/antifilter.network/`; + `route-update.timer` раз в 30 мин. Запуск: `./route-update.sh [--resolve|--force|-v]`, `--flush GROUP`, `--add|--del IP|DOMAIN GROUP`.
См. [[reference_igw_route_api]] (HTTP API поверх тех же списков), [[feedback_route_list_symlink_only]].
- ⚠️ **Грабля cutover (~9.5 ч простоя забора 2026-07-23):** rt-fetchmail создан в `/etc/cron.d`, но
давно работающий crond в LXC **не перечитал каталог** (inotify-провал на overlay) → забор встал.
Лекарство: `serv restart crond`. **После настройки системы перезапускать контейнер и перепроверять
автозапуск** — см. [[lesson_restart_container_verify_autostart]]. `/etc/cron.d` режим `0700` — пакетный
(vixie-cron), НЕ менять.
- Тест забора/autoreply — см. [[lesson-rt-mail-pickup-test]]: слать на `name@etersoft.ru`,
автопроверка в логах mail.etersoft.ru по `from=<noreply@etersoft.ru>`.
### Тестовые тикеты 2026-07-16 — удалены
#70337 (BOSS), #70338 (SUPPORT), #70339 (BOSS) — проверки забора/autoreply после миграции; переведены в status=deleted (SetStatus('deleted') под страховкой subject=~^TEST).
---
## ⏳ НИЖЕ — исторически по CT 233 (снят с прода 2026-07-16)
## Архитектура
-**ОС: ALT p9 (VERSION_ID=9, EOL), 32-bit i686.** RT 4.4.4 (`rt-4.4.4-alt1_2`). В ALT p11/Sisyphus RT только **4.4.5** (апстрим уже 6.0.x, 4.4.x — EOL, CVE непатчены: ALT bug 49420). План обновления: [plans/rt-update.md](plans/rt-update.md).
## Тестовый контур rt.pr (p11, 2026-07-13) — Этап 1 обновления
-**CT `rt-test`** на ноде **gefest** (НЕ enceladus), IP **192.168.0.191** (vmbr0 office LAN — ⚠️ тест-серверы должны быть на 10.20.30.x, [feedback_test_servers_on_dmz](feedback_test_servers_on_dmz.md); оставлено как есть). ALT **p11 x86_64**, RT **4.4.5-alt1_4**, MariaDB **11.8.8** локально (дамп боевой `eter_rt`).
- 🔴 **Блокер для p11:**[lesson_rt_condition_perl538](lesson_rt_condition_perl538.md) — `RT/Condition.pm:201` без `;` = fatal на Perl 5.38, падают все scrips. На тесте запатчено. Для прода — пересборка ALT-пакета.
- После миграции БД обязательно: `mariadb-check --analyze eter_rt` + `innodb_buffer_pool_size=2G` (иначе поиск/просмотр висят). `rt` не тянет `perl-Plack-FCGI`/`perl-FCGI`. Детали: [plans/rt-update.md](plans/rt-update.md).
## ⚠️ Известная причина 500 при ответах (2026-07-06): диск azbykar полон
-**Корневой диск azbykar (CT 102) всего 8 ГБ**. При ответе с цитированием тело POST > in-memory буфера → nginx пишет temp в `/var/spool/nginx/tmp/client` (на корне). **Диск 100% полон → temp не создаётся → 500 Internal Server Error (страница `nginx/1.28.1`).** POST не доходит до rt (в access-логе rt его нет); GET'ы (форма ответа) маленькие — работают.
- Забивали диск: `/var/log/nginx/gitlab.eterhost.ru-access.log` (~5.8 ГБ активный) + ротированные `.1`/`.xz`, `/var/log/fail2ban.log`. Логи azbykar `rt_access/rt_error` при этом «не пишутся с Jul 5» (nginx держит fd после ротации — слепая зона).
-**Починено 2026-07-06:** удалены ротированные логи gitlab/fail2ban + `journalctl --vacuum-size=100M`. Затем **диск расширен 8→16 ГБ** (CT 102 rootfs `pool0/subvol-102-disk-0`, ZFS на enceladus). И **настроена ротация gitlab-access.log по размеру** (`maxsize 200M`, см. ниже) — пик на диске ~1 ГБ вместо 5.8 ГБ. Текущее состояние: ~3 ГБ занято / ~14 ГБ свободно.
## Ротация логов на azbykar (настроено 2026-07-06)
-`/etc/logrotate.d/nginx`: станза `/var/log/nginx/*log` с **`maxsize 200M`** + `weekly rotate 7 compress delaycompress`, postrotate `nginx -s reopen`. `gitlab.eterhost.ru-access.log` растёт **~1 ГБ/день** → ротируется фактически ежедневно (проверка в cron.daily 04:11), пик ~1 ГБ. Мелкие логи как и прежде по неделям.
- ⚠️ **Бэкап станзы НЕ класть в `/etc/logrotate.d/`** — logrotate читает там ВСЕ файлы → дубль-станза → `duplicate log entry` → реальный cron падает с `EXIT=1` и не пишет state. Бэкап лежит в `/root/nginx.logrotate.bak.20260706`.
- Шум в gitlab-access (можно резать на уровне nginx, если надо меньше лога): `/assets/icons-*.svg` ~11% запросов, опрос `gitlab-runner``/api/v4/jobs/request`, краулер ClaudeBot.
- Предсуществующее (не чинил — чужая подсистема): станза `clamav` падает каждый прогон (`/var/log/clamav/{clamd,freshclam}.log` отсутствуют, нет `missingok`) → logrotate ежедневно `EXIT=1`. **nginx-ротацию не блокирует**, но маскирует ошибки. Фикс — добавить `missingok` в `/etc/logrotate.d/clamav`.
## Логирование (починено 2026-07-06)
- RT пишет в **`/var/log/rt/rt.log`** (`$LogToFile='notice'`, `$LogDir=/var/log/rt`, `$LogToFileNamed=rt.log`). **Важно:** файл пишется ТОЛЬКО при событиях уровня notice+ (warning/error). Если лог пустой/молчит — это нормально для спокойного RT, НЕ значит что лог сломан. Проверить: `su -s /bin/sh apache -c "cd /usr/share/rt && perl -MRT -e 'RT::LoadConfig();RT::Init();\$RT::Logger->warning(q{test});'"`.
-**Раньше stderr уходил в `/dev/null`** (systemd `StandardOutput=null`, `StandardError` наследовал). Добавлен drop-in `/etc/systemd/system/rt-fcgi@.service.d/logging.conf` → `StandardError=journal`. Теперь Perl `die`/`warn` видны: `journalctl -u 'rt-fcgi@*'`. (Менять `StandardOutput` НЕ надо — FCGI говорит по сокету fd 0, но безопаснее оставить null.)
-**logrotate**`/etc/logrotate.d/rt` теперь крутит и rt.log, и fetchmail_rt.log (`copytruncate`, без postreload). Раньше rt.log вообще не ротировался → дорос до 1.5 ГБ (sparse).
- fetchmail пишет отдельно в `/var/log/rt/fetchmail_rt.log` (очень многословный).
-**В server-блоке nginx для rt НЕ задан `fastcgi_read_timeout`** → дефолт **60s**. Тяжёлые ответы/входящая почта могут превышать 60s → nginx «upstream timed out» → 502. Кандидат на увеличение (напр. 300s) + `fastcgi_buffers`.
- systemd rt-fcgi: все лимиты infinity (MemoryMax/Limit/tasks). `Restart=always`.
## Ресурсы
- RAM: **3 ГБ** (поднят 2026-07-06 с 2 ГБ; контейнер LXC применяет memory при stop/start, `pct reboot` тоже сработал). 1 vCPU. Диск: rootfs `pool0/subvol-233-disk-0` (ZFS на enceladus), 240+ ГБ свободно.
-**loadavg в контейнере показывает нагрузку ХОСТА enceladus** (loadavg не изолирован в этом LXC) — `top` даёт реальную картину (обычно idle). Не пугаться «load 7».
## Известная проблема: периодические 5xx при работе с RT
- В nginx `rt-error.log`: `upstream prematurely closed connection` / `Connection reset by peer` / `upstream timed out` — rt-server.fcgi воркер **умирает посреди запроса**, `Restart=always` его поднимает (NRestarts копится). Бывает на разных тикетах/эндпоинтах, не только одном.
- До 2026-07-06 причина НЕ логировалась (stderr → /dev/null, а воркер умирал без Perl-level error → в rt.log ничего). Теперь ошибки ловятся в journal — при репорте 500 воспроизвести и смотреть `journalctl -u 'rt-fcgi@*'` + `tail /var/log/rt/rt.log` + nginx access/error.
## Быстрая диагностика при «internal server error» в RT
1.`journalctl -u 'rt-fcgi@*' --since '5 min ago'` — Perl-ошибка (с 2026-07-06).
2.`tail -50 /var/log/rt/rt.log` — RT-ошибки уровня error/warning.
3.`grep " 5" /var/log/nginx/rt-access.log*` и `tail /var/log/nginx/rt-error.log*` — конкретный запрос и сторона nginx.
4. К proxy `10.20.30.23` обращений нет — он фронтенд.
См. также [[reference-create-lxc-container-pve]], [[lesson-cyrus-sasl-restart]] (почта).
На каждом бэкенде: `/etc/telegraf/scrape-telemt-ja4.py` (python: curl localhost:9091 → агрегат по JA4 → influx line protocol) + `/etc/telegraf/telegraf.d/telemt-ja4.conf` (`[[inputs.exec]]` interval 60s). telegraf уже шлёт в **telegraf.office.etersoft.ru:8086, db `gateways`**, host-тег = имя бэкенда.
- Развёрнуто на ВСЕХ 4 бэкендах (2026-06-22): beget, schat, divserver, **e5/.105** (host-тег в InfluxDB: beget/schat/divserver/**telemt**).
-**e5 — ALT Sisyphus** (не p11!), telemt ALT-нативный пакет (НЕ epm-play): обновлять из girar-задания — `apt-repo add <task>` → `apt-get update` → `epm install telemt` → `apt-repo rm <task>`. Версия 3.4.18-alt1 уже в Sisyphus, задание #422377 — тест для p11. Юнит ALT-пакета: User=telemt → нужен CAP_NET_ADMIN drop-in.
- Картина по эндпоинтам (2026-06-22): **beget/divserver обслуживают живых клиентов** (auth сотни/десятки, разные JA4); **e5 (основной chat) — частично** (auth ~47, но probe ~988, тяжёлое зондирование); **schat — сплошь probe** (auth=0, 1000 probe) → блокировка РАЗНАЯ по эндпоинтам, beobachten это показывает в реальном времени.
-**e5 — ALT Sisyphus** (не p11!), telemt ALT-нативный пакет (НЕ epm-play): обновлять из girar-задания ОДНОЙ командой **`epm install <task_id>`** (epm через `is_taskarg`/`epm_install_alt_tasks` сам добавляет task-репо во **ВРЕМЕННЫЙ** apt-config, делает update, ставит, чистит — реальный `sources.list` вообще не трогается; `--full` = ставить даже -devel/-debuginfo, по умолчанию они excluded). **НЕ лепить `apt-repo add task` руками** — см. [[feedback_epm_install_girar_task]]. Вывод apt внутри epm может врать «Последняя версия уже установлена / 0 обновлено» — верить `rpm -q telemt`. Текущая версия: **3.4.22-alt1** (обновлено 2026-07-05 через task #424260 [test-only]; до этого было 3.4.18-alt1 из p11 task #422377). Юнит ALT-пакета: DynamicUser + `User=telemt` → нужен CAP_NET_ADMIN drop-in `/etc/systemd/system/telemt.service.d/caps.conf` (`AmbientCapabilities=CAP_NET_BIND_SERVICE CAP_NET_ADMIN` + `CapabilityBoundingSet=...`) — на e5 уже есть. Проверка после апгрейда: `rpm -q telemt` → `serv restart telemt` → `curl -sk --resolve chat.eterfund.ru:443:91.232.225.105 https://chat.eterfund.ru/` (HTTP 200), `curl 127.0.0.1:9091/v1/runtime/tls-fingerprints?limit=1` (HTTP 200). WARN «mismatched certificate metadata file=…json» при старте = stale TLS-cache от прошлой версии (benign, сервится cert всё равно). Бэкап перед апгрейдом: `cp /usr/bin/telemt /etc/telemt/config.toml ~/tmp/claude/telemt-backup-<ver>/`.
-**s.chat (Ubuntu 24.04) → 3.4.24** (2026-07-18, girar task #425671 [test-only] sisyphus telemt.git=3.4.24-alt1): на Ubuntu sisyphus-task НЕ применим (rpm/apt-rpm), ставим через **`epm play telemt=3.4.24`** — epm play умеет явную версию `NAME=VER`; `epm play --latest`/app-versions отстаёт (там 3.4.22, а 3.4.24 уже в girar). Процедура: бэкап `/usr/bin/telemt` (41B wrapper) + **`/opt/telemt/telemt`** (реальный бинарь) + `/etc/telemt/config.toml` в `~/tmp/claude/telemt-backup-pre-3.4.24/` → `epm ei` (3.64.63→3.64.66, от граблей repack [[reference_telemt_fleet_ja4_monitoring]]) → `epm play --remove telemt` → `epm play telemt=3.4.24` → `serv restart telemt`. Проверка: beobachten `curl 127.0.0.1:9091`/`:9090` → HTTP 200, слушает `0.0.0.0:443`. **Новый WARN в 3.4.24:**`Unknown config key timeouts.tg_connect, suggestion=tg_connect` — схема конфига изменилась, ключ надо мигрировать `timeouts.tg_connect` → `tg_connect` (пока игнор, таймаут = дефолт; конфиг НЕ правлю без подтверждения). WARN `mismatched certificate metadata file=chatgpt.com.json` — benign (stale TLS-cache). Был 3.4.18 → 3.4.24.
-**s.chat: synfix + диагноз блокировки (2026-07-18):** жалоба «клиент не подключается к s.chat». Применил **synfix с e5 1:1** (nftables table `mtpr_synfix`: iOS по TCP-fingerprint accept / прочие SYN 54/мин / reject host-unreachable + sysctl `99-telemt.conf` fastopen=3 somaxconn=65535 syn_backlog=65535 keepalive 45/15/3 + `listen_backlog` 4096→65535 + сервис `mtpr-nft-synfix.service`, файлы в `~/tmp/claude/telemt-synfix/`). **Клиенту НЕ помогло:** beobachten auth=0 хронически (с 2026-06), только DPI-зонды (30 шт, 1 JA4 `t13d1516h2_…`). **Root cause: IP `139.100.207.140` жёстко заблокирован ТСПУ** — DPI режет глубже SYN (ClientHello/payload), synfix отбивает лишь зонды (698 reject). telemt/конфиг ПОЛНОСТЬЮ исправны: `[censorship]` побайтово идентичен рабочему d.chat, **gre3→egw2→.27 работает** (`curl --interface 10.42.0.2 ipify` = 178.105.209.27 = registered для ad_tag; egress-mismatch ОПРОВЕРГНУТ — [[lesson_telemt_middle_proxy_egress_match]] не про этот случай), direct upstream 506/506 ok, ME pool пуст (`no live writers for DC groups [-3,-2,1,2,3]`) но direct-fallback работает. `chat.eterfund.ru` → только e5 (.105), s.chat раздаётся отдельно по `s.chat.eterfund.ru`→.140. **d.chat** (46.148.53.122 / egress 93.120.163.2 через ipsec1, ad_tag `a248912d…`, `middle_proxy_nat_ip` НЕ задан) и **b.chat** работают (другие IP, не в блоке). Варианты по s.chat: сменить IP / VLESS+Reality / вывести из клиентского доступа. **b.chat: nftables установлен (`epm install nftables`, v0.9.3), synfix отложен** — хост работает, применить схему можно по тем же файлам. **tg_connect в 3.4.24** перенесён `[timeouts]`→`[general]` (WARN `Unknown config key timeouts.tg_connect, suggestion=tg_connect`).
- Картина по эндпоинтам (2026-06-22, обновлено 2026-07-18): **beget/divserver обслуживают живых клиентов** (auth сотни/десятки, разные JA4); **e5 (основной chat) — частично** (auth ~47, но probe ~988, тяжёлое зондирование); **schat — сплошь probe** (auth=0 хронически с 2026-06 = IP .140 заблокирован ТСПУ, не лечится synfix) → блокировка РАЗНАЯ по эндпоинтам/IP.
## SYN FIX (nftables) на e5/.105 (2026-07-09)
Применён фикс из MTPROTO_FIX_By_MEKO (https://github.com/Mekotofeuka/MTPROTO_FIX_By_MEKO, v1.67) — решает проблему с 4 июня 2025, когда ТСПУ режет TCP SYN к MTProto прокси. Суть: двухуровневая фильтрация SYN на порту 443.
-**Скрипт**: `/opt/mtpr-synfix-nft.sh` (nftables, т.к. xt_u32 модуль отсутствует в ядре ALT 6.12.74)
-**listen_backlog** в config.toml увеличен до 65535
-**OpenSSL** 3.5.4 установлен (для SelfSteal SNI проверки)
## Блокировка ТСПУ (2026, из разведки + beobachten)
DPI палит по триаде **JA4 Telegram + один SNI + много ClientHello на один ip:port**. JA4 формирует КЛИЕНТ (мобильный TG с непатченным fingerprint — фиксы только Desktop). Лечится: рассредоточение ip:port, «одобренный» SNI (напр. rusk.ru=91.232.225.80, наш реальный сайт), смена протокола (VLESS+Reality). Конфиг маски: один SNI на инстанс telemt (`tls_domain`); фронтить реальный домен через `mask_host`.
-**`/usr/local/bin/zclaude`** — shell-wrapper (362 б): source'ит `/etc/opt/claude.ai/env-z.conf` и `~/.claude/env-z.conf` (через `set -a` allexport), затем `exec /opt/claude.ai/claude "$@"`. Штатный `/usr/bin/claude` — отдельный wrapper, source'ит `env.conf` (без z), для др. провайдера.
-**`/etc/opt/claude.ai/env-z.conf`** (root:eterworkers, 640) — тут живут `ANTHROPIC_AUTH_TOKEN`, `ANTHROPIC_BASE_URL=https://api.z.ai/api/anthropic`, `ANTHROPIC_DEFAULT_{HAIKU,SONNET,OPUS}_MODEL`, `CLAUDE_CODE_AUTO_COMPACT_WINDOW`, `API_TIMEOUT_MS`. Лежат 2 токена z.ai (один закомментирован). Пользовательского `~/.claude/env-z.conf` нет — всё из системного.
- Список моделей z.ai: GET `https://api.z.ai/api/anthropic/v1/models` (Bearer или x-api-key). 8 шт: glm-4.5/4.5-air/4.6/4.7/5/5-turbo/5.1/5.2. `[1m]` суффикс = 1M context window.
- Цены/доки: https://docs.z.ai/guides/overview/pricing ; https://docs.z.ai/devpack/faq (5h rolling-квота: Lite ~80 prompts/5h, Pro ~400, Max ~1600).
**Почему квота GLM тает «во много раз быстрее» Anthropic Opus** (подтверждено r/ZaiGLM + z.ai devpack):
1. В Coding Plan **cached tokens считаются как full usage** (у Anthropic cache hits не считаются против rate limit).
2.**glm-5.x имеет множитель 2–3×** (3× пик, 2× внепик) против glm-4.7.
**Tiered routing с 2026-07-06** (бэкап: `env-z.conf.bak.2026-07-06`): HAIKU=`glm-4.7`, SONNET=`glm-4.7`, OPUS=`glm-5.2` (без `[1m]`), `AUTO_COMPACT_WINDOW=200000`. Было: SONNET=OPUS=`glm-5.2[1m]`, compact=1M → квота горела. Менять только через sudo (tee сохраняет права); правки вступают только при новом запуске `zclaude`.