Cert Mirror — 무엇이고 왜 있나
ainote 계정에 fastlane match 의 cert + provisioning profile 묶음을 암호화 백업으로 저장할 수 있는 dev-facing API. iOS/macOS 빌드 자동화를 운영하는 사람이 GitHub 외에 두 번째 안전망을 원할 때 씁니다.
한 줄 요약
fastlane match 가 git push 한 디렉터리를 통째로 tarball 로 묶어서 ainote 에 HMAC 검증 업로드 → 나중에 GitHub repo 가 사라져도 ainote 에서 다시 받을 수 있게.
누구를 위한 기능인가
ainote 계정만 있으면 누구나 — 특히 다음 사용자들이 효용을 봅니다:
- Claude Code / Cursor / Windsurf 같은 AI agent — TestFlight 자동화 워크플로우를 운영하면서 자기가 만든 IPA 의 signing artefact 까지 안전하게 책임지고 싶을 때
- fastlane match 사용자 — GitHub repo 1개에 의존하기 불안할 때 두 번째 안전망으로
- 모든 ainote 사용자 — cert tarball 외에도 "암호화된 작은 바이너리 백업" 이 필요한 모든 케이스에 일반적으로 활용 가능 (현재 1차 use case 는 cert mirror)
ainote 의 노트 / 메모리 / 동기화 같은 end-user 기능과는 별도 layer 의 dev API 입니다. 계정만 있으면 동일 인증 체계 (McpKey 토큰) 안에서 추가 발급으로 바로 쓸 수 있습니다.
왜 만들었나
fastlane match 의 표준 저장 위치는 GitHub private repo. 이 자체가 git 이라 안전하지만:
- 단일 의존성 — GitHub 가 다운되거나 organization 이 정지되면 빌드 자동화 전체가 멈춤
- 계정 이전 — 회사·개인 GitHub 계정 옮길 때 cert repo migration 이 손이 많이 감
- AI agent 가 자기 빌드를 추적하기 어려움 — git 은 LLM 이 직접 들춰보기 까다로움. HTTP endpoint + JSON 응답이 훨씬 다루기 쉬움
ainote 의 cert_mirror 는 GitHub 를 대체하는 게 아니라 보조합니다. fastlane match 는 그대로 git 에 push 하고, push 가 성공하면 그 직후 동일 commit 의 tarball 을 ainote 에도 업로드. 둘 다 살아있어야 정상 — 한쪽이 사라지면 다른 쪽으로 복구.
무엇이 다른가
| 측면 | GitHub (fastlane match 기본) | ainote cert_mirror |
|---|---|---|
| 저장 형태 | git refs, 파일별 | 단일 tarball (commit 단위) |
| 인증 | SSH key / PAT | Authorization: McpKey <token> |
| 무결성 | git sha | HMAC-SHA256 (별도 secret) |
| 권한 모델 | repo 전체 collaborator | 사용자별 UserMirrorCredential 1개 |
| 사이즈 한도 | 100MB/file, 5GB/repo | 50MB/blob, 5GB/user (예정) |
| 다운로드 | git clone | HTTP GET (JSON metadata + 바이너리) |
어떻게 동작하나 (한 사이클)
┌────────────────────────────────────────────────────────────────┐
│ fastlane sync_signing │
│ 1. ASC API 로 cert / profile 발급 │
│ 2. seunghan91/apple-certs git push (1차 저장) │
│ 3. mirror_to_ainote 호출 ─┐ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ git archive --format=tar.gz HEAD │ │
│ │ → streaming HMAC-SHA256 (8KB chunk)│ │
│ │ → POST /api/cert_mirror │ │
│ │ Authorization: McpKey <token> │ │
│ │ X-Mirror-Signature: sha256=... │ │
│ │ X-Commit-Sha: <40 hex> │ │
│ │ Content-Type: application/octet-stream │ │
│ └────────────────┬────────────────────┘ │
│ │ │
└────────────────────────────┼────────────────────────────────────┘
│
▼
┌────────────────────────────────────┐
│ ainote_server │
│ ┌──────────────────────────────┐ │
│ │ Api::CertMirrorController │ │
│ │ - McpKey 인증 │ │
│ │ - HMAC 재계산 후 비교 │ │
│ │ - 50MB 초과 시 inline 413 │ │
│ │ - tempfile → storage_path │ │
│ └──────────────┬───────────────┘ │
│ │ │
│ ▼ │
│ CertMirrorBlob (PostgreSQL) │
│ - commit_sha, byte_size, sha256 │
│ - uploaded_at, uploaded_by │
│ - storage_path → 디스크 또는 S3 │
└────────────────────────────────────┘신뢰 모델
- 수신자(ainote) 는 token 으로 사용자 인증, HMAC 으로 무결성 검증. body 자체는 fastlane match 가 이미
MATCH_PASSWORD로 암호화한 상태라 ainote 가 평문 cert 를 만지지 않습니다. - 발신자(클라이언트) 는
MATCH_PASSWORD만 분실 안 하면 안전 — repo 도 mirror 도 둘 다 털려도 평문 cert 추출 불가. - 자기 자신(ainote 운영자) 에게도 cert 평문은 안 보임. UserMirrorCredential
hmac_secret은 non-deterministic 암호화,token은 deterministic (검색용).
AI agent 통합 시 추천 패턴
- 첫 실행 — 사용자에게
ainote-cert-mirror issue(또는 api 페이지 의 curl) 안내. CLI 가 Keychain 저장까지 자동 - fastlane 워크플로우의 마지막에 mirror_to_ainote 패턴 호출
- 주기적으로
ainote-cert-mirror verify호출 (heartbeat — endpoint 200 + Keychain present + blob count) - 새 dev 환경 셋업 시 setup-guide "다른 mac 에서 같은 계정 사용" 섹션 따라가면 끝
- 회전 (
issue --rotate) 후에는 반드시 fastlane sync_signing 재실행 — 기존 blob 무효화됨
다음
- API 레퍼런스 — 엔드포인트 / 인증 / 에러 코드
- fastlane 통합 예시 — Fastfile drop-in 스니펫
- 사용자 메모리 / dev docs 와 차이 — 일반 메모리와 다른 점
알려진 제약
UserMirrorCredential은 계정당 1개. 회전은 기존 entry 삭제 + 재생성 (자세한 절차는 api 인증 섹션)- blob 영구 보관 — soft delete 지원 안 함 (이력 보존 정책상)
- 한 계정당 5GB 정도까지를 권장 (강제 quota 는 미구현)
- Render edge WAF 가 default
Ruby/curlUser-Agent 를 차단. 클라이언트는 custom User-Agent 명시 필수 (ainote-fastlane-mirror/1.0등)