tgit — git в Телеграме

Команда коммитит код из Telegram, а бот проверяет его, разворачивает
в песочнице, следит за сервисами и отвечает на вопросы по логам
Проект курса «Python» · НИУ ВШЭ · 2026

Задача

Исходная постановка

  • бот загружает .py-файлы в отдельный репозиторий;
  • push автоматически запускает CI/CD;
  • развёртывание на VPS или локально.

Согласованные расширения

  • статус CI/CD в чате;
  • алерты о падениях и перегрузке;
  • удобные логи и LLM-помощник по ним.

Что важно при этом не сломать

  • случайный человек, нашедший бота, не должен ничего сделать;
  • чужой код запускается на общем сервере;
  • промт к LLM и сами логи — недоверенный ввод;
  • внешние API (Telegram, Gitea, SourceCraft) рвут соединения.

Безопасность — не отдельный пункт, а требование к каждому разделу.

2

Что умеет бот вход только по одноразовому QR

📝 Коммит

Дерево репозитория, новые файлы текстом или документом, замена, удаление.

Черновик, сообщение, push с проверкой конфликтов.

Статус CI → CD прямо в сообщении о push.

🩺 Статус

Все контейнеры: 🟢 🟡 🔴 ⚪.

Карточка: health, аптайм, рестарты, CPU/RAM от лимита, ревизия.

7 проверок с таймаутами, перезапуск (админ).

📜 Логи

Хвост за период, свой grep -i -v -E -c -C — в Python, не в shell.

🤖 Вопрос словами: LLM строит запрос, бот ищет сам.

🚨 Алерты

💥 краш · 🧠 OOM · 🔁 рестарты · 🩺 unhealthy · 🔥 перегрузка · ⛔ деплой · 💾 диск · 🔌 Docker · 🛡 атака.

Всем участникам, без спама, «✅ восстановлено» ответом.

👥 Участники (админ): выпуск QR, список, мгновенный отзыв доступа.

3

Архитектура

сервер (VPS) · Docker Compose
tgit-bot — один процесс asyncio · aiogram 3
bot/
меню, FSM в БД, роутеры, middleware доступа
repo/ · pipeline/
черновики, push, наблюдатель CI → CD
runtime/ · alerts/
состояние, проверки, детекторы, рассылка
logs/ · agent/
grep-движок, фильтр → страж → планировщик
access/
QR-приглашения, антиперебор
llm/
провайдер за протоколом
PostgreSQL
FSM, черновики, алерты
docker-socket-proxy
только GET: список, inspect, stats, logs
tgit-runner (ssh, forced command)
deploy · restart · remove — единственный способ запустить код
tgit-svc-*
сервисы команды в песочнице
Telegram
long polling — без входящих портов
Gitea
tgit-uploads: код команды · Actions: CI
LLM API (OpenAI-совместимый)
только промт и список контейнеров
Репозиторий бота
Gitea Actions: ruff · mypy · pytest → CD на сервер
4

Коммит → CI → CD

Черновикпроверка имени, UTF-8, 100 КБ, ast.parse
→
Pushодна очередь, конфликты с базой черновика, трейлер автора
→
CI · Gitea Actionscompile · ruff · bandit · pytest
→
PipelineWatcherпрогон по head_sha, опрос 10 с, только пока есть кого ждать
→
CD · tgit-runnerтолько после зелёного CI
✅ Запушено: a1b2c3d — feat: кэш CI: ✅ пройден https://gitea…/actions/runs/5225 CD: 🚀 hello развёрнуто

Сообщение о push редактируется на месте; исходы — отдельными сообщениями автору: правка не присылает уведомление.

  • ❌ CI — упавший шаг и хвост лога, длинный — файлом; CD не запускается;
  • состояние в БД — после рестарта бот продолжает с того же места;
  • CI закончились не по порядку — старый push не откатит сервис;
  • CI за протоколом CIProvider: SourceCraft — вторая реализация.
5

Песочница для чужого кода

docker run tgit-svc-hello --user 10001:10001 --read-only --tmpfs /tmp:size=16m,noexec --cap-drop ALL --security-opt no-new-privileges --memory 128m --memory-swap 128m --cpus 0.25 --pids-limit 64 --network none

Сеть — только если её просит сервис и разрешил админ на хосте. Не больше 10 сервисов.

  • бот не видит docker.sock: чтение — через read-only прокси, запуск — только tgit-runner;
  • SSH-ключ бота прибит к одной команде (command=…,restrict), аргументы валидируются;
  • код везёт бот: git archive в stdin — у хоста нет доступа к репозиторию;
  • новая версия заменяет старую, только став healthy — иначе ⛔ и алерт команде.

examples/escape_attempts запись в FS, интернет, DNS, docker.sock, setuid(0), raw-сокет, fork-бомба — все 9 BLOCKED

6

LLM-агент по логам LLM ничего не исполняет

1 · Фильтрбез LLM: длина, команды, ; $(…), пути к секретам, «игнорируй инструкции»
→
2 · Стражотдельный вызов LLM: JSON safe | attack; мусор = атака
→
3 · Планировщикотдельный вызов: только JSON LogQuery
→
4 · Валидацияpydantic, белый список контейнеров, ≤ 7 дней, анализ бэктрекинга
→
5 · Поискбот сам, модуль regex с таймаутом
— когда за час были ошибки подключения? grep -i -E 'connection (refused|reset)' --since 1h hello 14:02:11 ERROR connection refused …
  • логи в LLM не передаются — injection из логов невозможен;
  • атака → вежливый отказ и 🛡 администраторам;
  • LLM недоступен → «попробуйте позже», не атака;
  • провайдер за протоколом, в тестах — сценарный фейк; квота 30 в сутки.
7

Алерты без шторма

  • по состоянию, а не по событиям: каждые 15 с детекторы — чистые функции над снапшотами; условие появилось — алерт открыт, пропало — закрыт;
  • дедупликация kind + service: повтор в 10 мин — счётчик в исходном сообщении, не новое;
  • гистерезис: закрытие через 60 с без условия, правки — не чаще раза в минуту;
  • краш при restart-политике ловится по росту RestartCount;
  • Docker недоступен — алерты контейнеров не закрываются вслепую.
💥 Краш · tgit-svc-crash 14:31 UTC · повторов: 3 Traceback (most recent call last): … RuntimeError: boom [📜 Логи] [🩺 Статус] [📋 Список]
↳ ✅ Восстановлено · длилось 4 мин
8

Безопасность — сводка

УгрозаЗащита
Случайный пользовательодноразовый QR, 128 бит, 24 ч, в БД только хэш; middleware на каждом апдейте; 5 неудач в час — игнор
Чужой код на серверепесочница, лимиты, без сети; запуск только через tgit-runner с forced command
Docker-сокет = rootу бота только read-only прокси; сам бот отказывает в доступе к чужим контейнерам
Prompt injectionфильтр + отдельный страж + структурный вывод; grep выполняет Python; логи в LLM не идут
ReDoSмодуль regex с таймаутом, длина шаблона ≤ 200, анализ вложенных квантификаторов
Поломка CI из бота.gitea/, .sourcecraft/, .tgit/ из Telegram не редактируются
Утечка секретовтолько в .env, SecretStr, не в логах и не в LLM
9

Качество и процесс

358
тестов pytest, ~1 мин
92 %
покрытие (порог 80 %)
strict
mypy по всему app/ + ruff
16
ADR с альтернативами
10

Демонстрация

  1. Чужой пишет боту — одна фраза и тишина
  2. QR-приглашение, вход, повторный скан — отказ
  3. Коммит из Telegram → CI ✅ → сервис развёрнут
  4. Коммит с ошибкой ruff → CI ❌ с логом, сервис не тронут
  5. Статус и проверки, свой grep
  6. Вопрос агенту → grep + строки
  7. Атака на агента → отказ, 🛡 админу
  8. Падающий сервис → 💥 всем → ✅ восстановлено

Запасной план

Нет сети — видео того же сценария.

Нет сервера — make demo-repo · make demo-up · make run на ноутбуке.

Нет LLM — фильтр всё равно отсекает атаку без LLM.

Подробно: docs/DEMO.md

11

Итоги

Сделано

  • все четыре раздела меню и вход по QR;
  • CI/CD сервисов команды с уведомлениями;
  • песочница, проверенная попытками побега;
  • LLM-агент, который не может навредить;
  • автодеплой самого бота.

Дальше

  • CI загрузок в SourceCraft — второй CIProvider;
  • отдельный сервер под сервисы (ADR-005);
  • история и откат файла, «🔕 тишина» для алертов;
  • краткий вывод LLM по найденным строкам с маскированием секретов.

Код, ТЗ, архитектура, ADR: gitea.24studbank.ru/HSE/tgit

12