<PonsLink/>
작업
글
주제
시리즈
카테고리
연락
⌘K 검색
About
코드와 제품으로

코드와 제품으로

연결되는 세계

연결되는 세계

PonsLink는 만남 뒤 요청을 세션으로 잇고, PonsWarp는 서버 보관 없이 파일을 직접 전송합니다. 이 블로그는 기능 소개보다 설계 판단과 운영 증거를 남깁니다.

연락처

[email protected]github.com/DeclanJeon

운영

콘텐츠는 WordPress headless CMS, 공개 UI는 Next.js입니다.

초안 미리보기
© 2026 PonsLink Blog|Next.js · WordPress REST · Framer Motion
Local-first · Backup-before-deploy

PonsWarp 1:1 하드닝 회고

Declan Jeon·2026년 8월 19일

  • P2P/분산기술
  • 개발 회고

1:1 P2P 강제, 시그널링 서버 하드닝, CI 3-job stabilize, 배포 롤백 복구, warp-core Tauri 분리, 헤드리스 워커 + peers discover.

서론

얼마전 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') → HUD RECONNECTING... 띄우고, 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::parse origin strict compare + PayPal 토큰 60s 캐시.
  • SDP 256KiB / ICE 4KiB / Manifest 1MiB 입력 크기 제한.
  • Mesh memory bypass를 debug_assertions이나 명시적 PONSWARP_MESH_ALLOW_INSECURE_MEMORY env 없이는 차단.
  • 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 게이트

  • eslint 0 errors (12 no-explicit-any warn은 허용)
  • tsc 0 errors, vitest 167 pass, cargo test 67 pass, pnpm audit 0
  • prod-transfer QA: 1MB MATERIALIZED 486ms, 10MB 46% 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/hkdf crate로 교체 (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)*

Related

관련 운영 노트

같은 주제에서 이어서 읽기 좋은 글입니다.

  • [Blueprint L3] Pons 포트폴리오 — 의존성 0, 단일 index.html이 오래 가는 이유

    의존성 0, 단일 index.html 41KB. 디자인 토큰 SSOT와 13개 제품 쇼케이스로 만든 정적 포트폴리오가 오래 가는 이유.

  • [Blueprint L2] PonsLink Control Tower — Threads에 dry-run으로 쏘는 이유

    Python Threads 컨트롤타워. cardnews 재사용→생성 폴백, n8n 패치, dry-run 기본 게이트로 안전하게 쏘는 자동발행.

  • [Blueprint L1] PonsLink P2P Room — WebRTC 화상회의를 BFF로 감싼 이유

    React SPA와 Express BFF가 Socket.IO로 시그널링하고, 미디어는 mesh로 가는 2티어. TURN 그랜트와 presence lease로 룸을 만든 화상회의.

댓글

아직 댓글이 없습니다.

현재 이 글의 댓글은 닫혀 있습니다.