7076bbcc7849928a7a94e0e44dc420ba7707531b
Порты 3000/3001 закрыты для интернета (ufw allow только с IP VPS данных — Prometheus). Публичный вход только netrunner-vpn.com через nginx: "/" на сам лендинг, "/cms-api/" на PocketBase (со сменой префикса на "/api"), "/api/" на proxy-ws (comments/speedtest, уже с этим префиксом внутри).
Netrunner VPN — Landing
Промо-сайт и мини-платформа VPN-сервиса «Netrunner»: Next.js 16 (App Router) фронтенд с двумя языками (en/ru), PocketBase в роли headless CMS и два самописных Rust-сервиса — WebAssembly-движок анимированного фона «матрицы» и WebSocket/HTTP-шлюз с AI-модерацией и speedtest-эндпоинтами.
Стек
- Next.js 16 (App Router, React 19, Server Components) + TailwindCSS 4
- PocketBase — headless CMS/БД для блога, FAQ, ссылок на загрузки и гостевой книги
- Rust → WebAssembly (
matrix-engine/) — физика фоновой анимации, рендер через WebGL2 в Web Worker - Rust / Axum (
proxy-ws/) — прокси-шлюз: ONNX-модель токсичности для фильтрации сообщений + генератор нагрузки для speedtest - Docker Compose — три контейнера в проде:
landing,netrunner-cms(PocketBase),ws-proxy
Структура репозитория
app/[lang]/ Страницы Next.js (App Router), маршруты локализованы через /en/* и /ru/*
components/sections/ Крупные блоки страниц (Hero, Pricing, Terminal-гостевая книга и т.д.)
components/ui/ 低-уровневые UI-примитивы (shadcn/radix обвязка)
lib/ Общие утилиты: клиент PocketBase, загрузчик словарей, типы
dictionaries/ JSON со строками локализации (en.json / ru.json)
matrix-engine/ Rust/WASM-движок фонового "дождя из матрицы" + его README
proxy-ws/ Rust/Axum-шлюз: AI-модерация комментариев и speedtest + его README
public/wasm-matrix/ Скомпилированный вывод matrix-engine, который реально грузит браузер
public/games/doom.zip Пасхалка — DOOM в браузере на /doom
proxy.ts Next.js "proxy" (аналог middleware) — определение и редирект локали
Подробности по Rust-подсистемам — в их собственных README: matrix-engine/README.md и proxy-ws/README.md. Подробности по фронтенду — в app/README.md, components/README.md и lib/README.md.
Как это работает вместе
- Next.js middleware (
proxy.ts) на каждый запрос без/enили/ruв пути определяет язык браузера (Negotiator+@formatjs/intl-localematcher) и редиректит на/{locale}/..../doom,/doom.htmlи/games/*из этой логики специально исключены. - Серверные страницы в
app/[lang]/...подгружают словарь (lib/dictionaries.ts) и контент из PocketBase (lib/pb.ts) — блог, FAQ, ссылки на загрузки и т.п. - Фоновая WebGL-анимация (
matrix-engine) инициализируется клиентским компонентомNetrunnerMatrix— она работает в отдельном Web Worker поверхOffscreenCanvas, не блокируя основной поток. - Гостевая книга (
components/sections/TerminalGuestbook.tsx) отправляет сообщения не напрямую в PocketBase, а через/api/proxy/*(rewrite вnext.config.tsнаws-proxy:3001). Rust-шлюз прогоняет никнейм и текст через ONNX-классификатор токсичности и только потом пишет запись в PocketBase. - Спидтест (
components/sections/SpeedtestClient.tsx, страница/speedtest) бьёт напрямую вws-proxyза/api/speedtest/ping|download|upload.
Разработка
pnpm install
# WASM-движок собирается отдельно (нужен Rust + wasm-pack), результат кладётся в public/wasm-matrix
./build-wasm.sh
pnpm dev
Открыть http://localhost:3000 — редиректнёт на /en или /ru по языку браузера.
Полезные переменные окружения (.env.local):
| Переменная | Назначение |
|---|---|
NEXT_PUBLIC_PB_URL |
Публичный URL PocketBase (используется в браузере) |
PB_INTERNAL_URL |
Внутренний URL PocketBase для SSR (обычно http://netrunner-cms:8090 в докере) |
NEXT_PUBLIC_PROXY_URL |
Публичный URL Rust-шлюза (для клиентских fetch, минуя rewrite) |
NEXT_PUBLIC_API_URL |
База API бэкенда (netrunner-backend) для auth-эндпоинтов — /users/me, /auth/telegram. По умолчанию ${NEXT_PUBLIC_ACCOUNT_ORIGIN}/api/v1 |
NEXT_PUBLIC_ACCOUNT_ORIGIN |
Origin ЛК (netrunner-backend, сабдомен account.*) — куда редиректить после успешного логина |
NEXT_PUBLIC_BOT_USERNAME |
Юзернейм Telegram-бота для виджета логина |
PB_ADMIN_EMAIL / PB_ADMIN_PASS |
Только для одноразового скрипта upload.mjs (сид данных в PocketBase) |
Сборка и деплой
Dockerfile— многостадийная сборка: сначала компилируетmatrix-engineв WASM (wasm-pack build --target web --release), кладёт результат вpublic/wasm-matrix, затем собирает Next.js и упаковывает в минимальныйnode:20-alpineрантайм.proxy-ws/Dockerfile— отдельный образ Rust-шлюза (кэширует зависимости через BuildKit cache mounts, чтобы не пересобирать crates на каждый билд).docker-compose.yml— прод: поднимает три сервиса:landing,netrunner-cms(PocketBase) иws-proxy, все в одной bridge-сетиnetrunner-network.docker-compose.dev-remote.yml— dev: тот же набор сервисов на отдельном dev VPS (по аналогии сnetrunner-backend, тот же хост —45.76.161.137/ DNSdev.netrunner-vpn.com), сам лендинг без reverse-proxy (порт3000открыт напрямую, обычный HTTP), но смотрит на dev-бэкенд уже через Caddy наhttps://dev.netrunner-vpn.com(порт 443, не голый:8080) — уnetrunner-backendтам настоящий TLS, нужный и для Telegram Web Apps (кнопки бота) и Secure-cookie, и для Telegram Login Widget (требует в браузере именно порт 443). Домен, а не IP: Telegram Login Widget требует настоящий домен, зарегистрированный в BotFather под отдельным dev-ботом (BOT_USERNAMEдля этого стека — обязательная переменная, без дефолта на прод-бота, см. комментарии в файле)..github/workflows/build.yml— на пуш вmain(и вручную) собирает и пушит оба образа (netrunner-landing,netrunner-landing-proxy) в GHCR..github/workflows/deploy.yml— две джобы:deploy-devсрабатывает автоматически сразу послеbuild.ymlи обновляет dev VPS (docker-compose.dev-remote.yml);deploy-prod— только вручную (Actions → Run workflow), обновляет прод (docker-compose.yml). Секреты:VPS_IP/SSH_PRIVATE_KEY(прод),DEV_VPS_IP/DEV_SSH_PRIVATE_KEY(dev) — заводятся в Settings → Secrets репозитория.deploy.sh— тот же процесс вручную для прода, для локального запуска без ожидания CI.ecosystem.config.js— конфиг PM2, альтернативный способ запускатьnext startбез Docker (используется, если сервис поднимается процесс-менеджером на самом VPS).
Разовые dev-утилиты не являются частью приложения и не запускаются в проде:
collect_code.js— сбрасывает все.ts/.tsxфайлы изapp/,components/,lib/в один текстовый файл (удобно для передачи контекста в LLM).upload.mjs— одноразовый скрипт сидирования коллекцийdownloads_en/downloads_ruв PocketBase тестовыми данными.
Description
Languages
TypeScript
53.8%
JavaScript
28.4%
Rust
12.6%
CSS
1.8%
Shell
1.2%
Other
2.2%