140 lines
8.5 KiB
Markdown
140 lines
8.5 KiB
Markdown
# 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-проба, используется как
|
||
Docker `HEALTHCHECK` через встроенный CLI-режим `app -healthcheck`.
|
||
|
||
## Учётные данные Selectel
|
||
|
||
Cloud DNS v2 требует **project IAM-токен**, а не статический API-ключ. При
|
||
добавлении аккаунта Selectel в UI указываются данные **сервисного
|
||
пользователя**:
|
||
|
||
- имя сервисного пользователя,
|
||
- пароль,
|
||
- номер аккаунта (`account_id`, вида `123456`),
|
||
- имя проекта.
|
||
|
||
Сервисный пользователь создаётся в панели Selectel (раздел
|
||
[Пользователи и роли](https://my.selectel.ru/iam/users)) и ему выдаётся роль
|
||
на нужный проект. Приложение само обменивает эти данные на 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. Скопировать пример конфигурации:
|
||
|
||
```bash
|
||
cp .env.example .env
|
||
```
|
||
|
||
2. Сгенерировать ключ шифрования секретов (провайдеров/каналов) — ровно
|
||
32 байта в base64 — и вписать его в `.env` как `DNS_AR_ENC_KEY`:
|
||
|
||
```bash
|
||
openssl rand -base64 32
|
||
```
|
||
|
||
Также задать `POSTGRES_PASSWORD` (без дефолта — сервис не поднимется без
|
||
явного пароля).
|
||
|
||
3. Поднять стек (postgres + app), сборка образа приложения — на лету:
|
||
|
||
```bash
|
||
docker compose up -d --build
|
||
# или: make docker-up
|
||
```
|
||
|
||
`app` стартует только после того, как `postgres` станет healthy;
|
||
миграции схемы БД приложение накатывает само при старте.
|
||
|
||
4. Открыть 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)
|
||
|
||
```bash
|
||
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`.
|