Для установки/тестирования пакета из girar-задания ALT — одна команда:
```
epm install <task_id> # напр. epm install 424260
epm install --full <task_id> # если нужны даже -devel/-debuginfo (по умолчанию excluded)
```
epm сам распознаёт числовой аргумент (`is_taskarg` в `/usr/share/eepm/epm-install` → `epm_install_alt_tasks` в `epm-install-alt`), создаёт **временный** apt-config, добавляет туда `http://git.altlinux.org/repo/<task>/x86_64/task`, делает `apt-get update`, ставит пакет(ы), и убирает temp-config — реальный `sources.list` не меняется. Также можно `epm install <URL задачи>` (`is_taskurl`).
**НЕ делать вручную**`apt-repo add task <N>` → `apt-get update` → `epm install <pkg>` → `apt-repo rm` — это обход epm и засоряет реальный sources.list. См. [[feedback_use_epm_for_packages]], [[feedback_never_bypass_epm_play]].
**Why:** epm — единый интерфейс для пакетных операций на ALT; у него есть готовый механизм для task-репо, который к тому же изолирует изменения в temp-config (не оставляет мусора в sources.list). Ручной apt-repo add нарушает «пакеты ТОЛЬКО через epm».
**How to apply:** получил girar task `NNNNNN [...] pkg.git=ver-alt1` → `ssh root@<host> 'epm install NNNNNN'`. Граничный случай: apt внутри epm может соврать в выводе «Последняя версия уже установлена / 0 будет обновлено» — сверяться с `rpm -q <pkg>` (наблюдалось на e5/telemt 3.4.18→3.4.22, при этом пакет реально обновился). Применено: [[reference_telemt_fleet_ja4_monitoring]] (e5, task #424260, 2026-07-05).
Свободные IP в подсети 91.232.225.0/24 проверять через файл обратной зоны на ns1 (`/var/lib/bind/zone/225.232.91.in-addr.arpa`). Когда берём IP — сразу вписываем PTR-запись в зону, обновляем serial, reload.
**Why:** ping-проверка ненадёжна (хост может быть выключен), а reverse zone — единственный источник правды о занятых IP.
НИКОГДА не копировать библиотеки, бинарники и прочие системные файлы напрямую (cp/scp) — всегда устанавливать через пакетный менеджер (epm). Даже если пакета нет в репозитории — собрать/перепаковать через epm, а не копировать файлы.
Все заметки, справки и changelog — ТОЛЬКО внутри репозитория (`.claude/memory/` для feedback/reference/lesson, `.claude/docs/` для справочных документов). Session memory (notes.md) — только как промежуточный буфер во время работы, после завершения задачи всё переносить в репо.
description:Перед ответом о правилах проекта находить и полностью читать фактические файлы инструкций
type:feedback
---
Перед любым утверждением о том, какие инструкции прочитаны или какой файл является базовым, сначала найти и полностью прочитать фактические `AGENTS.md`, `CLAUDE.md` и другие применимые файлы инструкций в рабочем дереве.
Поиск должен включать скрытые, ignored и untracked файлы: обычный вывод `rg --files` может их не показать. Не опираться только на текст, переданный в сообщении, и не заявлять, что файла нет, без такой проверки.
При конфликте или различиях между файлами явно сообщить об этом и соблюдать более локальные инструкции. Для вопросов об инфраструктуре после чтения инструкций сначала смотреть `.claude/memory/`.
2.**Сразу записывать взятый IP** в reverse zone (PTR-запись + при необходимости комментарий о владельце/назначении), даже если хост не сразу поднят. Иначе тот же IP кто-то возьмёт повторно.
**Why:** Пользователь явно сказал «проверяй через файл обратной зоны. и всегда туда записывай взятые» (2026-05-28). До этого я взял `91.232.225.150`, опираясь на пустой `dig -x` и ping — а в файле там был комментарий «150-169 — адреса для контейнеров на devel.etersoft.ru», т.е. зарезервированный пул.
3.**Писать в ОБЕ зоны одновременно.** Когда IP задействован и для него есть домен (hostname) — добавлять и forward (A для v4 / AAAA для v6), и reverse (PTR). Не оставлять IP «в работе, но без DNS»: так dgw/dvpn выглядели IPv4-only при отсутствии AAAA, хотя ::12/::182 на интерфейсе были. Проверять парность: A↔PTR, AAAA↔PTR.
**Обратная зона — наш учёт занятости IP** (source of truth для «какие IP заняты»). Должна быть полной и актуальной: каждый задействованный IP → PTR (+ комментарий-назначение при необходимости).
**Why:** Пользователь явно сказал «проверяй через файл обратной зоны. и всегда туда записывай взятые» (2026-05-28). До этого я взял `91.232.225.150`, опираясь на пустой `dig -x` и ping — а в файле там был комментарий «150-169 — адреса для контейнеров на devel.etersoft.ru», т.е. зарезервированный пул. Дополнение (2026-07-13): «всегда, когда задействуешь IP и добавляешь для него домен — записывай и в прямую, и в обратную зону; по обратной зоне ведём учёт занятости IP».
**How to apply:**
- В dns skill при запросе «find free IP / verify free IP» всегда читать reverse zone file целиком (через ssh ns1 sudo cat), искать как PTR-записи так и комментарии-резервы.
При добавлении списка маршрутов в `routes.d/<group>/` или `routes6.d/<group>/` файл `.list` должен быть **символической ссылкой** на источник, а не обычным файлом.
Источники обычно: `/root/egw-route/<name>.list`, `/home/routeweb/route-web-api/<name>.list`, `/root/antifilter/*.lst`.
**Why:** Конвенция route-update.sh на igw — все реальные `.list` это симлинки (живут в `/root/egw-route/` и т.п., раздаются между хостами/группами). Единственные regular-файлы — `routes.d/hetzner/hetzner-dummy.list` и `routes6.d/hetzner/hetzner-dummy.list` (пустые placeholder'ы для dummy-группы).
**How to apply:** Создавая новую группу/список — `ln -s /root/egw-route/NAME.list routes.d/GROUP/NAME.list`, а не `echo > ... .list`. Касается и IPv4 (`routes.d/`), и IPv6 (`routes6.d/`). См. [[reference_igw_route_api]] (HTTP API поверх тех же списков).
Тестовые серверы/контейнеры (стенды, копии боевых сервисов) создавать в сети **10.20.30.x** (инфраструктурная/DMZ), а **не** в **192.168.0.x** (office LAN).
**Why:** 10.20.30.x — сеть, где живут боевые сервисы (RT 10.20.30.53, mysql.auth 10.20.30.202 и т.д.). Тестовый стенд рядом с продом → одинаковое окружение, маршрутизация и доступ (админка, мониторинг, тот же сегмент, что и копируемый сервис). 192.168.0.x — офисная клиентская LAN, не место для серверов-стендов.
**How to apply:** при создании тест-CT/VM — ставить на мост/сеть 10.20.30.x, IP из диапазона (190–199 эксперименты, 150–169 devel, см. [[lesson_dns_free_ip_check_both_zones]]). **Сначала подтверждать сеть**, не молча брать дефолтный мост ноды (vmbr0 gefest = 192.168.0.x → ошибся). Связано: [[reference_create_lxc_container_pve]], [[feedback_ip_openness_tiers]].
**Конкретный случай:** rt-test ошибочно создан на gefest vmbr0 = 192.168.0.191 (office), надо было 10.20.30.x. По указанию пользователя — оставлен как есть, не пересоздаём. См. [plans/rt-update.md](../plans/rt-update.md).
При клонировании LXC-контейнера (pct clone) iptables-правила на хосте НЕ копируются. Контейнер получает новый IP, но на хосте нет SNAT-правил для него → контейнер не может выйти в интернет (L2 работает, L3 нет).
## Симптомы
-`arping` от шлюза отвечает (L2 ok)
-`ping` до шлюза не проходит (L3 fail)
-`ntpd` внутри контейнера падает с segfault каждые ~8 сек (симптом отсутствия сети)
- Нет iptables DROP/REJECT правил — просто отсутствуют SNAT-правила
## Решение
После клонирования вручную добавить SNAT-правила на хосте:
Обратно совместимо: старые `i_interface`/`i_interface2` продолжают работать (assembleются в список). Рефакторинг: 4 iptables-функции (`create_iptables_rules`, `create_ip6tables_rules`, `destroy_iptables_rules`, `destroy_ip6tables_rules`) заменены с copy-paste per-interface на `for iface in wan_ifaces` цикл. Код: commit `9ac5f23` в `/srv/lav/Projects/git/eterban`.
## 2. Правила eterban — ДИНАМИЧЕСКИЕ, в файле их НЕТ
Все правила eterban (FORWARD `eterban_1` REJECT, nat PREROUTING `eterban_white`/`eterban_1`/`firehol_level1` DNAT→.67 — для ether1/ether3/vmbr0) создаются `create_iptables_rules()` через `iptables -I/-t nat -I PREROUTING` при старте `eterban.service`. **В `/etc/sysconfig/iptables` их ноль.** Live = файл + ~15 динамических eterban-правил.
## 3. ПОЧЕМУ не в файле — ipset-зависимость = риск падения iptables.service
Правила ссылаются на ipset'ы (`eterban_1`, `eterban_white`, `firehol_level1`, v6-аналоги), которые eterban создаёт в рантайме. Если положить такие правила в `/etc/sysconfig/iptables`, то при загрузке `iptables.service` стартует ДО создания ipset'ов → `iptables-restore` падает на правиле с `--match-set` → **весь файрвол не грузится (atomic fail)** → бордер голый. Поэтому ipset-зависимые правила eterban'а НИКОГДА не класть в `/etc/sysconfig/iptables`.
## 4. Как защищать новый WAN до обновления eterban
-**eterban-правила для нового WAN** → только live (`iptables -I FORWARD 1 -i enp8s0f0 ... --match-set eterban_1 ...` + 3 nat PREROUTING), переживают `systemctl restart eterban` (eterban трогает только свои 2 слота), но **НЕ переживают ребут**. Для персистентности без правки кода — sidecar systemd-сервис `After=eterban.service Requires=eterban.service`, выполняющий те же 4 команды (ipset'ы к тому моменту уже есть).
## 5. Диагностика
-`sudo iptables-save | grep -E 'eterban|firehol'` — все eterban-правила live.
-`sudo iptables -S FORWARD --line-numbers` работает для `-L`, но **НЕ для `-S`** (`-S` игнорирует `--line-numbers` → грабли для позиционных insert'ов).
eterban на priv (91.232.225.1) — архитектура persistence:
-**Ядро**: ipset `eterban_1` (IPv4) + `eterban_1_ipv6` — сами блокировки живут в kernel, переживают restart *службы* (switcher на старте пытается `ipset create` уже существующие наборы → cosmetic `Set cannot be created: ... already exists`, безобидно, чинить не надо). Переживают reboot — НЕТ.
-**Redis**: хранилище метаданных банов (источник/причина/время). Пишется в `/usr/share/eterban/autoban_manager.py:108`: `self.r.hset(meta_key, mapping=metadata)`. Из Redis читает **веб-интерфейс** eterban (eterban.etersoft.ru / eterban.eterhost.ru) и из него же eterban **восстанавливается после reboot**.
Баг redis-py: `mapping` kwarg у `redis.hset` появился только в 3.5.0. На priv (ALT p10) стоял redis-py **3.4.1** → на каждый бан в журнале: `AutoBanManager.on_ban error: hset() got an unexpected keyword argument 'mapping'`. Блокировки в ipset при этом шли (отдельная ветка кода), но **Redis оставался абсолютно пустым** → веб-интерфейс показывал 0 банов, а после reboot priv всё обнулилось бы. Тихая деградация — заметили по пустому веб-интерфейсу.
Фикс 2026-07-05: `p11/python3-module-redis-py` (4.5.5) поверх p10 + `serv restart eterban` (рестарт ОБЯЗАТЕЛЕН — старый процесс держит старую либу в памяти). После рестарта ошибка ушла, Redis начал заполняться.
Риски/заметки:
-**Cross-branch install** (p11-пакет на p10) — может откатиться к 3.4.1 на ближайшем `epm update`/`epm ei` → eterban снова сломается молча. Пинить пакет или чинить upstream: eterban реально требует redis-py ≥3.5.0, а p10 даёт 3.4.1 (баг упаковки eterban → в их багзиллу).
- Баны, жившие в ipset на момент фикса (~49), в Redis отсутствуют → при reboot пропадут (перебанятся быстро fail2ban на источниках).
- Веб-интерфейс eterban на priv обслуживается **php7-fpm**: nginx `eterban.conf` (:81) + `eterban.eterhost.ru.conf` (:80/:443) → `fastcgi_pass unix:/var/run/php7-fpm/php7-fpm.sock`. php7 на priv НЕ лишний — удалять нельзя (ломает бан-страницу).
Добавление линии суммы (Σ total) на Grafana-график с InfluxQL (InfluxDB 1.x OSS, db `gateways`, Grafana на https://telegraf.office.etersoft.ru , админ-пароль `ssh rooter@telegraf pass show grafana`). Пример: дашборд **BIRD BGP — priv**, UID `bird-bgp` (id 9).
**Правильный способ — серверный подзапрос** (добавить как второй target B в той же панели):
- Внутренний `GROUP BY time, "tag"` + внешний `sum(last())` по bucket'у → корректная сумма без triple-count (несколько scrape'ов на bucket нельзя просто `sum(field)`).
-**`fill(previous)` ЗАПРЕЩЁН внутри внутреннего GROUP BY подзапроса** → InfluxDB парсер падает с "EOF" (HTTP 400). Только во внешнем, либо не нужен — точек и так хватает (подтверждено: 360 точек).
**НЕ работает:** transformation `calculateField` (mode `reduceRow`) — InfluxQL-датасорс возвращает **ОТДЕЛЬНЫЙ dataframe на каждую серию** (по пирам), а reduceRow суммирует только внутри одного frame → сумма пустая. Для total нужна серверная агрегация, не клиентская.
**Override для линии суммы** (fieldConfig.overrides): matcher `byRegexp` с `options: "Σ total"`**без якорей**. Grafana byRegexp — substring-матчинг; имя поля для InfluxQL = `<measurement>.<alias>` (напр. `bird_bgp.Σ total`), так что `(?i)^(sum|total)...$` с якорями НЕ сматчится. Свойства: `color.mode=fixed, fixedColor=yellow`, `custom.lineWidth=3`, `custom.fillOpacity=0`, `custom.showPoints=never`.
**Проверка без браузера:** Grafana datasource proxy `POST /api/ds/query` с `{queries:[{refId, datasource:{type,influxdb,uid:bfd9ix15wbqpse}, format:"table", rawQuery:true, query}], from, to}`. Ответ: `results.<refId>.frames[]`, каждый frame = `{schema:{name, fields:[{name, config:{displayNameFromDS}}]}, data:{values:[[...]]}}`.
**Грабли Playwright MCP (главная причина «не вижу сумму»):** дефолтный viewport крошечный (~780×493). Grafana **НЕ рендерит панели вне экрана** → `browser_find`/`browser_evaluate`/скриншот по нижним панелям ничего не находят, `document.body.innerText` короткий (только верхняя панель). Лечение: `browser_resize` до ≥1280×900 → `scrollTo(0, <panel y>)` → wait 2-3s → проверка. Так легенда Routes Imported нашлась: `bird_bgp.Σ total — 3.17 Mil` (= gbl_v4 1.08M + petrosvyaz4 1.09M + …), на Routes Exported — `10`.
Связано: [[reference_route_update_sh]] (маршруты на igw), InfluxDB `gateways` (метрики шлюзов).
# Перевод нового LXC (шаблон 720, ALT p11) с etcnet на systemd-networkd
Шаблон CT 720 — **etcnet**-based (`/etc/net/ifaces/`), и в этом грабли: PVE `pct set -net0`**не пробивает** в etcnet (`ipv4address` остаётся шаблонным, `default`/шлюз отсутствует). Решение — уйти на **systemd-networkd** (см. заметку в CLAUDE.md про proxmox_create_ct: «pick a systemd-networkd image»).
## Процедура (отработана на CT 369 mariadb.dmz.etersoft.ru, 2026-07-16)
1. PVE при клонировании **уже пишет**`/etc/systemd/network/breth0.network` с корректным статическим конфигом (Match=Name, Address, Gateway, DHCP=no, IPv6AcceptRA=false) — **ничего править не надо**.
2.`systemd-networkd`**не установлен** в шаблоне → `epm install -y systemd-networkd` (через `pct exec` нужен `TMPDIR=/tmp`, т.к. нет pam-сессии → `/tmp/.private/root` не создан).
4.**🔴 `systemctl disable network` НЕ достаточно!** etcnet (`network.service`, SysV) оживает на следующем boot'е (его тянет статическая зависимость, а не rc-ссылки — `is-enabled` показывает `disabled`, но `Active: active`). Надо **`systemctl mask network`** (symlink → `/dev/null`) — только тогда etcnet не стартует.
6. Ребут (через `pct stop` + `pct start` — `pct reboot` ругается `failed to connect to monitor socket`, но контейнер перезагружается) → проверить: `network` inactive+masked, `systemd-networkd` active, `systemctl --failed` пусто.
Пакет `etcnet` при этом остаётся установлен (инертен — service замаскирован). Удалять пакет рискованно (фундамент ALT-сети).
## Альтернатива: остаться на etcnet (CT 762 vent.office.etersoft.ru, 2026-07-21, vmbr0)
На vmbr0 (office LAN) логичнее не тянуть networkd, а починить etcnet — но тот же root-cause:
-**Root cause общий:** PVE создаёт интерфейс с именем из `pct set --net0 name=...` (у меня `eth0`) и пишет под него `/etc/systemd/network/<name>.network`, а etcnet-шаблон 720 настроен на **другое** имя (`breth0`, с шаблонным IP 10.20.30.246). И etcnet, и networkd-config висят мимо реального интерфейса → **IP не поднимается**.
- Фикс (etcnet): настроить `/etc/net/ifaces/eth0/` (ipv4address, ipv4route `default via <gw>`, options BOOTPROTO=static TYPE=eth ONBOOT=yes), удалить `breth0` и мёртвые `/etc/systemd/network/*.network`.
-**🔴 `network` (etcnet) в шаблоне `active`-но-`is-enabled=disabled`** — стартует в текущем boot'е, но **после ребута не поднимется** → `systemctl enable network` (обязательно проверить `is-enabled` после клонирования!).
-`pct exec` здесь тоже спотыкался о perl-taint — `env -i HOME=/root PATH=... pct exec ...` (или скрипт через `pct exec -- bash -s` по base64-каналу).
## Связанное
-`pct exec` спотыкается о perl-taint `Insecure $ENV{ENV}` — лечится `unset ENV PERL5OPT PERL5LIB` в SSH-сессии на ноде. Для постоянной работы — [[feedback-lxc-via-ssh-not-pct]] (ходить через `ssh root@<ct-ip>`, не pct exec).
- loadavg в контейнере = нагрузка **хоста** (LXC не изолирует loadavg) — не пугаться «load 13» на свежем CT.
-[[reference-create-lxc-container-pve]] (процедура клонирования), [[plans-rt-update]] (mariadb.dmz для RT).
При создании ветки version-5.9 для Bugzilla я запустил на **прод-сервере** (bugs.etersoft.ru):
```bash
git checkout --orphan version-5.9
git rm-rf.
```
Это удалило ВСЕ файлы прод- Bugzilla. Восстановлено через `git checkout master && git reset --hard HEAD`.
## Правило
**Пока нет явной команды на обновление — прод НЕ ТРОГАТЬ.** Все git-операции, создание веток, cherry-pick — только на тестовом стенде (CT 944, bugs.pr.etersoft.ru).
Прод трогаем ТОЛЬКО после:
1. Полного тестирования на тестовом стенде
2. Явной команды пользователя "обновляй прод"
3. Бэкапа перед обновлением
## Правильный workflow
1. Работа с git: только на тестовом контейнере или локально
2. Тестирование: на bugs.pr.etersoft.ru
3. Прод: только чтение (проверка, дампы), никаких изменений без команды
-**Perl 5.38** (p11, rt-test на gefest): **fatal**`Can't modify undef operator in scalar assignment at .../RT/Condition.pm line 202` → не компилируется базовый `RT::Condition` → **падают ВСЕ scrip-условия** (AnyTransaction, OwnerChange, SLA_*, ...), уведомления/SLA не работают. Читайте на write-операциях: `Can't locate`/`Require of RT::Condition::X failed` в `/var/log/rt/rt.log`.
При проблемах с сетью **первым делом**`traceroute`/`mtr` — сразу виден путь и где обрыв. Не гадать по IP, не тыкать ping в лоб.
Пример: rpi (Франция) доступен через IPsec-туннель (.140 → 10.10.10.6). Вместо traceroute с .140 на rpi-хост — полез искать Docker bridge IP вслепую. Traceroute сразу бы показал путь через туннель и ноды.