diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b9d47d..2261361 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Не выпущено +- Добавлен production deployment baseline для Search API и OpenSearch с + постоянными хранилищами и healthcheck. - Добавлено восстановление незавершённой разметки из `localStorage` после перезагрузки страницы. - Добавлена внутренняя лаборатория проверки поисковой выдачи: просмотр документов, оценка релевантности 0–3, комментарии и SQLite-экспорт подписанных снимков. diff --git a/README.md b/README.md index 283f74d..e7a25cc 100644 --- a/README.md +++ b/README.md @@ -10,13 +10,13 @@ Telegram-бот — только часть рабочего окружения ## Текущее состояние Сейчас реализованы Telegram-бот-секретарь версии `0.2.2` и backend версии -`0.8.1`: добавлено сохранение черновика разметки в браузере. +`0.8.2`: добавлен production deployment baseline для Search API и OpenSearch. размеченном наборе запросов. | Компонент | Версия | Состояние | |---|---:|---| | Telegram-бот | `0.2.2` | на Synology работает `0.2.1`; обновление после слияния | -| Backend | `0.8.1` | добавлено сохранение черновика разметки | +| Backend | `0.8.2` | добавлен production deployment baseline | | Frontend | — | ещё не создан | | Сбор и обработка правовых данных | `0.8.0` | добавлены relevance set и оценка выдачи | | RAG и база знаний | — | ещё не созданы | @@ -59,4 +59,4 @@ python3 -m unittest discover -s tools/telegram-bot -v --- -Акылдаш · Telegram-бот v0.2.2 · Backend v0.8.1 · Frontend — не создан +Акылдаш · Telegram-бот v0.2.2 · Backend v0.8.2 · Frontend — не создан diff --git a/backend/README.md b/backend/README.md index a54e12b..3d53598 100644 --- a/backend/README.md +++ b/backend/README.md @@ -1,6 +1,6 @@ # Backend Акылдаш -Версия: `0.8.1` +Версия: `0.8.2` Первая backend-область проекта — загрузка правовых документов из официального Open Data API ЦБД Минюста Кыргызской Республики. Код расположен в @@ -240,4 +240,4 @@ PYTHONPATH=backend python3 -m search.query "ЖЧК ачуу тартиби" --la --- -Акылдаш · Backend v0.8.1 · Frontend — не создан +Акылдаш · Backend v0.8.2 · Frontend — не создан diff --git a/backend/ingestion/minjust_cbd.py b/backend/ingestion/minjust_cbd.py index 99b1760..e7ac1bb 100644 --- a/backend/ingestion/minjust_cbd.py +++ b/backend/ingestion/minjust_cbd.py @@ -21,7 +21,7 @@ from datetime import datetime, timezone from pathlib import Path from typing import Callable, Iterable -APP_VERSION = "0.8.1" +APP_VERSION = "0.8.2" API_BASE_URL = "https://cbd.minjust.gov.kg/api/v1/OpenData/" LANGUAGES = {"Rus": "ru", "Kyr": "ky"} IMAGE_LANGUAGES = {"Russian": "ru", "Kyrgyz": "ky"} diff --git a/backend/normalization/minjust_cbd.py b/backend/normalization/minjust_cbd.py index 6e94181..a3047f5 100644 --- a/backend/normalization/minjust_cbd.py +++ b/backend/normalization/minjust_cbd.py @@ -23,7 +23,7 @@ from pathlib import Path from typing import Callable from urllib.parse import urlsplit -APP_VERSION = "0.8.1" +APP_VERSION = "0.8.2" SCHEMA_VERSION = "1" NORMALIZER_VERSION = "1.0.0" LANGUAGES = ("ru", "ky") diff --git a/backend/search/minjust_opensearch.py b/backend/search/minjust_opensearch.py index 1dbcd11..385824d 100644 --- a/backend/search/minjust_opensearch.py +++ b/backend/search/minjust_opensearch.py @@ -17,7 +17,7 @@ from typing import Iterator from search.catalog import authority_codes, source_code -APP_VERSION = "0.8.1" +APP_VERSION = "0.8.2" LANGUAGES = {"ru", "ky"} DEFAULT_MAPPING = Path(__file__).with_name("minjust-fragments-index.json") diff --git a/deploy/production/.env.example b/deploy/production/.env.example new file mode 100644 index 0000000..bf593d3 --- /dev/null +++ b/deploy/production/.env.example @@ -0,0 +1,8 @@ +# Absolute path on the production host containing minjust-normalized/. +DATA_ROOT=/volume1/docker/akyldash/data +BACKEND_PORT=8080 +SEARCH_INDEX=akyldash-fragments-current +OPENSEARCH_MEM_LIMIT=4g +OPENSEARCH_JAVA_OPTS=-Xms2g -Xmx2g +# Generate with: openssl rand -hex 32 +REVIEW_SECRET=replace-with-a-random-secret diff --git a/deploy/production/Dockerfile.backend b/deploy/production/Dockerfile.backend new file mode 100644 index 0000000..9d63647 --- /dev/null +++ b/deploy/production/Dockerfile.backend @@ -0,0 +1,11 @@ +FROM python:3.12-slim + +WORKDIR /app +COPY backend /app/backend + +ENV PYTHONPATH=/app/backend +EXPOSE 8080 + +CMD ["python", "-m", "search.api", "--host", "0.0.0.0", "--port", "8080", "--url", "http://opensearch:9200", "--index", "akyldash-fragments-current", "--data", "/app/data/minjust-normalized", "--reviews-db", "/app/data/search-reviews.sqlite3"] + +HEALTHCHECK --interval=30s --timeout=5s --start-period=20s CMD ["python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8080/review', timeout=3)"] diff --git a/deploy/production/Dockerfile.opensearch b/deploy/production/Dockerfile.opensearch new file mode 100644 index 0000000..d1f050b --- /dev/null +++ b/deploy/production/Dockerfile.opensearch @@ -0,0 +1,3 @@ +FROM opensearchproject/opensearch:3.7.0 + +RUN /usr/share/opensearch/bin/opensearch-plugin install --batch analysis-icu diff --git a/deploy/production/README.md b/deploy/production/README.md new file mode 100644 index 0000000..5f200b1 --- /dev/null +++ b/deploy/production/README.md @@ -0,0 +1,37 @@ +# Production deployment + +This compose project runs the Search API and its private OpenSearch node. It +binds the API only to `127.0.0.1`; publish it through an authenticated reverse +proxy or VPN. OpenSearch is not published outside the compose network. + +The current compose intentionally disables the OpenSearch security plugin to +match the existing API client. Keep both services on a private host/network +until authenticated OpenSearch support is implemented. + +## First deployment + +1. Copy this directory to the host with the repository source. +2. Copy `.env.example` to `.env`, set an absolute `DATA_ROOT`, and replace + `REVIEW_SECRET` with a random value. Do not commit `.env`. +3. Put the normalized dataset under `$DATA_ROOT/minjust-normalized/`. +4. Check the rendered configuration: + + ```bash + docker compose --env-file .env -f compose.yaml config + ``` + +5. Start the services: + + ```bash + docker compose --env-file .env -f compose.yaml up -d --build + docker compose --env-file .env -f compose.yaml ps + curl -fsS http://127.0.0.1:${BACKEND_PORT:-8080}/review >/dev/null + ``` + +6. Load the versioned index and switch its alias only after the import and + validation succeed. Back up `DATA_ROOT` and the `opensearch-data` volume + before the first import. + +This is a deployment baseline, not a public internet exposure recipe. TLS, +authentication, backups, monitoring, and a production OpenSearch security +configuration must be provided by the host reverse proxy/operations setup. diff --git a/deploy/production/compose.yaml b/deploy/production/compose.yaml new file mode 100644 index 0000000..116897c --- /dev/null +++ b/deploy/production/compose.yaml @@ -0,0 +1,64 @@ +services: + opensearch: + build: + context: ../.. + dockerfile: deploy/production/Dockerfile.opensearch + restart: unless-stopped + environment: + discovery.type: single-node + bootstrap.memory_lock: "true" + DISABLE_SECURITY_PLUGIN: "true" + OPENSEARCH_JAVA_OPTS: ${OPENSEARCH_JAVA_OPTS:--Xms2g -Xmx2g} + mem_limit: ${OPENSEARCH_MEM_LIMIT:-4g} + expose: + - "9200" + ulimits: + memlock: + soft: -1 + hard: -1 + nofile: + soft: 65536 + hard: 65536 + volumes: + - opensearch-data:/usr/share/opensearch/data + healthcheck: + test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:9200/_cluster/health || exit 1"] + interval: 30s + timeout: 10s + retries: 10 + + backend: + build: + context: ../.. + dockerfile: deploy/production/Dockerfile.backend + restart: unless-stopped + environment: + REVIEW_SECRET: ${REVIEW_SECRET:?set REVIEW_SECRET in .env} + command: + - python + - -m + - search.api + - --host + - 0.0.0.0 + - --port + - "8080" + - --url + - http://opensearch:9200 + - --index + - ${SEARCH_INDEX:-akyldash-fragments-current} + - --data + - /app/data/minjust-normalized + - --reviews-db + - /app/data/search-reviews.sqlite3 + - --review-secret + - ${REVIEW_SECRET} + ports: + - "127.0.0.1:${BACKEND_PORT:-8080}:8080" + depends_on: + opensearch: + condition: service_healthy + volumes: + - ${DATA_ROOT:?set DATA_ROOT in .env}:/app/data + +volumes: + opensearch-data: