WebUSB를 통한 구형 프린터 구출기: 브라우저 기반 Linux VM 활용

Rescuing old printers with an in-browser Linux VM bridged to WebUSB over USB/IP

요약

지원 중단된 구형 프린터를 브라우저 기반 Linux VM과 WebUSB를 통해 복구하는 프로젝트입니다. v86 에뮬레이터로 Alpine Linux와 CUPS를 실행하고 JavaScript를 통해 프린터와 직접 통신하는 웹앱입니다.

핵심 포인트

  • v86을 이용해 브라우저에서 x86 머신 에뮬레이션하고 WebAssembly로 컴파일하여 성능 확보
  • WebUSB를 통해 브라우저에서 직접 프린터 제어 가능하게 구현
  • CUPS 백엔드와 v86 TTY 통신으로 원본 인쇄 데이터를 프린터로 전달

왜 중요한가

구형 하드웨어를 소프트웨어만으로 복구할 수 있다는 가능성을 보여주며, WebUSB와 WebAssembly의 창의적인 활용 사례를 제시합니다.

📄 전문 번역

오래된 프린터를 되살리다: WebUSB와 v86으로 만든 브라우저 기반 프린터 서버

시작: 뜻밖의 선물

얘기는 캐논 SELPHY 포토 프린터를 얻게 되면서 시작됩니다. 새 3D 프린터를 사면서 오래된 걸 친구 Mark에게 줄 수 있냐고 물었는데, 그 대신 이 포토 프린터를 받으면 어떨까 하더라고요. eBay에서 사온 건데 용지와 잉크까지 포함해서 용지값보다도 싸게 구했다고 했어요.

온 가족이 즐길 수 있는 새로운 기기라 들뜬 마음으로 받아들였습니다. 그런데 곧 이런 구형 프린터가 왜 이렇게 싼지 알게 됐어요. Mac과 Windows 모두 더 이상 지원하지 않거든요. Mark는 Linux 쓰는데, 그래서 문제를 못 느낀 거였어요.

해결책: Manjaro와 Linux

다행히 저는 책상 위에 Manjaro 머신이 있었어요. eBay에서 싸게 구한 작은 Lenovo ThinkCenter인데 주로 테스트용으로 쓰고 있던 거죠.

이 머신에 CUPS와 Gutenprint를 설치하니 SELPHY가 정말 잘 인쇄했어요. Avahi를 설정해서 AirPrint로 공유하니 온 가족이 Mac과 iPhone에서 바로 인쇄할 수 있게 됐어요.

1999년처럼 손으로 만질 수 있는 실제 사진을 인쇄하게 된 거네요. 다만 1999년과는 달리 쉽고 빠르고, 좋은 사진만 선별해서 인쇄하고, 먼저 자르거나 노출을 조정할 수 있다는 게 다릅니다. 게다가 이 프린터는 용지를 세 번 통과시키면서 먼저 노란색(Y), 그다음 자홍색(M), 마지막으로 파란색(C)을 올리는데, 그 과정이 실시간으로 보여요.

행복한 결말이지만, 이건 막 중반일 뿐입니다.

새로운 도전: 부모님을 위해

그러다 생각했어요. 부모님도 이런 프린터가 있으면 손주들 사진을 인쇄할 수 있을 텐데. Raspberry Pi를 프린트 서버로 구성할 수도 있겠지만, 그러면 비용이 늘어나요. 게다가 부모님이 추가 플러그와 케이블을 원할지도 의심스럽고요.

혹시 소프트웨어만으로 이걸 단순하게 만들 수 있지 않을까? 그렇게 되면 정말 좋을 텐데요. 백만 개의 프린터를 쓰레기장에서 구할 수 있으니까요.

그래서 Claude Code를 한 달에 £18에 구독하고 코딩을 시작했어요. (현대의 LLM은 정말 대단합니다. Steve Jobs가 말한 대로 컴퓨터가 마음의 자전거라면, Claude Code는 개인 제트기입니다.)

첫 시도: 네이티브 Mac 앱

Claude와 함께 우리는 가벼운 Linux VM을 조용히 실행할 네이티브 Mac 앱을 만들기로 했어요. Virtualization.framework를 쓰고, USB 트래픽을 전달하고, mDNS로 프린터를 광고하는 방식이었죠. 하지만 App Store에 올리기는 어려울 것 같았어요. 올린다 해도 Mac 사용자만 쓸 수 있고요.

방향 전환: 웹 앱으로

그러다 생각났어요. 가족 micro:bit가 Bluetooth로 Meccano 버스를 제어하는데, Chrome이 WebUSB를 지원한다는 걸 알고 있었거든요. 웹 앱으로 만들면 어떨까? 설치 없이 어디서나 쓸 수 있고, JavaScript로 멋진 걸 만드는 것도 즐거우니까요.

그래서 Xcode를 VS Code로 바꾸고 다시 시작했어요. 얼마 안 가 이게 잘 될 것 같다는 확신이 생겼고, printerface.appprintervention.app 도메인을 등록했습니다. (Claude는 전자를, 아내는 후자를 선호했는데, 아내가 이겼어요.)

핵심 기술: v86과 WebAssembly

이 앱의 심장은 놀라운 오픈소스 프로젝트 v86입니다. x86 CPU와 그 주변 기계 전체를 브라우저에서 에뮬레이트하거든요. 런타임에 머신 코드를 WebAssembly 모듈로 컴파일해서 속도를 괜찮은 수준으로 유지합니다.

이 v86 머신에서 Alpine Linux, CUPS, Gutenprint와 관련 패키지들을 실행하게 했어요.

브라우저는 WebUSB로 프린터에 연결하고 제조사와 모델을 가져옵니다. 삼중 문자열 패턴 매칭(trigrams)으로 가장 가까운 Gutenprint 드라이버 이름을 찾은 뒤, 에뮬레이트된 키보드로 lpadmin 명령을 보내서 드라이버를 설치합니다.

파일을 인쇄할 때는 에뮬레이트된 머신에 업로드한 후 lp 명령으로 인쇄하면, 마법처럼 그 머신에서 생성된 원본 이진 인쇄 데이터가 실제 프린터로 전달됩니다.

가장 까다로운 부분: 데이터 전송

가장 흥미로운 게 바로 이 마지막 단계예요. 여러 번 시행착오를 거쳤거든요.

첫 번째 방식: 커스텀 CUPS 백엔드를 만들었어요. 이건 Gemini의 아이디어였습니다. 백엔드는 간단한 셸 스크립트인데, 원본 인쇄 데이터를 받아 v86 TTY를 통해 한 바이트씩 브라우저로 돌려보냈어요. 거기서 바이트들을 재조합한 후 WebUSB의 transferOut 호출로 프린터에 보냈습니다.

이 방식이 좋았던 건, 정말 작동했다는 거예요. 처음엔 좀 의심했는데, 프린터는 정말 간단하게 편방향의 "그냥 보내면 돼" 이진 데이터 스트림으로 인쇄할 수 있더라고요.

Chrome에서 SELPHY로 인쇄한 첫 사진은 상자 속의 고양이였어요. 완전히 엉망이었지만요. Y, M, C 평면이 어긋나면서 추상화된 다채로운 예술 작품이 됐거든요. 이건 분리된 Y-M-C 인쇄 데이터를 텍스트를 예상하는 TTY로 보내서 그래요. 줄 바꿈과 제어 문자를 이상하게 다루거든요. 백엔드 스크립트에 stty raw 명령을 추가하자마자 해결됐어요.

두 번째 방식: 좀 더 효율적일 것 같은 대안적 CUPS 백엔드를 구현했어요. 이 방식은 v86의 9p 파일시스템을 써서 훨씬 더 큰 청크 단위로 데이터를 전송했습니다. 이것도 첫 사진들을 흥미롭게(이번엔 줄무늬로) 망가뜨렸지만, 계속 개선해나가고 있어요.