# DEV TASK: Настройка Webhooks (Plane + Gitea → Orchestrator) **Статус:** Ready for dev **Проект:** multi-agent **Фаза:** 3 **Предыдущая задача:** DEV_TASK_ORCHESTRATOR_QG.md (выполнена) --- ## Цель > Plane и Gitea автоматически отправляют webhook events в Orchestrator при действиях пользователя (создание задачи, комментарий, push, PR, CI status). ## Архитектура Nginx proxy уже настроен: `https://openclaw.mva154.duckdns.org/orchestrator/` → `localhost:8500`. Нужно: 1. Создать webhook в Gitea через API (events: push, pull_request, status) 2. Создать webhook в Plane через API или UI (events: work_item.created, comment.created) 3. Добавить HMAC-верификацию подписи в обоих handlers 4. Добавить webhook secret в .env Orchestrator'а 5. Проверить end-to-end: действие в UI → webhook → Orchestrator обрабатывает ## Стек / Зависимости - httpx (уже есть) - hmac + hashlib (stdlib) - Gitea API v1 - Plane API v1 --- ## Инфраструктура | Параметр | Значение | |----------|----------| | Сервер | `slin@82.22.50.71` (mva154) | | Orchestrator URL (external) | `https://openclaw.mva154.duckdns.org/orchestrator/` | | Orchestrator URL (internal) | `http://127.0.0.1:8500/` | | Gitea API | `http://localhost:3000/api/v1` | | Gitea token | в .env `ORCH_GITEA_TOKEN` | | Plane API | `http://localhost:8091/api/v1` | | Plane token | в .env `ORCH_PLANE_API_TOKEN` | | Plane workspace | `ag_proj` | | Gitea repo owner | `admin` | | Gitea repo | `enduro-trails` | --- ## Задачи ### Task 1: Создать webhook в Gitea через API **Шаги:** - [ ] **1.1** Создать webhook для репо `admin/enduro-trails`: ```bash # Сгенерировать secret GITEA_WEBHOOK_SECRET=$(openssl rand -hex 20) echo "ORCH_GITEA_WEBHOOK_SECRET=${GITEA_WEBHOOK_SECRET}" >> /home/slin/repos/orchestrator/.env # Создать webhook через API curl -s -X POST "http://localhost:3000/api/v1/repos/admin/enduro-trails/hooks" \ -H "Authorization: token $(grep ORCH_GITEA_TOKEN /home/slin/repos/orchestrator/.env | cut -d= -f2)" \ -H "Content-Type: application/json" \ -d '{ "type": "gitea", "active": true, "config": { "url": "https://openclaw.mva154.duckdns.org/orchestrator/webhook/gitea", "content_type": "json", "secret": "'${GITEA_WEBHOOK_SECRET}'" }, "events": ["push", "pull_request", "status"], "branch_filter": "*" }' ``` - [ ] **1.2** Проверить что webhook создан: ```bash curl -s "http://localhost:3000/api/v1/repos/admin/enduro-trails/hooks" \ -H "Authorization: token $(grep ORCH_GITEA_TOKEN /home/slin/repos/orchestrator/.env | cut -d= -f2)" | python3 -m json.tool ``` **Критерий готовности:** Webhook виден в Gitea UI (Settings → Webhooks), URL правильный. --- ### Task 2: Создать webhook в Plane **Шаги:** - [ ] **2.1** Проверить Plane API для webhooks: ```bash # Plane webhooks API curl -s "http://localhost:8091/api/v1/workspaces/ag_proj/webhooks/" \ -H "X-API-Key: $(grep ORCH_PLANE_API_TOKEN /home/slin/repos/orchestrator/.env | cut -d= -f2)" | python3 -m json.tool ``` - [ ] **2.2** Создать webhook: ```bash PLANE_WEBHOOK_SECRET=$(openssl rand -hex 20) echo "ORCH_PLANE_WEBHOOK_SECRET=${PLANE_WEBHOOK_SECRET}" >> /home/slin/repos/orchestrator/.env curl -s -X POST "http://localhost:8091/api/v1/workspaces/ag_proj/webhooks/" \ -H "X-API-Key: $(grep ORCH_PLANE_API_TOKEN /home/slin/repos/orchestrator/.env | cut -d= -f2)" \ -H "Content-Type: application/json" \ -d '{ "url": "https://openclaw.mva154.duckdns.org/orchestrator/webhook/plane", "is_active": true, "secret_key": "'${PLANE_WEBHOOK_SECRET}'", "project": null }' ``` Примечание: Plane API для webhooks может отличаться от документации. Если API не работает — настроить через UI (`plane.mva154.duckdns.org` → Settings → Webhooks). - [ ] **2.3** Если API не поддерживает webhooks — задокументировать и оставить TODO для ручной настройки через UI. **Критерий готовности:** Webhook создан (через API или UI), URL указывает на Orchestrator. --- ### Task 3: HMAC-верификация подписи в webhook handlers **Файлы:** - Изменить: `src/webhooks/plane.py` - Изменить: `src/webhooks/gitea.py` - Изменить: `src/config.py` **Шаги:** - [ ] **3.1** Добавить верификацию в `src/webhooks/gitea.py`: ```python import hmac import hashlib from ..config import settings def verify_gitea_signature(body: bytes, signature: str) -> bool: """Verify Gitea webhook HMAC-SHA256 signature.""" if not settings.gitea_webhook_secret: return True # Skip verification if no secret configured expected = hmac.new( settings.gitea_webhook_secret.encode(), body, hashlib.sha256 ).hexdigest() return hmac.compare_digest(f"sha256={expected}", signature) @router.post("/gitea") async def gitea_webhook(request: Request): body = await request.body() signature = request.headers.get("X-Gitea-Signature", "") if not verify_gitea_signature(body, signature): raise HTTPException(status_code=401, detail="Invalid signature") # ... rest of handler ``` - [ ] **3.2** Добавить верификацию в `src/webhooks/plane.py`: ```python # Plane использует свой формат подписи — проверить документацию # Обычно: X-Plane-Signature header с HMAC-SHA256 def verify_plane_signature(body: bytes, signature: str) -> bool: if not settings.plane_webhook_secret: return True expected = hmac.new( settings.plane_webhook_secret.encode(), body, hashlib.sha256 ).hexdigest() return hmac.compare_digest(expected, signature) ``` - [ ] **3.3** Добавить `from fastapi import HTTPException` в оба файла **Критерий готовности:** Запрос без правильной подписи → 401. С правильной → 200. --- ### Task 4: Пересобрать и протестировать **Шаги:** - [ ] **4.1** Пересобрать Orchestrator: ```bash cd /home/slin/repos/orchestrator && docker compose up -d --build ``` - [ ] **4.2** Проверить health: ```bash curl -s http://localhost:8500/health ``` - [ ] **4.3** Тест Gitea webhook — сделать push в enduro-trails: ```bash cd /home/slin/repos/enduro-trails echo "# webhook test $(date)" >> .webhook-test git add .webhook-test && git commit -m "test: webhook delivery check" && git push origin feature/ET-002-poi-toggle ``` - [ ] **4.4** Проверить что Orchestrator получил event: ```bash docker logs orchestrator --tail 10 2>&1 | grep -i "gitea\|push" ``` - [ ] **4.5** Проверить в БД: ```bash docker exec orchestrator python -c " import sqlite3 conn = sqlite3.connect('/app/data/orchestrator.db') rows = conn.execute('SELECT id, source, event_type, timestamp FROM events ORDER BY id DESC LIMIT 3').fetchall() for r in rows: print(r) " ``` - [ ] **4.6** Удалить тестовый файл: ```bash cd /home/slin/repos/enduro-trails git rm .webhook-test && git commit -m "test: cleanup webhook test" && git push origin feature/ET-002-poi-toggle ``` **Критерий готовности:** Push в Gitea → event появляется в Orchestrator БД. Логи показывают обработку. --- ### Task 5: Настроить Plane webhook через UI (если API не сработал) Если Task 2 не удался через API: - [ ] **5.1** Документировать инструкцию для ручной настройки: ``` 1. Открыть https://plane.mva154.duckdns.org 2. Settings → Webhooks → Add Webhook 3. URL: https://openclaw.mva154.duckdns.org/orchestrator/webhook/plane 4. Secret: <значение из ORCH_PLANE_WEBHOOK_SECRET в .env> 5. Events: All (или work_item.created, comment.created) 6. Save ``` - [ ] **5.2** Сохранить инструкцию в `/home/slin/repos/orchestrator/docs/SETUP_WEBHOOKS.md` **Критерий готовности:** Документация есть. Webhook либо настроен, либо есть чёткая инструкция. --- ## Проверка (Acceptance) | # | Проверка | Команда / Действие | Ожидаемый результат | |---|----------|-------------------|---------------------| | 1 | Gitea webhook создан | GET /api/v1/repos/admin/enduro-trails/hooks | Webhook с URL orchestrator | | 2 | Push → event в БД | git push + check DB | Новый event source=gitea | | 3 | Signature verification | curl без подписи → 401 | 401 Unauthorized | | 4 | Signature verification | curl с правильной подписью → 200 | 200 accepted | | 5 | Plane webhook | Создать issue в Plane → check DB | Event source=plane | --- ## Ограничения и контекст - ⚠️ Plane API для webhooks может не поддерживать программное создание — тогда через UI - ⚠️ Gitea webhook secret формат: `X-Gitea-Signature: sha256=` - ⚠️ НЕ удалять существующие данные в БД - ⚠️ Если signature verification ломает существующие smoke tests — сделать skip если secret пустой - 🚫 НЕ менять Nginx конфиг (уже настроен) - 🚫 НЕ менять порт Orchestrator'а --- ## Деплой-чеклист - [ ] Webhook secret сгенерирован и добавлен в .env - [ ] Gitea webhook создан через API - [ ] Plane webhook создан (API или UI) - [ ] HMAC verification добавлена в handlers - [ ] `docker compose up -d --build` успешен - [ ] Push в Gitea → event в Orchestrator - [ ] Логи чистые, нет ошибок --- *Создано: 2026-05-21 | Автор ТЗ: Стрим | Исполнитель: Dev-агент*