docs: ТЗ v2 — QR-доступ, меню разделов, коммит по дереву, агент со стражем, общие алерты
ADR-011 (QR-приглашения), ADR-012 (командная модель), ADR-013 (агент логов). Refs #24 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+169
-104
@@ -1,143 +1,200 @@
|
||||
# Архитектура tgit
|
||||
|
||||
> Требования — в [`SPEC.md`](SPEC.md), решения и их обоснования — в [`DECISIONS.md`](DECISIONS.md).
|
||||
> Требования — в [`SPEC.md`](SPEC.md) (v2), решения и их обоснования — в [`DECISIONS.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> контейнеры (песочница) │
|
||||
└─────┼─────────────────────────────────────────────────────────────────────┘
|
||||
┌────────────┴──────────────────────────────── хост (VPS / ноутбук) ───────────┐
|
||||
│ tgit-bot (один процесс, asyncio) │
|
||||
│ ├─ bot/ меню, FSM, роутеры разделов, middleware доступа │
|
||||
│ ├─ access/ приглашения (QR), участники, антиперебор │
|
||||
│ ├─ repo/ дерево репозитория, черновики, Publisher (git + SSH) ────────┼──► SourceCraft
|
||||
│ ├─ pipeline/ PipelineWatcher: CI и CD по sha, уведомления автору ◄────────┼─── REST API
|
||||
│ ├─ runtime/ состояние контейнеров, проверки, логи │
|
||||
│ ├─ logs/ grep-движок (Python, без shell) │
|
||||
│ ├─ agent/ фильтр → LLM-страж → LLM → LogQuery ─────────────────────────┼──► LLM API
|
||||
│ └─ alerts/ детекторы, антиспам, рассылка всем, восстановление │
|
||||
│ │ │ │
|
||||
│ ┌────┴─────┐ ┌────────┴────────┐ │
|
||||
│ │ postgres │ │ docker-proxy │ (только GET: containers/logs/ │
|
||||
│ └──────────┘ │ │ events/stats) │
|
||||
│ └────────┬────────┘ │
|
||||
│ │ /var/run/docker.sock │
|
||||
│ tgit-runner (скрипт на хосте) ─┤ │
|
||||
│ ▲ ssh, forced command ▼ │
|
||||
│ │ tgit-svc-<service> контейнеры (песочница) │
|
||||
└─────┼──────────────────────────────────────────────────────────────────────── ┘
|
||||
│
|
||||
SourceCraft CI (tgit-uploads): check → deploy ──┘
|
||||
```
|
||||
|
||||
**Два репозитория:**
|
||||
**Репозитории:**
|
||||
|
||||
| Репозиторий | Что внутри | CI/CD |
|
||||
|---|---|---|
|
||||
| `tgit` (Gitea → зеркало в SourceCraft) | код бота | push в любую ветку: lint + types + tests; push `main`: + деплой бота на хост |
|
||||
| `tgit-uploads` | `projects/<name>/…` от пользователей | push `main`: проверки проекта; успех → `ssh tgit-runner deploy <project> <sha>` |
|
||||
| `tgit-uploads` (SourceCraft) | общий репозиторий команды: `services/<name>/…` и любые файлы | push `main`: `check` → при успехе `deploy` изменённых сервисов через `tgit-runner` |
|
||||
|
||||
## 2. Компоненты
|
||||
|
||||
### 2.1 `app/bot` — интерфейс Telegram
|
||||
aiogram 3, роутеры по фичам (`upload`, `projects`, `logs`, `ask`, `admin`). Middleware: проверка доступа по белому
|
||||
списку, логирование апдейтов без содержимого файлов. Длинные ответы → файл.
|
||||
aiogram 3. Главное меню — reply-клавиатура (📝 Коммит · 🩺 Статус · 📜 Логи · 🚨 Алерты · 👥 Участники для админа);
|
||||
внутри разделов — инлайн-кнопки, сообщение раздела редактируется на месте.
|
||||
|
||||
### 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 → «изменений нет», коммита нет.
|
||||
- **Middleware доступа** — первым в цепочке, на `message` и `callback_query`: пускает только участников; исключение —
|
||||
`/start <token>`. Неавторизованным — одна фраза раз в сутки, остальное игнорируется.
|
||||
- **FSM** — `aiogram` storage поверх Postgres (своя реализация `BaseStorage`, таблица `fsm_state`) → состояние переживает рестарт.
|
||||
- **callback_data ≤ 64 байт** — в кнопки не кладём пути: `CallbackData` с коротким id, а соответствие id → путь
|
||||
хранится в данных FSM текущего экрана.
|
||||
|
||||
### 2.3 `app/sourcecraft` — клиент API
|
||||
### 2.2 `app/access` — приглашения и участники
|
||||
- `create_invite(admin) -> (link, png)`: `secrets.token_urlsafe(16)` → в БД `sha256(token)`, `expires_at`, `created_by`;
|
||||
QR — библиотека `segno` (PNG в памяти).
|
||||
- `redeem(token, tg_user)`: хэш → поиск → не истёк, не использован → в транзакции `used_by/used_at` + создание участника.
|
||||
- Антиперебор: счётчик неудачных попыток по `tg_id` за час (таблица `access_attempts`).
|
||||
- `revoke(user)`: `is_active = false`, удаление черновика, сброс FSM.
|
||||
|
||||
### 2.3 `app/repo` — дерево, черновики, пуш
|
||||
- **Локальный клон** `tgit-uploads` в volume. Перед открытием браузера — `git fetch` (не чаще раза в 10 с);
|
||||
дерево — `git ls-tree origin/main <path>`, содержимое — `git show origin/main:<path>`. Быстро и без API.
|
||||
- **Черновик** (`drafts` + `draft_changes`): `base_sha` (HEAD при начале), список изменений `add|modify|delete` с
|
||||
содержимым. Виртуальные новые папки живут только в FSM, пока в них не добавлен файл.
|
||||
- **Валидация** (`repo/validate.py`): имя файла, размер, UTF-8, NUL, `ast.parse` для `.py`, запрет `.sourcecraft/` и служебных файлов.
|
||||
- **Publisher** — единственная очередь (`asyncio.Queue`, один воркер), чтобы пуши не конфликтовали:
|
||||
`fetch` → проверка конфликта (`git diff --name-only base_sha origin/main` ∩ пути черновика) → `reset --hard origin/main`
|
||||
→ применить изменения → `commit` с трейлером `Telegram-User` → `push`; отказ push → `pull --rebase` (до 3 раз).
|
||||
git — через `asyncio.create_subprocess_exec` (аргументы списком), `GIT_SSH_COMMAND` с ключом бота.
|
||||
|
||||
### 2.4 `app/sourcecraft` — клиент API
|
||||
`httpx.AsyncClient`, Bearer-токен, ретраи на сетевые ошибки и 5xx (экспоненциально, до 5 попыток),
|
||||
типизированные ответы (pydantic). Методы: `list_runs`, `get_run`, `get_cube_logs`, `trigger_workflow`.
|
||||
Подробности API — [`dev/SOURCECRAFT_API.md`](dev/SOURCECRAFT_API.md).
|
||||
pydantic-модели ответов. Методы: `list_runs`, `get_run`, `get_cube_logs`. Подробности — [`dev/SOURCECRAFT_API.md`](dev/SOURCECRAFT_API.md).
|
||||
|
||||
### 2.4 `app/ci` — наблюдатель за CI
|
||||
Фоновая задача. Опрашивает `/cicd/runs` **только пока есть незавершённые загрузки** (интервал 10 с, бэкофф до 60 с
|
||||
при ошибках). Сопоставляет прогон с загрузкой по sha коммита, на смене статуса редактирует сообщение пользователя.
|
||||
При падении скачивает лог упавшего куба. Таймаут прогона — 30 мин → статус `timeout`.
|
||||
Webhooks у SourceCraft нет — отсюда polling (ADR-003).
|
||||
### 2.5 `app/pipeline` — наблюдатель CI/CD
|
||||
Фоновая задача. Пока есть незавершённые пуши — опрашивает `/cicd/runs` (10 с, бэкофф до 60 с), сопоставляет прогон с
|
||||
пушем по sha. Отслеживает workflow `check` (CI) и `deploy` (CD) отдельно; на смене статуса — редактирует сообщение
|
||||
о пуше и шлёт автору уведомление (успех / падение + лог упавшего куба). Падение `deploy` → событие для `alerts`.
|
||||
Таймаут — 30 мин. Webhooks у SourceCraft нет (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.
|
||||
### 2.6 `app/runtime` — контейнеры и проверки
|
||||
Через `docker-socket-proxy` (только `GET`):
|
||||
- **Список** — контейнеры с меткой `tgit.managed=true` (сервисы и служебные: bot, postgres, proxy).
|
||||
- **Карточка** — `inspect` (state, health, restart count, started_at, метки `tgit.sha`, `tgit.deployed_at`) + `stats`.
|
||||
- **Проверки** (`runtime/checks.py`) — набор независимых функций `Check -> CheckResult(ok, detail)`:
|
||||
`running`, `docker_health`, `restarts_stable` (сравнение с 5 мин назад, история из БД), `cpu_ok`, `mem_ok`,
|
||||
`no_errors_in_logs` (5 мин, шаблон из `config/checks.yaml`), `http_probe` (если в `services/<name>/tgit.yaml`
|
||||
объявлен `health_url`; проба из сети сервиса через runner). Результат — отчёт-таблица.
|
||||
- **Мониторинг** — поток `events` (`die`, `oom`, `health_status`, `restart`) и опрос `stats` раз в 15 с → события для `alerts`.
|
||||
|
||||
Управление (`stop`/`restart`) бот **не** делает через сокет — он вызывает `tgit-runner` (ADR-006).
|
||||
### 2.7 `app/logs` — grep-движок
|
||||
- `parse_grep(expr) -> GrepQuery` — подмножество синтаксиса grep: шаблон, `-i`, `-v`, `-E`, `-c`, `-C N`; всё
|
||||
остальное — ошибка с подсказкой. Разбор `shlex.split`, **никакого исполнения**.
|
||||
- `run(query, source)` — источник `runtime.logs(container, since, tail)`; поиск модулем `regex` с таймаутом на
|
||||
строку; лимиты: шаблон ≤ 200, вход ≤ 5 МБ, вывод ≤ 500 строк.
|
||||
- `to_command(query)` — человекочитаемый `grep …` для показа пользователю.
|
||||
|
||||
### 2.6 `app/alerts` — алерты
|
||||
Правила (пороги — в `config/alerts.yaml`) → событие → антиспам (ключ `project+kind`, окно 10 мин, счётчик) →
|
||||
доставка владельцу и подписчикам. Системные алерты (недоступен API/Docker/БД) — администраторам.
|
||||
|
||||
### 2.7 `app/llm` — ассистент по логам
|
||||
### 2.8 `app/agent` — LLM-агент для логов
|
||||
```
|
||||
вопрос ──► LLM #1 (JSON-схема LogQuery) ──► валидация ──► поиск по логам ──► маскирование ──► LLM #2 ──► ответ
|
||||
промт ─► prefilter ─✗─► отказ + алерт 🛡
|
||||
│✓
|
||||
▼
|
||||
guard (LLM, JSON {verdict, reason}) ─ attack / невалидный JSON ─► отказ + алерт 🛡
|
||||
│safe
|
||||
▼
|
||||
planner (LLM, JSON LogQuery: container, since, pattern, flags, limit) ─ невалидно ─► «не смог построить запрос»
|
||||
│
|
||||
▼
|
||||
validate (pydantic, белый список контейнеров, компиляция regex, анализ бэктрекинга)
|
||||
│
|
||||
▼
|
||||
logs.run(query) ─► ответ: grep-команда + строки
|
||||
```
|
||||
- `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).
|
||||
- `prefilter.py` — детерминированные правила из `config/agent_rules.yaml` (длина, шаблоны атак).
|
||||
- `guard.py` и `planner.py` — **отдельные** вызовы с узкими системными промптами (`config/prompts/`); в planner
|
||||
передаются только промт пользователя и список контейнеров — **логи в LLM не попадают** (ADR-007, ADR-013).
|
||||
- Провайдер — `LLMProvider` + фабрика (ADR-008); в тестах `mock`.
|
||||
- Журнал: `agent_requests` (промт, вердикт, запрос, число строк, токены).
|
||||
|
||||
### 2.8 `app/db` — хранилище
|
||||
### 2.9 `app/alerts` — алерты
|
||||
- **Детекторы** превращают события runtime/pipeline/хоста в `AlertEvent(kind, service, payload)`; пороги — `config/alerts.yaml`.
|
||||
- **Дедупликация**: ключ `kind + service`; открытый алерт с тем же ключом → `count += 1`, исходные сообщения
|
||||
редактируются (не чаще раза в 10 мин), новые не шлются.
|
||||
- **Рассылка** всем активным участникам (кроме 🛡 — только администраторам) с учётом лимита Telegram (~30 msg/s,
|
||||
очередь отправки); `alert_deliveries` хранит `chat_id`/`message_id` — чтобы обновлять счётчик и отвечать «✅ восстановлено».
|
||||
- **Восстановление**: детектор сообщает, что условие прошло → `resolved_at`, ответ на исходные сообщения.
|
||||
- **Список**: активные + история 7 дней, пагинация.
|
||||
|
||||
### 2.10 `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` |
|
||||
| `users` | `tg_id` PK, `username`, `full_name`, `role` (`member`/`admin`), `is_active`, `invited_by`, `joined_at`, `last_seen_at` |
|
||||
| `invites` | `id`, `token_hash` UNIQUE, `created_by`, `created_at`, `expires_at`, `used_by`, `used_at` |
|
||||
| `access_attempts` | `tg_id`, `at`, `ok` |
|
||||
| `fsm_state` | `key` PK, `state`, `data` JSON |
|
||||
| `drafts` | `id`, `user_id` UNIQUE, `base_sha`, `message`, `created_at`, `expires_at` |
|
||||
| `draft_changes` | `id`, `draft_id`, `path`, `op` (`add`/`modify`/`delete`), `content` BYTEA |
|
||||
| `pushes` | `id`, `user_id`, `sha`, `message`, `paths` JSON, `chat_id`, `message_id`, `ci_status`, `cd_status`, `created_at` |
|
||||
| `pipeline_runs` | `id`, `push_id`, `workflow` (`check`/`deploy`), `run_slug`, `status`, `failed_cube`, `url`, `updated_at` |
|
||||
| `container_snapshots` | `container`, `at`, `restart_count`, `cpu_pct`, `mem_pct`, `health` — для проверок «за 5 мин» (TTL 1 сутки) |
|
||||
| `alerts` | `id`, `kind`, `service`, `payload` JSON, `count`, `opened_at`, `last_at`, `resolved_at` |
|
||||
| `alert_deliveries` | `alert_id`, `chat_id`, `message_id` |
|
||||
| `log_queries` | `id`, `user_id`, `container`, `mode` (`tail`/`grep`/`agent`), `query` JSON, `lines`, `at` |
|
||||
| `agent_requests` | `id`, `user_id`, `prompt`, `verdict`, `reason`, `log_query` JSON, `tokens`, `at` |
|
||||
|
||||
### 2.9 Конфигурация
|
||||
`app/config.py` — `pydantic-settings`, один класс на подсистему (`TelegramSettings`, `SourceCraftSettings`,
|
||||
`DockerSettings`, `LLMSettings`, …), всё из `.env`. Пороги и тексты — `config/*.yaml`.
|
||||
### 2.11 Конфигурация
|
||||
`app/config.py` — `pydantic-settings`, класс на подсистему (`TelegramSettings`, `AccessSettings`, `SourceCraftSettings`,
|
||||
`GitSettings`, `DockerSettings`, `LLMSettings`, `AlertSettings`), всё из `.env`. Пороги, тексты, правила, промпты — `config/*.yaml`.
|
||||
|
||||
## 3. Потоки данных
|
||||
|
||||
### 3.1 Загрузка → CI → CD
|
||||
### 3.1 Вход по приглашению
|
||||
```
|
||||
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
|
||||
админ ─ «Пригласить» ─► access.create_invite ─► QR (PNG) + ссылка
|
||||
новый пользователь ─ скан QR ─► t.me/<bot>?start=<token> ─► /start <token> ─► redeem ─✓─► users + главное меню
|
||||
└✗─► отказ (+ счётчик попыток)
|
||||
```
|
||||
|
||||
### 3.2 Вопрос к логам
|
||||
`/ask` → квота → LLM #1 → `LogQuery` → валидация → `runtime.logs()` / `sourcecraft.get_cube_logs()` → фильтр regex →
|
||||
маскирование → LLM #2 (вопрос + найденные строки) → ответ + запрос + доказательства → запись в `llm_requests`.
|
||||
### 3.2 Коммит → CI → CD
|
||||
```
|
||||
📝 Коммит ─► дерево origin/main ─► файл/папка ─► изменения в черновик ─► 💬 сообщение ─► 🚀 Push ─► подтверждение
|
||||
│
|
||||
Publisher (очередь) ─ fetch, конфликт?, commit, push ──► tgit-uploads@main
|
||||
│ SourceCraft CI
|
||||
«a1b2c3d ⏳ CI» ◄──────────────────────────────────────────────────────────────────────────┘ check: py_compile · ruff · pytest · bandit
|
||||
PipelineWatcher ─ poll runs(sha) ─► «✅ CI» ─► «⏳ CD» ─► «🚀 CD: weather» deploy: ssh → tgit-runner deploy <svc> <sha>
|
||||
└► ❌ + лог шага автору; ❌ CD → алерт всем
|
||||
```
|
||||
|
||||
## 4. Песочница приложений (`tgit-runner`)
|
||||
### 3.3 Логи через агента
|
||||
См. §2.8; ответ — grep-команда + найденные строки; запись в `agent_requests` и `log_queries`.
|
||||
|
||||
`infra/runner/tgit-runner` — bash-скрипт на хосте; единственная команда, доступная ключу CI (`command="…"` в
|
||||
`authorized_keys` пользователя `tgit-deploy`). Подкоманды: `deploy <project> <sha>`, `stop <project>`, `restart <project>`.
|
||||
### 3.4 Алерт
|
||||
`runtime`/`pipeline`/хост → детектор → дедупликация → рассылка всем участникам → … → условие прошло → «✅ восстановлено».
|
||||
|
||||
`deploy`: забирает архив проекта по sha из `tgit-uploads` → собирает образ `tgit-app-<project>:<sha>` из шаблона
|
||||
(`python:3.12-slim` + `requirements.txt`) → запускает:
|
||||
## 4. Песочница сервисов (`tgit-runner`)
|
||||
|
||||
`infra/runner/tgit-runner` — bash-скрипт на хосте; единственная команда, доступная ключам CI и бота (`command="…"` в
|
||||
`authorized_keys` пользователя `tgit-deploy`). Подкоманды: `deploy <service> <sha>`, `restart <service>`,
|
||||
`probe <service> <path>` (HTTP-проба изнутри сети сервиса). Аргументы валидируются: имя `^[a-z][a-z0-9-]{1,30}$`, sha — hex.
|
||||
|
||||
`deploy`: забирает архив `services/<service>` по sha из `tgit-uploads` → собирает образ `tgit-svc-<service>:<sha>` из
|
||||
шаблона (`python:3.12-slim` + `requirements.txt`, `HEALTHCHECK` из `tgit.yaml` сервиса, если задан) → запускает:
|
||||
|
||||
```
|
||||
docker run -d --name tgit-app-<project> --label tgit.project=<project> --label tgit.sha=<sha>
|
||||
docker run -d --name tgit-svc-<service> --label tgit.managed=true --label tgit.service=<service>
|
||||
--label tgit.sha=<sha> --label tgit.deployed_at=<iso>
|
||||
--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 — без рестарта
|
||||
--network none # или tgit-svc-egress, если сеть разрешена администратором
|
||||
--restart on-failure:3
|
||||
--log-opt max-size=5m --log-opt max-file=2
|
||||
```
|
||||
Если новый контейнер не стал healthy за 60 с — откат на предыдущий образ и ненулевой код выхода (CD падает → алерт ⛔).
|
||||
|
||||
## 5. CI/CD
|
||||
|
||||
@@ -152,7 +209,11 @@ docker run -d --name tgit-app-<project> --label tgit.project=<project> --label t
|
||||
|
||||
### 5.2 Репозиторий загрузок (`tgit-uploads/.sourcecraft/ci.yaml`)
|
||||
Шаблон хранится в `tgit/infra/uploads-template/` и ставится в `tgit-uploads` один раз.
|
||||
Определение изменённого проекта — `git diff --name-only HEAD~1 -- projects/` внутри куба.
|
||||
|
||||
| Workflow | Шаги |
|
||||
|---|---|
|
||||
| `check` | изменённые файлы (`git diff --name-only HEAD~1`) → `py_compile` · `ruff check` (мягкий набор правил) · `pytest` для затронутых сервисов · `bandit` |
|
||||
| `deploy` (после `check`) | изменённые `services/<name>/` → `ssh tgit-deploy@host deploy <name> <sha>` для каждого |
|
||||
|
||||
## 6. Деплой
|
||||
|
||||
@@ -166,18 +227,18 @@ docker run -d --name tgit-app-<project> --label tgit.project=<project> --label t
|
||||
(хост режет часть соединений). Хост задаётся `DEPLOY_HOST` — тот же скрипт работает для любого сервера.
|
||||
|
||||
Бюджет ресурсов на VPS (свободно ~1,9 ГБ RAM): бот ≤ 256 МБ, postgres ≤ 256 МБ, proxy ≤ 32 МБ,
|
||||
приложения ≤ 10 × 128 МБ.
|
||||
сервисы ≤ 10 × 128 МБ.
|
||||
|
||||
## 7. Структура репозитория
|
||||
|
||||
```
|
||||
app/ код бота: bot, uploads, sourcecraft, ci, runtime, alerts, llm, db, config.py, main.py
|
||||
config/ alerts.yaml, texts.yaml, llm-промпты
|
||||
app/ код бота: bot, access, repo, sourcecraft, pipeline, runtime, logs, agent, alerts, db, config.py, main.py
|
||||
config/ alerts.yaml, checks.yaml, agent_rules.yaml, texts.yaml, prompts/
|
||||
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)
|
||||
runner/ tgit-runner, шаблон Dockerfile сервиса, authorized_keys.example
|
||||
uploads-template/ .sourcecraft/ci.yaml, ruff.toml, README для tgit-uploads
|
||||
examples/ демо-сервисы: ok, syntax error, lint fail, crash, cpu hog, unhealthy, escape attempts
|
||||
tests/ pytest: unit + интеграционные с фейками
|
||||
docs/ SPEC, ARCHITECTURE, DECISIONS, dev/, презентация
|
||||
Dockerfile · docker-compose.yml · Makefile · pyproject.toml · .env.example
|
||||
@@ -187,13 +248,17 @@ Dockerfile · docker-compose.yml · Makefile · pyproject.toml · .env.example
|
||||
|
||||
| Риск | Что делаем |
|
||||
|---|---|
|
||||
| Чужой код на одном сервере с прод-Vobot | Песочница §4, лимиты, сеть выключена; общий лимит 10 приложений; в перспективе — отдельный хост (ADR-005) |
|
||||
| Случайные люди и спам | Только одноразовые приглашения с TTL; middleware на каждом апдейте; антиперебор токенов; лимиты пушей и запросов |
|
||||
| Утечка QR | Одноразовость + 24 ч; админ видит, кто вошёл по какому приглашению, и может отозвать доступ |
|
||||
| Участник ломает CI из бота | Запрет правок `.sourcecraft/` и служебных файлов (C-16) |
|
||||
| Чужой код на одном сервере с прод-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 | Маскирование перед отправкой; секреты бота не попадают в логи |
|
||||
| Пользователь `tgit-deploy` в группе `docker` ≈ root | Ключ ограничен одной командой; `tgit-runner` валидирует аргументы |
|
||||
| Атака через агента (injection, exfiltration) | Фильтр → LLM-страж → структурированный `LogQuery` → валидация; логи в LLM не передаются; алерт админам |
|
||||
| ReDoS в regex (свой grep и от LLM) | Модуль `regex` с таймаутом, лимит длины шаблона и объёма логов |
|
||||
| Утечка секретов | Секреты не логируются и не идут в LLM; маскирование в выводе P2 |
|
||||
| Нестабильный SourceCraft API | Ретраи, идемпотентность, проверка перед повторным POST |
|
||||
| Гонки пушей | Одна очередь GitPublisher, `pull --rebase` при отказе |
|
||||
| Лимиты Telegram (4096 символов, ~30 msg/s) | Длинное — файлом; редактирование одного сообщения вместо потока |
|
||||
| Диск хоста занят на 75% | Ротация логов контейнеров, `docker image prune` старых ревизий приложений в `tgit-runner` |
|
||||
| Гонки пушей и конфликты | Одна очередь Publisher; проверка изменений с `base_sha`; `pull --rebase` при отказе |
|
||||
| Лимиты Telegram (4096 символов, 64 байта callback_data, ~30 msg/s) | Длинное — файлом; короткие id в кнопках; очередь рассылки алертов |
|
||||
| Шторм алертов | Дедупликация `kind+service`, счётчик вместо новых сообщений, «восстановлено» ответом |
|
||||
| Диск хоста занят на 75 % | Ротация логов контейнеров, `docker image prune` старых ревизий в `tgit-runner`, алерт 💾 |
|
||||
|
||||
Reference in New Issue
Block a user