Установка и запуск
Экосистема 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_bot | Telegram-бот (Python) |
dg-twa | Android-приложение 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 за один проход делает:
- Разреженно клонирует три внешних репозитория рядом с
dg-node— по умолчанию в../dg-data/:suttacentral/sc-data(sc_bilara_data/,structure/) — палийские текстыdhammagift/offline-data(dhammagift/) — переводы проектаdhammagift/dg(assets/,4nt/,config/,login/,memo/,read/) — легаси-ассеты
- Пересоздаёт симлинки
siteroot/{assets,4nt,config,login,memo,read}иsiteroot/data/{suttacentral.net,dhammagift}на свежие клоны (то же самое делает CI, только безsudoи системных путей — эта версия рассчитана на обычную машину разработчика). npm install— и в корнеdg-node, и вdg-docs/(этот портал документации).npm run build-db— скелет поискаdg_db_light.json, его использует легаси-сервер на Express.npm run build-search-db—dg.db, корпус SQLite/FTS5, который читает продакшен-сервер. Около 600 МБ и минута-две; это самый долгий шаг.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.js | Fastify, SQLite FTS5 (dg.db) | ./scripts/start.sh, npm start или npm run start:fastify | 3000 |
dg-light.js | Express, ищет вызовом grep — legacy, в проде не используется | npm run start:express | 3001 |
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, живёт по соседству в этом же разделе.