vasyanskandClaude Opus 5 caebc06076 feat(web): filter domains by template and status
The domains list had no way to narrow itself down, so an operator with
dozens of zones had to scan the whole table to find the drifted ones.

Add two client-side filters above the table (the full list already
arrives in one request, so server-side filtering would only add contract
surface). The status filter compares against the same derived value the
row's badge renders — a domain with no template shows "без шаблона" and
must not match "drift" just because it kept a stale last_check_status
from before its template was detached, so that derivation lives in one
function used by both.

Sorting is now decided in the store: ListDomains orders by zone_name
instead of created_at, so every consumer of the list gets the same order
and the filters never reorder anything.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Yr8frsaxBgab1Aa7yfPuU
2026-08-19 19:24:58 +07:00
2026-07-05 17:01:33 +07:00

DNS Autoresolver

Утилита автонастройки и проверки DNS-зон: multi-tenant сервис, который сверяет фактическое состояние зоны у провайдера (Selectel DNS API v2) с шаблоном записей, показывает диф и применяет изменения только вручную — никакого автоматического apply без подтверждения оператора.

Возможности

  • Multi-tenant: проекты, аккаунты провайдера, домены — с авторизацией (регистрация/логин, сессии); всё изолировано по проекту.
  • Провайдер Selectel (Cloud DNS v2): авторизация через project IAM-токен сервисного пользователя (не статический API-ключ — см. ниже), чтение зон/RRSet, импорт зон, диф против шаблона, ручной apply.
  • Шаблоны записей с плейсхолдером {{domain_name}}: один шаблон переиспользуется на многих доменах — при проверке подставляется имя зоны. Шаблон можно завести вручную или снять снимком с существующей зоны («создать шаблон из зоны», с авто-параметризацией имени домена).
  • Просмотр зоны без шаблона: текущие записи зоны видны даже до привязки шаблона; статус домена без шаблона — «без шаблона», а не unknown.
  • Диф + выборочный ручной apply: чекбоксы на каждой записи (updates и prunes), удаления по умолчанию сняты (opt-in). Удаления применяются перед обновлениями — иначе провайдер отвергает конфликт (например CNAME на имени, где ещё жива A-запись). При ошибке показывается реальный ответ провайдера, а не generic-текст.
  • Custom-записи: запись можно пометить как намеренно оставленную вне шаблона — она уходит из Prunes, не считается drift и не может быть применена, пока пометка не снята; как только шаблон начинает описывать этот ключ сам, пометка автоматически перестаёт действовать.
  • Импорт зон — синхронизация: повторный импорт аккаунта провайдера не только заводит домены новых зон, но и удаляет домены зон, исчезнувших у провайдера; пустой ответ провайдера ничего не удаляет.
  • Расписание проверок: планировщик периодически гоняет read-only check+notify (без Apply), пишет историю проверок и статус drift.
  • Уведомления: каналы Telegram и Webhook, per-channel статус доставки.
  • Метрики: Prometheus /metrics (публичный, без auth, только агрегаты).
  • Health-check: /healthz — liveness-проба, используется как Docker HEALTHCHECK через встроенный CLI-режим app -healthcheck.

Учётные данные Selectel

Cloud DNS v2 требует project IAM-токен, а не статический API-ключ. При добавлении аккаунта Selectel в UI указываются данные сервисного пользователя:

  • имя сервисного пользователя,
  • пароль,
  • номер аккаунта (account_id, вида 123456),
  • имя проекта.

Сервисный пользователь создаётся в панели Selectel (раздел Пользователи и роли) и ему выдаётся роль на нужный проект. Приложение само обменивает эти данные на 24-часовой IAM-токен (Identity API cloud.api.selcloud.ru) и кэширует его; данные хранятся зашифрованными (AES-256-GCM), пароль не логируется. Учётные данные проверяются пробным логином прямо при добавлении аккаунта.

Рабочий процесс

  1. Зарегистрироваться (self-registration, автоматически создаётся личный проект).
  2. Добавить аккаунт Selectel (данные сервисного пользователя, см. выше).
  3. Импортировать зоны аккаунта — на каждую зону заводится домен.
  4. Привязать шаблон: создать снимком из зоны или собрать вручную с плейсхолдерами {{domain_name}}; без шаблона доступен только просмотр записей.
  5. Открыть диф домена, отметить нужные изменения/удаления, применить.

Стек

Go 1.26 (statically-linked бинарь, SPA встроена через embed), React + Vite (SPA), PostgreSQL 17, Prometheus client, distroless/static-debian12 рантайм-образ.

Запуск в Docker

Требуется Docker Engine + Docker Compose v2.

  1. Скопировать пример конфигурации:

    cp .env.example .env
    
  2. Сгенерировать ключ шифрования секретов (провайдеров/каналов) — ровно 32 байта в base64 — и вписать его в .env как DNS_AR_ENC_KEY:

    openssl rand -base64 32
    

    Также задать POSTGRES_PASSWORD (без дефолта — сервис не поднимется без явного пароля).

  3. Поднять стек (postgres + app), сборка образа приложения — на лету:

    docker compose up -d --build
    # или: make docker-up
    

    app стартует только после того, как postgres станет healthy; миграции схемы БД приложение накатывает само при старте.

  4. Открыть UI: http://localhost:8080

Остановить стек: docker compose down (или make docker-down). Логи приложения: docker compose logs -f app (или make docker-logs).

Переменные окружения (.env)

Переменная Назначение По умолчанию
POSTGRES_USER пользователь PostgreSQL dnsar
POSTGRES_PASSWORD пароль PostgreSQL — обязателен, без дефолта
POSTGRES_DB имя БД dnsar
APP_PORT порт публикации приложения на хосте 8080
DNS_AR_ENC_KEY ключ шифрования секретов, base64 → ровно 32 байта — обязателен

Секреты передаются только через переменные окружения, никогда — в образ, логи или git.

Локальная разработка (без Docker)

make build       # go build ./...
make test        # go test ./...  (тесты internal/store требуют Docker — testcontainers)
make web         # сборка SPA (npm ci && npm run build) в internal/web/dist
make build-all   # web + build

go test ./internal/service/ -run TestName -v   # один тест / пакет
cd web && npm run test -- --run                # фронт-тесты (Vitest)
cd web && npx tsc --noEmit                     # проверка типов SPA

Для запуска бинаря напрямую нужны те же переменные окружения: DNS_AR_DB_DSN, DNS_AR_ENC_KEY (обязательные), DNS_AR_LISTEN (по умолчанию :8080).

internal/web/dist/ — цель go:embed; в git коммитится только плейсхолдер index.html. npm run build перезаписывает его — перед коммитом выполнить git checkout internal/web/dist/index.html, а реальный бандл собирать через make web.

S
Description
No description provided
Readme
690 KiB
Languages
Go 60.8%
TypeScript 37.8%
CSS 1.1%
Dockerfile 0.1%