출시 전 가장 익숙한 장면: Slack에서 「TestFlight 눌러 줄 사람」이 외치고, 다른 사람은 SSH로 수동xcodebuild하며, 또 다른 사람은 「내 쪽은 통과하는데」라고 말합니다. 클라우드 Mac은 하드웨어 문제를 해결하지만, 점검이 여전히 수작업으로 이어지면「내 PC에서는 된다」는 운 요소는 그대로입니다. OpenClaw를 Zutcloud 전용 노드에 연결한 뒤 목표는 분명했습니다:오케스트레이션 계층 + 고정 노드로 트리거, 빌드, 서명 검증, 영수증을 감사 가능한 한 줄의 체인에 기록하는 것입니다.
「수동 출시」에서 「체인 출시」로
자동화 도입 전, 중형 iOS 팀의 출시 전 작업을 집계했을 때 평균 40분 이상이 「환경 확인」에 쓰였습니다:
| 수동 단계 | 숨은 비용 | 자동화 후 |
|---|---|---|
| Xcode / 인증서 버전 확인 | 구두 공유, 누락 쉬움 | 이미지 버전을 pipeline manifest에 기록 |
| 로컬 Archive 시험 빌드 | 사람마다 환경 상이 | 전용 노드 고정 DerivedData 경로 |
| TestFlight 업로드 | 시간 되는 사람이 처리 | 서명 stage가 altool / Transporter 자동 트리거 |
| QA 검수 알림 | 빌드 번호 불일치 | 영수증 webhook에 commit + 아티팩트 해시 |
5단계 출시 전 체인
출시 전 점검을 독립 재시도 가능한 다섯 단계로 나눕니다. 각 단계 종료 시 stderr만 던지지 않고 오케스트레이션 계층에 구조화 JSON을 보고합니다:
- 트리거(Trigger)— PR merge, tag push 또는 예약 야간 빌드; 기록
trigger_id과 브랜치 - 환경 고정(Freeze)— 노드 이미지, Xcode 버전,
openclaw.yaml의 region과 cache 루트 경로 검증 - 빌드(Build)—
xcodebuild/fastlane; .xcarchive와 단위 테스트 리포트 산출 - 게이트(Gate)— 정적 분석 임계값, 테스트 커버리지, 패키지 크기 전주 대비; 미통과 시 다운스트림 차단
- 영수증(Receipt)— 아티팩트 해시, 노드 ID, 소요 시간을 webhook / 읽기 전용 채널에 기록
오케스트레이션 계층 구성 예시
아래는 Zutcloud 전용 노드를 OpenClaw runner로 등록하고 트리거 시 메타데이터를 주입하는 간소화 예시입니다(실제 필드명은 사용 CI 기준):
name: pre-release-mac on: push: tags: ['v*'] jobs: build-on-cloud-mac: runs-on: [self-hosted, macOS, zutcloud-ap-northeast] steps: - run: openclaw daemon health --json - run: | export OPENCLAW_NODE_ID="vn-apne1-m4-01" export ARTIFACT_ROOT="/Volumes/artifacts/ap-northeast-1" ./scripts/ci-build.sh - run: openclaw receipt publish \ --trigger-id "${{ github.run_id }}" \ --sha "${{ github.sha }}" \ --artifact-hash "$(shasum -a 256 dist/*.ipa)"
세 가지 디테일에 주의하세요:health 체크를 첫 단계에두어 daemon 이상 시 30분 컴파일 낭비를 막으세요.환경 변수는 shell 블록에서 명시적으로 export하고 SSH 로그인 시 profile에 의존하지 마세요.receipt를 별도 단계로두면 TestFlight 업로드가 실패해도 「빌드는 성공」 증거 체인을 남길 수 있습니다.
전용 노드: 공유 Runner를 쓰지 않는 이유
공유 Mac CI는 저렴해 보이지만 출시 전 점검에는 세 가지 치명적 약점이 있습니다: 캐시 디렉터리 비통제, 인증서 정책 격리 어려움, 대기 시간 예측 불가. 전용 노드는 OpenClaw가 DerivedData, Pods 캐시, 서명 키체인을 같은 물리 기기에 묶게 하여체인 확정성이 「매번 임의의 기기」보다 훨씬 높습니다.
| 차원 | 공유 Runner | Zutcloud 전용 + OpenClaw |
|---|---|---|
| 캐시 | 작업 종료 시 즉시 삭제 | 볼륨 스냅샷, Xcode 버전에 묶임 |
| 인증서 | 다중 테넌트 리스크 | 단일 테넌트 키체인, 오프라인 서명기 가능 |
| 감사 | 로그 분산 | trigger → node → artifact 끝까지 한 줄로 |
| 대기열 | 피크는 시간 단위일 수 있음 | 전용 컴퓨트, 야간 빌드 예약 가능 |
영수증과 감사 가능성
「감사 가능」이란 거대한 로그 tarball을 쌓는 것이 아니라, 매 릴리스마다 다섯 가지 질문에 답할 수 있는 것입니다:
- 누가 트리거했나? (PR 작성자 / tag / 예약 작업)
- 어느 노드, 어느 리전에서 빌드했나? (
node_id+region) - 어떤 Xcode와 캐시 스냅샷을 썼나? (이미지 버전 + cache 루트 경로)
- 아티팩트 해시는? (IPA/dSYM 재현 가능)
- 어느 stage가 실패했나? (freeze/build/gate/upload)
영수증을 팀 읽기 전용 Slack 채널(또는 동등 도구)로 보냅니다. 형식은 한 줄 JSON 요약 + 상세 링크로 고정합니다. QA는 더 이상 「이게 어느 빌드지?」라고 묻지 않아도 됩니다 — 빌드 번호, commit, 해시가 메시지에 정렬되어 있습니다.
{
"trigger_id": "gh-1849201",
"sha": "a1b2c3d",
"node_id": "vn-apne1-m4-01",
"region": "ap-northeast-1",
"xcode": "16.2",
"stages": {
"freeze": "ok",
"build": "ok",
"gate": "ok",
"receipt": "ok"
},
"artifact_sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
}
점진적 도입 제안
첫 주에 모든 로컬 Archive를 끌 필요는 없습니다. 더 안전한 경로는:
2주차: gate stage를 켜고 테스트 실패 시 merge 차단.
3주차: 태그 릴리스는 전체 5단계 체인을 실행합니다. 로컬 아카이브는 예외 경로가 됩니다.
함께 읽기리전·디스크 선택 가이드함께 활용: 1주차에 노드 메타데이터를 짝지으면 3주차 버전 컷 때 「링크는 연결됐는데 산출물 경로가 틀림」 재작업이 없습니다.
실패했을 때 할 일
자동화 링크의 가치는 절반은 성공, 절반은실패를 찾을 수 있음. 간단한 플레이북을 유지합니다:
- freeze 실패— 이미지 드리프트 또는 openclaw.yaml이 노드와 불일치 → 노드를 freeze하고 이미지 태그 롤백
- build 실패— 코드·의존성 문제 → R&D가 코드 수정, 인프라와 무관
- gate 실패— 커버리지·패키지 용량 회귀 → 릴리스 차단, 명시적 면제 승인 필요
- receipt 실패— 빌드는 성공했으나 알림/업로드 실패 → 전체 재빌드 없이 receipt stage만 재실행 가능
stage를 분리하면 「재실행」 비용이 1시간 전체 컴파일에서 receipt·gate 재시도 몇 분으로 줄어듭니다 — 오케스트레이션 계층이 실제로 절약하는 시간입니다.
클라우드 Mac에서 전체 링크 실행
OpenClaw daemon, 헬스 체크, pipeline 스크립트는 Zutcloud Mac mini M4에서 7×24 무인 실행 가능합니다. 대기 전력 약 4W로 상주 CI 노드에 적합합니다. 전용 컴퓨트로 DerivedData가 인증서 정책·오케스트레이션 계층 설정과 1:1 대응합니다.
출시 전 점검을 「사람이 버튼 클릭」에서 「트리거 시 영수증」으로 바꾸면 팀 논의가 「누구 환경이 문제냐」에서 「어느 gate 규칙을 조정할까」로 이동합니다 — 클라우드 Mac이 가져와야 할 확실성입니다.
첫 출시 전 링크를 만들 준비가 되셨나요?아태 또는 미서 전용 노드 + 야간 빌드 cron부터 시작하세요——Mac 클라우드 호스트 요금제 보기, 고정 하드웨어에서 OpenClaw 실행.