В декабре 2025 года Стас приехал к родителям и обнаружил, что РКН тестирует белые списки: его регион попал в первую волну блокировок. ChatGPT, Claude, YouTube, Telegram — всё недоступно. OpenVPN, которым он пользовался через университет, тоже заблокировали. Из этой точки вырос полноценный SaaS-сервис.

Решением стал протокол Hysteria2 — он работает поверх QUIC (UDP) и маскируется под обычный HTTPS-трафик, что делает его трудноразличимым для систем глубокой инспекции пакетов (DPI). Развернув VPS с контейнером Hysteria2, автор обнаружил, что сервер предоставляет REST API: через него можно в реальном времени получать список подключённых пользователей, управлять трафиком, добавлять и удалять клиентов. Это стало отправной точкой — сначала curl-запросы, потом Python-скрипт с библиотекой requests, потом желание сделать из этого полноценный продукт.

НодаСтранаНазначение
docker-compose.ge.yamlГрузияHysteria2 VPN-нода
docker-compose.nl.yamlНидерландыHysteria2 VPN-нода
docker-compose.pl.yamlПольшаHysteria2 VPN-нода
docker-compose.se.yamlШвецияHysteria2 VPN-нода

Архитектура проекта htrBox разделена на три изолированных Docker-контейнера: бэкенд на FastAPI с PostgreSQL, фронтенд на React + Vite и сам Hysteria2-сервер. Зоны ответственности не пересекаются: бэкенд общается с Hysteria2 по REST, фронт — с бэкендом по REST, VPN-сервер ничего не знает об остальных частях системы. Весь стек поднимается одной командой через docker-compose. В продакшне работают четыре Hysteria2-ноды — в Грузии, Нидерландах, Польше и Швеции, бэкенд живёт в Yandex Cloud отдельно.

Проект разбит на три изолированных Docker-контейнера: бэкенд (FastAPI + PostgreSQL), фронтенд (React + Vite) и сам VPN-сервер.

Авторизация построена на JWT с двумя токенами: короткоживущий access-токен и refresh-токен, который хранится в httpOnly cookie. Это осознанное решение безопасности: JavaScript на странице не имеет доступа к такому cookie, поэтому XSS-атака не сможет украсть токен через document.cookie. Хранить refresh-токен в localStorage — распространённая ошибка, которую автор явно исключил. Параллельно написана WebSocket-инфраструктура, но она закомментирована: на текущем масштабе поллинг через TanStack Query справляется, усложнять без необходимости не стали.

Отдельного внимания заслуживает подход к работе с ИИ-ассистентом. Проблема стандартная: ИИ не удерживает контекст между сессиями, и каждый раз объяснять структуру проекта — потеря времени и токенов. Решение — bash-скрипт pack.sh с тремя режимами упаковки: весь проект, только бэкенд или только фронтенд. Claude и Manus умеют работать с tar-архивами напрямую. Если правится роутер на бэкенде, 50+ компонентов фронтенда в контекст не попадают — экономия токенов ощутимая.

Рабочий процесс с ИИ тоже формализован. Вместо «давай реализуем фичу» — сначала запрос на план с TODO-маркерами, подробным описанием каждого шага. После согласования плана — реализация пошагово. ИИ отдаёт только изменённые файлы и обновлённый TODO.md после каждого шага. Если ИИ галлюцинирует или заканчиваются токены на аккаунте — архив с актуальным TODO.md переносится на новый аккаунт, и работа продолжается с той же точки.

Самым нетривиальным решением оказалась дизайн-система для ИИ. Без неё ИИ расставляет Tailwind-классы случайно: на одной странице text-sm, на другой text-xs, отступы и цвета вразнобой. Файл PROMT.md — 250+ строк инструкций, описывающих дизайн-систему и алгоритм работы с ней. Стили разбиты по файлам с жёстким правилом маппинга: компонент из components/ui/ получает стили только из uiStls.ts, компонент из pages/ — только из pagesStls.ts. ИИ не может положить стили куда попало, и визуальная консистентность сохраняется на всём проекте.

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