Перейти к основному содержимому

Установка и запуск

Экосистема Dhamma.gift — это несколько независимых git-репозиториев, которые вместе образуют один продукт:

РепозиторийЧто это
dg-nodeОсновной сайт (Express + SPA) — поиск, ридер, этот docs-портал (dg-docs/)
dg-app-fullОфлайн-приложение Android/iOS (Capacitor). Свой UI собирает из чекаута dg-node — руками ничего не копируется
dgЛегаси PHP-сайт — источник ассетов (assets/), 4nt, TTS-плеера (read/) и старых страниц (config/, login/, memo/), на которые dg-node ссылается симлинками
offline-dataСобственные переводы проекта (лучший ru/en перевод, второе мнение, AI-перевод)
suttacentral/sc-dataВнешний репозиторий SuttaCentral — пали-тексты и переводы в формате Bilara (не наш, публичный)
dgift_botTelegram-бот (Python)
dg-twaAndroid-приложение Dhamma.Gift online (Bubblewrap/TWA)
dictPluginБраузерное расширение (Chrome/Firefox) + userscript

Только сам сайт (dg-node) можно поднять полностью автоматически одним скриптом — остальное (легаси-репо, тексты) он использует как внешние данные, подключаемые симлинками (см. CLAUDE.md → "Прод: пути и symlinks"), а не как npm-зависимости.

Сайт (dg-node) — с нуля до работающего сервера

Требуется Node.js 22.5 или новее (в продакшене 24.20.0), npm и git. Ограничение по версии не пожелание: сервер читает поисковую базу через node:sqlite, а он появился только в 22.5. setup.sh проверяет это первым делом и останавливается с внятным сообщением, а не даёт упасть позже.

git clone https://github.com/dhammagift/dg-node.git
cd dg-node
./scripts/setup.sh # несколько минут, в основном клонирование текстов
./scripts/start.sh # → http://localhost:3000

Это всё. setup.sh за один проход делает:

  1. Разреженно клонирует три внешних репозитория рядом с dg-node — по умолчанию в ../dg-data/:
    • suttacentral/sc-data (sc_bilara_data/, structure/) — палийские тексты
    • dhammagift/offline-data (dhammagift/) — переводы проекта
    • dhammagift/dg (assets/, 4nt/, config/, login/, memo/, read/) — легаси-ассеты
  2. Пересоздаёт симлинки siteroot/{assets,4nt,config,login,memo,read} и siteroot/data/{suttacentral.net,dhammagift} на свежие клоны (то же самое делает CI, только без sudo и системных путей — эта версия рассчитана на обычную машину разработчика).
  3. npm install — и в корне dg-node, и в dg-docs/ (этот портал документации).
  4. npm run build-db — скелет поиска dg_db_light.json, его использует легаси-сервер на Express.
  5. npm run build-search-dbdg.db, корпус SQLite/FTS5, который читает продакшен-сервер. Около 600 МБ и минута-две; это самый долгий шаг.
  6. node test-search-db.js — проверяет собранную базу на вменяемость ещё до первого запуска сервера.

Запускать повторно безопасно: уже склонированные репозитории не трогаются (если не передан --force), симлинки просто пересоздаются, базы пересобираются с нуля.

DATA_DIR=/другой/путь ./scripts/setup.sh — если не хочется клонировать рядом с dg-node.

Проверить, что работает:

http://localhost:3000/search?q=kacchapa&scope=dhamma&langs=ru,en
http://localhost:3000/dn22:1.1

Какой сервер запущен

dg-fastify.js — прод, крутится под pm2 как dg-prod.

ДвижокЗапускПорт по умолчанию
dg-fastify.jsFastify, SQLite FTS5 (dg.db)./scripts/start.sh, npm start или npm run start:fastify3000
dg-light.jsExpress, ищет вызовом greplegacy, в проде не используетсяnpm run start:express3001

scripts/start.sh поднимает первый — именно он работает в продакшене. dg-light.js оставлен только для истории — его никто не require()'ит и он больше не синхронизируется с клиентом.

Портал документации (то, что вы сейчас читаете) собирается отдельно, в двух локалях:

cd dg-docs
npm run build # английская версия → build/
npm run build:ru # русская версия → build-ru/

Результат сборки в .gitignore, поэтому на свежем клоне dg-docs/build/ нет вовсе — маршрут /docs будет пустым, пока не соберёте.

Поисковая база (dg.db) и сервер на Fastify

Fastify-сервер появился потому, что медленным местом был не grep: полный /search?q=dukkha занимал около 42 секунд, из которых на сам grep уходило полсекунды — остальное съедало перечитывание тысяч JSON-файлов, чтобы набрать для находок цитаты, переводы и заголовки. Перенос всего корпуса в SQLite превратил это обогащение из обхода файловой системы в индексную выборку, и тот же запрос теперь отвечает примерно за две секунды.

Сборка

npm run build-search-db # → dg.db, около 600 МБ, ~85 секунд

Скрипт читает корпус напрямую — те же деревья, которые подключает scripts/setup.sh, — и всё нужное выводит сам, поэтому он не зависит ни от dg_db_light.json, ни от предварительного npm run build-db. (Скелет по-прежнему нужен Express-серверу; два конвейера сознательно независимы.) scripts/setup.sh его пока не вызывает, так что на свежем клоне запустите руками перед первым стартом Fastify-сервера.

dg.db — генерируемый файл, он в .gitignore. Править его руками нельзя: следующая сборка перезапишет файл целиком. Всё, что пишет человек — подписи переводчиков, режимы ридера, приоритет переводчиков — живёт в configs/.

Проверить свежесобранную базу:

node test-search-db.js

Проверяются инварианты, которые молча сломала бы регрессия сборки: что корпус на месте, что индекс ищет подстроку, а не префикс, что сворачиваются диакритика и ё/е, что скрытые переводы в индекс не попали, что ни один сегмент не сохранён дважды и что поисковые запросы идут по индексам, а не сканированием.

Версия Node

Сервер читает базу через node:sqlite — он встроен в Node начиная с 22.5, никакого better-sqlite3 или другого нативного модуля собирать не нужно. Из-за этого версия Node становится жёстким требованием, а не пожеланием: на более старой require('node:sqlite') бросает ERR_UNKNOWN_BUILTIN_MODULE, и сервер не стартует.

Запуск

npm run start:fastify # порт 3000
PORT=3005 npm run start:fastify # или любой другой

Под PM2

pm2 start dg-fastify.js --name dg-fastify -i 2 --max-memory-restart 700M
pm2 save

Прежде чем так делать, стоит знать две вещи.

В кластерном режиме PM2 запускает воркеров своим собственным Node, а не тем, что в PATH. Если демон PM2 был поднят на старой версии, каждый воркер умирает на первом же require('node:sqlite') — а поскольку в кластере PM2 гонит вывод воркера через IPC, умирает он до того, как что-то попадёт в лог: получается бесконечный цикл рестартов при пустых лог-файлах. В fork-режиме проблема не видна, там запускается свежий node из PATH. Проверить: ls -l /proc/$(pgrep -f 'God Daemon')/exe; лечится правкой PATH в /etc/systemd/system/pm2-root.service на актуальный Node и затем systemctl daemon-reload && pm2 save && pm2 update.

Задайте потолок по памяти. Каждый воркер держит свою копию транслитерационного рантайма и под нагрузкой устаканивается на 550–600 МБ, поэтому два воркера на машине с 4 ГБ — комфортно, три — уже нет. max_memory_restart перезапустит разбухший воркер, пока второй продолжает обслуживать запросы.

Кластер здесь оправдан тем, что тяжёлый поиск занимает событийный цикл на всё время работы: node:sqlite синхронный, а обогащение — это CPU-нагруженный JavaScript. В одном процессе дешёвый запрос, пришедший во время тяжёлого, ждёт его окончания (замерено: 0,07 с сам по себе и 2,2 с за спиной у поиска dukkha); с двумя воркерами он отвечает сразу.

Telegram-бот (dgift_bot)

Требуется: Python 3.9+.

git clone https://github.com/dhammagift/dgift_bot.git
cd dgift_bot
./setup.sh

Скрипт создаёт venv (telegram/), ставит зависимости (python-telegram-bot>=20, watchdog) и заготавливает два конфига — config.dgift_bot.json и config.dhammagift_bot.json (это один и тот же код, две учётные записи бота под разными именами — см. страницу Telegram-бота). В каждый нужно вписать настоящий TOKEN. Дальше:

telegram/bin/python main.py config.dgift_bot.json

Для прод-варианта (systemd-сервисы, автозапуск обоих ботов) — готовый install_sysctl_bots.sh в том же репозитории.

Автодополнение слов и уведомления watcher.py рассчитаны на соседний запущенный dg-node (читают assets/texts/...) — при их отсутствии бот не падает, просто эти функции молча отключаются.

Android-приложение (dg-twa)

Требуется: JDK 17, Android SDK (API 36, build-tools 36.0.0).

git clone https://github.com/dhammagift/dg-twa.git
cd dg-twa
./scripts/build.sh

Собирает APK/AAB той же командой, что и CI (./gradlew app:assembleRelease/bundleRelease). Результат не подписан — в CI подпись накладывается отдельным шагом из секретов (KEYSTORE_BASE64 и т.п.), локально нужно подписать самостоятельно (apksigner) перед установкой на устройство.

Браузерное расширение (dictPlugin)

Сборки для Chrome и Firefox уже лежат готовыми папками в репозитории (browser-extention/dictLookup-extention-*-{chrome,firefox}/) — сборочный шаг не нужен. Для локальной проверки: chrome://extensions → «Режим разработчика» → «Загрузить распакованное расширение» → выбрать нужную папку.

См. также

Справочник по всем эндпоинтам сайта — Swagger/OpenAPI, живёт по соседству в этом же разделе.