Files
orchestrator/docs/operations/SETUP_WEBHOOKS.md

6.3 KiB
Raw Blame History

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 (альтернатива)

  1. Открыть https://plane.mva154.duckdns.org
  2. Workspace Settings → Webhooks → Add Webhook
  3. URL: https://openclaw.mva154.duckdns.org/orchestrator/webhook/plane
  4. Secret: значение из ORCH_PLANE_WEBHOOK_SECRET в .env
  5. Events: Issue, Issue Comment
  6. 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-агент