Skip to content
dizzzablePublic

About

No description, website, or topics provided.

Resources

Stars

16 stars

Watchers

0 watching

Forks

Latest commit

 

History

591 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Reiwa Logo

Reiwa

User-facing edge сервис Rezeis: Telegram-бот, Mini App и web-кабинет в одном образе

Version GHCR License Node.js Express grammY React TypeScript Vite

Релизы • Быстрый старт • Настройка окружения • Возможности • Архитектура


🎯 О проекте

Reiwa — пользовательский edge/BFF-слой Rezeis. Это лицо сервиса для конечного клиента: один TypeScript-кодбейс отдаёт Telegram-бота, Telegram Mini App и web-кабинет (PWA). Reiwa не владеет реляционной БД — вся бизнес-истина (платежи, подписки, тарифы) живёт в rezeis-admin, а reiwa общается с ним по приватной сети через типизированный AdminClient.

Что выделяет Reiwa:

  • 🧩 Один образ — три поверхности — API (Express BFF) + бот (grammY) + воркер + SPA в едином Docker-образе
  • 🔌 BFF-дисциплина — провайдерские/админские вызовы только на сервере; в браузер не утекают Remnawave-UUID, ссылки провайдеров, токены и device-идентификаторы
  • 📱 Source-aware redirect — оплата возвращает пользователя туда, откуда он пришёл (Mini App ↔ web)
  • 💳 Единый платёжный UX — покупка, продление, улучшение и доп-опции одинаковыми полноэкранными мастерами
  • 🗑 Самостоятельное удаление подписки long-press'ом по карточке с отзывом доступа
  • 🎨 PWA-кабинет на React 19 с операторским брендингом: 104 концептуальные темы, настраиваемые карточки и безопасные GPU/CSS-эффекты
  • 🌍 Полная i18n (ru/en) — ноль захардкоженных строк
  • 🔐 Redis-сессии, rate-limit, CSRF/Origin-защита, fail-closed в проде

📦 Готовые Docker-образы

GitHub Container Registry публикует единый образ при каждом push'е в main и при каждом теге v*, но latest двигает только тег.

# Latest release (moves only when a v* tag is pushed)
docker pull ghcr.io/dizzzable/reiwa:latest

# Pin to a specific release
docker pull ghcr.io/dizzzable/reiwa:v0.9.7.63

Доступные теги: latest (последний выпуск — двигается только push'ем тега v*), v0.9.7.63 (тег релиза), main (текущая ветка main, ещё не выпуск), плюс sha-<short> для каждого собранного коммита. Прод-docker-compose.yml использует latest, то есть получает только выпуски.

Один образ обслуживает всё: API на REIWA_PORT (по умолчанию node dist/api/main.js) раздаёт собранную SPA из /app/web, бот — dist/bot/main.js, воркер — dist/worker/main.js. Роль выбирается командой запуска контейнера.


🚀 Quick Start

Установка на VPS (production, готовый образ)

Образ тянется из GHCR — исходники на сервере не нужны, только два файла.

# 1. Каталог установки
mkdir -p /opt/reiwa && cd /opt/reiwa

# 2. Скачать compose и шаблон окружения
curl -fsSL -o docker-compose.yml https://raw.githubusercontent.com/dizzzable/reiwa/main/docker-compose.yml
curl -fsSL -o .env               https://raw.githubusercontent.com/dizzzable/reiwa/main/.env.example

# 3. Общая docker-сеть с rezeis (создать, если Remnawave/rezeis-стек её ещё
#    не создал — нужна для связи reiwa ↔ rezeis по имени `rezeis:8000`):
docker network create remnawave-network 2>/dev/null || true

# 4. Сгенерировать ЛОКАЛЬНЫЙ секрет прямо в .env (он нужен только reiwa):
sed -i "s|^REDIS_PASSWORD=.*|REDIS_PASSWORD=$(openssl rand -hex 16)|" .env

# 5. Дозаполнить вручную:
#      REIWA_DOMAIN            публичный домен кабинета (app.example.com)
#      REZEIS_HOST=rezeis      имя контейнера админки на одном VPS (или panel.example.com при split)
#      REZEIS_TOKEN            API-токен, созданный в админке rezeis (Настройки → API-токены)
#      REZEIS_WEBHOOK_SECRET   = WEBHOOK_SECRET_HEADER из rezeis (если включаете push)
#      REZEIS_INTERNAL_SHARED_SECRET
#                              = REZEIS_INTERNAL_SHARED_SECRET из rezeis, тот же
#                              самый. НЕ генерируйте его здесь: панель проверяет
#                              этим значением подпись каждого запроса от reiwa.
#                              Сгенерируйте один раз (openssl rand -hex 32) и
#                              вставьте одну и ту же строку в ОБА .env.
#      BOT_TOKEN, BOT_USERNAME от @BotFather
nano .env

# 6. Запуск (reiwa + reiwa-bot)
docker compose up -d

В продакшене сервис fail-closed: без Redis или без гарантии secure-cookie запуск останавливается. Ослабить только осознанно — REIWA_ALLOW_DEGRADED=true / REIWA_ALLOW_INSECURE_COOKIES=true. Полный разбор переменных и таблицу совпадений с rezeis — см. docs/environment.md.

Обновление:

cd /opt/reiwa && docker compose pull && docker compose up -d

Reverse proxy: на одном VPS с rezeis удобнее единый Caddy — см. caddy-combined в репозитории rezeis. Отдельные конфиги — в deploy/proxies/.

Если панель на другом VPS и DNS ненадёжен

Раздельная установка сама по себе ничего дополнительного не требует: REZEIS_HOST в .env указывает на публичный домен панели, и обращения к ней идут через обычный DNS.

Но если резолвер у провайдера подводит, getaddrinfo EAI_AGAIN кладёт все бизнес-операции разом — запросы висят до таймаута и отвечают 503, а в логе панели при этом пусто: они до неё не дошли. Оператору приходит карточка про отменённый запрос, в которой про DNS не сказано ничего; настоящая причина видна только в логе контейнера:

WARN: [api] Upstream temporarily unreachable
  err: getaddrinfo EAI_AGAIN <домен панели>

Чинится на хосте кабинета, а не в compose — резолверами демона Docker.

⚠️ daemon.json может уже существовать (драйвер логов, зеркала реестра, storage-driver). Не перезаписывайте его целиком — потеряете эти настройки. Сначала посмотрите, что там:

sudo cat /etc/docker/daemon.json 2>/dev/null || echo "файла нет"

Если файла нет — создайте:

echo '{ "dns": ["1.1.1.1", "8.8.8.8"] }' | sudo tee /etc/docker/daemon.json

Если файл есть — добавьте в него только ключ dns, сохранив остальное. С резервной копией, потому что ошибка в JSON не даст демону подняться:

sudo cp /etc/docker/daemon.json /etc/docker/daemon.json.bak
sudo jq '. + {"dns": ["1.1.1.1", "8.8.8.8"]}' /etc/docker/daemon.json.bak | sudo tee /etc/docker/daemon.json

Нет jq — поставьте (sudo apt install -y jq) либо впишите ключ руками: верхним уровнем объекта, рядом с существующими, не трогая их.

Перед рестартом убедитесь, что JSON цел — с битым файлом демон не запустится, и поднимать его придётся уже из резервной копии:

python3 -m json.tool /etc/docker/daemon.json > /dev/null && echo "JSON валиден"

Затем перезапустите демон:

sudo systemctl restart docker

⚠️ Перезапуск демона перезапускает контейнеры — короткий простой кабинета. Делайте в спокойное время. ("live-restore": true в том же файле его избегает, но на смену настроек DNS всё равно нужен рестарт.)

Проверка, что помогло — резолвим домен панели изнутри самого контейнера, то есть ровно так, как это делает приложение:

docker exec reiwa node -e "require('dns').resolve4(process.env.REZEIS_HOST, (e, a) => console.log(e ? 'ОШИБКА: ' + e.code : 'ОК: ' + a))"

Должно напечатать ОК: и адрес. ОШИБКА: EAI_AGAIN означает, что резолверы всё ещё не отвечают.

Почему именно демон, а не dns: в compose. Ключ dns: у сервиса замещает встроенный резолвер Docker (127.0.0.11), а это он превращает REZEIS_HOST=rezeis в адрес контейнера. На одиночном VPS панель после такого перестанет находиться вообще. Настройка демона так не ломает: встроенный резолвер остаётся на месте и лишь пересылает внешние имена новым адресам — поэтому чинятся сразу и панель, и api.telegram.org, и оба сервиса кабинета (reiwa и reiwa-bot, который ходит к панели сам).

Локальная разработка / сборка из исходников

Прод-docker-compose.yml ссылается только на готовый образ. Чтобы собрать из исходников локально — build-оверлей:

git clone https://github.com/dizzzable/reiwa.git && cd reiwa
cp .env.example .env   # заполнить секреты
docker compose -f docker-compose.yml -f docker-compose.build.yml up -d --build --force-recreate reiwa reiwa-bot

«Горячая» разработка (bind-mount + watch/HMR) — docker-compose.dev.yml, либо без docker:

npm install
npm run dev:api       # Express BFF + раздача SPA
npm run dev:bot       # grammY Telegram bot
npm run dev:worker    # фоновые задачи
cd web && npm install && npm run dev   # фронт (отдельный терминал)

Системные требования: Node.js 26+, Redis/Valkey 8+, доступный экземпляр rezeis-admin.


✨ Возможности

🤖 Telegram-бот (src/bot)

  • grammY с single-screen контрактом (edit-in-place — экран перерисовывается, а не засоряет чат)
  • Определение локали, operator-managed конфиг-кэш, баннеры и динамические экраны из Bot Studio
  • /start payment_return — возврат после оплаты из Mini App
  • Команды: меню, подписки, покупка, промокод, рефералы, помощь и переходы в Mini App

📱 Telegram Mini App + 🌐 web-кабинет (web/)

Один React 19 + Vite PWA работает и как Mini App внутри Telegram, и как самостоятельный web-кабинет:

  • Dashboard — карусель карточек подписок с брендингом оператора: градиент, паттерн, логотип, геометрия, стекло, текст и анимированный фон; полоса трафика, срок и устройства
  • Покупка — мастер: тариф → срок → платёжная система → оплата
  • Продление — мульти-выбор своих подписок, единый платёж, учёт скидок и промокодов
  • Улучшение тарифа — выбор подписки → новый тариф → срок → новая цена
  • Доп-опции — полноэкранный мастер: подписка → опция (трафик/устройства) → оплата; бесплатные активируются мгновенно
  • Удаление подписки — long-press по карточке → подтверждение → отзыв доступа (финально, без возврата)
  • Устройства — список HWID, отзыв, перегенерация ссылки
  • Рефералы / партнёрка, обмен баллов, промокоды, тикеты поддержки, FAQ, настройки, уведомления
  • Онбординг-тур с подсветкой реальных элементов (включая жест удаления)

🙋 Самообслуживание и коммуникация

  • Дополнительные услуги — покупка и управление расширениями тарифа, включая трафик и устройства
  • Уведомления — лента событий, настройки категорий, переход к связанному действию и отметка всех сообщений прочитанными
  • Поддержка — обращения из кабинета и гостевой вход в поддержку до авторизации; FAQ поддерживает операторские медиа-материалы
  • AI-помощник — пользовательская точка входа в поддержку, когда оператор включает её в Rezeis
  • Программа роста — реферальные ссылки, партнёрский статус, баллы, промокоды, задания и рекламные предложения, если они активированы оператором
  • Настройки кабинета — язык, PWA-установка, уведомления и разрешённая оператором светлая/тёмная вариация бренда

🔑 Вход и связка аккаунтов

  • Telegram Mini App: подписанный initData автоматически создаёт или восстанавливает web-сессию; пользователь, пришедший из бота, не должен видеть форму логина
  • Обычный браузер: вход по логину/паролю, существующей cookie-сессии или одноразовой ссылке возврата
  • Единый аккаунт: Telegram-first пользователь может создать web-учётную запись или привязать существующую, чтобы один кабинет работал и в Telegram, и в браузере
  • Внешняя авторизация: на странице входа отображаются только включённые оператором способы OAuth

💳 Платежи

Reiwa не хранит платежи — он проксирует к rezeis-admin, который владеет 15 шлюзами. На стороне reiwa:

  • Единый выбор платёжной системы с настоящими SVG-иконками провайдеров и валют
  • Source-aware return URL: Mini App → t.me/<bot>?start=payment_return, web → /payment-return
  • Поллинг статуса оплаты + анимированный экран возврата

🔐 Безопасность и BFF-дисциплина

  • Redis-сессии, express-rate-limit, CSRF/Origin allow-list, request-id трейсинг, Helmet
  • Brute-force detection с backoff
  • Типизированный UpstreamError (status/body) — апстрим-ошибки логируются, но не форвардятся в браузер
  • Наружу не выходят сырые Remnawave-UUID, ссылки провайдеров, токены, device/Telegram-идентификаторы — только стабильные safe-labels и opaque public id

🌍 Интернационализация

Полная поддержка ru/en через react-i18next. Все пользовательские строки — в web/src/i18n/{ru,en}.ts; ноль захардкоженных строк (бренды и формат-локали дат — исключение).

📲 PWA

vite-plugin-pwa (injectManifest) + собственный service worker (web/src/sw.ts): прекэш ассетов, офлайн-старт, устанавливаемость.

🎨 Контракт брендинга WEB Reiwa

Внешний вид задаёт оператор в Rezeis → WEB Reiwa; Reiwa получает уже разрешённые токены брендинга через публичный bootstrap-конфиг и не зависит от каталога админ-панели во время отрисовки. При кратковременной ошибке обновления браузер сохраняет последнюю валидную конфигурацию, поэтому кабинет не должен мигать стандартной темой.

  • Пресеты: восемь исторических тем и каталог из 104 концептуальных пресетов. Концепт передаёт палитру, фон приложения, типографику, поверхности, радиусы, градиент/паттерн и параметры карточки.
  • Светлый/тёмный режим: оператор выбирает сам концепт и режим по умолчанию. При политике user-selectable пользователь может сменить только светлую/тёмную вариацию этого же концепта — выбрать другой операторский пресет он не может.
  • Карточки подписок: глобальные параметры являются базой для всех карточек. Для позиций можно создать до 20 слотов: новый слот наследует глобальный эффект, а отдельный режим override осознанно меняет эффект и его параметры только для этой позиции. Статический градиент слота независим от режима эффекта.
  • Читаемость и композиция: режим текста (auto, светлый, тёмный или свой цвет) и стеклянный слой настраиваются отдельно от эффекта. Изменение контраста не должно подменять градиент или палитру, выбранные оператором.
  • Безопасный рендер: градиент и паттерн всегда являются базовым слоем. Если браузер или GPU не поддерживает выбранный Canvas/WebGL-эффект, Reiwa показывает CSS-версию того же эффекта с операторской палитрой и прозрачностью, а не подменяет его чужим эффектом или пустой карточкой.

🏗 Архитектура

reiwa/
├── src/                          # TypeScript edge (3 entrypoints)
│   ├── api/                      # Express BFF
│   │   ├── routes/               # /api/v1/* (subscription, payments, gateways, ...)
│   │   ├── middleware/           # session, user-identity, CSRF/Origin, rate-limit
│   │   └── lib/                  # safe error responses
│   ├── bot/                      # grammY Telegram bot (single-screen contract)
│   ├── worker/                   # background runtime (scheduled work)
│   ├── infrastructure/
│   │   ├── admin-client/         # AdminTransport (undici pool, bearer + HMAC)
│   │   │   └── namespaces/       # типизированный фасад над internal API rezeis-admin
│   │   ├── redis/                # Redis-backed web sessions
│   │   ├── bot-config/           # operator-managed конфиг бота
│   │   └── i18n/                 # translator + locale packs (ru/en)
│   ├── core/                     # zod-config, errors, enums, version
│   └── lib/                      # client-source, payment-return-url, helpers
├── web/                          # React 19 + Vite PWA (Mini App + web cabinet)
│   └── src/
│       ├── features/             # экран-в-папке (lazy-loaded)
│       ├── components/           # shared UI (Radix + reactbits/WebGL)
│       ├── hooks/                # useLongPress, onboarding tour, session, ...
│       ├── stores/               # Zustand (purchase / renewal / upgrade / addons)
│       ├── assets/{payments,currency}/  # настоящие SVG-иконки
│       ├── i18n/                 # ru.ts + en.ts
│       └── lib/                  # api-client, client-source, utils
├── deploy/proxies/               # caddy / nginx / angie / traefik edge-стек
├── Dockerfile                    # unified image (API + bot + worker + SPA)
├── docker-compose.yml            # production stack
└── .github/workflows/docker-publish.yml  # CI: единый образ → GHCR

🛠 Технологический стек

Edge (backend)

Технология Версия Назначение
Node.js 24 Runtime
TypeScript 6 Type safety
Express 5 BFF / HTTP-сервер
grammY 1.42 Telegram-бот
undici 8 HTTP-клиент к rezeis-admin (persistent pool)
ioredis 5 Сессии, rate-limit, brute-force
zod 4 Валидация конфига и входных данных
pino + pino-http — Структурное логирование + request-id
helmet 8 Security headers
vitest + fast-check 4 Unit + property-based тесты

Frontend (web)

Технология Версия Назначение
React 19 UI framework
TypeScript 6 Type safety
Vite 8 Build tool (rolldown)
TanStack Query 5 Server state
Zustand 5 Client state (wizard stores)
Radix UI — Component primitives
Tailwind CSS 4 Styling
three / @react-three/fiber — WebGL-фон карточек
Motion 12 Анимации
react-i18next — i18n (ru/en)
vite-plugin-pwa + workbox 1.3 PWA / service worker
sonner — Тосты

Infrastructure

Технология Назначение
Docker + Compose Контейнеризация (единый образ)
Caddy / Nginx / Angie / Traefik Reverse proxy (TLS 443)
GitHub Actions CI/CD
GHCR Container registry

📋 Переменные окружения

Все настройки задаются через окружение и валидируются один раз на старте в app.config.ts (zod). Скопируйте .env.example → .env и заполните change_me.

Переменная Обязательная Описание
REIWA_DOMAIN ✅ Публичный домен — драйвит CORS/CSRF allow-list, webApp-кнопки и payment-return ссылки бота
REIWA_HOST, REIWA_PORT — Хост/порт API (раздаёт также SPA в режиме единого образа; по умолчанию 0.0.0.0:5000)
BOT_TOKEN ✅ Токен Telegram-бота
BOT_USERNAME ✅ Username бота без @ — для t.me/<bot>?start=payment_return
BOT_MINI_APP, BOT_SUPPORT_USERNAME — Режим Mini App и контакт поддержки
REZEIS_HOST, REZEIS_PORT ✅ Адрес внутреннего API rezeis-admin (источник истины)
REZEIS_TOKEN ✅ Bearer для вызовов rezeis-admin
REZEIS_INTERNAL_SHARED_SECRET ✅ (prod) HMAC-подпись запросов reiwa→admin и хопа reiwa-api→bot (≥32 симв.). Должен быть идентичен одноимённой переменной в rezeis — панель проверяет подпись именно им
REZEIS_WEBHOOK_SECRET ✅ Проверка входящих вебхуков от rezeis-admin (X-Rezeis-Signature)
REDIS_HOST, REDIS_PORT, REDIS_NAME, REDIS_PASSWORD ✅ Redis для сессий / rate-limit / FSM бота
REIWA_COOKIE_SECURE true в production Secure-флаг session-cookie; cookie хранит opaque UUID, валидируемый через Redis
REIWA_ALLOW_DEGRADED — true — разрешить старт при недоступном Redis (по умолчанию fail-closed)
REIWA_ALLOW_INSECURE_COOKIES — true — разрешить небезопасные cookie (только осознанно)

Полный список с комментариями — в .env.example.


🔌 API (/api/v1/*)

Edge-поверхность, которую потребляет кабинет (все эндпоинты за сессией):

  • /api/v1/session — сессия пользователя
  • /api/v1/subscription, /subscriptions/all — подписки пользователя
  • /api/v1/subscription/renewal-options, /upgrade-options — опции продления/улучшения
  • DELETE /api/v1/subscription/:id — самостоятельное удаление подписки
  • /api/v1/gateways — активные платёжные системы
  • /api/v1/payments/checkout, /renewal-checkout — создание оплаты (source-aware)
  • /api/v1/payments/:id — статус оплаты
  • /api/v1/add-ons/plan/:planId, /add-ons/purchase — доп-опции
  • /api/v1/devices/* — устройства (список, отзыв, перегенерация ссылки)
  • /api/v1/internal/metrics — health/метрики reiwa для дашборда rezeis-admin

🧪 Quality Gates

# Edge (backend)
npm run check              # TypeScript no-emit (0 errors policy)
npm test                   # vitest unit suite
npm run test:pbt           # property-based (node:test + fast-check)

# Frontend
cd web
npm run build              # tsc -b + vite build

В reiwa ESLint не настроен — гейты держим через tsc + тесты + сборку.


🐳 Docker Build

Единый Dockerfile собирает и обслуживает:

  • dist/api/main.js — Express BFF (раздаёт SPA из /app/web)
  • dist/bot/main.js — Telegram-бот
  • dist/worker/main.js — фоновый воркер
  • web/dist/ — собранная PWA

Образ публикуется автоматически в GHCR через .github/workflows/docker-publish.yml: push в main даёт теги main и sha-<short>, push тега v* — тег версии, latest и sha-<short>. CI собирает один unified-образ (не отдельные backend/web).

deploy/proxies/ содержит Remnawave-style reverse-proxy стеки (caddy / nginx / angie / traefik), фронтящие кабинет по 443 с bring-your-own сертификатом — см. deploy/proxies/README.md.


🔗 Связь с Rezeis

Reiwa и rezeis-admin версионируются в lockstep (одинаковая версия в обоих репозиториях). Reiwa — это лицо для пользователя; админ-истина и операционные инструменты остаются в rezeis-admin. Reiwa не является копией Remnawave Panel — это Rezeis-owned сервис с интеграцией Remnawave за admin-owned швами.


📜 История изменений

Полные release notes по каждой версии — на странице GitHub Releases с детальным changelog.


📄 Лицензия

MIT — свободное использование, модификация и распространение.


🤝 Contributing

  1. Fork репозитория
  2. Создайте feature-ветку: git checkout -b feature/your-feature
  3. Commit по Conventional Commits: feat(scope): description
  4. Push и откройте Pull Request с описанием изменений

Перед PR: npm run check, npm test и npm run build (в web/) должны проходить без ошибок.

About

No description, website, or topics provided.

Resources

Stars

16 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages