← На главную

Опубликовано

Сайт под ключ руками агента: от домена до fail2ban

Автор: Гусев Николай [портфолио]

Темы: ai-agents, devops, llm-api

Четыре адреса: pii-guard.ru, argon-khimmash.ru, shturman.ai, gusevnikolay.ru. Лендинги продуктов, сайт-визитка, демо-стенд корпоративного приложения. Разные задачи, разный дизайн, разные стеки. Один процесс: от покупки домена до настроенного fail2ban на хосте. В этой статье - весь путь по шагам, с реальными командами, реальными багами и тем, что агент делал сам, а что пришлось чинить руками.

Это ответ на вопрос, который мне задают чаще всего: "покажите живой пример от начала до конца, а не набор скиллов". Показываю.

Этап 0. Хостинг и DNS: куда вообще класть

До первого пикселя - три решения.

Первое: где живёт сайт. У меня несколько узлов, и это осознанная структура, а не "где досталось". Один узел в Нидерландах под основные продукты (nginx, acme.sh, PostgreSQL). Один под демо-стенды и рабочие сервисы (Docker: SearXNG, Matrix, статики побольше). Один под прокси-обвязку. Новые лендинги я раскладываю по нагрузке: статика - на узел с наименьшим количеством движущихся частей, тяжёлые демо - туда, где Docker уже крутится.

Второе: DNS - и вот где ручная работа заканчивается. У Cloudflare зрелый API, и доменами управляет сам агент: в профиль кладётся API-токен, и DNS-записи создаются кодом, а не руками в панели.

Токен - не глобальный ключ аккаунта, а узкий: scope только на Zone.DNS конкретной зоны, права Edit на DNS и Read на Zone. Такой токен не может выписать себе соседние привилегии, и его утечка стоит ровно одной записи, которую видно в логах.

Схема вызова - REST, ничего хитрого:

`curl -X POST "https://api.cloudflare.com/client/v4/zones/<ZONE_ID>/dns_records"       -H "Authorization: Bearer <API_TOKEN>"       -H "Content-Type: application/json"       --data '{"type":"A","name":"example.com","content":"IP_СЕРВЕРА","ttl":1,"proxied":false}'`

Агент идёт по чек-листу: найти ID зоны по имени (GET /zones), создать A-запись с proxied:false - проксирование включается последним, когда SSL на origin уже проверен, иначе Cloudflare отдаёт свою страницу-предупреждение и вы ловите ложный "сайт не работает". Потом www как CNAME. Потом, после деплоя и проверки сертификата, - PATCH той же записи с proxied:true, и origin-IP исчезает из публичного DNS. Всё это - четыре API-вызова, которые агент делает сам в конце этапа деплоя; инструкция "пропишите у регистратора" нужна ровно один раз - при переносе NS на Cloudflare.

Бонус зрелости: те же вызовы закрывают эксплуатацию. Purge cache после деплоя (POST /zones/<id>/purge_cache), проверка статуса SSL, список записей для аудита - всё через тот же токен, всё из одного скрипта.

Третье: как заходить на сервер. SSH по ключу от root, и никаких паролей. Это станет важным на этапе защиты - но ключи надо положить до первого деплоя, а не после инцидента.

Решения занимают десять минут и экономят часы. Самая частая ошибка новичка - деплой на первый попавшийся хост, а потом переезд.

Этап 1. Разведка: что продаём и кому

Агент начинает не с дизайна, а с исследования. На примере pii-guard.ru это выглядело так.

Сначала - сам продукт: агент прочитал репозиторий, версию, метрики качества, перечень распознаваемых сущностей, юридическую рамку и способ интеграции. Это не формальность: текст лендинга пишет тот же агент, и он должен понимать, что продаёт, на уровне инженера.

Потом - конкуренты. Поиск шёл через SearXNG, поднятый на собственном сервере: Microsoft Presidio, LLM Guard, PrivateAI с ценником от 50 тысяч долларов в год, PrivacyScrubber MCP за 15 долларов в месяц. Вышка - не для списка "мы лучше всех", а для позиционирования: в нише бесплатных опенсорс-детекторов Presidio - главный референс, а вот готовых MCP-шлюзов под корпоративный ландшафт почти нет. Это и стало главным посылом страницы.

Отдельное исследование - юридическая рамка. Фоновый запуск codex по продажам нашёл то, что я бы сам искал полдня: приказ РКН №140 про обезличивание, оборотные штрафы 1-3 процента выручки по ч.15 ст.13.11 КоАП (20-500 миллионов рублей), требование ФСТЭК по нейросетям с сентября 2026. Из этого родилась продающая логика страницы: не "у нас детектор лучше", а "вот штраф, вот документ, вот как закрывается".

Урок этапа: разведка - не задержка перед работой, это половина контента. Каждый блок лендинга потом ссылается на конкретный факт из исследования.

Этап 2. Дизайн: DNA вместо "сделай красиво"

"Сделай красиво" - худшее ТЗ в мире. Рабочая схема - дизайн-DNA с референса: берём сайт, чей стиль нравится, и вытаскиваем из него машиночитаемую выжимку.

Для pii-guard.ru референсом стал shturman.ai. Утилита dembrandt (open source, ставится одной командой) сняла с него дизайн-геном: шрифт, акцентный цвет, фон, сетка, тени, скругления - на выходе W3C design tokens. Это не картинка "для вдохновения", а JSON с токенами и DESIGN.md, которые можно отдать кодогенератору как спецификацию.

Тот же конвейер работал и в обратную сторону: когда делали сайт для демо Штурмана, dembrandt снял DNA с целевого референса, и рядом легли бренд-гайд в PDF и токены в формате DTCG - готовый пакет для дизайнера-человека, если он подключится позже.

Почему это важно: дизайн-токены уничтожают самый дорогой цикл правок. "Переделайте дизайн" превращается в "поменяйте три значения в tokens.json" - и агент перегенерирует страницу в новом стиле, сохранив вёрстку.

Этап 3. ТЗ и план: формализация до кода

Дальше - техзадание. Не в чат, а в файл: структура секций, целевое действие каждой, тексты, тарифы, контакты. План работ - отдельным документом с фазами.

Для pii-guard.ru ТЗ собиралось через набор скиллов планирования (в основе - портированные скиллы из открытого репозитория cursor/plugins) - план в формате, пригодном для делегирования: каждая фаза - самостоятельная задача с критерием готовности. Потом план уходит в codex, и там, где нужна была визуализация, работал gpt-5.6.

Здесь же - решение о цене и тарифах. Цифры берутся не с потолка, а из исследования рынка этапа 1: позиционирование относительно конкурентов, стоимость внедрения, объём работ по каждому тарифу.

Этап 4. Генерация и делегация: кто что пишет

Код страницы писал codex, не я. Схема из прошлой статьи цикла работает и здесь: тяжёлая визуализация - флагманской модели, аудиты и правки - дешёвой.

После генерации - ревью не глазами, а инструментами. Прогон страницы в браузере: ноль JS-ошибок в консоли. Vision-ревью скриншотов моделью с оценкой по десятибалльной шкале: первая итерация дала 8 из 10 с конкретными замечаниями - и только после правок сайт получил внутреннюю планку "премиум". Замечания vision-модели конкретны до смешного: "кнопка залезает на заголовок в мобильном лейауте", "контраст текста в футере ниже читаемого".

Важная деталь: самоотчёту агента веры нет. Codex рапортует "готово" - проверяй файлами и прогонами. На argon-khimmash.ru после одного из деплоев серверные файлы оказались чистыми, а незакоммиченные правки жили только на локальной машине; деплой сделал точечными sed-правками на сервере, чтобы не утянуть в прод чужую незаконченную работу. Правило: прод проверен = git log на сервере плюс curl, не слова агента.

Этап 5. Деплой: nginx, SSL и один живой баг

Статика кладётся в /var/www/домен, фронт собирается в dist. Для argon-khimmash это вылилось в systemd-юнит и реверс-прокси на 3001 порт; для pii-guard - чистую статику с nginx.

Конфиг nginx - самое честное место этой статьи, потому что здесь был баг. В свежем nginx 1.24 на Ubuntu 24.04 директива http2 on отдельной строкой не поддерживается - она появилась в 1.25.1, а в 1.24 флаг http2 ещё вешался прямо на listen. Конфиг с новой синтаксической формой не поднялся. Лечится одной строкой, но именно такие вещи не описаны в туториалах двухлетней давности, по которым пишет генератор. Проверка nginx -t перед reload - обязательна, всегда.

Рабочий скелет конфига:

`server {
    listen 80;
    server_name домен www.домен;
    return 301 https://домен$request_uri;
}
server {
    listen 443 ssl;
    server_name домен www.домен;
    ssl_certificate /etc/letsencrypt/live/домен/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/домен/privkey.pem;
    root /var/www/домен;
    index index.html;
    if ($host = www.домен) {
        return 301 https://домен$request_uri;
    }
    location ~* \.(css|js|png|jpg|ico|webp|svg|woff2?)$ {
        expires 7d;
        add_header Cache-Control "public, max-age=604800";
    }
    location / { try_files $uri $uri/ =404; }
}`

Сертификаты - Let's Encrypt через acme.sh, обновление по крону (у меня - ночная задача в 7:31). После выдачи сертификата - обязательно http-на-https редирект и проверка curl -I: код 301 с http, 200 с https.

DNS переключается последним: сначала сайт отвечает на Host-заголовок по IP, потом A-запись летит в мир.

Этап 6. SEO-обвязка: то, что все забывают

Сайт без SEO-обвязки - реклама без адреса. Минимальный набор, который агент закрывает сразу после деплоя.

robots.txt и sitemap.xml - реальные файлы в корне, а не fallback на index.html (типовая ошибка SPA-деплоя: robots.txt отдаёт HTML и поисковик молча его игнорирует).

Canonical-теги без хвостовых слешей. На argon-khimmash это была отдельная охота: в canonical, sitemap и schema.org-разметке часть URL генерировалась со слешем на конце, часть без - поисковик видел дубли. Точечные sed-правки по всем файлам страниц, и canonical, sitemap и IndexNow-пингер стали выдавать одинаковые URL.

Редиректы устаревших путей. Старые страницы (/vacansii, /contacts со старыми путями) - не 404, а 301 на актуальные адреса.

IndexNow - если целевая аудитория ищет в Яндексе, пинг нового контента ускоряет индексацию с недель до часов.

Этап 7. Защита периметра: fail2ban и не только

Вот чего нет в туториалах "как задеплоить лендинг": сервер с сайтом начинают брутфорсить в первый же день. Не потому что вы кому-то нужны - просто сканеры бьют всё подряд.

Аудит одного из узлов показал закономерную картину: SSH открыт, парольная аутентификация разрешена, fail2ban не установлен. После включения jail для sshd fail2ban за первые часы заблокировал 25 адресов. Двадцать пять ботов, долбящих ваш сервер, пока вы читаете эту статью.

Минимальный контур после каждого нового сайта:

fail2ban с jail для sshd и nginx (limit_req на login-эндпоинты, если есть форма).

SSH только по ключам: PasswordAuthentication no в sshd_config. Ключи мы положили ещё на этапе 0 - теперь просто выключаем пароли.

Файрвол: наружу только 80, 443 и SSH. Всё остальное - по явной надобности.

Cloudflare-проксирование как внешний слой: origin видит только адреса Cloudflare, прямой скан вашего IP упирается в дефолтную страницу.

Бэкапы конфигов: cp Caddyfile Caddyfile.bak.дата, копия /etc/nginx перед каждым изменением. Откат - одна команда, а не вечер археологии.

Этап 8. Эксплуатация: сайт - это процесс

Последнее отличие "сделал агент" от "сделал фрилансер и исчез": агент остаётся на подхвате, и это тоже настройка.

Обновление сертификатов - крон acme.sh, проверка логов раз в неделю. Мониторинг доступности - внешний пингер с алертом в Telegram: curl код ответа каждые пять минут, алерт если не 200. Обновление контента - правка секции в TDD-файле и перегенерация, а не ручная вёрстка. На одном из проектов рабочий процесс выглядел так: задача в трекере Штурмана - агент проверяет, что прод соответствует последнему коммиту, - при расхождении перегенерация и деплой.

Счёт за такой процесс: домен 200-800 рублей в год, хостинг уже существует (новый сайт на существующем узле стоит ноль дополнительной памяти - статика в мегабайтах), время агента на полный цикл - один рабочий вечер.

Карта процесса

Девять этапов, каждый - конкретный инструмент:

DNS и хостинг: Cloudflare + существующий узел, SSH-ключи

Разведка: агент читает репозиторий, SearXNG по конкурентам, юридическая рамка фоновым codex

Дизайн: dembrandt снимает DNA референса, токены в JSON + DESIGN.md

ТЗ и план: /cursor, фазы с критериями готовности

Генерация: codex на флагмане, тексты из исследования, тарифы из рынка

Ревью: консоль браузера на ноль ошибок, vision-оценка, правки до планки

Деплой: nginx + Let's Encrypt, nginx -t всегда, curl -I после

SEO: robots, sitemap, canonical без хвостов, 301-редиректы, IndexNow

Защита: fail2ban, SSH по ключам, файрвол, бэкапы конфигов

Принцип, ради которого всё это писалось: сайт - не артефакт, а конвейер. Каждый этап выдаёт машиночитаемый артефакт следующему - токены дизайнера превращаются в спецификацию, спецификация в план, план в код, код в деплой. Поэтому четвёртый сайт делается быстрее первого, а пятый будет быстрее четвёртого: конвейер не стареет, он накапливается.

Предыдущие части цикла: как сжатие контекста теряет ваши данные и почему агент без памяти опасен - на Хабре; как разнести задачи по моделям и не платить флагманом за рутину - в прошлой статье канала.