CI / ruff · mypy · pytest (pull_request) Successful in 4m3s
- infra/runner/tgit-runner: deploy/restart/remove/list по SSH forced command; аргументы не исполняются и не глобятся; архив — только файлы и папки, без «..», ≤ 20 МБ, есть main.py; health_url — только localhost - песочница: uid 10001, read-only, tmpfs noexec, cap-drop ALL, no-new-privileges, 128 МБ без swap, 0.25 CPU, 64 PID, --network none (сеть — только по egress-allow админа), лимит 10 сервисов, 2 образа - новая версия стартует рядом (-next) и заменяет старую, только став healthy; иначе удаляется, старая работает, exit 3 - бот: после push git archive изменённых services/<name>/ → runner; автору — «🚀 работает» или «⛔ не развёрнут» с логом, команде — алерт ⛔; удалённая папка → remove; «♻️ Перезапустить» в «Статусе» для админа (S-5) - examples/escape_attempts: все попытки выбраться — BLOCKED на живом Docker - install.sh, authorized_keys.example; CD бота обновляет runner на хосте Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
208 lines
16 KiB
Markdown
208 lines
16 KiB
Markdown
# tgit — git в Телеграме
|
||
|
||
Телеграм-бот для команды, которая ведёт общий репозиторий небольших Python-сервисов. Прямо из чата можно поправить
|
||
код и закоммитить его, посмотреть, живы ли контейнеры, поискать по логам — своим grep или вопросом LLM-агенту, —
|
||
и получать алерты, когда что-то упало.
|
||
|
||
Учебный проект, курс Python, НИУ ВШЭ.
|
||
Основной репозиторий и задачи — [Gitea `HSE/tgit`](https://gitea.24studbank.ru/HSE/tgit);
|
||
[SourceCraft](https://sourcecraft.dev/rogachevegorushka/tgit) — зеркало.
|
||
|
||
| Документ | О чём |
|
||
|---|---|
|
||
| [`docs/SPEC.md`](docs/SPEC.md) | ТЗ: требования A-, M-, C-, S-, L-, AL- |
|
||
| [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) | модули, потоки данных, хранилище |
|
||
| [`docs/DECISIONS.md`](docs/DECISIONS.md) | решения ADR-001…015 с альтернативами |
|
||
| [`docs/dev/DEPLOY.md`](docs/dev/DEPLOY.md) | CI/CD и деплой на сервер |
|
||
| [`examples/README.md`](examples/README.md) | демо-сервисы и сценарии для проверки |
|
||
|
||
---
|
||
|
||
## 1. Цели и задачи
|
||
|
||
**Цель** — сделать так, чтобы мелкие правки в общем коде и разбор инцидентов не требовали ноутбука с git и SSH:
|
||
всё, что обычно делают в терминале, доступно из Телеграма, причём безопасно для сервера.
|
||
|
||
**Задачи:**
|
||
- пускать в бота только своих — по одноразовому QR-приглашению, без регистрации;
|
||
- редактировать файлы репозитория и коммитить их из чата с проверкой до пуша;
|
||
- показывать состояние контейнеров и запускать проверки здоровья по кнопке;
|
||
- давать логи — хвост, grep и вопрос на естественном языке, — не открывая shell и не отдавая логи в LLM;
|
||
- сообщать всей команде о падениях и перегрузках без спама;
|
||
- защищаться от «случайных людей» и попыток взлома через бота.
|
||
|
||
## 2. Что реализовано
|
||
|
||
| Раздел | Возможности | Требования |
|
||
|---|---|---|
|
||
| **Доступ** | вход только по одноразовому QR (TTL 24 ч), роли админ/участник, отзыв доступа сразу, молчание на перебор токенов (> 5 неудач в час) | A-1…A-8 |
|
||
| **📝 Коммит** | навигация по папкам, просмотр файла, новый файл/папка, замена (текстом или документом), удаление; проверка имени, размера, UTF-8 и синтаксиса Python; черновик до 50 изменений; сообщение коммита; push с проверкой конфликтов и «Перезаписать» | C-1…C-34 |
|
||
| **🩺 Статус** | все контейнеры 🟢/🟡/🔴/⚪; карточка — health, аптайм, рестарты, CPU/RAM от лимита, ревизия и время деплоя; «🩺 Запустить проверки» — 7 независимых проверок с таймаутами | S-1…S-4 |
|
||
| **📜 Логи** | хвост 50/200 строк или 5 мин/1 ч/24 ч; свой grep (`-i -v -E -c -C N`), выполняется в Python, не в shell; длинный ответ — файлом; журнал поисков | L-1…L-6 |
|
||
| **🤖 Агент** | вопрос словами → фильтр → LLM-страж → LLM строит только `LogQuery` → валидация → grep; показывает построенную команду и строки; атака — отказ и 🛡 админам; 30 вопросов в сутки | L-10…L-17 |
|
||
| **🚨 Алерты** | 💥 краш, 🧠 OOM, 🔁 цикл рестартов, 🩺 unhealthy, 🔥 перегрузка, 💾 мало места, 🔌 Docker недоступен, 🛡 атака; всем участникам, с хвостом лога и кнопками; повторы — счётчик, «✅ восстановлено» ответом; список за 7 дней | AL-1…AL-6 |
|
||
| **🚀 Деплой сервисов** | после push бот отправляет изменённые `services/<name>/` в `tgit-runner` на хосте: сборка образа, запуск в песочнице (uid 10001, read-only, без capabilities и сети, 128 МБ / 0.25 CPU / 64 процесса); старая версия заменяется только здоровой новой, иначе ⛔ с логом; «♻️ Перезапустить» в «Статусе» для админа | #13, S-5 |
|
||
| **CI/CD бота** | Gitea Actions: ruff, mypy strict, миграции up/down/up на PostgreSQL, pytest с порогом покрытия 80 %; автодеплой `dev` на сервер с проверкой health | ADR-015 |
|
||
|
||
Качество: 296 тестов, покрытие 92 %, `mypy --strict`, `ruff`. Telegram в тестах — фейковая сессия aiogram,
|
||
Docker — фейковый Engine API на `httpx.MockTransport`, LLM — сценарный фейк.
|
||
|
||
## 3. Развёртывание и запуск
|
||
|
||
### Требования
|
||
|
||
- Python 3.12 и [uv](https://docs.astral.sh/uv/) — для запуска без Docker и тестов;
|
||
- Docker 24+ с Compose v2 — для полного стека;
|
||
- токен бота от [@BotFather](https://t.me/BotFather) и ваш Telegram ID (узнать — у [@userinfobot](https://t.me/userinfobot));
|
||
- для агента логов — OpenAI-совместимый LLM с structured output (`response_format: json_schema`).
|
||
|
||
Один токен — один запущенный экземпляр: Telegram отдаёт обновления только одному процессу.
|
||
|
||
### Вариант А — локально за 5 минут (без Docker для бота)
|
||
|
||
```bash
|
||
git clone https://gitea.24studbank.ru/HSE/tgit.git && cd tgit
|
||
make install # uv sync
|
||
cp .env.example .env # впишите TELEGRAM_BOT_TOKEN и ADMIN_IDS
|
||
make demo-repo # локальный репозиторий загрузок из шаблона
|
||
# → допишите напечатанную строку GIT_UPLOADS_URL=... в .env
|
||
make run # бот запущен, база — SQLite ./tgit.db
|
||
```
|
||
|
||
Чтобы заработали «Статус», «Логи» и алерты, нужен Docker API:
|
||
|
||
```bash
|
||
make local-proxy # read-only прокси Docker на 127.0.0.1:23750
|
||
echo DOCKER_URL=http://127.0.0.1:23750 >> .env
|
||
make demo-up # демо-сервисы: здоровый, падающий, CPU-hog, unhealthy
|
||
make run
|
||
```
|
||
|
||
### Вариант Б — полный стек в Docker
|
||
|
||
```bash
|
||
cp .env.example .env # токен, ADMIN_IDS, репозиторий загрузок и ключ (см. ниже)
|
||
make up # бот + PostgreSQL 16 + docker-socket-proxy
|
||
make demo-up # по желанию: демо-сервисы рядом с ботом
|
||
make logs # логи бота
|
||
```
|
||
|
||
В Docker бот ходит в репозиторий загрузок по SSH: нужен отдельный репозиторий (шаблон —
|
||
[`infra/uploads-template/`](infra/uploads-template/)) и deploy-ключ с правом записи. Ключ передаётся в
|
||
`GIT_SSH_KEY_B64` (`base64 -w0 <ключ>`): смонтированный файл контейнер прочитать не сможет.
|
||
|
||
### Сервер
|
||
|
||
CD из Gitea Actions разворачивает `dev` на сервер скриптом `infra/deploy/deploy.sh` — его же можно запускать
|
||
руками для любого хоста. Подробно — в [`docs/dev/DEPLOY.md`](docs/dev/DEPLOY.md).
|
||
|
||
### Переменные окружения
|
||
|
||
Полный список с комментариями — [`.env.example`](.env.example). Секреты хранятся только в `.env`, в git не попадают.
|
||
|
||
| Переменная | Обязательна | Что задаёт |
|
||
|---|---|---|
|
||
| `TELEGRAM_BOT_TOKEN` | да | токен бота |
|
||
| `ADMIN_IDS` | да | Telegram ID первых администраторов через запятую |
|
||
| `TELEGRAM_PROXY_URL` | нет | HTTP/SOCKS-прокси до Telegram (если сервер не видит api.telegram.org) |
|
||
| `GIT_UPLOADS_URL` | да | репозиторий загрузок: путь, `ssh://…` или `git@…` |
|
||
| `GIT_SSH_KEY_B64` / `GIT_SSH_KEY_PATH` | для SSH | deploy-ключ бота |
|
||
| `DATABASE_URL` | нет | по умолчанию SQLite; в Compose — PostgreSQL |
|
||
| `DOCKER_URL` | нет | docker-socket-proxy; без него «Статус», «Логи» и алерты отвечают «Docker недоступен» |
|
||
| `LLM_PROVIDER`, `LLM_BASE_URL`, `LLM_MODEL`, `LLM_API_KEY` | нет | LLM для агента; при `mock` агент выключен |
|
||
|
||
### Проверки
|
||
|
||
```bash
|
||
make lint # ruff + mypy --strict
|
||
make test # pytest
|
||
make cov # pytest с покрытием (порог 80 %)
|
||
```
|
||
|
||
## 4. Входные и выходные данные
|
||
|
||
### Команды и кнопки
|
||
|
||
| Ввод | Что происходит |
|
||
|---|---|
|
||
| `/start <токен>` (ссылка из QR) | регистрация по приглашению, главное меню |
|
||
| `/start`, `/menu` | главное меню; из любого шага диалога |
|
||
| `/help` | справка |
|
||
| 📝 Коммит · 🩺 Статус · 📜 Логи · 🚨 Алерты | разделы; у админа ещё 👥 Участники (приглашения, отзыв) |
|
||
|
||
Внутри разделов — инлайн-кнопки. Текстом бот принимает имя файла или папки, содержимое файла (текстом, в том числе
|
||
в блоке ```` ``` ````, или документом до 100 КБ), сообщение коммита, выражение grep и вопрос агенту.
|
||
|
||
### Выходные данные
|
||
|
||
- **В Телеграме**: экраны разделов, файлы (`.log` для длинных результатов, исходник для больших файлов),
|
||
QR-приглашения PNG, алерты.
|
||
- **В репозитории загрузок**: коммиты от `tgit-bot` с автором из Телеграма; push в `main` запускает CI репозитория.
|
||
- **В БД** (13 таблиц, миграции Alembic): участники и приглашения, черновики, состояние диалогов, снапшоты
|
||
контейнеров, алерты и их доставки, журналы поисков по логам и вопросов агенту.
|
||
|
||
### Используемые API
|
||
|
||
| API | Зачем | Как |
|
||
|---|---|---|
|
||
| Telegram Bot API | весь интерфейс | aiogram 3, long polling |
|
||
| git (SSH) | чтение и коммиты в репозиторий загрузок | `git` подпроцессом со своим ключом, без shell |
|
||
| Docker Engine API | контейнеры, stats, логи | только GET через `docker-socket-proxy` |
|
||
| OpenAI-совместимый `/chat/completions` | страж и планировщик агента | httpx, `response_format: json_schema` |
|
||
| SSH → `tgit-runner` | деплой и перезапуск сервисов на хосте | `ssh` подпроцессом, ключ с forced command |
|
||
| SourceCraft REST API | статус прогонов CI репозитория загрузок | httpx, клиент готов, в UI — #11 |
|
||
|
||
## 5. Ограничения и особенности
|
||
|
||
**Безопасность:**
|
||
- Docker-сокет есть только у прокси, и тот пропускает лишь `GET /containers/*`. Бот показывает только контейнеры с меткой
|
||
`tgit.managed=true` и отказывает по чужому id ещё до запроса: на сервере живут и другие проекты.
|
||
- grep разбирается `shlex.split` и выполняется модулем `regex` с таймаутом на строку и общим бюджетом 3 с;
|
||
катастрофический шаблон вроде `(a|aa)+$` бота не вешает.
|
||
- Агент: логи в LLM **не передаются** (только вопрос и имена контейнеров), поэтому prompt injection из логов невозможен.
|
||
LLM может вернуть только `LogQuery` — контейнер из белого списка, период ≤ 7 дней, шаблон ≤ 200 символов без
|
||
вложенных квантификаторов. Даже обманутый страж даёт в худшем случае медленный grep.
|
||
- Пути `.git/`, `.sourcecraft/`, `.tgit/` и `ruff.toml` из Телеграма не редактируются. Бот проверяет синтаксис
|
||
до пуша, но код сервисов не исполняет.
|
||
|
||
**Лимиты:**
|
||
|
||
| Что | Лимит |
|
||
|---|---|
|
||
| файл из Телеграма | 100 КБ, UTF-8 |
|
||
| черновик | 50 изменений |
|
||
| push | 20 в час на участника |
|
||
| шаблон grep | 200 символов; вывод — 500 строк |
|
||
| чтение логов | 50 000 строк и 5 МБ (новейшие) |
|
||
| вопрос агенту | 500 символов, 30 в сутки |
|
||
| повтор алерта | в течение 10 мин — счётчик, а не новое сообщение |
|
||
|
||
**Что пока не сделано** (открытые задачи):
|
||
- проверки CI репозитория загрузок (ruff, bandit, тесты) перед деплоем: сейчас бот разворачивает сервис сразу после
|
||
push, проверив только синтаксис; ожидание зелёного CI появится вместе с #11;
|
||
- уведомления о прогонах CI/CD репозитория загрузок и алерты 📦/⛔ — #11, ждут возврата к SourceCraft;
|
||
- «🔕 Тишина на час» для алертов (P2);
|
||
- prod-окружение: нужен отдельный бот, деплой `main` пропускается, пока его нет.
|
||
|
||
## Структура репозитория
|
||
|
||
```
|
||
app/
|
||
access/ приглашения, QR, проверка доступа
|
||
bot/ диспетчер, меню, роутеры разделов, FSM в БД
|
||
repo/ клон репозитория загрузок, валидация, черновики, push
|
||
runtime/ Docker API, проверки, снапшоты
|
||
logs/ grep-движок и журнал
|
||
agent/ фильтр, страж, планировщик, схема LogQuery
|
||
alerts/ детекторы, сервис алертов, рассылка, монитор
|
||
runner/ SSH-клиент tgit-runner и деплой после push
|
||
llm/ провайдеры: OpenAI-совместимый, mock
|
||
sourcecraft/ клиент API прогонов
|
||
db/ модели, миграции Alembic, очистка
|
||
config/ тексты интерфейса, пороги проверок и алертов, правила и промпты агента
|
||
tests/ pytest; фейки Telegram, Docker и LLM
|
||
examples/ демо-сервисы и сценарии
|
||
infra/ деплой бота, tgit-runner (песочница сервисов), шаблон репозитория загрузок
|
||
docs/ ТЗ, архитектура, ADR, процесс
|
||
```
|