From 8da6fa4d1da0d5ea6dd312fc44862b2080ee0cc8 Mon Sep 17 00:00:00 2001 From: dmitriylaukhin Date: Sat, 27 Jun 2026 10:30:00 +0500 Subject: [PATCH] =?UTF-8?q?feat(infra):=20=D0=BA=D0=BE=D0=BD=D1=82=D0=B5?= =?UTF-8?q?=D0=B9=D0=BD=D0=B5=D1=80=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8F=20?= =?UTF-8?q?=D0=B8=20=D0=B4=D0=B5=D0=BF=D0=BB=D0=BE=D0=B9=20=D0=B7=D0=B0=20?= =?UTF-8?q?Caddy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- infra/Caddyfile | 15 +++++++++++ infra/deploy/Caddyfile | 23 ++++++++++++++++ infra/deploy/Dockerfile | 24 +++++++++++++++++ infra/deploy/README.md | 42 +++++++++++++++++++++++++++++ infra/deploy/docker-compose.yml | 35 ++++++++++++++++++++++++ infra/docker-compose.yml | 48 +++++++++++++++++++++++++++++++++ 6 files changed, 187 insertions(+) create mode 100644 infra/Caddyfile create mode 100644 infra/deploy/Caddyfile create mode 100644 infra/deploy/Dockerfile create mode 100644 infra/deploy/README.md create mode 100644 infra/deploy/docker-compose.yml create mode 100644 infra/docker-compose.yml diff --git a/infra/Caddyfile b/infra/Caddyfile new file mode 100644 index 0000000..2a9f3b4 --- /dev/null +++ b/infra/Caddyfile @@ -0,0 +1,15 @@ +# Маршрутизация MedArchive за Caddy на Selectel KZ. +# /api/* — бэкенд, /demo — презентация (reveal.js), остальное — приложение. +# Reload только через stdin (sed -i ломает bind mount). +med.secondbrain.tools { + handle_path /api/* { + reverse_proxy api:8000 + } + handle /demo* { + root * /srv + file_server + } + handle { + reverse_proxy web:3000 + } +} diff --git a/infra/deploy/Caddyfile b/infra/deploy/Caddyfile new file mode 100644 index 0000000..f93f81e --- /dev/null +++ b/infra/deploy/Caddyfile @@ -0,0 +1,23 @@ +{ + email admin@secondbrain.tools + acme_ca https://acme-v02.api.letsencrypt.org/directory +} + +med.secondbrain.tools { + handle_path /api/* { + reverse_proxy api:8000 + } + handle /demo* { + root * /srv + file_server + } + handle /day1* { + root * /srv + file_server + } + handle { + root * /web + file_server + encode gzip + } +} diff --git a/infra/deploy/Dockerfile b/infra/deploy/Dockerfile new file mode 100644 index 0000000..a66383c --- /dev/null +++ b/infra/deploy/Dockerfile @@ -0,0 +1,24 @@ +# Образ API MedArchive: зависимости ставятся один раз в слой образа, код проекта +# монтируется томом (/root/medarchive:/app) — правки кода не требуют пересборки. +# Тяжёлые зависимости (pdfplumber, pymupdf, google-genai) нужны для приёма ZIP через +# интерфейс (/ingest): распаковка, распознавание и нормализация идут прямо на сервере. +FROM python:3.12-slim + +WORKDIR /app + +# Зависимости рантайма. Версии — как в pyproject.toml. +RUN pip install --no-cache-dir \ + "fastapi>=0.115" \ + "uvicorn[standard]>=0.32" \ + "pydantic>=2.9" \ + "python-multipart>=0.0.12" \ + "openpyxl>=3.1" \ + "xlrd>=2.0" \ + "python-docx>=1.1" \ + "pdfplumber>=0.11" \ + "pymupdf>=1.24" \ + "rapidfuzz>=3.10" \ + "numpy>=2.0" \ + "google-genai>=1.0" + +CMD ["uvicorn", "api.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/infra/deploy/README.md b/infra/deploy/README.md new file mode 100644 index 0000000..5894d92 --- /dev/null +++ b/infra/deploy/README.md @@ -0,0 +1,42 @@ +# Деплой MedArchive + +Контейнер API (FastAPI) за общим Caddy на Selectel KZ. Read-only поиск работает на +готовой SQLite-базе; приём ZIP через интерфейс (`/ingest`) дораспаковывает и нормализует +новые прайсы прямо на сервере, поэтому в образ включены тяжёлые зависимости (pdfplumber, +pymupdf, google-genai), а контейнеру нужен ключ Gemini. + +## Раскладка на сервере + +- `/root/medarchive/` — код и данные, монтируется в контейнер как `/app`: + - `api/`, `contracts/`, `etl/` — код (правки кода не требуют пересборки образа); + - `web/` — статический фронт (его же отдаёт Caddy); + - `data/medarchive.db` — собранная база, `data/quality_report.json` — отчёт сборки; + - `data/dict_emb.npy` — кэш эмбеддингов справочника (чтобы догрузка не пересчитывала его); + - `data/reference/dictionary.xlsx` — эталонный справочник услуг для нормализации. +- `/root/med-hackathon/` — каталог Caddy: `docker-compose.yml`, `Dockerfile`, `Caddyfile`, + `site/` (презентации) и `.env` с `GEMINI_API_KEY` (в публичный репозиторий не попадает). + +## Шаги + +1. Залить код и данные в `/root/medarchive/` (см. раскладку выше). +2. Положить `docker-compose.yml`, `Dockerfile`, `Caddyfile` в `/root/med-hackathon/`. +3. Создать `/root/med-hackathon/.env`: + ``` + GEMINI_API_KEY=… + ``` +4. Собрать образ и поднять стек: + ```bash + cd /root/med-hackathon + docker compose build api + docker compose up -d + ``` +5. Маршруты Caddy: `/api/*` → контейнер API, `/` → фронт, `/demo` и `/day1` → презентации. + +## Проверка + +```bash +curl https://med.secondbrain.tools/api/health +curl https://med.secondbrain.tools/api/stats +# приём архива через интерфейс: +curl -X POST https://med.secondbrain.tools/api/ingest -F "file=@archive.zip" +``` diff --git a/infra/deploy/docker-compose.yml b/infra/deploy/docker-compose.yml new file mode 100644 index 0000000..fb5fab1 --- /dev/null +++ b/infra/deploy/docker-compose.yml @@ -0,0 +1,35 @@ +services: + caddy: + image: caddy:2-alpine + container_name: caddy + restart: unless-stopped + ports: + - "80:80" + - "443:443" + volumes: + - ./Caddyfile:/etc/caddy/Caddyfile:ro + - ./site:/srv:ro + - /root/medarchive/web:/web:ro + - caddy_data:/data + - caddy_config:/config + + api: + build: + context: . + dockerfile: Dockerfile + image: medarchive-api:latest + container_name: medarchive-api + restart: unless-stopped + working_dir: /app + volumes: + - /root/medarchive:/app + environment: + MEDARCHIVE_DB: /app/data/medarchive.db + # Ключ Gemini для распознавания и эмбеддингов при загрузке архива (/ingest). + # Значение — из .env рядом с этим файлом (в публичный репозиторий не попадает). + GEMINI_API_KEY: ${GEMINI_API_KEY:-} + command: ["uvicorn", "api.main:app", "--host", "0.0.0.0", "--port", "8000"] + +volumes: + caddy_data: + caddy_config: diff --git a/infra/docker-compose.yml b/infra/docker-compose.yml new file mode 100644 index 0000000..20c74c0 --- /dev/null +++ b/infra/docker-compose.yml @@ -0,0 +1,48 @@ +# Стек MedArchive: PostgreSQL+pgvector, API (FastAPI), фронт (Next.js). +# Деплой — на Selectel KZ за общим Caddy (см. Caddyfile). Dockerfile'ы api/web +# добавляет агент F. Секреты — только из .env (в репозиторий не попадают). +services: + db: + image: pgvector/pgvector:pg16 + restart: unless-stopped + environment: + POSTGRES_DB: medarchive + POSTGRES_USER: medarchive + POSTGRES_PASSWORD: ${DB_PASSWORD} + volumes: + - db_data:/var/lib/postgresql/data + - ../db/migrations:/docker-entrypoint-initdb.d:ro # схема накатывается при инициализации + healthcheck: + test: ["CMD-SHELL", "pg_isready -U medarchive -d medarchive"] + interval: 5s + timeout: 3s + retries: 12 + + api: + build: + context: .. + dockerfile: infra/api.Dockerfile + restart: unless-stopped + environment: + DATABASE_URL: postgresql://medarchive:${DB_PASSWORD}@db:5432/medarchive + GEMINI_API_KEY: ${GEMINI_API_KEY} + depends_on: + db: + condition: service_healthy + ports: + - "8000:8000" + + web: + build: + context: ../web + dockerfile: ../infra/web.Dockerfile + restart: unless-stopped + environment: + NEXT_PUBLIC_API_BASE: /api + depends_on: + - api + ports: + - "3000:3000" + +volumes: + db_data: