From 4171ecc410be57da190115bb68edae46d0411168 Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Sun, 4 Oct 2026 05:42:00 +0400 Subject: [PATCH] =?UTF-8?q?feat(dev):=20=D0=BF=D1=80=D0=BE=D1=84=D0=B8?= =?UTF-8?q?=D0=BB=D1=8C=20services=20=D0=B2=20docker-compose=20=E2=80=94?= =?UTF-8?q?=20arm-core,=20tabular-parser,=20scan-gateway,=20scan-emulator?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Сервисы собираются из соседних репозиториев (../../), образы :dev; порты хоста 8000/8001/8002 на ${BIND_ADDR:-127.0.0.1}, внутри контейнера все слушают 8000. Одноразовый arm-core-migrate выполняет alembic upgrade head и seed --dev до старта arm-core. Фрагмент scan-gateway/scan-emulator взят из open-questions-proposal.md scan-gateway; образ каждого репозитория собирается одним сервисом (arm-core, scan-gateway), второй использует тег с pull_policy: never. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2 --- dev/docker-compose.yml | 154 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 153 insertions(+), 1 deletion(-) diff --git a/dev/docker-compose.yml b/dev/docker-compose.yml index 7949922..a3d72c1 100644 --- a/dev/docker-compose.yml +++ b/dev/docker-compose.yml @@ -4,11 +4,35 @@ # одноразовый minio-init завершается (хотя и с кодом 0) — для скриптов указывайте сервисы явно. # Все учётные данные, порты хоста и имя бакета берутся из .env (значения по умолчанию — dev). # Порты публикуются только на 127.0.0.1; адрес привязки меняется через BIND_ADDR (см. .env.example). -# Профиль `services` (arm-core, tabular-parser, scan-gateway, scan-emulator) добавляется в фазе 2. +# Профиль `services` — сервисы системы, собранные из соседних репозиториев workspace (../../, Dockerfile в корне): +# docker compose --profile services up -d --build +# docker compose --profile services up -d --wait arm-core tabular-parser scan-gateway scan-emulator +# Одноразовые minio-init и arm-core-migrate (миграции + seed) в список --wait не входят: они завершаются с кодом 0. +# Внутри контейнера каждый сервис слушает 8000; на хост публикуются 8000 (arm-core), 8001 (tabular-parser), 8002 (scan-gateway). # Фиксированное имя проекта: одинаковые тома и сети из любого worktree (.wt/<срез>/docs/dev). name: arm-ka +# Общие переменные сервисов профиля `services` (формат шаблона команды; адреса — внутри сети compose, не localhost). +x-service-env: &service-env + DEBUG: "true" + OPENBAO__MINIO__MINIO_URL: minio:9000 + OPENBAO__MINIO__MINIO_ADMIN_LOGIN: ${MINIO_ROOT_USER:-minioadmin} + OPENBAO__MINIO__MINIO_ADMIN_PASSWORD: ${MINIO_ROOT_PASSWORD:-minioadmin} + OPENBAO__MINIO__MINIO_BUCKET: ${MINIO_BUCKET:-arm-ka} + OPENBAO__MINIO__MINIO_SECURE: "false" + OPENBAO__NATS__NATS_URL: nats://nats:4222 + NATS_ENABLED: "true" + +# arm-core и его одноразовые миграции: те же переменные плюс БД и dev-эмитент токенов. +x-arm-core-env: &arm-core-env + <<: *service-env + OPENBAO__DATABASE__DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER:-armka}:${POSTGRES_PASSWORD:-armka}@postgres:5432/${POSTGRES_DB:-armka} + # Dev-эмитент JWT (POST /api/v1/dev/token) — только при DEBUG=true; секрет публичный, только для dev. + # Порт хоста по умолчанию на 127.0.0.1 (BIND_ADDR): при BIND_ADDR=0.0.0.0 токен может получить любой в сети. + AUTH_DEV_ISSUER: "true" + AUTH_DEV_SECRET: dev-only-secret-do-not-use-in-production-0123456789 + services: postgres: image: postgres:16.10-alpine @@ -79,6 +103,134 @@ services: mc alias set local http://minio:9000 "$$MINIO_ROOT_USER" "$$MINIO_ROOT_PASSWORD" && mc mb --ignore-existing "local/$$MINIO_BUCKET" + # --- Профиль `services` ------------------------------------------------------------------------------------------- + + # Одноразово: миграции arm-core (alembic upgrade head) и справочники с dev-пользователями (seed --dev). + # Оба шага идемпотентны; при каждом `up` профиля контейнер снова выполняется и завершается с кодом 0. + # Образ собирает сервис arm-core (build только у него): две сборки одного тега дают разные ID образа, и каждый + # следующий `up` пересоздавал бы arm-core-migrate и arm-core. pull_policy: never — образ локальный, не из реестра. + arm-core-migrate: + profiles: ["services"] + image: arm-core:dev + pull_policy: never + restart: "no" + depends_on: + postgres: + condition: service_healthy + environment: *arm-core-env + command: ["sh", "-c", "alembic upgrade head && python -m scripts.seed --dev"] + + arm-core: + profiles: ["services"] + build: + context: ../../arm-core + image: arm-core:dev + restart: unless-stopped + depends_on: + postgres: + condition: service_healthy + nats: + condition: service_healthy + minio: + condition: service_healthy + minio-init: + condition: service_completed_successfully + arm-core-migrate: + condition: service_completed_successfully + environment: *arm-core-env + ports: + - "${BIND_ADDR:-127.0.0.1}:${ARM_CORE_PORT:-8000}:8000" + healthcheck: + # curl в python:3.11-slim нет; /healthcheck всегда отвечает 200, поэтому читаем поле nats + test: + - CMD + - python + - -c + - "import json,sys,urllib.request as u; d=json.load(u.urlopen('http://127.0.0.1:8000/healthcheck', timeout=3)); sys.exit(0 if d['nats']=='ok' else 1)" + interval: 5s + timeout: 5s + retries: 12 + start_period: 10s + + tabular-parser: + profiles: ["services"] + build: + context: ../../tabular-parser + image: tabular-parser:dev + restart: unless-stopped + depends_on: + nats: + condition: service_healthy + minio: + condition: service_healthy + minio-init: + condition: service_completed_successfully + environment: *service-env + ports: + - "${BIND_ADDR:-127.0.0.1}:${TABULAR_PARSER_PORT:-8001}:8000" + healthcheck: + test: + - CMD + - python + - -c + - "import json,sys,urllib.request as u; d=json.load(u.urlopen('http://127.0.0.1:8000/healthcheck', timeout=3)); sys.exit(0 if d['nats']=='ok' else 1)" + interval: 5s + timeout: 5s + retries: 12 + start_period: 10s + + # Эмулятор ИС Сканирование: тот же образ, что у шлюза (собирает сервис scan-gateway, см. arm-core-migrate); общий + # том hotfolder с шлюзом. Переменные MinIO/NATS не нужны. + # Фикстуры ответов ИС — ../../fixtures/ocr (только чтение): пара <имя>.json для входного файла UUID_<имя>.ext. + scan-emulator: + profiles: ["services"] + image: scan-gateway:dev + pull_policy: never + command: ["python", "-m", "src.scan_emulator"] + restart: unless-stopped + environment: + SCAN_HOTFOLDER_PATH: /data/hotfolder + SCAN_FIXTURES_DIR: /fixtures/ocr + SCAN_EMULATOR_DELAY: "2" + volumes: + - hotfolder:/data/hotfolder + - ../../fixtures/ocr:/fixtures/ocr:ro + + scan-gateway: + profiles: ["services"] + build: + context: ../../scan-gateway + image: scan-gateway:dev + restart: unless-stopped + depends_on: + nats: + condition: service_healthy + minio: + condition: service_healthy + minio-init: + condition: service_completed_successfully + environment: + <<: *service-env + SCAN_HOTFOLDER_MODE: local + SCAN_HOTFOLDER_PATH: /data/hotfolder + volumes: + - hotfolder:/data/hotfolder + ports: + - "${BIND_ADDR:-127.0.0.1}:${SCAN_GATEWAY_PORT:-8002}:8000" + healthcheck: + # /healthcheck всегда отвечает 200, поэтому читаем поля nats и hotfolder + test: + - CMD + - python + - -c + - "import json,sys,urllib.request as u; d=json.load(u.urlopen('http://127.0.0.1:8000/healthcheck', timeout=3)); sys.exit(0 if d['nats']=='ok' and d['hotfolder']=='ok' else 1)" + interval: 5s + timeout: 5s + retries: 12 + start_period: 10s + volumes: postgres-data: minio-data: + # Общая горячая папка scan-gateway и scan-emulator (Input/, Output/) + hotfolder: