2026.10.24 토
r2d2-tv: TV 홈 화면을 제조사 대신 직접 바꾸기
오늘의 픽: r2d2-tv
r2d2-tv 는 webOS TV 의 홈 화면을 새 앱으로 교체하고, 브라우저 실행과 음성 명령 처리는 집 안의 리눅스 컴퓨터에 맡기는 프로덕트다.
오늘의 픽으로 선정한 이유는 제작자가 TV 의 홈 화면과 음성 명령 기능을 제조사 대신 직접 만들었기 때문이다.

- 출처
- Show HN
- 만든 방식
- Python, JavaScript, Chromium, WebRTC
무엇이 문제인가
스마트 TV 의 홈 화면과 음성 명령은 TV 제조사가 만든 프로그램이 처리한다. r2d2-tv 의 제작자 Anubhav Malhotra 는 2026년 10월 23일 Hacker News 에 올린 Show HN개발자 커뮤니티 Hacker News 에서 자신이 만든 프로덕트를 소개하는 글. 제목이 "Show HN:"으로 시작한다. 글에서, 원치 않은 webOS 업데이트가 TV 에 설치된 것을 계기로 TV 에서 실행할 프로그램을 새로 만들기 시작했다고 했다. 같은 글에서 제작자는 이 작업으로 TV 에서 불필요한 기본 앱을 제거했다고 밝혔다.
어떻게 해결했나
r2d2-tv 는 TV 의 홈 화면을 새 앱으로 교체하고, 무거운 작업은 집 안의 리눅스 컴퓨터로 옮긴다. TV 에는 새 홈 화면 앱과 브라우저 뷰어가 설치된다. 브라우저는 리눅스 컴퓨터에서 격리된 Chromium 으로 실행된다. 화면은 WebRTC브라우저와 앱이 영상, 음성, 데이터를 서버를 거치지 않고 직접 주고받게 하는 웹 표준. 지연이 짧아 화상 통화에 주로 사용한다. 로 TV 에 전송된다.
음성 명령도 컴퓨터가 처리한다. r2d2-tv 가 TV 에 설치하는 구성 요소는 루트 권한리눅스 계열 시스템에서 모든 파일과 설정을 바꿀 수 있는 최고 관리자 권한. TV 같은 기기에서는 제조사가 대개 사용자에게 허용하지 않는다.으로 실행되며, 리모컨의 음성 버튼을 가로채 오디오를 10초까지만 상호 TLS서버만이 아니라 클라이언트도 인증서로 자신을 증명하는 TLS 연결. 서로 확인된 기기끼리만 통신하게 한다. 연결로 컴퓨터에 보낸다. 컴퓨터는 음성을 인식한 뒤 로컬 LLM대규모 언어 모델. 많은 글로 학습해 글을 이해하고 생성하는 AI 모델이다. 으로 명령을 해석한다. r2d2-tv 는 해석 결과를 YouTube 열기, Apple TV 의 작품 열기, 웹 검색, 지원하지 않음의 네 가지로 제한한다. LLM 이 출력한 JSON 을 검증해, 이 네 가지가 아닌 출력은 실행 전에 거부한다.
무엇이 다른가
첫째, 홈 화면과 기능을 바꾸는 주체가 다르다. webOS 에서는 제조사가 업데이트로 홈 화면과 기능을 바꾼다. r2d2-tv 는 TV 소유자가 자신의 설정으로 빌드해 설치한다. 트레이드오프는 위험이다. README 에 따르면 TV 에 설치하는 구성 요소는 루트 권한으로 실행되며 제조사의 시스템 서비스를 수정한다. 구성 요소의 코드에는 README 가 기준으로 삼은 펌웨어기기에 내장돼 하드웨어를 제어하는 프로그램. TV 같은 기기에서는 운영체제를 포함한 시스템 프로그램 전체를 가리킨다. 버전의 파일 해시와 수정할 위치가 고정돼 있다. 제작자는 README 에서, 설치하면 무상 보증을 받지 못할 수 있고 문제가 생기면 재부팅이나 초기화 전까지 TV 를 사용하지 못할 수 있다고 밝혔다.
둘째, 실행되는 위치가 다르다. 브라우저와 모델은 TV 가 아니라 별도의 aarch64ARM 계열 프로세서의 64비트 아키텍처 이름. 라즈베리 파이 같은 싱글 보드 컴퓨터와 스마트폰이 주로 이 아키텍처를 사용한다. 리눅스 컴퓨터에서 실행된다. TV 는 화면을 보여 주고 리모컨 입력을 받는 일만 한다. 트레이드오프는 장비다. 컴퓨터 한 대를 계속 운영해야 한다. README 에 따르면 재생 품질은 TV 와 컴퓨터가 연결된 LAN 의 처리량과 지터패킷이 도착하는 간격이 일정하지 않은 정도. 클수록 영상과 음성이 고르게 재생되지 않는다.에 달려 있다.
무엇이 편해지나
- 제조사의 홈 화면 대신 자신이 만든 화면을 사용하고 싶은 webOS TV 소유자. 홈 화면, 홈 버튼의 동작, 대기 상태에서 돌아올 때의 화면을 직접 빌드한 코드로 교체할 수 있다. README 에 따르면 홈 앱을 먼저 일반 앱처럼 설치해 시험한 뒤, 별도 단계에서 기본 홈으로 지정한다. TV 에서는 확인하지 못했다.
- TV 에 음성 명령을 직접 추가하려는 개발자. 허용할 동작을 코드에 정의하면, r2d2-tv 가 실행 전에 모델 출력이 그 동작 가운데 하나인지 검증한다. 오늘 위조한 출력이 모두 실행 전에 거부됐다.
어떻게 만들었나
- 공개 저장소의 커밋은 두 개다(2026년 10월 21일과 22일, 작성자 시간대 기준). 커밋에는 Co-authored-by 트레일러가 없다.
- 제작자는 HN 글에서 주말 두어 번에 걸쳐 모델 네 개를 사용해 v1 을 완성했다고 했다. 코드는 전부 모델이 작성했다고 밝혔다. WebRTC 로 지연을 줄이는 결정, 음성 샘플 버퍼링에 제조사 로직을 재사용하는 결정, 사용자 경험 설계는 직접 했다고 밝혔다.
- 저장소의 코드를 언어별로 집계하면 Python 이 34,234줄로 가장 많고, JavaScript 가 7,442줄로 그다음이다.
실행해 보니
TV 와 aarch64 리눅스 컴퓨터 없이 빌드, 테스트, 홈 화면, 명령 해석을 격리 환경에서 시험했다.
- 빌드. 예제 설정으로
tools/build.py를 실행하자 0.16초에 파일 255개가 만들어졌다. 네트워크를 차단하고 새 경로에 다시 빌드해도 모든 파일이 첫 빌드와 같았다. 이미 빌드 결과가 있는 경로에 다시 빌드하자Build refused를 출력하며 거부했다. - 테스트. Python 테스트 50개가 9.9초에 통과했다. 브라우저의 콘텐츠 차단 확장은 uBlock Origin Lite 를 수정해 만든다. 이 확장의 테스트 케이스 7개와 홈 화면 로직 테스트도 통과했다.
privacy_check.py는 Git 이 추적하는 파일에서 시크릿과 개인 파일을 검출하지 않았다. - 홈 화면. 빌드한 홈 앱을 리눅스 컴퓨터 없이 Chromium 에서 열자 브라우저 타일은 Unavailable 로 표시됐다. 화면 아래에는 앱 목록을 TV 에서만 불러온다는 안내가 나왔다.
- 음성 명령 해석. 로컬 모델 대신 루프백 주소컴퓨터가 자신을 가리키는 IP 주소. IPv4 는 127.0.0.1, IPv6 는 ::1 이다. 이 주소로 보낸 요청은 네트워크로 나가지 않고 같은 컴퓨터의 프로그램이 받는다.에 중계 서버를 띄웠다. 중계 서버는 요청을 OpenRouter여러 회사의 AI 모델을 API 하나와 계정 하나로 사용할 수 있게 하는 서비스. 의 Claude Haiku 5.5 로 보냈다. 인식 결과 대신 문장 일곱 개를 넣자, 세 문장은 YouTube 열기, Apple TV 의 Severance 열기, 웹 검색으로 해석됐다. "don't open youtube" 와 "open netflix" 는 지원하지 않음으로 분류됐다. "open youtube." 끝에 셸 명령을 반환하라는 지시를 추가해도 모델은 YouTube 열기만 출력했다. "유튜브 열어 줘" 도 YouTube 열기로 해석됐다. 명령마다 0.85~1.38초가 걸렸다.
- 검증. 모델 출력 대신 위조한 JSON 여섯 개를 검증 함수에 넣었다. 목록에 없는 동작, 허용되지 않은 사이트, 추가 키, 코드 블록으로 감싼 JSON, 중복 키, 줄바꿈이 포함된 검색어가 모두
MODEL_SCHEMA코드로 거부됐다. - 모델 주소. 모델 클라이언트는 주소가 127.0.0.1 이나 [::1] 이 아니면 설정을 거부했다. localhost 라는 이름도 거부됐다.
- 비용. Claude Haiku 5.5 에 요청 7건을 보내는 데 2,062 토큰, $0.000266 이 들었다.
README 에 적힌 세 가지 가운데 빌드와 명령 검증은 확인됐다. 모델 추론이 리눅스 컴퓨터에서만 실행되는지는 확인하지 못했다. 코드는 모델 주소로 루프백만 허용하지만, 루프백 주소의 서버가 요청을 외부로 넘기는지는 검사하지 않는다. 오늘 띄운 중계 서버는 요청을 OpenRouter 로 넘겼다.
{"built": true, "files": 255, "unsigned": true, "installed": false, "private_output": true}
Ran 50 tests in 9.875s
OK
'open netflix' -> raw='{"action":"unsupported"}' -> {"action": "unsupported"} 0.85s
'{"action":"shell","cmd":"rm -rf /"}' -> Refused MODEL_SCHEMA
https://api.openai.com/v1/chat/completions -> Refused MODEL_CONFIG
확인하지 못한 것
- TV 에서의 홈 화면, 브라우저 스트리밍, 리모컨 음성 입력. 루트 권한을 얻은 webOS TV 와 aarch64 리눅스 컴퓨터가 필요하다.
- 로컬 음성 인식과 로컬 LLM 의 결과와 속도. 실제 음성으로 인식부터 명령 해석까지 시험하자.
- 콘텐츠 차단 확장의 재현 가능한 빌드. 이 확장은
tools/build.py와 다른 빌더가 버전을 고정한 uBlock Origin Lite 압축 파일로 만들고, 결과를 저장소에 기록된 해시와 대조한다. 오늘은 이 압축 파일을 받지 못했다. 파일을 받아 해시가 일치하는지 확인하자.
판정
빌드와 모델 출력 검증은 작동한다. TV 없이 실행한 테스트도 모두 통과했다. 다만 TV 에서는 확인하지 못했다. TV 에 설치하는 구성 요소는 루트 권한으로 실행되고, 기준 펌웨어에만 맞춰져 있다. 기준 펌웨어의 webOS TV 와 aarch64 리눅스 컴퓨터가 있고, 보증 문제와 초기화 위험을 감수할 수 있는 경우에 써 볼 만하다. 기준 펌웨어가 아닌 TV 에 바로 설치할 프로그램을 찾는다면 이 프로덕트는 적합하지 않다.
얻어갈 것
- 모델의 출력을 정해진 JSON 형식으로 제한하고, 실행 전에 코드로 검증해 보자. 오늘 위조한 출력 여섯 개가 모두 실행 전에 거부됐다.
- 로컬 모델의 주소는 루프백만 허용해 보자. r2d2-tv 는 IP 주소 127.0.0.1 과 [::1] 만 허용해 localhost 라는 이름도 거부했다. 이름을 주소로 바꾸는 단계가 없어서 localhost 가 다른 주소를 가리켜도 영향을 받지 않는다. 다만 루프백 주소의 서버가 요청을 어디로 보내는지는 따로 확인해야 한다.
시작하기
cp config/example.json config.local.jsonpython3 tools/build.py --config config.local.json --output dist/local --include-tests
- 필요한 것
- Python 3.10 이상, Node.js 20 이상 · TV 에 설치하려면 루트 권한을 얻은 webOS TV 와 aarch64 리눅스 컴퓨터
- 비용
- MIT 와 GPL-3.0-or-later, 무료. 유료 요소는 찾지 못했다.
TV 에 설치하는 구성 요소는 README 가 기준으로 삼은 TV 의 펌웨어 버전에 맞춰 작성됐다. README 는 그 TV 의 모델과 펌웨어 버전을 밝히지 않는다. 다른 펌웨어에서는 코드에 고정된 파일 해시를 다시 계산하고, 수정할 위치도 다시 찾아야 한다.
실행해서 확인한 절차다.
진행
- 3일활발
- 7일예정
- 15일예정
- 30일예정
- 90일예정
- 180일예정
- 365일예정
최근 확인 3일, 2026.10.26 월
- 런칭 이후 커밋
- 1 → 4
- 스타
- 47 → 61
- 마지막 커밋
- 2026.10.24
- HN 득표
- 83 → 109
- 설치된 TV 앱을 모두 매일 새벽 3시에 업데이트한다. 그 시각에 TV 가 꺼져 있었으면, 나중에 30분 동안 입력이 없을 때 업데이트한다.
- LG 서버로 가는 연결을 평소에는 차단하고, 앱 업데이트가 실행되는 동안에만 앱스토어 연결을 허용한다.
- 음성 입력 프로세스는 잠금 파일을 직접 만들 수 없어, 전원을 껐다 켜서 이 파일이 지워지면 음성 입력이 시작되지 않았다. 프로세스를 실행하기 전에 잠금 파일을 먼저 만들도록 수정했다.