5f9d607d07
netrunner-backend dev теперь проксирует Caddy на стандартный порт 443 (VPN-нода, ранее занимавшая его, перенесена на другой порт) — Telegram Login Widget в браузере требует именно этот порт. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
114 lines
9.1 KiB
Markdown
114 lines
9.1 KiB
Markdown
# 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](matrix-engine/README.md) и [proxy-ws/README.md](proxy-ws/README.md).
|
||
Подробности по фронтенду — в [app/README.md](app/README.md), [components/README.md](components/README.md)
|
||
и [lib/README.md](lib/README.md).
|
||
|
||
## Как это работает вместе
|
||
|
||
1. Next.js middleware (`proxy.ts`) на каждый запрос без `/en` или `/ru` в пути определяет
|
||
язык браузера (`Negotiator` + `@formatjs/intl-localematcher`) и редиректит на `/{locale}/...`.
|
||
`/doom`, `/doom.html` и `/games/*` из этой логики специально исключены.
|
||
2. Серверные страницы в `app/[lang]/...` подгружают словарь (`lib/dictionaries.ts`) и контент
|
||
из PocketBase (`lib/pb.ts`) — блог, FAQ, ссылки на загрузки и т.п.
|
||
3. Фоновая WebGL-анимация (`matrix-engine`) инициализируется клиентским компонентом
|
||
`NetrunnerMatrix` — она работает в отдельном Web Worker поверх `OffscreenCanvas`,
|
||
не блокируя основной поток.
|
||
4. Гостевая книга (`components/sections/TerminalGuestbook.tsx`) отправляет сообщения не
|
||
напрямую в PocketBase, а через `/api/proxy/*` (rewrite в `next.config.ts` на `ws-proxy:3001`).
|
||
Rust-шлюз прогоняет никнейм и текст через ONNX-классификатор токсичности и только потом
|
||
пишет запись в PocketBase.
|
||
5. Спидтест (`components/sections/SpeedtestClient.tsx`, страница `/speedtest`) бьёт напрямую
|
||
в `ws-proxy` за `/api/speedtest/ping|download|upload`.
|
||
|
||
## Разработка
|
||
|
||
```bash
|
||
pnpm install
|
||
|
||
# WASM-движок собирается отдельно (нужен Rust + wasm-pack), результат кладётся в public/wasm-matrix
|
||
./build-wasm.sh
|
||
|
||
pnpm dev
|
||
```
|
||
|
||
Открыть [http://localhost:3000](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` / DNS
|
||
`dev.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 тестовыми данными.
|