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
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-проба, используется как DockerHEALTHCHECKчерез встроенный 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), пароль не логируется. Учётные данные
проверяются пробным логином прямо при добавлении аккаунта.
Рабочий процесс
- Зарегистрироваться (self-registration, автоматически создаётся личный проект).
- Добавить аккаунт Selectel (данные сервисного пользователя, см. выше).
- Импортировать зоны аккаунта — на каждую зону заводится домен.
- Привязать шаблон: создать снимком из зоны или собрать вручную с
плейсхолдерами
{{domain_name}}; без шаблона доступен только просмотр записей. - Открыть диф домена, отметить нужные изменения/удаления, применить.
Стек
Go 1.26 (statically-linked бинарь, SPA встроена через embed), React +
Vite (SPA), PostgreSQL 17, Prometheus client, distroless/static-debian12
рантайм-образ.
Запуск в Docker
Требуется Docker Engine + Docker Compose v2.
-
Скопировать пример конфигурации:
cp .env.example .env -
Сгенерировать ключ шифрования секретов (провайдеров/каналов) — ровно 32 байта в base64 — и вписать его в
.envкакDNS_AR_ENC_KEY:openssl rand -base64 32Также задать
POSTGRES_PASSWORD(без дефолта — сервис не поднимется без явного пароля). -
Поднять стек (postgres + app), сборка образа приложения — на лету:
docker compose up -d --build # или: make docker-upappстартует только после того, какpostgresстанет healthy; миграции схемы БД приложение накатывает само при старте. -
Открыть UI: http://localhost:8080
- Метрики (Prometheus): http://localhost:8080/metrics
- Health-check: http://localhost:8080/healthz
Остановить стек: 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.