이 레포가 무엇을 하는 물건인가.
AssppWeb은 Apple ID로 로그인해 App Store 앱의 라이선스를 취득하고, 해당 IPA 파일을 서버에 컴파일하여 itms-services:// 프로토콜로 iOS 기기에 직접 설치할 수 있게 해주는 셀프호스팅 웹 도구다.
핵심 차별점은 브라우저 안의 WebAssembly(libcurl.js)가 Apple 서버와 TLS 1.3으로 직접 통신한다는 것 — 서버(백엔드)는 암호화된 Wisp TCP 스트림을 맹목적으로 중계할 뿐, Apple 자격증명을 절대 볼 수 없다.
Payload/앱이름.app/ 구조를 담고 있다. 앱스토어 외 배포 시 DRM 서명 파일(SINF)이 포함되어야 실행된다.SC_Info/ 폴더에 위치한다. AssppWeb 서버는 이 SINF를 브라우저에서 받아 CDN에서 내려받은 IPA에 주입한다.트렌딩 이유, 유사 도구 대비 장점.
AssppWeb이 TrendShift에서 주목받는 데는 두 가지 맥락이 있다.
첫째, Lakr233이라는 작성자의 명성이다. Lakr233은 iOS/macOS 생태계에서 Asspp(앱스토어 앱 취득 CLI), Reveil(iOS 시스템 정보 앱), Copilot for Xcode 등 다수의 인기 오픈소스 도구로 알려진 개발자다. AssppWeb은 그의 기존 CLI 도구 Asspp의 웹 기반 후속작이다.
둘째, Zero-Trust 아키텍처라는 기술적 차별성이다. 유사한 앱 취득 도구(예: ipatool, 과거의 다양한 스크립트들)는 서버나 로컬 환경에 Apple ID/비밀번호를 평문으로 전달하는 경우가 많았다. AssppWeb은 libcurl.js(WebAssembly)와 Mbed TLS를 브라우저 안에 탑재해 이 문제를 정면으로 해결한다.
Zero-Trust 아키텍처는 서버 측 도청을 막지만, 악의적인 호스트가 변조된 프론트엔드를 배포하면 브라우저가 암호화하기 전에 자격증명을 탈취당할 수 있다. 레포는 이를 명시하며 공개 인스턴스 사용을 강하게 경고한다.
또한 Apple의 이용약관상 서드파티를 통한 앱 취득은 허용되지 않을 수 있다. 학습 목적 이상의 사용은 개인이 책임져야 한다.
Docker Compose 한 줄(docker compose up -d)로 내 서버에 배포하고, SSL 인증서를 직접 확인하는 것이 유일한 안전한 사용법이다. Cloudflare Workers+Containers 배포는 클릭 한 번으로 가능하다.
| 도구 | 방식 | 서버 자격증명 노출 | 배포 방법 |
|---|---|---|---|
| AssppWeb | WebAssembly TLS + Wisp 중계 | 없음 (Zero-Trust) | Docker / Cloudflare / Railway |
| ipatool (CLI) | 로컬 CLI, Apple ID 직접 입력 | 로컬 프로세스에만 | 로컬 설치 |
| 기존 웹 스크립트들 | 서버가 Apple API 직접 호출 | 서버에 평문 전달됨 | 다양 |
| AltStore / Sideloadly | 데스크탑 앱 + Apple ID | 로컬 앱에만 | 데스크탑 설치 |
실제 package.json과 Dockerfile을 분석한 결과.
| 기술 | 버전 | 역할 |
|---|---|---|
| React | 19.0 | UI 컴포넌트 트리. Account · Search · Download · Settings 4개 주요 페이지 |
| TypeScript | 5.7 | 전 소스 타입 안전성 — Apple 프로토콜 타입까지 엄격 정의 |
| Vite | 6.0 | 빌드 도구 · 개발 서버. ESM 네이티브, 빠른 HMR |
| Tailwind CSS | 4.0 | 유틸리티 CSS. Vite 플러그인으로 통합 |
| React Router | 7.1 | SPA 라우팅 — /accounts /search /downloads /settings |
| Zustand | 5.0 | 전역 상태 관리 — accounts · downloads · settings · toast 4개 스토어 |
| libcurl.js | 0.7.4 | 핵심! WebAssembly로 컴파일된 libcurl + Mbed TLS 1.3. 브라우저에서 Apple API와 직접 TLS 통신 |
| idb | 8.0 | IndexedDB 래퍼. Apple ID 자격증명·세션 쿠키를 브라우저 로컬에 암호화 저장 |
| i18next | 23.10 | 다국어(en-US / zh-CN / zh-TW / ja / ko / ru) 지원 |
| qrcode.react | 4.2 | iOS 설치 URL QR코드 표시 |
| Vitest | 4.0 | 프론트엔드 단위 테스트 — Apple 프로토콜 파싱·인증·쿠키 테스트 |
| 기술 | 버전 | 역할 |
|---|---|---|
| Node.js | 20 LTS | 런타임 (Alpine Linux Docker 이미지) |
| Express | 4.21 | HTTP API 서버. /api/* 라우트 처리 |
| @mercuryworkshop/wisp-js | 0.4.1 | Wisp 서버 — WebSocket으로 들어오는 암호화 TCP를 Apple 서버로 맹목적 중계 |
| yauzl-promise | 4.0 | 비동기 ZIP/IPA 파일 읽기 (스트리밍) |
| adm-zip | 0.5 | ZIP 파일에 SINF · iTunesMetadata.plist 주입 |
| bplist-parser / bplist-creator | 최신 | Apple 바이너리 plist 파싱 및 생성 |
| plist | 3.1 | XML plist 파싱 (Info.plist · Manifest.plist) |
| uuid | 11.0 | 다운로드 작업 고유 ID 생성 |
| Vitest (backend) | 4.0 | 백엔드 단위 테스트 — Wisp 프록시·라우트·SINF 주입 테스트 |
| 레이어 | 기술 | 설명 |
|---|---|---|
| 컨테이너화 | Docker (멀티스테이지) | 3단계 빌드: ① 프론트엔드 빌드 ② 백엔드 빌드 ③ 런타임 통합. 최종 이미지에 zip CLI 포함 (SINF 주입용) |
| 컨테이너 레지스트리 | GitHub Container Registry | ghcr.io/lakr233/assppweb:latest 공개 이미지 |
| 로컬 셀프호스팅 | Docker Compose | compose.yml 한 파일로 서비스 · 볼륨 · 환경변수 정의 |
| 클라우드 배포 1 | Cloudflare Workers + Containers | wrangler.jsonc + Durable Objects. 유료 플랜 필요 |
| 클라우드 배포 2 | Railway | GHCR 이미지 직접 배포. 볼륨 마운트 + 환경변수 설정 |
| CI/CD | GitHub Actions | .github/workflows/docker.yml — GHCR 이미지 자동 빌드·푸시 |
| 리버스 프록시 | Caddy / Nginx (선택) | iOS 설치(itms-services://)에 HTTPS 필수 — TLS 종료 |
| WebSocket 필수 | Wisp over WS (/wisp/) | CDN·프록시에서 WebSocket 업그레이드 허용 필수 |
Zero-Trust 설계의 실제 작동 흐름.
frontend/src/apple/ 폴더가 전체 Apple 프로토콜 스택을 담는다. authenticate.ts가 로그인 흐름을, purchase.ts가 라이선스 취득을, download.ts가 CDN URL·SINF 수집을 담당한다. 이 모든 HTTP 요청은 request.ts를 통해 libcurl.js WebAssembly로 실행된다 — 브라우저 내장 fetch()가 아니다.
Apple 인증 후 서버는 pod 헤더를 반환한다. 이후 모든 Store/Purchase API 호출은 p{pod}-buy.itunes.apple.com으로 라우팅된다. IndexedDB의 Account 객체가 pod 번호를 저장하며, storeAPIHost(pod?)·purchaseAPIHost(pod?) 함수가 동적으로 호스트를 결정한다.
서버의 sinfInjector.ts는 IPA(ZIP) 파일을 열어 Manifest.plist에서 SINF 경로를 읽고, 브라우저에서 받은 base64 SINF 데이터를 임시 디렉토리에 쓴 뒤 시스템 zip 명령으로 IPA에 갱신한다. 경로 순회(path traversal) 공격 방어 로직도 포함되어 있다.
택배 봉투 봉인 비유: 브라우저는 Apple에게 보내는 편지(자격증명)를 스스로 봉투에 넣고 봉인한다. 서버(Wisp)는 그 봉인된 봉투를 그냥 Apple 주소로 배달만 한다 — 봉투를 열어볼 수 없다. Apple이 답장(SINF, CDN URL)을 보내면 브라우저가 직접 뜯어보고 서버에는 공개 정보(CDN URL, SINF)만 전달한다. 서버는 CDN에서 앱 본체를 내려받아 SINF 도장을 찍어 iOS 기기에 전달하는 '포장·배달부' 역할이다.
실제 레포 구조를 직접 확인한 결과.
frontend/src/apple/이 폴더가 AssppWeb의 핵심이다. Apple iTunes Store 프로토콜을 TypeScript로 완전히 재구현했으며, Swift 참조 구현(references/ApplePackage/)을 소스 오브 트루스로 삼아 CodingKeys·인증 흐름·bag 엔드포인트를 동기화한다. 웹 표준 Fetch API 대신 libcurl.js WebAssembly를 사용하기 때문에 실제 curl의 TLS 스택이 브라우저 안에서 실행된다.
이 레포에서 무엇을 배울 수 있는가.
libcurl.js 패키지는 libcurl C 라이브러리를 Emscripten으로 WebAssembly로 컴파일한 것이다. frontend/src/apple/libcurl-init.ts와 request.ts를 읽으면 브라우저에서 WASM 모듈을 초기화하고 C 함수를 JavaScript에서 호출하는 패턴을 배울 수 있다.
자격증명을 서버에 절대 보내지 않는 설계는 보안 아키텍처의 교과서적 사례다. Wisp 프로토콜을 통한 맹목 TCP 중계, IndexedDB를 이용한 브라우저-사이드 비밀 저장, TLS 1.3 세션을 브라우저가 직접 관리하는 패턴 — 이 세 가지를 함께 공부하면 된다.
idb 라이브러리로 Apple 세션 정보(쿠키, 토큰, DSID)를 브라우저 로컬에 영구 저장하는 패턴. Zustand 인메모리 상태와 IndexedDB 영구 상태를 함께 쓰는 방식을 store/accounts.ts에서 볼 수 있다.
프론트엔드(Node Alpine)·백엔드(Node Alpine + python3/make/g++)·런타임 세 단계로 분리하여 최종 이미지 크기를 최소화하는 패턴. Dockerfile이 교과서적 예시다.
wrangler.jsonc와 cloudflare/src/index.ts는 Cloudflare의 Durable Objects가 Container 인스턴스를 관리하는 새로운 배포 모델을 보여준다. 2026년 현재 비교적 새로운 기능으로, 실제 사용 사례를 탐구하기 좋다.
references/ApplePackage/(Swift 참조 구현)와 TypeScript 구현을 대조하면 iTunes Store API의 비공개 프로토콜(bag.xml, pod 라우팅, SINF 형식, bplist 형식)을 학습할 수 있다. 단, 이는 역공학 결과물이며 Apple의 공식 문서가 없는 영역이다.
직접 배포하는 세 가지 방법.
| 항목 | 최소 요구사항 | 비고 |
|---|---|---|
| 서버 OS | Linux (Alpine 권장), macOS | Docker 컨테이너 내부는 Alpine Linux 3.x |
| Docker | Docker Engine 24+, Compose v2 | 단독 배포 필수. 빌드 시 python3·make·g++ 필요(자동 설치) |
| HTTPS | 유효한 SSL/TLS 인증서 | iOS itms-services:// 설치에 HTTPS 필수. Let's Encrypt + Caddy 추천 |
| WebSocket | 리버스 프록시 WS 업그레이드 허용 | Wisp 프로토콜 필수 경로: /wisp/ |
| 디스크 | IPA 파일 크기에 따라 수 GB | DATA_DIR에 마운트된 볼륨 권장 |
| Cloudflare 배포 | Workers 유료 플랜 | Containers 기능이 Free 플랜 미지원 |
| 클라이언트 | iOS Safari (앱 설치 시) | 앱 검색·다운로드는 일반 브라우저 가능 |
# compose.yml 받기curl -O https://raw.githubusercontent.com/Lakr233/AssppWeb/main/compose.yml# 시작 (포트 8080, /mnt/asspp-data 에 IPA 저장)docker compose up -d# 비밀번호 설정 (선택)# compose.yml 환경변수에 ACCESS_PASSWORD=your_password 추가
README의 "Deploy to Cloudflare" 버튼을 클릭하면 Workers Scripts Edit + Containers Edit + Cloudchamber Edit 권한을 가진 API 토큰을 요구한다. 유료 플랜 필수.
# 1. railway.com/new/image 에서 이미지 입력ghcr.io/lakr233/assppweb:latest# 2. Settings → Healthcheck Path: /api/settings# 3. 볼륨 마운트: /data → 환경변수: DATA_DIR=/data# 4. Networking → 퍼블릭 도메인 생성
| 변수 | 기본값 | 설명 |
|---|---|---|
| PORT | 8080 | 서버 리슨 포트 |
| DATA_DIR | ./data | 컴파일된 IPA 저장 디렉토리 |
| PUBLIC_BASE_URL | 자동감지 | 설치 매니페스트 URL 생성용 공개 도메인 |
| ACCESS_PASSWORD | (없음) | 웹 UI 및 API 접근 비밀번호 (빈 값 = 비활성화) |
| DOWNLOAD_THREADS | 8 | IPA 병렬 다운로드 스레드 수 (1~32) |
| AUTO_CLEANUP_DAYS | 0 | N일 이상 된 IPA 자동 삭제 (0=비활성) |
| AUTO_CLEANUP_MAX_MB | 0 | 최대 캐시 크기 초과 시 오래된 IPA 삭제 (0=비활성) |
| MAX_DOWNLOAD_MB | 0 | 이 크기 초과 다운로드 거부 (0=무제한) |
| UNSAFE_DANGEROUSLY_DISABLE_HTTPS_REDIRECT | false | HTTP→HTTPS 리다이렉트 비활성화 (위험, 최후 수단) |
난이도별 3~5개 과제.
공식 Docker 이미지를 받아 로컬에서 띄우고 웹 UI를 탐색해본다. 프론트엔드 구조(Account · Search · Download 탭)와 네트워크 탭에서 /wisp/ WebSocket 연결이 어떻게 열리는지 확인한다.
레포를 클론해 프론트엔드(npm run dev)와 백엔드(npm run dev)를 별도 터미널에서 동시에 실행한다. frontend/src/apple/config.ts에서 Pod 라우팅 함수를 읽고 어떤 Apple 서버 주소가 생성되는지 이해한다.
Chrome DevTools의 Network 탭 → WS → Messages에서 Wisp 메시지를 캡처한다. backend/src/services/wsProxy.ts 소스와 대조하며 @mercuryworkshop/wisp-js 라이브러리가 어떻게 TCP를 멀티플렉싱하는지 분석한다. 실제 패킷이 암호화되어 있어 내용은 보이지 않음을 확인한다(Zero-Trust 증명).
backend/src/services/sinfInjector.ts를 읽고 IPA(ZIP) 파일에서 Manifest.plist를 찾아 SINF 경로를 추출하는 로직을 이해한다. backend/tests/sinfInjector.test.ts를 참고해 테스트용 더미 IPA를 만들고 SINF 주입이 올바르게 동작하는지 검증하는 추가 테스트를 작성한다.
frontend/src/apple/libcurl-init.ts와 request.ts를 분석해 WebAssembly 모듈 로딩·초기화·C 함수 바인딩 과정을 정리한다. 브라우저 내장 fetch()와 어떻게 다른지(TLS 세션 제어, 쿠키 관리 수준) 비교 문서를 작성한다. 선택적으로 libcurl.js npm 패키지 소스(libcurl.js)도 탐색한다.
AssppWeb을 완전히 이해하기 위한 주차별 학습 계획.
| 주차 | 주제 | 학습 자료 / 실습 |
|---|---|---|
| 1주차 | React 19 + TypeScript 기초 | 공식 React 문서 → frontend/src/components/ 컴포넌트 읽기. Zustand 공식 문서 → store/accounts.ts 분석 |
| 2주차 | Vite + Tailwind CSS 4 | Vite 공식 가이드 → frontend/vite.config.ts 분석. Tailwind 4의 Vite 플러그인 방식(PostCSS 불필요)을 기존 방식과 비교 |
| 3주차 | IndexedDB + idb 라이브러리 | MDN IndexedDB API → idb 패키지 README → store/accounts.ts에서 계정 저장 코드 분석. LocalStorage와의 차이(용량·트랜잭션·비동기) |
| 4주차 | WebAssembly 기초 + Emscripten | MDN WebAssembly 가이드 → Emscripten 공식 문서 → libcurl.js 레포 → libcurl-init.ts 분석 |
| 5주차 | WebSocket + Wisp 프로토콜 | MDN WebSocket API → @mercuryworkshop/wisp-js GitHub → wsProxy.ts 분석. TCP 멀티플렉싱 개념 이해 |
| 6주차 | TLS/SSL 심화 | Cloudflare의 TLS 1.3 학습 자료 → Mbed TLS 개요 → 브라우저 내장 TLS와 libcurl.js WASM TLS의 차이 |
| 7주차 | Node.js Express + TypeScript | Express 4 공식 문서 → backend/src/routes/ 전체 읽기 → middleware/ 패턴 이해 |
| 8주차 | Docker 멀티스테이지 빌드 | Docker 공식 멀티스테이지 문서 → Dockerfile 분해 → compose.yml 실습 배포 |
| 9주차 | Cloudflare Workers + Durable Objects | Cloudflare Workers 공식 문서 → Durable Objects 가이드 → cloudflare/src/index.ts + wrangler.jsonc 분석 |
| 10주차 | Apple 이진 포맷 (plist · IPA) | Apple plist 포맷 문서 → bplist-parser 소스 → sinfInjector.ts에서 바이너리/XML plist 파싱 비교 분석 |
이 레포를 읽으며 만나는 핵심 개념 정리.
| 키워드 | 설명 |
|---|---|
| Zero-Trust Architecture | 서버를 신뢰하지 않는 설계 원칙. 자격증명·민감 데이터를 서버에 전달하지 않고 클라이언트가 직접 처리 |
| libcurl.js | libcurl C 라이브러리를 Emscripten + WebAssembly로 컴파일한 npm 패키지. 브라우저에서 실제 TLS 스택 실행 가능 |
| Wisp 프로토콜 | WebSocket 위에서 여러 TCP 연결을 다중화하는 프로토콜. 브라우저가 임의 TCP 서버와 통신하는 터널 역할 |
| IPA (iOS Package Archive) | iOS 앱 배포 패키지. 확장자 .ipa. 내부는 ZIP 구조로 Payload/.app 디렉토리 포함 |
| SINF (Signature Info) | Apple FairPlay DRM 라이선스 바이너리. 특정 Apple ID에 묶인 앱 소유 증명 파일 |
| plist (Property List) | Apple의 설정·데이터 직렬화 포맷. XML 형식과 바이너리(bplist) 형식 두 가지 존재 |
| itms-services:// | Apple OTA(Over-The-Air) 앱 설치 URL 스킴. HTTPS 서버에 manifest.plist가 있어야 함 |
| Apple bag.xml | Apple 서비스 URL 디렉토리. init.itunes.apple.com/bag.xml — 다양한 Apple API 엔드포인트 URL을 반환 |
| Pod 라우팅 | Apple 인증 후 반환되는 pod 번호에 따라 p{N}-buy.itunes.apple.com으로 API 라우팅하는 Apple 내부 메커니즘 |
| IndexedDB | 브라우저 내장 NoSQL 데이터베이스. LocalStorage보다 용량이 크고 트랜잭션 지원. AssppWeb이 Apple 세션을 저장하는 곳 |
| Zustand | React용 경량 전역 상태 관리 라이브러리. Redux보다 보일러플레이트가 적음 |
| Durable Objects | Cloudflare Workers에서 상태를 영구 보존하는 서버리스 인스턴스. AssppWeb은 Container를 1개 DO로 관리 |
| Emscripten | C/C++ 코드를 WebAssembly로 컴파일하는 툴체인. libcurl을 브라우저에서 실행 가능하게 만드는 데 사용됨 |
| 멀티스테이지 Docker 빌드 | 하나의 Dockerfile에 여러 FROM을 써서 빌드 의존성을 최종 이미지에서 제거하는 패턴 |
| Mbed TLS | ARM이 관리하는 경량 TLS 구현체. libcurl.js가 브라우저 WASM 환경에서 TLS 1.3을 구현하는 데 사용 |
출처 · 정리 기준일 2026-06-27 · 솔직한 한계 명시.