DNS-зона — место, где ошибка не падает с исключением. Вторая запись SPF ломает всю почту домена, второй MX на ту же цель уводит половину писем, повторный запуск пресета дублирует DKIM, а пропавшая запись обнаруживается через неделю по bounced-письму. Fornex Control 1.1.0 — self-hosted панель для DNS-зон Fornex, построенная вокруг одного правила: сначала предпросмотр плана, потом запись, и снимок зоны перед каждым изменением. Ниже — что внутри, что показали прогон тестов и проверка живого экземпляра, и на чём спотыкаются при первом развёртывании.

TL;DR: что это и как проверить
- Что это. Панель над публичным API Fornex: веб-интерфейс, JSON API и CLI — все три через один планировщик правил.
- Главное правило. Изменение всегда сначала печатается планом; запись — вторым шагом, с
--apply. - Снимок перед записью. BIND-файл и JSON с идентификаторами записей, плюс
sha256; запись журнала называет снимок. - Почта Google в четыре шага. Проверка → MX/SPF → DKIM → DMARC, каждый шаг отдельно и заблокирован до предыдущего.
- Стек. PHP 8.5, SQLite, ноль зависимостей: ни
composer.json, ниpackage.jsonв дереве.
php backend/bin/fornex domains # зоны аккаунта
php backend/bin/fornex zone:show example.com # записи
php backend/bin/fornex verify example.com # NS, MX, SPF, DKIM, DMARC, PTR
php backend/bin/fornex audit --limit=30 # журнал
Проблема, которую это решает
У Fornex есть публичный API для DNS, и технически зону можно править чем угодно — хоть curl, хоть панелью провайдера. Проблема в том, что зона не прощает небрежности, и цена ошибки отложена.
Три типичных сценария. Первый: добавили SPF-запись, а старая осталась — теперь у домена две SPF, и по стандарту это permerror, то есть письма не проходят вовсе. Второй: для Google нужны три MX с одинаковой целью, но Fornex отклоняет вторую запись с тем же значением — и человек правит руками, получая то одну запись, то две. Третий: DKIM-ключ вставлен из письма кусками, с переносами строк и пробелами внутри p= — запись попадает в зону невалидной, и подпись не проверяется.
Панель делает эти сценарии проверяемыми заранее: план показывает, что именно изменится, слияние SPF сохраняет существующие механизмы, DMARC не понижается, а ключ приводится к нормальному виду автоматически.
Что такое Fornex Control
flowchart TB WEB["браузер: интерфейс без сборки"] --> RT["index.php и роутер"] RT --> CTRL["контроллер"] CTRL --> SVC["сервисы"] CLI["CLI: 13 команд"] --> SVC API["JSON API: 27 маршрутов"] --> RT SVC --> PLAN["ZonePlanner: правила зоны"] SVC --> API2["API Fornex"] SVC --> DB["SQLite: журнал, токены, снимки"]
| Свойство | Значение |
|---|---|
| Тип | self-hosted панель над API Fornex |
| Стек | PHP 8.5, SQLite, интерфейс без сборки |
| Зависимости | ноль: ни composer, ни npm |
| Входы | браузер, JSON API, CLI |
| Маршрутов API | 27 |
| Команд CLI | 13, изменение только с --apply |
| Тесты | 52 проверки в tests/plan_test.php |
| Лицензия | MIT |
Важная деталь: зона не хранится локально. Она читается из API Fornex на каждый запрос, поэтому панель показывает то, что реально в зоне, а не устаревшую копию. Локально лежит только журнал, токены, попытки входа и снимки.
Возможности
Работа с зоной
| Что | Как работает |
|---|---|
| Просмотр записей | id, тип, хост, значение, приоритет, TTL |
| Правка | только через план: сначала предпросмотр, потом применение |
| Удаление | по id, без масок и «все TXT с таким префиксом» |
SOA и NS |
не редактируются: на них держится зона |
| Снимок | BIND-файл плюс JSON с id записей и sha256 перед каждой записью |
| Журнал | кто, что, цель, был ли это dry-run, результат, идентификатор запроса |
Почтовый мастер
flowchart TD S1["шаг 1: TXT проверки"] --> S2["шаг 2: MX 1/5/10 и SPF"] S2 --> S3["шаг 3: DKIM google._domainkey"] S3 --> S4["шаг 4: DMARC"] S2 --> SKIP["слияние SPF: include, ip4, a, mx сохраняются"] S3 --> NORM["нормализация ключа: префикс v=DKIM1; k=rsa;"] S4 --> KEEP["p=quarantine и p=reject не понижаются"]
| Шаг | Что пишет | Подтверждение |
|---|---|---|
| 1. Проверка | TXT @ с google-site-verification=… |
ввод имени домена |
| 2. Почта | MX 1, MX 5, MX 10 и SPF | не требуется |
| 3. DKIM | TXT google._domainkey |
ввод имени домена |
| 4. DMARC | TXT _dmarc |
не требуется |
Подтверждение именем стоит там, где данные заменяются, — на ключе подписи и строке проверки. Там, где записи только добавляются, лишнего шага нет, и это осмысленно: подтверждение ради подтверждения быстро превращается в привычку жать «ок».
Правила, встроенные в планировщик
| Правило | Что делает |
|---|---|
| Слияние SPF | сохраняет существующие include:, ip4:, a, mx и добавляет недостающие |
| Без изменений — не обновление | если результат совпал с текущим, это пропуск, а не правка |
| DMARC не ослабляется | p=quarantine и p=reject никогда не понижаются до none |
| Нормализация DKIM | к голому p=… добавляется v=DKIM1; k=rsa;, переносы и пробелы внутри p= убираются |
| Третий MX | приоритет 10 с точкой в конце и иным регистром, потому что Fornex отвергает дубль значения |
Безопасность и доступ
| Что | Как работает |
|---|---|
| Токены API | только чтение по умолчанию, запись — если выдан с флагом |
| CSRF | X-CSRF-Token для записей из браузера |
| Предпросмотр | доступен и токену только для чтения |
| Секреты | в отчётах и логах только имя, маска и отпечаток, никогда значение |
| Отказы Fornex | 403 про создание зоны показывается как готовый текст тикета, а не «ошибка» |
Как это работает
flowchart LR
REQ["запрос изменения"] --> PRV["предпросмотр плана"]
PRV --> CONF{"шаг требует подтверждения именем?"}
CONF -- да --> NAME["ввод домена"]
CONF -- нет --> SNAP
NAME --> SNAP["снимок зоны: BIND, JSON, sha256"]
SNAP --> WR["запись в API Fornex"]
WR --> AUD["запись в журнал: кто, что, результат"]Все три входа — браузер, API и CLI — идут через одни и те же сервисы и один планировщик ZonePlanner. Именно поэтому одинаково ведут себя и кнопка в интерфейсе, и команда в консоли: правила живут в одном месте, а не дублируются в трёх.
| Слой | Ответственность |
|---|---|
index.php и роутер |
маршрутизация, разбор запроса |
| Контроллеры | валидация входа, ответы и коды |
| Сервисы | сценарии: зона, пресеты, проверка, журнал |
ZonePlanner |
правила: слияние SPF, нормализация DKIM, защита DMARC |
| Клиент Fornex | HTTP к API, повторные попытки, разбор отказов |
| SQLite | журнал, токены, попытки входа, снимки |
Замеры и цифры
Проверено 10.11.2026: прогон дерева релиза и осмотр живого развёртывания.
| Что проверено | Значение | Как |
|---|---|---|
Архив исходников v1.1.0 |
171 357 байт, SHA-256 de5c103a…679b47 |
скачивание тега и подсчёт хеша |
| Тесты | 52 проверки, все пройдены | php tests/plan_test.php |
| Версия PHP при прогоне | 8.5.0 (cli, NTS) | php -v |
| Команд CLI | 13 | вывод php backend/bin/fornex без аргументов |
| Маршрутов API | 27 | подсчёт в backend/config/routes.php |
| PHP-файлов в дереве | 45 (по README) | — |
| Строк кода | ~6 965 (по README) | — |
| Живой экземпляр | развёрнута копия 1.1.0, снаружи 403 | defaults.php и curl |

Отдельно про тесты: в выводе видно, что проверяются именно правила, а не вёрстка — четыре шага мастера, флаги подтверждения, состояние, видимое интерфейсу, и поведение плоского пресета по умолчанию. Для панели, которая пишет в зону, это правильный набор: он проверяет то, что может испортить почту.
Чего замеры не показывают: реальную запись в зону. Для неё нужен ключ API Fornex и живая зона, а трогать боевые записи ради статьи смысла нет. Также не проверялись интерфейс и маршруты API на живом экземпляре: снаружи панель закрыта (403), а локальный запрос по хосту отдаёт 404 — доступ настроен под конкретный адрес, и разбираться в чужой конфигурации nginx я не стал.
Сильные стороны продукта
- План до записи. Один и тот же предпросмотр для интерфейса, API и CLI.
- Снимок перед изменением. BIND и JSON с
idзаписей плюсsha256, а не «мы помним, что было». - Слияние SPF, а не перезапись. Существующие механизмы выживают, добавляются только недостающие.
- DMARC нельзя ослабить.
p=quarantineиp=rejectне превращаются вnoneповторным запуском. - Нормализация DKIM. Ключ, вставленный кусками, становится валидной записью.
- Ноль зависимостей. PHP 8.5 и SQLite: развернуть можно на любом хостинге с PHP.
- Токены с двумя правами. Чтение по умолчанию, запись — осознанно и с CSRF.
- Честные ограничения Fornex. Лимиты провайдера не спрятаны, а описаны вместе с обходом.
Установка
git clone https://github.com/paulmann/fornex-control.git /srv/fornex-control
# конфигурация — уровнем выше приложения, не в веб-корне
install -m 640 /srv/fornex-control/.env.example /srv/fornex.env
$EDITOR /srv/fornex.env # FORNEX_API_KEY, ADMIN_PASSWORD_HASH
php -r "echo password_hash('your-password', PASSWORD_DEFAULT), PHP_EOL;"
install -d -m 750 /srv/fornex-control/var /srv/fornex-control/var/log /srv/fornex-control/var/snapshots
| Требование | Зачем |
|---|---|
PHP 8.5 с curl, json, mbstring, openssl, pdo_sqlite |
весь стек без композера |
| Ключ публичного API Fornex | чтение и запись зон |
Каталог var/ с правами на запись |
SQLite, журнал, снимки |
| Веб-сервер с PHP | nginx с FPM или даже php -S для осмотра |
Две вещи, которые стоит сделать до первого запуска: вынести .env за пределы веб-корня и проверить, что var/snapshots пишется. Снимок зоны — единственный способ откатиться, если план окажется не тем, что вы ожидали.
Настройка
| Что | Где задаётся |
|---|---|
| Ключ API Fornex | FORNEX_API_KEY в .env |
| Пароль администратора | ADMIN_PASSWORD_HASH, хеш из password_hash |
| Каталог снимков | SNAPSHOT_DIR |
| Пресеты | backend/config/presets.json |
| Порог журнала | --limit в команде audit |
| Токены | выдаются через интерфейс, право записи — флагом |
Пресеты вынесены в JSON не случайно: новый сценарий (например, почта другого провайдера) — это описание шагов и параметров, а не правка кода. Шаг объявляет, нужно ли для него подтверждение именем, поэтому поведение интерфейса следует за данными.
Проверка после установки
| Шаг | Команда | Признак успеха |
|---|---|---|
| Виден ли аккаунт | fornex domains |
список зон |
| Читается ли зона | fornex zone:show <домен> |
записи с id |
| Работает ли снимок | fornex zone:snapshot <домен> |
файл в var/snapshots |
| Видны ли пресеты | fornex preset:list |
четыре пресета |
| Проверка DNS | fornex verify <домен> |
отчёт по NS, MX, SPF, DKIM, DMARC, PTR |
| Журнал | fornex audit --limit=5 |
записи с идентификаторами запросов |
Порядок именно такой: сначала чтение, потом снимок, и только затем команды с --apply. Если domains не отвечает — проблема в ключе, а не в панели.
Совместимость и ограничения Fornex
| Ограничение провайдера | Что делает панель |
|---|---|
Создание зоны через API даёт 403 на части аккаунтов |
показывает ссылку в кабинет и готовый текст тикета; записи и проверки работают, когда зона уже есть |
Второй MX с тем же значением отклоняется (400 … already exists) |
использует точку в конце и иной регистр для лишних MX |
Запрос без браузерного User-Agent зависает на TLS |
клиент всегда отправляет заголовок |
| Зона пересобирается минутами, резолверы кешируют ответ | проверка идёт через ns1.fornex.com, а не через кеш |
Смена NS возможна только для доменов, заказанных у Fornex |
печатает инструкцию для регистратора |
Показательные случаи
| Что видно в работе | Причина | Что делать |
|---|---|---|
domains возвращает пустой список |
ключ не от того аккаунта или лишён права | проверить ключ в кабинете Fornex |
403 при создании зоны |
право выключено на аккаунте | заказать зону в кабинете, панель заработает с ней |
400 … already exists на MX |
Fornex сравнивает значение, а не приоритет | не плодить записи руками, дать пресету сделать своё |
verify показывает старые данные |
ответ взят из кеша резолвера | проверять через ns1.fornex.com |
| кнопка шага неактивна | предыдущий шаг не завершён | смотреть состояние шагов, а не гадать |
шаг вернул 200 и текст о нехватке поля |
пустое значение — не отказ | заполнить то, что названо в ответе |
Почему сниппет зоны важнее кнопки «отменить»
В DNS не бывает отката в один клик: TTL, кеши резолверов и время пересборки зоны делают любое изменение отложенным. Поэтому ставка сделана не на отмену, а на предсказуемость.
| Подход | Когда помогает | Где не спасает |
|---|---|---|
| Кнопка «отменить» | сразу после ошибки, пока ничего не ушло в кеш | через минуту уже бесполезна |
| Снимок зоны перед записью | всегда: есть точное состояние до изменения | требует ручного возврата записей по id |
| Предпросмотр плана | до записи: видно, что именно изменится | не заменяет проверку после |
Проверка по ns1.fornex.com |
после записи: видно фактическое состояние | нужно повторить через несколько минут |
Из этой таблицы и складывается рабочий порядок: план → снимок → запись → проверка у авторитетного сервера → запись в журнал. Шаг «снимок» не роскошь: это единственный источник истины о том, как было.
Интерфейс, API и CLI — одно поведение
Три входа в одну систему — частая причина расхождений: кнопка в панели делает одно, а та же операция из консоли — другое. Здесь этой проблемы нет по устройству: все три идут через одни сервисы.
| Ситуация | Интерфейс | CLI |
|---|---|---|
| Посмотреть план пресета | предпросмотр в мастере | preset:apply без --apply |
| Применить шаг | кнопка после проверки плана | та же команда с --apply |
| Посмотреть журнал | страница аудита | audit --limit=N |
| Проверить DNS | отдельный экран | verify <домен> |
| Снимок зоны | перед каждой записью автоматически | zone:snapshot вручную |
Практический смысл: можно вести диагностику из консоли, не боясь, что план разойдётся с тем, что сделала бы кнопка. И наоборот — действие из браузера попадает в тот же журнал, что и команда из скрипта.
Отдельно про SPA без сборки: один app.js без бандлера — это осознанный выбор в пользу простоты развёртывания. Плата — нет модульности фронтенда и нет типизации; выигрыш — нет node_modules на сервере и нет шага сборки при обновлении.
Подводные камни
- Конфигурация в веб-корне.
.envс ключом API и хешем пароля должен лежать уровнем выше приложения, иначе ключ доступен через браузер. - Снимок не откат. Файл в
var/snapshots— это материал для восстановления, а не автоматический откат; применять его придётся руками. - Второй MX и «одинаковое значение». Fornex сравнивает строку значения без учёта приоритета, поэтому трюк с точкой и регистром — не хитрость, а необходимость.
- Кеш резолверов врёт. После записи проверять через
ns1.fornex.com, иначе легко решить, что запись не применилась. - Пустое поле — не ошибка. Шаг с пустым значением вернёт
200и текст «чего не хватает»; блокирует кнопку только состояниеblocked. SOAиNSне редактируются. Это защита: правка этих записей ломает зону целиком.- Токен только для чтения не запишет. Для изменений нужен токен с флагом записи, а из браузера ещё и
X-CSRF-Token. - Только PHP 8.5. Дерево исполнялось на 8.5, CI тоже 8.5; на 8.3 могут всплыть синтаксические мелочи.
- 403 при создании зоны — не поломка панели. Это ограничение аккаунта Fornex; обходится заказом зоны в кабинете.
- Интерфейс без сборки — это плюс и минус. Никакого
npm build, но и модульности фронтенда не ждите: одинapp.js.
Чек-лист
- [ ] Проверить PHP 8.5 и расширения
curl,json,mbstring,openssl,pdo_sqlite. - [ ] Скопировать
.env.exampleуровнем выше приложения и заполнить ключ и хеш пароля. - [ ] Создать
var,var/log,var/snapshotsс правами на запись. - [ ] Проверить чтение:
fornex domainsиzone:show. - [ ] Сделать снимок зоны до любых изменений.
- [ ] Прогнать
preset:applyбез--applyи прочитать план. - [ ] Применить шаг 1 (проверка), затем дождаться подтверждения у провайдера.
- [ ] Повторить для шагов 2–4, каждый раз читая план.
- [ ] Проверить результат через
fornex verifyпоns1.fornex.com. - [ ] Убедиться, что в журнале есть записи с идентификаторами запросов.
FAQ
Зачем панель, если есть API?
API не мешает ошибиться: он принимает вторую SPF-запись и вторую MX с тем же значением. Панель добавляет правила — слияние SPF, нормализацию DKIM, защиту DMARC — и предпросмотр до записи.
Что именно сохраняется в снимке?
BIND-файл зоны и JSON с идентификаторами записей, плюс sha256 содержимого. Запись в журнале называет снимок, так что связь «изменение — состояние до него» восстанавливается.
Почему для двух шагов нужно вводить имя домена?
Эти шаги заменяют данные: ключ подписи и строку проверки. Подтверждение именем не даёт заменить ключ случайным кликом. Шаги, которые только добавляют записи, такого требования не имеют.
Что будет, если запустить пресет повторно?
Ничего разрушительного: совпавший результат считается пропуском, SPF сливается с существующим, DMARC не понижается, дубликат DKIM нормализуется и не создаёт вторую запись.
Можно ли хранить зону локально и работать без сети?
Нет, и это осознанно: зона читается из API на каждый запрос, чтобы панель не показывала устаревшую копию. Локально живут только журнал, токены и снимки.
Нужен ли отдельный сервер?
Достаточно любого хостинга с PHP 8.5 и SQLite. Для осмотра хватает php -S, но для постоянной работы — nginx с FPM и .env вне веб-корня.
Что было в релизе 1.1.0 и почему это первый публичный выпуск
Дерево, опубликованное как 1.1.0, — это код, который работал приватной панелью (внутренние версии 1.0.0 → 1.1.0), с удалёнными значениями конкретной установки: имена хостов, адреса, абсолютные пути и заглушка вместо ключа API.
| Что добавилось к 1.0.0 | Почему это важно снаружи |
|---|---|
| четырёхшаговый мастер почты | сценарий Google не сводится к одной кнопке |
| подтверждение именем только там, где данные заменяются | лишние шаги не превращаются в рефлекс |
| слияние SPF | чужие механизмы в зоне не теряются |
| защита DMARC от понижения | повторный запуск не ломает политику |
| нормализация DKIM | ключ из письма кусками становится валидным |
| снимок и журнал | появляется след и точка возврата |
| токены с двумя правами | автоматизация без права писать |
Для читателя важно одно: это не «панель для себя», переписанная под публикацию, а работающий код с удалёнными приватными значениями. Отсюда и честные ограничения в документации: где Fornex мешает, там это написано, а не спрятано.
Второе следствие — установочные значения пришлось вынести в конфигурацию. Поэтому ключ API и хеш пароля живут в .env уровнем выше приложения, а не в файлах дерева: в приватной панели такого разделения можно было не делать.
Итог
Fornex Control 1.1.0 — редкий случай, когда панель над чужим API добавляет не удобство, а дисциплину: план до записи, снимок перед изменением, правила, которые нельзя случайно обойти кнопкой. Проверка подтвердила главное, что проверяется без ключа: 52 теста проходят, CLI показывает 13 команд, маршрутов 27, зависимостей ноль, а живой экземпляр развёрнут версией 1.1.0 и закрыт снаружи. Ограничения честные: только PHP 8.5, запись в зону требует ключа Fornex, снимок — материал для ручного откката, а не кнопка «вернуть как было».
Цитата: В DNS опасна не ошибка, а её отложенность — поэтому единственная полезная кнопка там называется «покажи план».
Связанные материалы
- RAGRAF 0.10.0 — ещё один инструмент, где проверка стоит до записи.
- Cert Unifier 3.1.1 — про сертификаты и отказы, которые лучше получить заранее.
- HTML.PHP: шаблонизатор PHP в одном файле — PHP без зависимостей, как и здесь.
- ml-switcher: переключатель языков WordPress — про настройки, которые переживают выключение.
- Релиз v1.1.0 на GitHub — заметки и дерево исходников.
LLM Summary
- Тема:
Fornex Control1.1.0 — self-hosted панель для DNS-зон Fornex: веб-интерфейс, JSON API и CLI над одним планировщиком правил. - Ключевой принцип: предпросмотр плана, затем запись; снимок зоны (BIND, JSON с
idзаписей,sha256) перед каждым изменением; запись журнала называет снимок. - Стек: PHP 8.5, SQLite, ноль зависимостей (нет
composer.jsonиpackage.json), 45 PHP-файлов, около 6 965 строк. - Поверхность: 27 маршрутов API, 13 команд CLI, 4 пресета, 52 проверки в
tests/plan_test.php. - Почтовый мастер: четыре шага — проверка (TXT), MX 1/5/10 и SPF, DKIM, DMARC; подтверждение именем домена только на шагах замены данных; шаг заблокирован до выполнения предыдущего.
- Правила: слияние SPF сохраняет
include:,ip4:,a,mx; совпавший результат — пропуск; DMARCp=quarantine/p=rejectне понижается; DKIM нормализуется (префиксv=DKIM1; k=rsa;, удаление переносов и пробелов вp=). - Проверено 10.11.2026: архив тега 171 357 байт, SHA-256
de5c103a…679b47;php tests/plan_test.php— 52 проверки пройдены на PHP 8.5.0; CLI перечисляет 13 команд; вbackend/config/routes.php27 маршрутов; живой экземпляр развёрнут версией 1.1.0 и снаружи отдаёт 403. - Не проверялось: реальная запись в зону (нужен ключ API Fornex), интерфейс и маршруты API на живом экземпляре (доступ закрыт), поведение на PHP ниже 8.5.
- Ограничения Fornex: создание зоны через API может давать 403; второй MX с тем же значением отклоняется; запрос без
User-Agentзависает; кеш резолверов требует проверки черезns1.fornex.com. - Ссылки: https://github.com/paulmann/fornex-control, https://github.com/paulmann/fornex-control/releases/tag/v1.1.0
Промт для агента (установка и настройка)
Готовый промт: скопируйте блок целиком. Шаги взяты из AGENTS.md и README.md репозитория (файл AGENTS.md в дереве есть) и сверены с прогоном тестов.
Задача: развернуть Fornex Control 1.1.0, настроить почту Google в четыре шага и не сломать существующую зону.
Материалы:
- репозиторий: https://github.com/paulmann/fornex-control
- AGENTS.md: https://github.com/paulmann/fornex-control/blob/main/AGENTS.md
- README: https://github.com/paulmann/fornex-control/blob/main/README.md
- релиз: https://github.com/paulmann/fornex-control/releases/tag/v1.1.0
Шаг 1. Проверить предпосылки: php -v (нужен 8.5), php -m (curl, json, mbstring, openssl, pdo_sqlite).
Шаг 2. Развернуть дерево и конфигурацию уровнем выше приложения:
git clone https://github.com/paulmann/fornex-control.git /srv/fornex-control
install -m 640 /srv/fornex-control/.env.example /srv/fornex.env
php -r "echo password_hash('пароль', PASSWORD_DEFAULT), PHP_EOL;"
Шаг 3. Заполнить FORNEX_API_KEY и ADMIN_PASSWORD_HASH, создать var, var/log, var/snapshots с правами на запись.
Шаг 4. Проверить чтение: php backend/bin/fornex domains; zone:show <домен>
Шаг 5. Снять снимок до любых изменений: zone:snapshot <домен>
Шаг 6. Прочитать план шага 1: preset:apply <домен> google-workspace --param:dkim='...'
Шаг 7. Применить шаг 1 с --apply только после прочтения плана; дождаться подтверждения домена у провайдера.
Шаг 8. Повторить для шагов 2-4, каждый раз читая план; шаг 2 и 4 не требуют ввода имени домена.
Шаг 9. Проверить результат: verify <домен> — NS, MX, SPF, DKIM, DMARC, PTR по ns1.fornex.com.
Шаг 10. Проверить журнал: audit --limit=10 — записи с идентификаторами запросов и именами снимков.
Запрещено:
- применять изменения без предпросмотра плана и без снимка зоны;
- класть .env с ключом API в веб-корень;
- удалять записи масками: удаление только по id;
- править SOA и NS через панель;
- считать ответ публичного резолвера истиной сразу после записи: проверять через ns1.fornex.com;
- выдавать токен с правом записи, если достаточно чтения;
- печатать значение ключа API или хеш пароля в отчёте.
Что считать успехом: domains и zone:show отвечают, снимок создан, четыре шага применены по одному, verify показывает корректные MX/SPF/DKIM/DMARC, в журнале есть записи со ссылками на снимки.