6.3 KiB
6.3 KiB
Webhook Setup: Plane + Gitea → Orchestrator
Архитектура
Gitea (push/PR/CI) ──→ Nginx proxy ──→ Orchestrator /webhook/gitea
Plane (work_item/comment) ──→ Nginx proxy ──→ Orchestrator /webhook/plane
External URL: https://openclaw.mva154.duckdns.org/orchestrator/
Internal URL: http://127.0.0.1:8500/
Gitea Webhook (per-repo)
Gitea-webhook — per-repo: создаётся для КАЖДОГО подключаемого к оркестратору репозитория
(<repo> ниже). Для новых проектов его создаёт onboarding-CLI
(scripts/onboard_project.py apply) — полный процесс см. docs/operations/ONBOARDING.md;
команды ниже — для ручной проверки/пересоздания на любом репо.
- URL:
https://openclaw.mva154.duckdns.org/orchestrator/webhook/gitea - Events:
push,pull_request,status - Secret: значение
ORCH_GITEA_WEBHOOK_SECRETв.env— ОДИН глобальный секрет на все репо (приёмник валидирует только его; новый секрет на одном репо сломал бы HMAC остальных — при ротации меняется на всех репо разом) - Signature header:
X-Gitea-Signature(HMAC-SHA256 hex digest)
Проверка
GITEA_TOKEN=$(grep ORCH_GITEA_TOKEN /home/slin/repos/orchestrator/.env | cut -d= -f2)
curl -s "http://localhost:3000/api/v1/repos/<owner>/<repo>/hooks" \
-H "Authorization: token ${GITEA_TOKEN}" | python3 -m json.tool
Пересоздание (если нужно)
# Секрет переиспользуй из .env (ORCH_GITEA_WEBHOOK_SECRET); генерируй новый ТОЛЬКО при
# первичной настройке/осознанной ротации (и обнови вебхуки ВСЕХ репо):
GITEA_WEBHOOK_SECRET=$(grep ORCH_GITEA_WEBHOOK_SECRET /home/slin/repos/orchestrator/.env | cut -d= -f2)
curl -X POST "http://localhost:3000/api/v1/repos/<owner>/<repo>/hooks" \
-H "Authorization: token ${GITEA_TOKEN}" \
-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": "*"
}'
Plane Webhook
Создан напрямую в PostgreSQL (Plane CE не экспортирует webhook API через внешний /api/v1/).
- URL:
https://openclaw.mva154.duckdns.org/orchestrator/webhook/plane - Events:
issue(work_item.created),issue_comment(comment.created) - Secret: значение
ORCH_PLANE_WEBHOOK_SECRETв.env - Signature header:
X-Plane-Signature(HMAC-SHA256 hex digest)
Проверка
docker exec -e PGPASSWORD=plane plane-app-plane-db-1 psql -U plane -d plane -c \
"SELECT id, url, is_active FROM webhooks;"
Ручная настройка через UI (альтернатива)
- Открыть
https://plane.mva154.duckdns.org - Workspace Settings → Webhooks → Add Webhook
- URL:
https://openclaw.mva154.duckdns.org/orchestrator/webhook/plane - Secret: значение из
ORCH_PLANE_WEBHOOK_SECRETв.env - Events: Issue, Issue Comment
- Save
Пересоздание через SQL
PLANE_WEBHOOK_SECRET=$(openssl rand -hex 20)
# Обновить в .env: ORCH_PLANE_WEBHOOK_SECRET=<new_secret>
WORKSPACE_ID=$(docker exec -e PGPASSWORD=plane plane-app-plane-db-1 psql -U plane -d plane -t -A -c \
"SELECT id FROM workspaces WHERE slug='ag_proj'")
WEBHOOK_ID=$(cat /proc/sys/kernel/random/uuid)
docker exec -e PGPASSWORD=plane plane-app-plane-db-1 psql -U plane -d plane -c "
INSERT INTO webhooks (id, created_at, updated_at, deleted_at, workspace_id, url, is_active, secret_key, project, issue, module, cycle, issue_comment, is_internal, version)
VALUES ('${WEBHOOK_ID}', NOW(), NOW(), NULL, '${WORKSPACE_ID}',
'https://openclaw.mva154.duckdns.org/orchestrator/webhook/plane',
true, '${PLANE_WEBHOOK_SECRET}', true, true, false, false, true, false, 'v1');
"
HMAC Signature Verification
Оба handler'а проверяют подпись:
- Если secret пустой в
.env— верификация пропускается (для dev/debug) - Если secret задан — запрос без валидной подписи получает
401 Unauthorized
Формат подписи
| Source | Header | Algorithm | Format |
|---|---|---|---|
| Gitea | X-Gitea-Signature |
HMAC-SHA256 | hex digest (без префикса) |
| Plane | X-Plane-Signature |
HMAC-SHA256 | hex digest |
Тест подписи вручную
SECRET=$(grep ORCH_GITEA_WEBHOOK_SECRET /home/slin/repos/orchestrator/.env | cut -d= -f2)
BODY='{"ref":"refs/heads/test","repository":{"name":"enduro-trails"},"commits":[]}'
SIG=$(echo -n "${BODY}" | openssl dgst -sha256 -hmac "${SECRET}" | awk '{print $NF}')
curl -X POST http://localhost:8500/webhook/gitea \
-H "Content-Type: application/json" \
-H "X-Gitea-Event: push" \
-H "X-Gitea-Signature: ${SIG}" \
-d "${BODY}"
# Expected: {"status":"accepted"}
Переменные окружения (.env)
| Переменная | Описание |
|---|---|
ORCH_GITEA_WEBHOOK_SECRET |
HMAC secret для Gitea webhook |
ORCH_PLANE_WEBHOOK_SECRET |
HMAC secret для Plane webhook |
ORCH_GITEA_TOKEN |
API token для Gitea |
ORCH_PLANE_API_TOKEN |
API token для Plane |
Troubleshooting
# Логи Orchestrator
docker logs orchestrator --tail 50 2>&1 | grep -i "webhook\|signature\|401"
# События в БД
docker exec orchestrator python3 -c "
import sqlite3
conn = sqlite3.connect('/app/data/orchestrator.db')
for r in conn.execute('SELECT id, source, event_type, timestamp FROM events ORDER BY id DESC LIMIT 10').fetchall():
print(r)
"
# Gitea webhook delivery history
# Gitea UI → Settings → Webhooks → click webhook → Recent Deliveries
Создано: 2026-05-21 | Автор: Dev-агент