Refs #2 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
15 KiB
Архитектура 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 |