Add a "Clone" action to the templates table: it fills the create form
with a copy of the selected template's name and records instead of
creating the copy right away, so the operator can adjust the name or
records before saving. No new API — the existing POST /templates is
reused.
Cloning resets editingId so the submit goes to createTemplate even when
edit mode was active, and the copy name is picked to be free among the
listed templates ("X (копия)", then "X (копия 2)"). Template names have
no unique constraint in the DB, so this is a UX hint, not a guarantee.
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-текст. - Расписание проверок: планировщик периодически гоняет 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.