Service Hook이 빌드 이벤트를 원격 Mac의 OpenClaw 게이트웨이로 전달되면 에이전트 전에 프리페치하고, PAT로 timeline·로그 일부를 읽어 JSON만 내부 URL로 회송합니다. Harbor·TeamCity 글과 달리 ADO 구독 필드·추가 HTTP 헤더·dev.azure.com REST에 초점을 둡니다. 게이트웨이 보안, GHCR 매트릭스, 빌드 풀 FAQ.
  • 조직 URL 혼동: 클라우드 dev.azure.com/{org}와 Azure DevOps Server 컬렉션 URL이 PAT·웹훅 페이로드에 섞이면 404·401이 납니다.
  • 본문 재직렬화: 프록시가 JSON을 예쁘게 인쇄하면 헤더 비밀만 맞고도 필드 경로가 달라집니다. 원시 바이트로 검증·파싱하세요.
  • 과도한 PAT: 쓰기 권한까지 한 토큰에 묶으면 회송 스크립트 유출 시 빌드 정의까지 위험합니다. 읽기와 쓰기를 분리합니다.

목표·2026 게이트웨이 전제

목표. 헤더 secret 검증 → buildId로 프리페치 → PAT로 읽기 API만 → 허용 URL로 JSON POST.

런타임. Node 22+, openclaw doctor, 가능 시 openclaw onboard --install-daemon. HTTP는 127.0.0.1+openclaw.json의 listen.port(예시 18765)로 통일, 외부는 TLS 프록시만. OPENCLAW_GATEWAY_TOKEN 파일 chmod 600.

최소 성공 기준. 스테이징에서 Service Hook “테스트” 페이로드를 재생했을 때 Mac 로그에 동일 buildId가 남고, 잘못된 secret은 항상 401로 거절되며, PAT 없이는 timeline 호출이 실패해야 합니다.

Azure DevOps 트리거·Service Hook 필드 체크리스트

Project settings → Service hooks → Create subscription에서 이벤트를 고르고 아래 표를 Runbook에 복사합니다.

UI·페이로드 관심사Mac 게이트웨이에서 할 일
프로젝트·구독 이름감사 로그에 그대로 남기고, 멱등 캐시 키에 subscriptionId 포함.
이벤트 유형(예: 빌드 완료)eventType 화이트리스트. 큐만 필요하면 “빌드 큐됨”으로 분리 구독.
필터: 빌드 정의·리포지토리·분기·결과셀프호스티드 Mac 에이전트 풀과 동일 정의만 남겨 노이즈 제거.
Web hook URL + TLS경로 토큰 포함 HTTPS, SNI·인증서 체인을 점프박스에서 재현.
Additional HTTP headersX-Webhook-Secret: … 등 공유 비밀을 상수 시간 비교. ADO 기본 본문 HMAC이 없으면 헤더 검증이 최소선.
Resource details to send초기에는 All, 안정화 후 Minimal로 줄여 대역·로그 비용 절감.

resources.webhooks는 외부→ADO 파이프라인 트리거이고, 본 글의 Mac 프리페치는 Service Hook→Mac 축입니다. Runbook에 두 화살표를 분리해 적습니다.

Webhook secret·게이트웨이 Bearer·PAT 최소 권한

Secret. “추가 HTTP 헤더”의 공유 값을 원문 그대로 상수 시간 비교, 실패 시 본문 미에코.

게이트웨이. 프록시 뒤 Authorization: Bearer … 이중화 가능, 토큰 경로는 plist 환경변수만.

PAT. Build(읽기)+프로젝트 읽기로 timeline·로그 메타만. 태그·코멘트 쓰기는 별 PAT·별 주체로 분리.

프리페치·REST 회송·재시도·로그 템플릿

resource.id로 WORK_ROOT에서 npm ci --dry-run 등 읽기 위주 프리체크. 컨테이너 풀은 GHCR 글과 조합.

회송은 타임라인 실패 작업명·로그 꼬리를 아래 JSON에 넣고 2·4·8초 백오프·--max-time·Retry-After 준수.

#!/usr/bin/env bash
set -euo pipefail
URL="${SUMMARY_INGEST_URL:?}"
SECRET="${INGEST_HMAC_SECRET:?}"
ATTEMPT="${ATTEMPT:-1}"
PREFETCH_JSON="false"; [[ "${PREFETCH_OK:-0}" == "1" ]] && PREFETCH_JSON="true"
BODY="$(jq -n \
  --arg src "azure-devops-service-hook" \
  --argjson buildId "${BUILD_ID:?}" \
  --arg result "${BUILD_RESULT:-unknown}" \
  --argjson prefetch_ok "$PREFETCH_JSON" \
  --argjson attempt "$ATTEMPT" \
  --arg tail "$(tail -c 4000 "${LOG_SNIPPET_PATH:-/dev/null}" 2>/dev/null | base64)" \
  '{source:$src,buildId:$buildId,result:$result,prefetch:{ok:$prefetch_ok},log_b64:$tail,attempt:$attempt}')"
# 실제 배포: HMAC-SHA256 헤더 + HTTPS POST + 2/4/8 백오프 루프
for d in 2 4 8; do
  code="$(curl -sS -o /tmp/ado-sum.res -w '%{http_code}' --max-time 25 -X POST "$URL" \
    -H "Content-Type: application/json" -d "$BODY" || true)"
  [[ "$code" =~ ^2 ]] && exit 0
  [[ "$code" == "429" ]] && sleep "${d}"
  sleep "$d"
done
exit 1
{
  "source": "azure-devops-service-hook",
  "subscriptionId": "…",
  "eventType": "build.complete",
  "project": "ContosoMacCI",
  "definitionName": "ios-self-hosted",
  "buildId": 184502,
  "result": "partiallySucceeded",
  "prefetch": { "ok": true, "steps": ["npm_ci_dry_run","pod_repo_update"], "duration_ms": 6120 },
  "ado_rest": { "timeline_failed_tasks": ["XcodeArchive"] },
  "log_tail": "…마지막 2k 텍스트…",
  "host": "mac-ci-ado-03",
  "attempt": 2
}

실행 명령·검수 체크리스트

1
node -v가 v22 이상인지 확인하고, openclaw doctor 경고를 Runbook에 연결합니다.
2
로컬 리스너 포트 일치: lsof -nP -iTCP:18765 -sTCP:LISTEN → curl -fsS -H "Authorization: Bearer $(cat ~/.config/openclaw/gateway.token)" http://127.0.0.1:18765/health (경로·헤더는 환경에 맞게 조정).
3
ADO에서 “테스트” 전송으로 샘플 JSON을 저장해 fixture 검증 후, 동일 헤더로 curl --data-binary @sample.json 재생합니다.
4
PAT로 GET https://dev.azure.com/{org}/{project}/_apis/build/builds/{id}/timeline?api-version=7.1를 수동 호출해 스코프를 좁혔는지 확인합니다.
5
회송 URL이 프록시 화이트리스트에 있는지, 실패 시 재시도가 중복 태그를 만들지 않는지 스테이징에서 한 번에 검수합니다.

검수 완료 기준. ① 테스트 웹훅이 2xx이며 Mac 로그에 buildId가 기록됨 ② 잘못된 secret은 항상 동일한 401 응답 ③ PAT 없이는 timeline이 실패하고, PAT는 읽기만으로 충분함 ④ 회송 JSON이 정책상 크기(예: 32KB) 이하 ⑤ 프리페치 실패 시에도 요약에 prefetch.ok:false가 명시됨.

FAQ

PAT로 timeline을 읽는데 401이 납니다

만료·잘못된 조직 URL·스코프 부족·프로젝트 범위 불일치를 순서대로 확인합니다. plist의 PAT가 교체·회전됐는지, self-hosted 에이전트와 동일 조직인지 비교합니다.

같은 빌드 이벤트가 두 번 옵니다

구독 중복 또는 필터가 넓습니다. 구독을 하나로 줄이고 정의·분기 필터를 좁히며, Mac에서 (projectId, buildId, eventType) 멱등 캐시를 둡니다.

게이트웨이 포트 충돌(EADDRINUSE)

openclaw.json·리버스 프록시 업스트림·Service Hook URL을 동일 포트로 맞춥니다. 자세한 하드닝은 게이트웨이 보안 글을 참고합니다.

요약

기본 HMAC이 없을 때는 헤더 secret + 원시 본문 + 좁은 PAT가 최소선입니다.

홈·구매·도움말·기술 블로그는 로그인 없이 이용할 수 있습니다.

MacPull 전용 Apple Silicon으로 ADO 연동 OpenClaw 게이트웨이를 안정적으로 상시 가동할 수 있습니다.

ADO 연동 원격 Mac 게이트웨이

전용 노드에서 포트·토큰·launchd를 고정하세요. 도움말·구매·요금·기술 블로그는 로그인 없이 이용할 수 있습니다.

전용 노드 토큰·포트 고정 상시 게이트웨이 고객 지원