Files
tgit/docs/ARCHITECTURE.md
T

15 KiB
Raw Blame History

Архитектура tgit

Требования — в SPEC.md, решения и их обоснования — в DECISIONS.md.

1. Общая схема

 Telegram ◄──long polling──┐
                           │
              ┌────────────┴─────────────────────────── хост (VPS / ноутбук) ─────────────┐
              │  tgit-bot (один процесс, asyncio)                                         │
              │   ├─ bot/        aiogram: команды, загрузка файлов, кнопки                │
              │   ├─ uploads/    валидация → GitPublisher (очередь, git + SSH) ───────────┼──► SourceCraft
              │   ├─ ci/         CIWatcher: polling прогонов по sha, логи кубов  ◄────────┼─── REST API
              │   ├─ runtime/    Monitor: события и метрики контейнеров, логи             │
              │   ├─ alerts/     правила, антиспам, доставка                              │
              │   └─ llm/        LogAssistant: вопрос → LogQuery → поиск → ответ ─────────┼──► LLM API
              │        │                         │                                        │
              │   ┌────┴─────┐          ┌────────┴────────┐                               │
              │   │ postgres │          │ docker-proxy    │ (только чтение: ps/logs/      │
              │   └──────────┘          │ socket-proxy    │  events/stats)                │
              │                         └────────┬────────┘                               │
              │                                  │ /var/run/docker.sock                   │
              │   tgit-runner (скрипт на хосте) ─┤                                        │
              │     ▲ ssh, forced command        ▼                                        │
              │     │                    tgit-app-<project> контейнеры (песочница)        │
              └─────┼─────────────────────────────────────────────────────────────────────┘
                    │
      SourceCraft CI (tgit-uploads): check → deploy ──┘

Два репозитория:

Репозиторий Что внутри CI/CD
tgit код бота PR/push dev: lint + types + tests; push main: + деплой бота на хост
tgit-uploads projects/<name>/… от пользователей push main: проверки проекта; успех → ssh tgit-runner deploy <project> <sha>

2. Компоненты

2.1 app/bot — интерфейс Telegram

aiogram 3, роутеры по фичам (upload, projects, logs, ask, admin). Middleware: проверка доступа по белому списку, логирование апдейтов без содержимого файлов. Длинные ответы → файл.

2.2 app/uploads — загрузка и коммит

  • validate(file) -> ValidatedUpload — размер, кодировка, ast.parse, режим # tgit: mode=….
  • GitPublisher — держит локальный клон tgit-uploads в volume; единственная очередь (asyncio.Queue, один воркер), чтобы пуши не конфликтовали. Шаги: fetch → reset --hard origin/main → запись файла → commit → push; при отказе push — повтор с pull --rebase (до 3 раз). git вызывается через asyncio.create_subprocess_exec с GIT_SSH_COMMAND="ssh -i <deploy_key> -o IdentitiesOnly=yes".
  • Пустой diff → «изменений нет», коммита нет.

2.3 app/sourcecraft — клиент API

httpx.AsyncClient, Bearer-токен, ретраи на сетевые ошибки и 5xx (экспоненциально, до 5 попыток), типизированные ответы (pydantic). Методы: list_runs, get_run, get_cube_logs, trigger_workflow. Подробности API — dev/SOURCECRAFT_API.md.

2.4 app/ci — наблюдатель за CI

Фоновая задача. Опрашивает /cicd/runs только пока есть незавершённые загрузки (интервал 10 с, бэкофф до 60 с при ошибках). Сопоставляет прогон с загрузкой по sha коммита, на смене статуса редактирует сообщение пользователя. При падении скачивает лог упавшего куба. Таймаут прогона — 30 мин → статус timeout. Webhooks у SourceCraft нет — отсюда polling (ADR-003).

2.5 app/runtime — мониторинг приложений

Через docker-socket-proxy (только GET на containers/logs/events/stats):

  • поток events с фильтром label=tgit.project → die (код выхода), oom, restart;
  • раз в 15 с stats по контейнерам приложений → CPU% / RAM% от лимита, скользящее окно 1 мин;
  • logs с since/tail — для /logs и LLM.

Управление (stop/restart) бот не делает через сокет — он вызывает tgit-runner (ADR-006).

2.6 app/alerts — алерты

Правила (пороги — в config/alerts.yaml) → событие → антиспам (ключ project+kind, окно 10 мин, счётчик) → доставка владельцу и подписчикам. Системные алерты (недоступен API/Docker/БД) — администраторам.

2.7 app/llm — ассистент по логам

вопрос ──► LLM #1 (JSON-схема LogQuery) ──► валидация ──► поиск по логам ──► маскирование ──► LLM #2 ──► ответ
  • LogQuery: source (app | ci), since (≤ 7 д), pattern (regex ≤ 200 символов), level, limit (≤ 500).
  • Поиск — Python regex с таймаутом (защита от ReDoS), никакого shell (ADR-007).
  • Маскирование: токены, KEY=…, password=…, JWT, e-mail, IP → ***.
  • Провайдер — за протоколом LLMProvider + фабрика по конфигу: OpenAI-совместимый API (YandexGPT, OpenAI, локальная модель) и mock для тестов (ADR-008).

2.8 app/db — хранилище

SQLAlchemy 2 (async) + Alembic. PostgreSQL в Compose; в тестах — SQLite.

Таблица Поля (основные)
users tg_id PK, username, role (user/admin), is_allowed, llm_quota_used, created_at
projects id, name UNIQUE, owner_id → users, mode (service/job), network_enabled, alerts_enabled
uploads id, project_id, user_id, filename, sha, status (committed/ci_running/ci_ok/ci_failed/deployed/timeout), chat_id, message_id, created_at
ci_runs id, upload_id, run_slug, status, failed_cube, url, updated_at
alerts id, project_id, kind, payload JSON, count, first_at, last_at, delivered_at
llm_requests id, user_id, project_id, question, log_query JSON, tokens, created_at

2.9 Конфигурация

app/config.py — pydantic-settings, один класс на подсистему (TelegramSettings, SourceCraftSettings, DockerSettings, LLMSettings, …), всё из .env. Пороги и тексты — config/*.yaml.

3. Потоки данных

3.1 Загрузка → CI → CD

user ──.py──► bot ── validate ──✗──► «ошибка, строка N»
                       │✓
                       ▼
                 GitPublisher ──push──► tgit-uploads@main
                       │                     │ (SourceCraft CI)
   «✅ a1b2c3d, ⏳ CI» ◄┘                     ├─ check: py_compile · ruff · pytest · bandit
                                             └─ deploy: ssh tgit-deploy@host → tgit-runner deploy <project> <sha>
                                                              │
   CIWatcher ──poll runs(sha)──► статус ──► edit message      ▼
                                                   docker build + run tgit-app-<project>
   Monitor ◄── events/stats ── docker-proxy ◄──────────────────┘
      └──► alerts ──► user

3.2 Вопрос к логам

/ask → квота → LLM #1 → LogQuery → валидация → runtime.logs() / sourcecraft.get_cube_logs() → фильтр regex → маскирование → LLM #2 (вопрос + найденные строки) → ответ + запрос + доказательства → запись в llm_requests.

4. Песочница приложений (tgit-runner)

infra/runner/tgit-runner — bash-скрипт на хосте; единственная команда, доступная ключу CI (command="…" в authorized_keys пользователя tgit-deploy). Подкоманды: deploy <project> <sha>, stop <project>, restart <project>.

deploy: забирает архив проекта по sha из tgit-uploads → собирает образ tgit-app-<project>:<sha> из шаблона (python:3.12-slim + requirements.txt) → запускает:

docker run -d --name tgit-app-<project> --label tgit.project=<project> --label tgit.sha=<sha>
  --user 10001:10001 --read-only --tmpfs /tmp:size=16m
  --cap-drop ALL --security-opt no-new-privileges
  --memory 128m --memory-swap 128m --cpus 0.25 --pids-limit 64
  --network none            # или tgit-apps-egress, если сеть разрешена
  --restart on-failure:3    # service; для job — без рестарта
  --log-opt max-size=5m --log-opt max-file=2

5. CI/CD

5.1 Репозиторий бота (tgit/.sourcecraft/ci.yaml)

Триггер Workflow Шаги
PR в dev/main, push в dev check uv sync --frozen → ruff check + ruff format --check → mypy app → pytest --cov=app --cov-fail-under=70
push в main check + deploy → infra/deploy/deploy.sh по SSH (секреты DEPLOY_SSH_KEY, DEPLOY_HOST) → smoke: healthcheck контейнера

5.2 Репозиторий загрузок (tgit-uploads/.sourcecraft/ci.yaml)

Шаблон хранится в tgit/infra/uploads-template/ и ставится в tgit-uploads один раз. Определение изменённого проекта — git diff --name-only HEAD~1 -- projects/ внутри куба.

6. Деплой

Где Как
Любая машина git clone → cp .env.example .env → заполнить → make up
VPS vobot (основной) push в main → CI → infra/deploy/deploy.sh → /srv/tgit (пользователь tgit-deploy, Compose-проект tgit)

deploy.sh (по образцу meditron): git archive → scp → распаковка в app.new → перенос .env → атомарная замена app → docker compose -p tgit up -d --build → ожидание healthcheck → запись REVISION. Все SSH-шаги с ретраями (хост режет часть соединений). Хост задаётся DEPLOY_HOST — тот же скрипт работает для любого сервера.

Бюджет ресурсов на VPS (свободно ~1,9 ГБ RAM): бот ≤ 256 МБ, postgres ≤ 256 МБ, proxy ≤ 32 МБ, приложения ≤ 10 × 128 МБ.

7. Структура репозитория

app/            код бота: bot, uploads, sourcecraft, ci, runtime, alerts, llm, db, config.py, main.py
config/         alerts.yaml, texts.yaml, llm-промпты
infra/
  deploy/       deploy.sh, docker-compose.server.yml
  runner/       tgit-runner, шаблон Dockerfile приложения, authorized_keys.example
  uploads-template/  .sourcecraft/ci.yaml и README для tgit-uploads
examples/       демо-скрипты для загрузки (ok, syntax error, lint fail, crash, cpu hog)
tests/          pytest: unit + интеграционные с фейками
docs/           SPEC, ARCHITECTURE, DECISIONS, dev/, презентация
Dockerfile · docker-compose.yml · Makefile · pyproject.toml · .env.example

8. Что важно учесть

Риск Что делаем
Чужой код на одном сервере с прод-Vobot Песочница §4, лимиты, сеть выключена; общий лимит 10 приложений; в перспективе — отдельный хост (ADR-005)
Docker-сокет = root на хосте Бот — только через read-only proxy; запуск — через tgit-runner с forced command
Пользователь tgit-deploy в группе docker ≈ root Ключ ограничен одной командой; tgit-runner валидирует аргументы (^[a-z][a-z0-9-]{1,30}$, sha hex)
Prompt injection через логи LLM только возвращает LogQuery, который валидируется; ответ LLM — просто текст пользователю
ReDoS в regex от LLM Модуль regex с таймаутом, лимит длины паттерна и объёма логов
Утечка секретов в LLM Маскирование перед отправкой; секреты бота не попадают в логи
Нестабильный SourceCraft API Ретраи, идемпотентность, проверка перед повторным POST
Гонки пушей Одна очередь GitPublisher, pull --rebase при отказе
Лимиты Telegram (4096 символов, ~30 msg/s) Длинное — файлом; редактирование одного сообщения вместо потока
Диск хоста занят на 75% Ротация логов контейнеров, docker image prune старых ревизий приложений в tgit-runner