콘텐츠로 이동

리그

사실만 다룹니다. 그 배경이 되는 모델은 리그와 인클로저를, 머신을 여는 방법은 사용 가이드를 참고하십시오.

표면 구동 대상 백엔드 값 이그레스 격리
터미널(exec) VM의 exec 채널을 통한 셸 마이크로VM smolvm 예
브라우저 헤드리스 Chromium(CDP), Firefox(WebDriver BiDi) 또는 WebKit(W3C WebDriver) 마이크로VM smolvm 예
컴퓨터 X11 데스크톱: 입력은 하이퍼바이저로, 캡처는 게스트 에이전트로 QEMU qemu-hvf(macOS), qemu-kvm(Linux), qemu-tcg(가속 없는 폴백, 자동으로 선택되지 않음) 예
Android ADB를 통한 Android Google의 에뮬레이터 android-emulator 아니요 — 에뮬레이터가 자체 네트워킹을 관리합니다
iOS WebDriverAgent를 통한 iOS 시뮬레이터 CoreSimulator ios-simulator 아니요 — 네트워킹은 호스트 Mac을 따릅니다

터미널과 computer_use 표면은 computer 표면 값을 공유합니다. 터미널은 exec 이미지 마커로 구별되며, 조작 가능한 데스크톱으로 재사용되지 않습니다(그 반대도 마찬가지입니다). 한 대화는 터미널 머신 하나를 재사용합니다.

엔진 값 프로토콜 라이브 뷰 정지 이미지
Chromium chromium(기본값) Chrome DevTools Protocol 스크린캐스트 — 페이지가 다시 그려질 때 브라우저가 프레임을 밀어 넣습니다 JPEG
Firefox firefox WebDriver BiDi 폴링 방식 정지 이미지(BiDi에는 스크린캐스트가 없음), 최대 6 fps JPEG
WebKit webkit W3C WebDriver(classic) 폴링 방식 정지 이미지, 호스트의 ffmpeg로 트랜스코딩 PNG

리그는 평생 하나의 엔진으로만 실행됩니다. 한 대화는 엔진마다 리그 하나를 가질 수 있으며, 이것이 나란히 비교를 가능하게 합니다. “Firefox (VM)” 옆에 “Chromium (VM)“를 열면 머신 두 개가 되고, 하나를 닫아도 다른 하나는 계속 실행됩니다.

engine은 rig.open이나 browser_use 도구에 전달합니다. 알 수 없는 이름은 기본값으로 처리되지 않고 거부됩니다 — 브라우저를 요청했는데 조용히 다른 브라우저를 받는다면, 호환성 질문에 틀린 답을 주는 셈입니다.

엔진별 한계는 우회하지 않고 그대로 밝힙니다:

  • Firefox에는 캐시를 우회하는 새로 고침이 없습니다(BiDi의 ignoreCache를 아예 거부합니다). 따라서 강제 새로 고침은 일반 새로 고침과 같습니다.
  • WebKit에도 캐시를 우회하는 새로 고침이 없고, 콘솔 피드도 없습니다(탐색할 때마다 console.*를 후킹해 출력을 캡처하므로 로드 중에 기록된 내용은 놓치며 — 모든 콘솔 추출이 그렇게 밝힙니다). 또한 라이브 뷰를 위해 호스트에 ffmpeg가 필요합니다.
  • 둘 다 프로토콜을 통해 플랫폼 접근성 트리를 노출하지 않으므로, extract (a11y)는 DOM에서 파생한 근사치를 반환하며, 그렇게 표시됩니다.
  • 둘 다 실제 파일 드롭을 받을 수 없습니다. 파일 입력은 여전히 머신 내부의 경로를 가리킬 수 있습니다.
표면 이미지 가져오기
터미널 ubuntu:24.04, 다이제스트로 고정 첫 부팅 때 머신 자체의 게이트된 네트워크를 통해 자동으로
브라우저(Chromium, Firefox, WebKit) debian:trixie-slim, 다이제스트로 고정, 그리고 엔진 자체 패키지 첫 부팅 때 자동으로, 이후 워밍된 팩으로 캐시됨
컴퓨터 Chromium, Firefox, WebKit과 터미널이 워밍하는 것과 동일한 GitHub 호스팅 러너 CLI 기본 환경이 포함된 qcow2 데스크톱 이미지 설정 → 서버 → 리그 아래의 가져오기(공개된 항목은 체크섬으로 고정된 다운로드)
Android Google의 시스템 이미지 Android SDK를 통해
iOS Xcode 시뮬레이터 런타임과 체크섬으로 고정된 WebDriverAgent 런타임은 Xcode를 통해, 자동화 브리지는 설정 → 서버 → 리그에서

각 마이크로VM 종류가 처음 부팅한 뒤, 서버는 미리 추출한 사본을 데이터 디렉터리 아래에 캐시하고 이후의 모든 머신을 그 사본에서 부팅합니다 — 패키지가 이미 설치된 미리 워밍된 이미지도 포함합니다. 터미널은 GitHub 호스팅 러너 스타일의 CLI 도구 모음(git, gcc, python3, jq, rsync, 압축 도구, 그리고 나머지 apt 기본 환경. Docker나 언어 툴캐시는 아님)을 워밍합니다. 컴퓨터 qcow2는 이미지 빌드 시점에 같은 CLI 이름들을 XFCE와 세 브라우저 옆에 구워 넣습니다. 모든 브라우저는 자신의 엔진을 워밍합니다. 세 엔진 모두 Debian 베이스를 공유하지만 팩은 결코 공유하지 않습니다. 캐시는 이미지, 엔진 그리고 오디오 리비전(audio-v1-<engine>)을 키로 삼습니다. 한 엔진을 위해 워밍된 머신 — 또는 PulseAudio와 ffmpeg가 워크로드에 포함되기 전에 워밍된 머신 — 에는 다른 엔진이 전혀 들어 있지 않기 때문입니다. 터미널 팩도 같은 이유로 도구 모음 리비전(tools-v1)을 키로 삼습니다. 컴파일러가 이미지에 포함되기 전에 워밍된 팩이, 컴파일러가 있어야 하는 머신에 제공되어서는 안 되기 때문입니다.

가져다 쓸 만한 미리 구운 자동화 이미지는 없습니다 — Chromium의 chromedp/headless-shell은 ALSA와 PulseAudio를 모두 빌드에서 제외하므로 결코 소리를 낼 수 없고, 유지 관리되는 유일한 Firefox 이미지는 Debian 베이스의 33배 크기이면서 여전히 socat을 포함하지 않으며, 유지 관리되는 WebKit 이미지는 아예 없습니다. 그래서 모든 엔진은 처음 시작할 때 설치되며 (chromium, firefox-esr, webkit2gtk-driver), 설치하는 동안 Debian 아카이브(deb.debian.org, security.debian.org, cloudfront.debian.net)에 접속합니다. Chromium은 배포판 바이너리로, chromium --headless=new로 실행됩니다.

설정 키 재정의 대상
rigs.smolvm.execImage 터미널 (VM) 이미지
rigs.smolvm.browserImage Chromium 브라우저 (VM) 이미지

관리자 권한이 필요한 워크스페이스 설정 저장소를 통해 기록합니다(설정 → 서버 → 리그 → 커스텀 이미지 (이 워크스페이스)). 값은 레지스트리 참조여야 하며 — 로컬 경로, 아카이브, 플래그 같은 문자열은 필드에서 한 번, 부팅 시 다시 한 번 거부됩니다. 저장된 값이 유효하지 않으면 요란하게 무시되고 대신 기본값으로 부팅합니다. 이미지 자체의 레지스트리(Docker Hub, GHCR, Quay, GitLab, GCR의 알려진 blob-CDN 동반 호스트 포함)는 풀을 위해 이그레스 게이트를 통해 허용됩니다.

터미널 이미지 계약 — bash가 있어야 하고, git, curl, socat이 미리 설치되어 있거나 또는 Debian/Ubuntu apt 베이스여야 합니다(인클로저가 처음 시작할 때 이 세 가지와 GitHub 호스팅 러너 스타일의 CLI 도구 모음 — 컴파일러, Python 3, jq, 압축 도구, rsync — 을 설치합니다. 언어 툴캐시와 Docker는 설치하지 않습니다).

브라우저 이미지 계약 — bash와 socat이 있는 Debian/Ubuntu apt 베이스(또는 이미 설치된 패키지)에, 서버가 직접 chromium --headless=new로 실행하고 DevTools는 게스트 루프백 9223에 두며 게스트 포트 9222로 중계하는 chromium 바이너리가 있어야 합니다. PulseAudio와 ffmpeg는 오디오 레인을 위해 처음 시작할 때 설치됩니다. FROM chromedp/headless-shell로 빌드하지 마십시오. 업스트림 headless.gn이 ALSA와 PulseAudio를 모두 빌드에서 제외하므로 그 바이너리는 소리를 낼 수 없습니다. 이 재정의는 Chromium 리그에만 적용됩니다 — 부팅하는 워크로드 명령이 Chromium의 것이므로, Firefox나 WebKit에 넘기면 없는 바이너리를 실행하게 됩니다. Firefox나 WebKit 리그는 이를 요란하게 무시하고 고정된 이미지로 부팅합니다.

자동화 포트 — 모든 브라우저 머신은 호스트 루프백 포트를 정확히 하나만 게스트 9222로 포워딩합니다. 그 뒤에는 Chromium의 DevTools와 Firefox의 원격 에이전트가 socat 릴레이를 앞세운 채 게스트 루프백 9223에 있습니다(둘 다 무조건 루프백에 바인딩합니다). WebKit의 드라이버는 --host를 받아 게스트 NIC에 직접 바인딩합니다. Firefox는 추가로 Host 헤더를 자체 포트와 대조해 검증하므로, 클라이언트는 접속한 주소가 아니라 게스트 쪽 authority를 보냅니다.

모든 동작은 전수 파싱됩니다. 잘못된 형식의 페이로드는 필드 이름을 밝히는 메시지를 반환할 뿐, 예외를 던지지 않습니다. 관찰 동사(○ 표시)는 사람이 제어를 쥐고 있는 동안에도 계속 허용되고, 변경 동사는 거부됩니다.

screenshot○, cursor_position○, mouse_move, left_click, right_click, middle_click, double_click, triple_click, left_mouse_down, left_mouse_up, left_click_drag, scroll, key, hold_key(0–60초), type, wait○(0–60초), set_display, clipboard_read○, clipboard_write.

좌표는 게스트의 현재 모드에서의 게스트 픽셀입니다. 모든 스크린샷은 디스플레이 크기를 밝힙니다. 키 조합은 X11 표기를 사용합니다(ctrl+s, alt+Tab, Return).

clipboard_read는 selection을 받습니다: clipboard(ctrl+C가 채우는 것, 기본값), primary(X11의 선택-복사) 또는 xdnd(진행 중인 드래그). 빈 결과는 세 가지 모두에서 정상적인 답입니다. clipboard_write는 text만 받습니다 — 이미지와 파일은 파일 레인으로 다니는데, 동작의 인자는 감사 로그에 남고 이미지 바이트가 거기 들어갈 이유는 없기 때문입니다. 클립보드 콘텐츠는 신뢰할 수 없는 데이터로 울타리를 두고 반환됩니다.

navigate(http/https만), reload(hard 선택 사항), stop_loading, click(선택자 또는 좌표), fill, type, key(DOM 키 이름), scroll, mouse_move, drag, left_mouse_down, left_mouse_up, extract○(a11y | dom | console), screenshot○(full_page 선택 사항), set_viewport(최소 320×240), history, wait_for○(선택자, 0.1–30초), clipboard_read○, clipboard_write.

추출된 페이지 콘텐츠는 신뢰할 수 없는 데이터로 울타리를 두고 반환됩니다.

브라우저에는 클립보드가 정확히 하나뿐이므로, clipboard_read는 selection을 받지 않습니다. 두 클립보드 동사 모두 보안 컨텍스트가 필요합니다. 평범한 http://로 제공되는 페이지에는 클립보드 API가 아예 없으므로, 동사는 빈 결과를 보고하는 대신 그렇다고 말합니다 — 그런 곳에서는 type을 사용하십시오. 클립보드를 거치지 않고 커서 위치에 삽입합니다.

tap, swipe(50–5000밀리초), type, key(별칭 back, home, recents, enter, delete, backspace, tab, escape, search, menu, volume_up, volume_down, power 또는 임의의 KEYCODE_*), screenshot○, ui_dump○, install_apk(리그의 워크트리와 서버 데이터 디렉터리로 한정), start_app, stop_app, clear_app_data, uninstall_app, open_url, shell. shell은 argv 목록을 받아 Android 안에서 실행되며, 호스트 셸을 결코 거치지 않습니다. logcat, dumpsys, pm grant 같은 개발자 진단을 처리합니다.

mobile_use에는 클립보드 동사가 없습니다. Android 10부터는 포커스된 앱만 클립보드를 읽을 수 있습니다.

tap, swipe(50–5000밀리초), type, key(enter, backspace, tab, escape, 화살표 키 또는 인쇄 가능한 문자 하나. 선택적으로 command, control, option, shift 한정자), home, lock, unlock, screenshot○, ui_dump○, install_app(리그 워크트리나 서버 데이터 디렉터리로 한정된 시뮬레이터 .app 디렉터리), start_app, stop_app, uninstall_app, open_url, spawn. spawn은 argv를 받아 simctl을 통해 시뮬레이터 안에서 실행되며, 호스트 셸을 결코 호출하지 않습니다.

좌표는 스크린샷 픽셀이 아니라 iOS 화면 포인트입니다. 라이브 스트림은 시뮬레이터의 고정 크기를 유지하며 기기의 크기를 바꾸는 대신 레터박스 처리합니다. 호스트→시뮬레이터와 시뮬레이터→호스트 텍스트 클립보드 전송은 다른 리그와 같은 사용자별·방향별 권한 정책을 따릅니다. 파일과 이미지는 iOS 페이스트보드 레인에서 허용되지 않습니다.

각 리그는 소유한 CoreSimulator 기기와 WebDriverAgent 세션을 만듭니다. 리그를 닫으면 WebDriverAgent를 중지하고, 그 기기를 종료하고 삭제하며, 레지스트리 항목도 제거합니다. iOS 리그는 Xcode, 설치된 iOS 런타임, 고정된 자동화 브리지를 갖춘 macOS 서버에서만 실행됩니다. 오디오, 마이크, 파일 드롭, 호스트 셸, 인클로저 네트워크 보장을 제공하지 않습니다.

도구 설명
rig_list 워크스페이스의 리그를 표면, 상태, 디스플레이, 컨트롤러와 함께 나열
rig_close 리그를 파괴하고 디스크를 폐기
단계 의미
provisioning 부팅 중. 상태 세부 정보가 현재 단계를 그대로 담습니다
ready 접근 가능하며 동작을 받습니다
parked 유휴 파킹: vCPU는 중지되고 RAM은 여전히 상주합니다. 다음 동작이 깨웁니다
closing 종료 중
closed 사라짐. 감사 추적을 위해 행은 남습니다
failed 죽었거나 끝내 올라오지 못함. UI가 재시도를 제안할 수 있도록 구별됩니다
종료 사유 발생 시점
requested 사람이나 에이전트가 요청함
idleTimeout 유휴 시간이 유휴 윈도우의 2배를 넘었거나 메모리 때문에 축출됨
ttlExpired 강제 수명이 다함
conversationEnded 소유 대화가 종료됨
workspaceGone 워크스페이스가 삭제되었거나 멤버십을 잃음
serverShutdown 서버가 중지 중임
backendFailure 그 아래의 하이퍼바이저가 죽음
값 터미널 브라우저 컴퓨터 모바일
메모리 512 MB 2048 MB(Chromium); 2560 MB(Firefox, WebKit) 4096 MB 4096 MB
vCPU 2 2 4 4
강제 TTL 8시간 2시간 2시간 2시간
유휴 윈도우 45분 15분 15분 15분
부팅 디스플레이 — 1280×800 1280×800 1080×1920
  • 에이전트 레인의 정지 이미지는 1280×800으로 제한되고, 사람 레인은 최대 2560×1600까지 협상합니다.
  • 모든 리그에 대한 호스트의 상주 메모리 예산은 기본 12 GB이며, 상주 메가바이트로 계산합니다(파킹된 VM도 RAM을 그대로 쥐고 있습니다). 축출은 최근 최소 사용 방식이며, 누군가 조작하거나 지켜보고 있거나 터미널을 열어 둔 머신은 결코 가져가지 않습니다.
  • 열린 터미널이나 지켜보기 레인이 있으면 리그는 파킹과 유휴 정리 대상에서 고정됩니다. 강제 TTL은 여전히 적용됩니다.
포트 위치 예약 대상
7911 터미널 게스트 모든 포워딩이 타고 다니는 포트 멀티플렉서
9222, 9223 브라우저 게스트 자동화 릴레이와 엔드포인트(WebKit은 9222에 직접 바인딩하며 릴레이를 사용하지 않음)
80, 443 브라우저 게스트 루프백 개발 도메인 HTTP/HTTPS 레인
  • 탐색은 서버 측에서 터미널 게스트가 리슨하는 TCP 포트를 약 4초마다 폴링합니다.
  • 리그당 최대 16개 포트가 자동 포워딩됩니다. 그보다 많으면 로그에 나열될 뿐 포워딩되지 않습니다.
  • 호스트 측 포워딩은 루프백에 바인딩하며 게스트의 포트 번호를 우선 사용합니다. 로컬 네트워크에서 공유는 OS가 할당한 LAN 포트를 추가합니다.
  • 개발 도메인은 name.test 또는 name.localhost와 일치해야 합니다(소문자, 레이블 최대 63자). 도메인 하나가 포트 하나에 매핑됩니다.
  • 개발 도메인 HTTPS는 서버 로컬 CA(EC P-256, <dataDir>/rigs/tls/ 아래, 키는 0600이고 게스트에는 결코 들어가지 않음)를 사용하며, *.test / *.localhost용 와일드카드 리프 인증서가 하나 있습니다. 호스트에 openssl이 없으면 HTTPS 레인이 없고, 평범한 HTTP는 여전히 라우팅됩니다.
표면 기본 허용 목록
터미널 샌드박스 개발자 기본값(forge, 패키지 레지스트리, 모델 공급자) + Ubuntu apt 미러 + 워크트리 자체의 forge 호스트
브라우저 usectrl.dev(제품 사이트) — 그리고 모든 엔진이 처음 부팅해 패키지를 설치하는 동안에는 Debian 아카이브가 추가됩니다. 홈 페이지는 로컬 file:// 문서이므로 이그레스가 필요 없습니다
컴퓨터 비어 있음 — 호출자가 호스트를 허용할 때까지 모든 것을 거부
모바일 강제되지 않음(에뮬레이터가 관리하는 네트워킹)

모든 마이크로VM은 추가로 자신의 이미지 레지스트리 호스트를 풀할 때만 사용할 수 있습니다. 자격 증명 브로커의 허용 호스트 집합은 같은 허용 목록에서 파생됩니다. 게스트가 도달할 수 없는 호스트를 위한 토큰은 발급되지 않습니다.

복사/붙여넣기의 양방향, 그리고 파일을 끌어 넣고 빼는 것까지. 리그를 조작하는 사람이 사용할 수 있습니다. 에이전트는 위의 동사들을 통해 클립보드를 얻을 뿐, 파일 전송은 전혀 받지 못합니다(인클로저 안에 이미 셸이 있기 때문입니다).

클립보드 통과는 사용자별·방향별입니다. 호스트→리그 붙여넣기는 기본적으로 허용되고, 리그→호스트 복사는 확인을 요청합니다(10분 허용, 또는 설정 → 서버 → 리그 → 클립보드 접근에서 항상 허용). 두 선택지는 독립적이며 결코 워크스페이스 설정이 아닙니다.

레인 라우트 전달 내용
클립보드 GET/POST /rig/clipboard/<rigId> 텍스트, PNG/JPEG 하나(≤16 MB), 그리고 클립보드에 있는 파일의 게스트 경로
파일 POST /rig/files/<rigId> 최대 64개 파일, 각 ≤256 MB, 드롭당 ≤512 MB, 선택적인 게스트 픽셀 드롭 지점 포함
파일 GET /rig/files/<rigId>?p=<base64url path> 파일 하나의 바이트를 다시 꺼냄
  • RPC가 아니라 HTTP입니다. RPC 소켓은 256 KB를 넘는 인바운드 프레임이 오면 연결을 닫습니다 — JSON 명령에는 옳고 붙여넣은 스크린샷에는 치명적인 DoS 가드입니다. 바이트는 리그 프레임과 회의 오디오가 이미 타는 것과 같은 종류의 경로를 탑니다.
  • 인증은 서명된 대상 rig-files:<workspaceId>/<rigId>와 워크스페이스 멤버십입니다 — 그 자체로 별도의 대상이므로, 머신을 지켜보기 위해 발급된 URL이 그 머신에 파일을 쓸 수도 없습니다.
  • 테이크오버가 적용됩니다. 클립보드 쓰기와 파일 드롭은 변경 동작이므로 다른 사람이 제어를 쥐고 있는 동안 거부됩니다. 클립보드 읽기와 파일 읽어내기는 관찰 동작이므로 거부되지 않습니다.
  • 드롭된 파일이 놓이는 곳: ~/Drops(데스크톱), ~/drops(터미널 — 워크트리 옆이며 결코 안이 아니므로, 드롭이 추적되지 않는 변경으로 나타나는 일이 없습니다), /tmp/cc-drops(브라우저). 디렉터리는 서버가 고르며 호출자가 지정할 수 없습니다. 기존 파일은 결코 덮어쓰지 않고, 이름이 겹치면 2-name.ext로 놓입니다.
  • 게스트가 받는 것: 브라우저 페이지는 요청한 지점에 실제 드롭 이벤트(Input.dispatchDragEvent)를 받습니다. 데스크톱은 파일과 그 URI를 클립보드에 받습니다. 게스트 안의 권한 있는 데몬 없이는 어떤 호스트도 임의의 툴킷에 XDND 드래그를 합성할 수 없기 때문입니다. 응답은 둘 중 어느 쪽이 일어났는지 밝힙니다.
  • 끌어내기는 데스크톱 전용이며 드래그가 진행 중일 때 게스트의 XdndSelection을 읽습니다. 누름이 시작될 때 게스트가 이미 쥐고 있던 것과 동일한 페이로드는 새 선택이 아니라 끝난 드래그의 낡은 선택으로 취급됩니다 — X에는 “드래그가 끝났다”는 단계가 없습니다.
  • 이미지 요구 사항: 데스크톱 이미지는 프로토콜 2 이상이어야 합니다(클립보드 지원이 들어온 뒤에 빌드된 것). 더 오래된 이미지는 404로 답하고, 앱은 머신이 고장 났다고 보고하는 대신 이미지를 다시 빌드하라고 안내합니다.
  • 감사 로그는 형태만 기록하고 내용은 결코 기록하지 않습니다: clipboard_read, clipboard_write, drop_files, read_file 행은 개수, 크기, 경로를 담습니다. 클립보드는 자격 증명이 몇 초씩 머무는 곳이며, 그중 어느 것도 기록되지 않습니다.
  • 리그 도구는 enclosureControl 액션 클래스를 선언합니다(사용 도구에는 networkEgress와 processSpawn도 추가됩니다. mobile_use와 ios_use는 packageInstall도 선언합니다). rig_close는 enclosureControl을, rig_list는 아무것도 선언하지 않습니다. 기본적으로 허용되며, 읽기 전용 모드는 이를 일괄 거부하고, 스페이스별 자율성 다이얼은 이를 먼저 묻기로 격하할 수 있습니다.
  • 도메인 이벤트: RigClosedEvent(종료 사유 포함), RigControlChanged(테이크오버 / 제어 반환), RigReaped(유휴, TTL 또는 메모리 축출, 조작 중인 에이전트 포함). 리그를 여는 것은 이벤트가 아닙니다. 도메인 이벤트를 참고하십시오.
  • 저장소: 각 워크스페이스 데이터베이스의 rig_sessions과 rig_action_log. 액션 로그는 모든 입력 이벤트를 주체와 리그별 단조 증가 시퀀스와 함께 기록합니다. 입력한 텍스트는 기록되기 전에 길이와 해시만 남기고 가려집니다.
  • 데이터 디렉터리: rigs/images/(가져온 qcow2 이미지), rigs/smolvm/(리그별 런타임 상태), rigs/smolvm-packs/(미리 추출한 머신 팩), rigs/tls/(개발 도메인 CA), rigs/run/(QEMU 오버레이와 시드).
  • 라우트: 라이브 뷰는 스페이스나 PR 페이지의 탭이고, /settings/server/rigs는 리그 설정 화면입니다.