docs: plan Phase 12 - service status page + domain DNS checks
Adds a new agreed-upon phase covering /status (supervisord processes, Postfix queue, TLS cert expiry, milter sockets, PTR/FCrDNS check) and per-domain DNS correctness status (DKIM/SPF-heuristic/DMARC) on the domain page. Not part of v1.0 spec scope; scoped and agreed with the user before implementation. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -46,6 +46,51 @@
|
||||
|
||||
---
|
||||
|
||||
## Фаза 12 (v1.x) — Страница статуса сервиса + DNS-проверка доменов
|
||||
|
||||
**Статус:** запланирована, не начата.
|
||||
|
||||
**Цель:** дать оператору один взгляд на «сервис жив и почта не будет зарубаться из-за DNS» — два независимых экрана: здоровье процесса (`/status`) и корректность DNS для каждого отправляющего домена (встроено в существующую страницу домена). Выходит за рамки ТЗ v1.0 (не было в разделе 7.2), согласовано с пользователем как отдельная фаза.
|
||||
|
||||
### A. `/status` — здоровье сервера (только сервер, без доменов)
|
||||
|
||||
1. **Процессы supervisord** — `supervisorctl status` (fixed-argv, без shell, как остальные exec-вызовы проекта), статус opendkim/postfix/panel/cert-reload/logrotate.
|
||||
2. **Очередь Postfix** — переиспользовать `internal/postfix.Queue()`.
|
||||
3. **TLS-сертификат** — распарсить `x509.NotAfter` из `TLS_CERT_FILE`, предупреждение при приближении срока истечения.
|
||||
4. **Milter-сокеты** (`os.Stat` на `opendkim.sock`/`journal.sock`): отсутствие `opendkim.sock` — **ошибка** (при `default_action=tempfail` почта не уходит, см. [progress.md:132](progress.md)); отсутствие `journal.sock` — **предупреждение** (fail-open, почта уходит, но Send Log не пишется).
|
||||
5. **PTR / прямое-обратное соответствие (FCrDNS) — ключевая проверка для доставляемости:**
|
||||
- резолвим A/AAAA `SELFPOST_HOSTNAME` → IP сервера;
|
||||
- резолвим PTR этого IP → имя;
|
||||
- сравниваем PTR-имя с `SELFPOST_HOSTNAME`.
|
||||
- **OK** — совпадают; **ошибка** — PTR отсутствует или не совпадает (многие принимающие сервера отклоняют/спамят почту без корректного PTR).
|
||||
- Резолвленный IP сервера переиспользуется в пункте B.2 (SPF) — отдельный env для IP не нужен.
|
||||
|
||||
Дешёвые проверки (1–4) — HTMX-polling как на остальных экранах мониторинга (~5с). PTR/hostname-lookup чуть дороже сети — кэш с TTL (например 1 мин) или отдельная кнопка «Recheck», не завязывать на 5-секундный polling.
|
||||
|
||||
### B. DNS-статус домена (на странице `domain_detail.html`, рядом с существующей DKIM TXT-записью)
|
||||
|
||||
1. **DKIM** — резолвим `<selector>._domainkey.<domain>` TXT, сравниваем с реальным ключом OpenDKIM (переиспользовать логику генерации записи из [dkim.go](../internal/domain/dkim.go)).
|
||||
2. **SPF (поверхностная проверка, по решению пользователя)** — резолвим TXT домена, ищем запись с `v=spf1`. Если найдена — проверяем **только присутствие** механизма, покрывающего IP сервера (`ip4:`/`ip6:` буквально, либо `a`/`mx` без аргумента, резолвящийся в IP сервера из A.5). **Без** рекурсии по `include`, без полной RFC 7208-оценки pass/fail/softfail — осознанное упрощение (без новых зависимостей).
|
||||
3. **DMARC** — TXT `_dmarc.<domain>`, наличие + политика (`none`/`quarantine`/`reject`).
|
||||
4. Статусы по каждому пункту: не найдено / найдено, но не покрывает наш сервис / корректно. Кэш на домен (TTL ~5–10 мин) + кнопка «Recheck» (без autopolling — DNS дороже, чем локальные проверки блока A).
|
||||
|
||||
### Архитектура
|
||||
|
||||
- Новый пакет `internal/dnscheck`: `ServerIP(hostname)` (A/AAAA lookup), `PTRCheck(hostname, ip)`, `DKIMCheck(domain, selector, expectedKey)`, `SPFCheck(domain, serverIP)`, `DMARCCheck(domain)` — все с таймаутом на DNS-запрос (~5с), через `net.DefaultResolver`/контекст.
|
||||
- Кэш результатов в памяти (мьютекс, TTL), без изменений схемы БД/миграций.
|
||||
- Веб: `GET /status` + `GET /status/fragment` (быстрый блок A, для polling); секция DNS-статуса на `domain_detail.html` + `POST /domains/{id}/dns-recheck` (форс, обход кэша).
|
||||
- Безопасность: ничего нового по ТЗ 7.6 не добавляет — DNS/exec-вызовы только за авторизованным админом, без пользовательского ввода в exec (доменные имена уже провалидированы при добавлении домена), таймауты на все сетевые вызовы (защита от зависания страницы).
|
||||
|
||||
**Готово, когда:** `/status` показывает живой статус всех пяти пунктов блока A и обновляется polling'ом; на странице домена отображается статус DKIM/SPF/DMARC с кнопкой Recheck; PTR-проверка корректно ловит и совпадение, и несовпадение (проверено на реальном домене `selfpost.mixfed.ru`, у которого PTR уже настроен, — см. [selfpost-prod-deployment.md]); `gofmt`/`vet`/`test`/`docker build` зелёные.
|
||||
|
||||
**Риски:** DNS-резолверы могут быть медленными/недоступными — все проверки таймаутят и не блокируют остальной UI (страница рендерится с "неизвестно/timeout", а не висит). Ложные срабатывания SPF-эвристики (например, домен покрывает IP сервера через `include:` стороннего сервиса, который в свою очередь резолвится в IP сервера) — задокументировать как известное ограничение поверхностной проверки.
|
||||
|
||||
**Модель:** Sonnet (UI + рутинные DNS-lookup, не риск-критичный тракт доставки/безопасности).
|
||||
|
||||
**Зависимости:** не блокирует и не блокируется существующими фазами; использует уже реализованные `internal/postfix.Queue()`, DKIM-логику домена, паттерны HTMX-фрагментов из Фазы 7.
|
||||
|
||||
---
|
||||
|
||||
## Опциональные фазы — целевой релиз 2.x.x (вне базового объёма v1.0)
|
||||
|
||||
Эти фазы **не входят** в линейный базис 0→11 и не являются частью поставки v1.0 (v1.x — только исходящий релей). Они отнесены к **релизной линии 2.x.x** и добавлены в дорожную карту как согласуемые расширения. **Реализация — только после явного согласования (ТЗ 12.6):** ТЗ v1.0 раздел 3 явно исключает приём входящей почты из объёма, поэтому включение этой функциональности — сознательное расширение границ проекта (major-релиз 2.0), а не доработка по своей инициативе. Внесение в план фиксирует намерение и дизайн; кодирование начинается отдельным решением.
|
||||
|
||||
Reference in New Issue
Block a user