맥미니 OpenClaw 비서 봇 만들기 (26) — 폰으로 말 걸기까지, 하룻밤의 네트워크 삽질

2026. 7. 11. 23:25맥미니 OpenClaw 비서 봇 만들기

맥미니 OpenClaw 비서 봇 만들기 (26) — 폰으로 말 걸기까지, 하룻밤의 네트워크 삽질

한나에게 폰으로 말을 걸고 싶었다. ClawVoice라는 서드파티 iOS 앱을 깔면 된다길래 가볍게 시작했는데, 결과적으로 게이트웨이 네트워크 설정 전체를 세 번 갈아엎고 나서야 연결됐다.

시작 — Spotify는 잠깐 미뤄두고

원래는 "한나가 Spotify로 노래를 틀어줄 수 있는지" 물어보다 시작됐다. 조사해보니 Spotify Web API로 가능하긴 한데, 이건 한나가 직접 소리를 내는 게 아니라 이미 켜져 있는 이사장님 기기에 재생 명령을 원격으로 보내는 "리모컨" 구조였다. Ultron과 설계 방향(봉투 표준과 무관한 별도 트랙, 위험 결정 정지 적용)까지 정리했지만, 우선순위가 낮아 착수는 보류했다.

그러다 폰 음성 앱 얘기가 나오면서 방향이 바뀌었다.

게이트웨이는 원래 자기 자신만 볼 수 있었다

맥미니의 OpenClaw 게이트웨이는 127.0.0.1(loopback)에만 열려 있었다. 폰에서 접속하려면 이걸 어떻게든 열어야 했다. LAN으로 그냥 열까 하다가, 이건 같은 와이파이 쓰는 누구나 시도해볼 수 있는 문이라 위험하다고 판단했다. Tailscale이라는 사설 터널을 쓰기로 했다 — 이사장님 기기끼리만 통하고, 바깥에서는 존재 자체를 모르는 통로.

bind=tailnet은 존재하지 않는 설정이었다

여기서 첫 삽질이 시작됐다. gateway.bind"tailnet"으로 바꿨더니 게이트웨이가 127.0.0.1 리스닝을 아예 그만두고 tailnet IP 하나로만 완전 전환됐다. 이게 나중에 진짜 문제를 일으켰다 — 로컬 CLI도, 브라우저도 신뢰 경로를 잃어버렸다.

순환 잠금 — 아무도 아무도 승인할 수 없는 상태

폰에서 페어링을 시도하면 "device is not approved yet"이 떴다. CLI로 승인하려 하니, CLI 자기 자신도 스코프 승인이 안 된 상태였다. openclaw devices approve를 실행할 때마다 CLI가 자기 자신의 새 승격 요청을 만들고, 그 요청이 또 승인이 필요해서 무한히 새 ID만 발급되는 루프에 빠졌다. 승인해줄 수 있는 유일한 신뢰 경로(로컬 브라우저)는 bind=tailnet 때문에 접근 자체가 끊겨 있었다.

되돌아가는 수밖에 없었다. bindloopback으로 롤백하고, 로컬 신뢰 경로부터 복구했다.

진짜 방법 — bind가 아니라 tailscale.mode

로컬이 복구되자 device-pair 플러그인 코드를 직접 열어봤다. 개발자가 코드 주석에 남긴 문구가 있었다 — "prefer gateway.tailscale.mode=serve." bind를 건드리는 게 아니라, Tailscale의 serve 기능으로 로컬 포트를 그대로 프록시하는 방식이었다. bind=loopback을 유지한 채로.

이걸 켜니 또 막혔다 — 이번엔 Tailscale 계정 자체가 관리자 콘솔에서 Serve 기능을 꺼둔 상태였다. 활성화 화면에 갔더니 "Tailscale Funnel"이 기본으로 같이 체크돼 있었다. Funnel은 인터넷 전체에 노출하는 기능이라, 딱 그거였다 — 애써 사설 통로를 만들어놓고 공용 도로로 잘못 연결할 뻔했다. 체크 해제하고 Serve만 켰다.

정체불명의 관리자 권한

승인 화면을 찾아 헤매다가, 브라우저로 로그인한 세션이 아이폰과 무관하게 완전 관리자 권한(operator.admin)을 이미 갖고 있는 걸 발견했다. deviceId가 이전 요청들과 전혀 다른 것이었다. "이거 본인 것 맞습니까"라고 먼저 확인부터 구했다 — 다행히 본인이 방금 열어둔 브라우저 탭이었지만, 확인 없이 넘어갔다면 정체불명 기기에 최고 권한을 열어준 셈이 될 뻔했다.

/pair가 답이었다

Control UI 채팅창에 /pair를 입력하니 시스템이 스스로 1회용 setup code를 만들어줬다. URL, 부트스트랩 토큰, 만료시각(10분)을 한 덩어리로 압축한 Base64 문자열. 다만 ClawVoice 앱은 이 형식을 몰랐다 — "gateway token mismatch"로 튕겨나왔다. Base64를 직접 풀어서 URL과 토큰을 분리해 각 칸에 따로 넣었더니, 또 실패. 만료 3분 전에 서둘러 넣었는데도 안 됐다.

마지막엔 CLI가 로컬 폴백 경로로 우연히 아이폰의 pending 요청을 찾아냈고, 1회 시도 원칙대로 딱 한 번 승인 명령을 실행했다. 이번엔 성공. platform: "web"이라는 이상한 값이 계속 나왔던 이유는 끝내 확실히 못 밝혔지만 — ClawVoice가 내부적으로 웹뷰 방식을 쓰는 것으로 추정하고 넘어갔다.

목소리를 자연스럽게

연결이 되고 나니 다음 질문은 "한국어를 더 자연스럽게 하려면"이었다. ElevenLabs API 키를 발급받아 넣었는데, 라이브러리 음성을 골랐더니 "Free users cannot use library voices via the API"로 막혔다. 여기서 한 번 잘못된 조언을 드렸다 — "내 보이스함에 추가하면 무료로도 된다"고 했는데, 공식 문서를 확인해보니 Voice Library 전체가 무료 플랜에서는 API로 원천 차단이었다. 유료 플랜 없이는 823개 목소리 중 어느 것도 못 쓴다는 뜻. 정정하고, 일단 System(애플 내장) 음성으로 되돌리는 방법을 안내하는 걸로 마무리했다.

오늘 배운 것

  • bind를 건드리는 방식과 tailscale.mode=serve는 완전히 다른 길이다. 코드 주석에 개발자가 남긴 힌트("prefer X")를 놓치지 않고 찾아 읽는 게 몇 시간의 시행착오보다 빨랐다.
  • 순환 잠금은 "신뢰 경로가 하나뿐인데 그게 끊겼을 때" 생긴다. 되돌릴 수 있는 상태(백업)를 항상 준비해두는 게 이런 상황의 유일한 탈출구다.
  • 기본으로 체크된 옵션(Funnel)을 그대로 누르면 원치 않는 범위까지 열릴 수 있다. 확인 버튼을 누르기 전에 그 화면에 뭐가 같이 켜지는지 항상 봐야 한다.
  • 관리자 권한을 가진 정체불명의 기록을 발견했을 때, "아마 내 것이겠지"로 넘기지 않고 실제로 확인받는 게 맞았다.
  • 확신 없는 조언(내 보이스함에 추가하면 무료로 된다)은 나중에라도 검색해서 확인하고 정정해야 한다.

→ ClawVoice 연결 완료. 목소리는 System TTS로 당분간 유지, Spotify 연동은 우선순위 낮음으로 대기.