서론
얼마전 PonsWarp과 ponswarp-desktop 두 리포지토리를 동시에 한 번의 스프린트로 마무렀다.
PonsWarp은 브라우저 기반 P2P 파일 전송 툴이고, ponswarp-desktop은 이를 데스크탑(Tauri) + CLI/데몬 형태로 분리한 프로젝트다.
핵심 전환점: “스웜/릴레이 멀티 피어 UI” → “1:1 P2P 강제”와 “Tauri에 의존한 CLI” → “warp-core 독립 추출(WebKit-free)” — 기능을 덜고, 믿을 수 있게 만드는 방향이다.
여기서는 어떤 문제 → 판단 → 수정 → 실패/복구가 이어졌는지 적는다.
—
1. PonsWarp — 2026-08-19 릴리스
릴리즈 ID 20260819055024-4df9a90, prod warp.ponslink.com 포트 5502 healthy.
1-1. 네트워크 끊김 감지·재연결 (feat(reconnect))
문제: 모바일/노트북은 Wi-Fi가 자꾸 끊겼다 되는데, PonsWarp은 네트워크 복구를 감지하지 못해 전송이 영원히 멈추는 상황이 있었다. receiver가 online 이벤트를 듣지 못하면 sender도 재시도 없이 pending 상태로 머물렀다.
판단: 브라우저의 window 'online'/'offline' 이벤트를 signaling 간헐 신호로 승격한다. 재연결은 한 번의 강제 종료 → RESUME 흐름이어야지, 무한 재시도나 큐 중복으로 가면 안 된다.
수정:
- receiver:
window.addEventListener('online')→ HUDRECONNECTING...띄우고, sender에 RESUME 힌트를 즉시 보냄. - sender:
online감지 시requestMoreChunks()로 중단된 조각을 바로 요청.store/transferStore.ts에서completedPeers/queuedPeers필드를 삭제하고WAITING/CONNECTED/READY단일 상태로 단순화. - UI: “Receivers N/3” → “WAITING”, “QUEUED Position N” → 단일 WAITING 상태. 릴레이/스웜 멀티피어 UI 제거 → 1:1 P2P 강제.
삽질/교훈: 처음엔 재연결을 자동 재시도 루프로 구현했는데, NAT가 복구되는 데 2~3초 걸리고 그 동안 재시도 폭풍이 퍼져 서버 메모리가 늘어남. 해결을 online 이벤트 한 번에 처리하고, 중복 시도는 rate-limit guard (debounce(500ms))로 억제했다.
1-2. 시큐리티 하드닝 (fix(security))
문제/판단: 시그널링 서버(TURN/HMAC, billing, SDP)에 입력 한계가 없었고, WASM core의 암호 루틴이 edge case에서 메모리 bypass를 허용하더라.
수정 (fail-closed 기조):
- TURN HMAC-SHA1 + expiry constant-time 비교, Billing
Url::parseorigin strict compare + PayPal 토큰 60s 캐시. - SDP 256KiB / ICE 4KiB / Manifest 1MiB 입력 크기 제한.
- Mesh memory bypass를
debug_assertions이나 명시적PONSWARP_MESH_ALLOW_INSECURE_MEMORYenv 없이는 차단. - WASM HKDF salt RFC5869 준수(hash >32B), LZ4 256MiB 캡, ReorderingBuffer 지연 할당(기존 128MiB prealloc 제거).
교훈: fail-closed는 “기능을 더 껴도 된다”는 게 아니라, 예외가 명시적일 때만 열리게 한다. strict: true는 M5(다음 스프린트)로 미루고, 지금은 hand-rolled 경로에만 constant-time 검증을 넣어 WASM binding이 깨지지 않게 했다.
1-3. 배포/인프라(fix(deploy) + chore(infra))
문제 → 복구:
1. limit_req_zone을 nginx server context에 넣어 nginx -t 실패 → 롤백이 불완전해 current symlink이 깨져 500. 복구: limit_req_zone을 conf.d/ponswarp-limit-req.conf (http context)로 옮기고, proxy_next_upstream 추가.
2. backend.inc가 없는 오래된 릴리즈에서 Hard substitution 에러 + 롤백 스크립트 충돌.
수정: backend.inc 존재 확인 guard, STAGING_PATH guard, blue/green 5502↔5503 전환 시 wait_http /health + /ready 45회 폴링, current symlink 원자적 mv -Tf.
교훈: 배포 스크립트의 rollback path도 배포 코드만큼 테스트해야 한다. restore_after_failure()가 복구를 검증하지 않아서 두 번 배포를 날렸다.
1-4. CI stabilize
ci.yml 3-job(frontend/backend/wasm-provenance):
wasm:build가type-check보다 먼저 와야 한다 (TS가 WASM bindings을 import하기 때문).wasm-pack를cargo install→--locked로 고정 (rustc 1.85→stable toolchain문제).cargo test를 두 crate(frontend + backend WASM) 모두--locked로.
삽질: 3번 연속 CI 실패했는데, 원인은 rustc toolchain cache (CI runner가 1.84로 고정돼 있었음). rust-toolchain pin + stable 지정으로 3번째 시도 4feb0ef→8015f51 성공.
1-5. QA 게이트
eslint0 errors (12no-explicit-anywarn은 허용)tsc0 errors,vitest167 pass,cargo test67 pass,pnpm audit0- prod-transfer QA: 1MB
MATERIALIZED 486ms, 10MB46% 1.04 MB/s
잔제품: CHANGELOG.md가 stale(dup 0.7.0 ×5), 2026-08-19 하드닝 릴리즈가 아직 거기에 반영 안 됨 → docs/FINAL-REPORT-2026-08-19.md가 실제 change log 역할.
—
2. ponswarp-desktop v1.1.46 — warp-core 추출
2-1. 왜 warp-core를 뺐는가
문제: 기존 warp 커맨드는 ponswarp_lib에 연결돼 있었고, 이는 Tauri → webkit2gtk를 필요로 했다. 데스크탑에서는 괜찮지만, 서버/컨테이너/NAS에서 P2P 그리드에 참여하려면 WebKit 의존성이 폭풍이다.
판단: “GUI 데스크탑”과 “헤드리스 참여자”는 서로 다른 제품이다. 한 코드베이스에서 둘 다 강요하면, 컨테이너 이미지 800MB (webkit 포함)가 되고 유지보수 비용만 폭증한다.
수정: warp-core(cargo crate)를 분리 → warp(CLI) + warp-agentd(daemon)는 Tauri 없이 빌드. ldd 기준 GTK/WebKit 0, 5.2/5.4MB. GUI ponswarp_lib는 pub use warp_core as warpinfra로 재사용 (invariant INV-6: single Rust core).
2-2. warp peers discover와 헤드리스 참여
문제: CLI 참여자가 “근처 피어가 누구 있는지” 모르면 --peer host:port를 일일이 입력해야 해, AI 에이전트나 서버에선 불가능.
수정: src-tauri/warp-core/src/mdns_peers.rs → _warpinfra._udp.local. mDNS advertise/browse → warp peers discover --ms 2000 --json 출력:
{"ok":true,"command":"peers.discover","nodes":[...],"count":N}
이걸로 warp release get-remote --discover가 mDNS 브라우징 → 자동으로 근처 seed를 찾아서 다운로드. JSON 규약은 docs/06-cli-first-and-ai-agents.md에 정의돼 있어.
교훈: “CLI-first”는 UX 선택이지 아니라 에이전트 친화성 선택이다. AI 에이전트가 파싱하기 쉬운 JSON 출력을 기본으로 하면, warp을 shell tool call 하나로 붙잡고 orchestrate할 수 있어.
2-3. 헤드리스 검증
docs/reports/warpinfra-headless-2026-08-19.md에 검증 흐름 기록:
warp release publish-local payload.bin --manifest payload.warp.json
warp-agentd --manifest payload.warp.json --bind 127.0.0.1:4242 --node-id node-demo-1 --site-id site-1
warp peers discover --ms 2000 # {"ok":true,..., "count":N}
warp release get-remote payload.warp.json --peer 127.0.0.1:4242 --out ./out --insecure-dev
결과: finalHashVerified:true
sandbox(mDNS multicast 필터)에서는 count:0이 나왔지만, 실제 LAN IP가 있는 환경에선 정상 enumerate. 데이터 평면은 loopback QUIC으로 검증 완료.
2-4. 릴리즈 패키징
1.1.45= 하드닝 배치 초안,1.1.46= warp-core 추출 + compose rename 포함한 재릴리즈.1.1.47예정:warp-core-bin/SHA256SUMS를release.yml에 자동 업로드 +install-agent.sh배포.scripts/sync-version.js가package.json→tauri.conf.json+ 양쪽Cargo.toml(GUI + warp-core) 동기화. semantic versioning drift 방지.
교훈: 버전 동기화 스크립트가 없으면 GUI Cargo와 warp-core Cargo가 서로 다른 버전을 유지한다. 1.1.45 배포 직후 이걸 눈치채서 1.1.46으로 즉시 재출시.
—
3. 배운 것
1. 멀티 피어 UI ≠ 멀티 피어 품질. 멀티피어(mesh/릴레이)는 복잡도 폭증과 재연결 불가를 낳았다. 1:1 강제로 단순화하니, 재연결/복구/테스트 커버리지가 전부 개선됐다.
2. 헤드리스 ≠ 데스크탑 경량화. 헤드리스 참여자를 위해선 CLI/JSON 계약이 먼저다. GUI 없으면 warp status 같은 커맨드조차 JSON 출력 규약이 필요하다.
3. 배포 rollback path를 코드만큼 테스트하라. limit_req_zone 롤백이 불완전한 게 500을 2건 만들었다. restore_after_failure()가 복구 검증을 잊지 말라.
4. CI는 deterministic 해야 한다. wasm:build → type-check 순서, cargo install --locked, toolchain pin이 없으면 매번 다른 사람의 CI 실패를 디버깅하게 된다.
4. 다음 작업
- F1: 물리적 2호스트 20GB 이양 검증 (Host B 부재 → blocked)
- F2:
strict:true전역 CSP + Control TLS proxy packaging - M5:
aes-gcm/hkdfcrate로 교체 (hand-rolled 유지 중) CHANGELOG.md정리 및 2026-08-19 하드닝 릴리즈 반영
—
*발행 채널: portfolio(local Prisma DB) + blog.ponslink.com(WordPress)*
*릴리즈 기준: PonsWarp release 20260819055024-4df9a90, ponswarp-desktop v1.1.46 (feat/warpinfra-rc1-baseline @ 0a21341)*
댓글