diff --git a/.env.example b/.env.example index f4d86e2..aa0c465 100644 --- a/.env.example +++ b/.env.example @@ -15,4 +15,12 @@ OLLAMA_MODEL=qwen2.5:3b # MCP-сервер (M5): bearer-токен для ИИ-агентов; пусто — /mcp закрыт -MCP_TOKEN=change-me-mcp-token \ No newline at end of file +MCP_TOKEN=change-me-mcp-token + +# docker-compose (M7) +POSTGRES_USER=gntodo +POSTGRES_PASSWORD=change-me +POSTGRES_DB=gntodo +WEB_PORT=8135 +# GAUTH_REDIRECT_URI в проде — https-адрес callback, зарегистрированный в SSO +# GAUTH_REDIRECT_URI=https://<ваш-домен>/auth/callback \ No newline at end of file diff --git a/backend/.dockerignore b/backend/.dockerignore new file mode 100644 index 0000000..378793d --- /dev/null +++ b/backend/.dockerignore @@ -0,0 +1,7 @@ +.venv +__pycache__ +**/__pycache__ +data +tests +.env +*.egg-info diff --git a/backend/Dockerfile b/backend/Dockerfile index 57d6bfd..88cbb1c 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -2,11 +2,21 @@ COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv +# uv ставит git-зависимости (gnexus-gauth) — нужен git +RUN apt-get update && apt-get install -y --no-install-recommends git \ + && rm -rf /var/lib/apt/lists/* + WORKDIR /srv/app COPY pyproject.toml uv.lock ./ RUN uv sync --frozen --no-dev +# Миграции выполняются при старте контейнера (см. CMD) +COPY alembic.ini ./ +COPY alembic ./alembic COPY app ./app +ENV ATTACHMENTS_PATH=/data/attachments +RUN mkdir -p /data/attachments + EXPOSE 8134 -CMD ["uv", "run", "--no-dev", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8134"] \ No newline at end of file +CMD ["sh", "-c", "uv run --no-dev alembic upgrade head && uv run --no-dev uvicorn app.main:app --host 0.0.0.0 --port 8134"] diff --git a/docker-compose.yml b/docker-compose.yml index bc17dca..8b3610e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,43 +1,74 @@ -# Прод-композиция для VPS. Разработка — docker-compose.dev.yml или нативно (uv, npm). +# M7: упаковка в docker-compose — API, PostgreSQL, фронт (nginx), Ollama + модель. +# Настройки читаются из .env в корне репо (шаблон — .env.example). services: - postgres: + db: image: postgres:17-alpine environment: - POSTGRES_USER: gntodo + POSTGRES_USER: ${POSTGRES_USER:-gntodo} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-gntodo} - POSTGRES_DB: gntodo + POSTGRES_DB: ${POSTGRES_DB:-gntodo} volumes: - pgdata:/var/lib/postgresql/data healthcheck: - test: ["CMD-SHELL", "pg_isready -U gntodo"] + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-gntodo}"] interval: 5s - retries: 5 + timeout: 3s + retries: 12 + + ollama: + image: ollama/ollama:latest + volumes: + - ollama:/root/.ollama + healthcheck: + test: ["CMD-SHELL", "ollama ls >/dev/null 2>&1"] + interval: 10s + timeout: 5s + retries: 12 + + # Одноразовая загрузка модели (idempotent: pull обновляет, если уже есть) + ollama-init: + image: ollama/ollama:latest + depends_on: + ollama: + condition: service_healthy + environment: + OLLAMA_HOST: http://ollama:11434 + command: ["ollama", "pull", "${OLLAMA_MODEL:-qwen2.5:3b}"] + restart: "no" api: build: ./backend - env_file: .env - environment: - DATABASE_URL: postgresql+psycopg://gntodo:${POSTGRES_PASSWORD:-gntodo}@postgres:5432/gntodo - depends_on: - postgres: - condition: service_healthy restart: unless-stopped + depends_on: + db: + condition: service_healthy + ollama-init: + condition: service_completed_successfully + environment: + DATABASE_URL: postgresql+psycopg://${POSTGRES_USER:-gntodo}:${POSTGRES_PASSWORD:-gntodo}@db:5432/${POSTGRES_DB:-gntodo} + GAUTH_BASE_URL: ${GAUTH_BASE_URL} + GAUTH_CLIENT_ID: ${GAUTH_CLIENT_ID} + GAUTH_CLIENT_SECRET: ${GAUTH_CLIENT_SECRET} + GAUTH_REDIRECT_URI: ${GAUTH_REDIRECT_URI} + SESSION_SECRET: ${SESSION_SECRET} + OLLAMA_BASE_URL: http://ollama:11434 + OLLAMA_MODEL: ${OLLAMA_MODEL:-qwen2.5:3b} + MCP_TOKEN: ${MCP_TOKEN:-} + ATTACHMENTS_PATH: /data/attachments + volumes: + - attachments:/data/attachments + expose: + - "8134" web: build: ./frontend - ports: - - "80:80" + restart: unless-stopped depends_on: - api - restart: unless-stopped - - # Включается при M2 (автодетализация LLM). Модель — конфигом (OLLAMA_MODEL). - # ollama: - # image: ollama/ollama - # volumes: - # - ollama:/root/.ollama - # restart: unless-stopped + ports: + - "${WEB_PORT:-8135}:80" volumes: pgdata: - # ollama: \ No newline at end of file + attachments: + ollama: \ No newline at end of file diff --git a/frontend/.dockerignore b/frontend/.dockerignore new file mode 100644 index 0000000..06e6038 --- /dev/null +++ b/frontend/.dockerignore @@ -0,0 +1,3 @@ +node_modules +dist +*.tsbuildinfo diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 8ea3c41..c951db6 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,12 +1,12 @@ FROM node:22-alpine AS build WORKDIR /build -COPY package.json ./ +COPY package.json package-lock.json .npmrc ./ # Зависимости (gnexus-ui-kit и др.) через git — нужен git -RUN apk add --no-cache git && npm install +RUN apk add --no-cache git && npm ci COPY . . RUN npm run build FROM nginx:alpine COPY --from=build /build/dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf -EXPOSE 80 \ No newline at end of file +EXPOSE 80 diff --git a/frontend/nginx.conf b/frontend/nginx.conf index 8b9dbb5..1fa3b76 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -9,6 +9,25 @@ try_files $uri $uri/ /index.html; } + # SSO-флоу gnexus-gauth проходит через backend + location /auth/ { + proxy_pass http://api:8134; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # MCP для ИИ-агентов: streamable HTTP (SSE) — без буферизации + location /mcp/ { + proxy_pass http://api:8134; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_buffering off; + proxy_read_timeout 3600s; + } + location /api/ { proxy_pass http://api:8134; proxy_set_header Host $host; @@ -16,4 +35,4 @@ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } -} \ No newline at end of file +}