# Windows에서 자동 검증 붙이기 개발은 리눅스에서 하고 있어서 **Windows 전용 경로는 테스트가 불가능**합니다. 오디오 캡처, 전역 단축키, 모니터 열거, exe 빌드가 전부 여기에 해당합니다. 그래서 "리눅스에서 다 통과했는데 윈도우에서 안 되더라"가 나올 수 있습니다. Windows 러너를 하나 붙이면 `main` 에 push할 때(또는 Actions 탭에서 수동 실행할 때) 이 경로들이 자동으로 검증됩니다. ## 어디에 올릴까 — 서버 Windows VM 권장 개인 PC를 안 쓰고 서버에 올려 완전 자동화하는 게 가장 깔끔합니다. **GPU가 없어도 됩니다.** 실제로 확인해봤습니다. | 검증 항목 | GPU 필요? | GPU 없는 서버 VM | |---|---|---| | 단위 테스트 189개 | ❌ | ✅ | | WASAPI 장치·프로그램 열거 | ❌ | ✅ | | 전역 단축키 | ❌ | ✅ | | 모니터 열거·자막 배치 | ❌ | ✅ | | 포터블 exe 빌드·기동 | ❌ | ✅ | | **오디오 → 한국어 자막 (전 과정)** | ❌ CPU로 됨 | ✅ | | CUDA 인식·VRAM 상한 | ✅ | ⚠️ 경고만 (빌드 통과) | ### GPU 검증은 `.9`(리눅스)에서 — 이미 돌아갑니다 GPU는 `.9`에 고정입니다. 그래서 GPU 검증은 Windows가 아니라 **여기서** 합니다. 한 줄이면 됩니다. ```bash scripts/gpu-check.sh ``` RTX 5050 실측 결과입니다. ``` [OK] CUDA 사용 가능 RTX 5050 · 7.5GB [OK] VRAM 상한이 실제로 걸리는지 35% 상한 적용됨 [OK] GPU 로 실제 번역 '적 왼쪽에서 오는' [OK] 티어 게이트가 뭐라고 하는지 정밀=autoawq 없음 / 극한=VRAM 부족(16GB 필요, 7.5GB) 6/6 통과 ``` **이 GPU로는 4·5티어를 쓸 수 없습니다.** 정밀은 `autoawq`가 필요하고 (`pip install autoawq`), 극한은 16GB가 필요한데 카드가 7.5GB입니다. 극한은 카드를 바꾸지 않는 한 영영 불가능합니다. 1~3티어는 정상 동작합니다. ### GPU가 필요한 테스트는 어떻게 하나 **GPU는 한 VM만 독점합니다.** 컨슈머 NVIDIA는 vGPU/SR-IOV를 지원하지 않아서 쪼개 쓸 방법이 없습니다. 지금 RTX 5050은 `.9`(리눅스)에 물려 있습니다. 그래서 러너를 **라벨로 둘로 나눠뒀습니다.** 지금은 앞의 것만 있으면 되고, 나중에 GPU 머신이 생기면 뒤의 것을 붙이기만 하면 됩니다. 워크플로는 이미 그렇게 짜여 있습니다. | 러너 라벨 | 어디에 | 언제 도나 | 무엇을 | |---|---|---|---| | `windows:host` | GPU 없는 서버 VM | `main` push마다 자동 | 테스트·Windows 경로·오디오→자막·exe 빌드 | | `windows-gpu:host` | GPU 달린 머신 | **수동 실행만** | CUDA 인식·VRAM 상한·4·5티어·GPU 번역 | GPU 러너를 상시로 띄울 필요는 없습니다. 필요할 때만 `daemon`을 켜고 Actions 탭에서 `Windows GPU` → `Run workflow` 하면 됩니다. 러너가 없으면 큐에 쌓이기만 하고 아무 일도 일어나지 않습니다. GPU 워크플로는 GPU가 없으면 **분명히 실패합니다.** GPU를 확인하겠다고 부른 작업이 조용히 통과하면 안 되니까요. `nvidia-smi`가 없으면 그 자리에서 멈춥니다. #### 여러 프로젝트가 GPU 러너를 같이 쓸 때 — 동시에 돌리면 안 됩니다 **이게 가장 중요합니다.** GPU는 나눠 쓸 수 없어서, 두 작업이 동시에 VRAM을 잡으면 둘 다 OOM으로 죽거나 하나가 조용히 느려져 원인을 못 찾습니다. CPU 러너와 달리 GPU 러너는 **반드시 한 번에 한 작업만** 돌아야 합니다. 보장 장치가 두 겹입니다. 1. **러너 `capacity: 1`** — 진짜 보장입니다. 저장소가 달라도 막힙니다. `act_runner` 설정 파일(`config.yaml`)에서 확인하세요. 기본값이 1이지만 GPU 러너에서는 반드시 확인하고 넘어가세요. ```yaml runner: capacity: 1 # GPU 러너는 절대 올리지 말 것 ``` 설정 파일이 없으면 만들고 `--config config.yaml` 로 띄웁니다. ```powershell .\act_runner.exe generate-config > config.yaml .\act_runner.exe daemon --config config.yaml ``` 2. **워크플로 `concurrency`** — 같은 저장소 안에서 겹치는 것만 막습니다. `windows-gpu.yml` 에 이미 들어 있습니다. 다른 저장소에는 효력이 없으니 1번을 대신하지 못합니다. 한 대에 러너 프로세스를 **여러 개 띄우지도 마세요.** capacity 1짜리 러너가 둘이면 결국 동시에 두 작업이 돕니다. #### GPU 머신을 어디서 구하나 다른 프로젝트도 이 러너에서 GPU 테스트를 돌리실 거라면 셋 중 하나입니다. 1. **서버에 GPU 한 장 더** — 가장 깔끔합니다. PCIe 슬롯과 파워 여유만 있으면 그 GPU를 Windows VM에 패스스루하고 `windows-gpu` 라벨로 등록하면 끝입니다. 이후로는 두 러너가 각자 계속 돕니다. 2. **RTX 5050을 Windows VM으로 옮기기** — `.9`가 GPU를 포기해야 합니다. `.9`에서 하던 GPU 작업(이미지 생성 등)을 못 하게 되니 득실을 따져보세요. 3. **필요할 때만 실물 PC에 GPU 러너** — 평소엔 꺼두고 GPU 검증이 필요할 때만 켭니다. 수동 실행이라 게임 중에 갑자기 도는 일은 없습니다. 1번이 가능한지는 서버에서 이걸로 확인할 수 있습니다. ```bash lspci | grep -i vga # GPU가 몇 장 꽂혀 있나 dmidecode -t slot | grep -i "in use" # 빈 PCIe 슬롯이 있나 ``` ### 선택지 A — `.9` 안에 Windows VM (중첩 가상화) Proxmox를 건드리지 않고 `.9`(우분투) 안에서 바로 Windows VM을 돌릴 수 있습니다. **실제로 확인했습니다.** ``` /dev/kvm 존재 kvm_intel nested Y qemu -enable-kvm kvm support: enabled ← 실측 여유 RAM / 디스크 25GB / 294GB ``` **가장 큰 장점은 사람 손이 안 간다는 것입니다.** `.9` 안이라 에이전트가 VM 생성부터 러너 등록까지 직접 합니다. Proxmox 자격증명도 필요 없습니다. **단, GPU는 여전히 안 됩니다.** 중첩 패스스루가 불가능합니다. ``` IOMMU 그룹 수 0개 커널 cmdline iommu 옵션 없음 ``` 되게 하려면 Proxmox가 `.9`에 vIOMMU를 열어주고 `.9` 커널에 `intel_iommu=on`을 넣은 뒤 GPU를 `vfio-pci`에 묶어야 하는데, 그러면 **`.9`가 GPU를 잃습니다.** 결국 "GPU를 누가 가질 것인가" 문제로 되돌아오므로 중첩으로는 해결되지 않습니다. #### 만드는 법 — 스크립트 한 줄, 사람 손 없음 `scripts/win-ci/` 에 전부 들어 있습니다. Windows 설치 화면을 사람이 클릭할 방법이 없으므로 **무인 설치**로 짰습니다. ```bash scripts/win-ci/build.sh # ISO 받기 + 무인설치 ISO 굽기 + 디스크 생성 /home/claude/win-ci/start-vm.sh # VM + 제어 서버 기동 /home/claude/win-ci/boot-cd.sh reset # CD 부팅 프롬프트 넘기기 (최초 1회) /home/claude/win-ci/status.sh # 진행 상황 /home/claude/win-ci/shot.sh a.png # 화면 스냅샷 ``` 네 부분이 이어달리기를 합니다. | 파일 | 어디서 도나 | 하는 일 | |---|---|---| | `unattend/autounattend.xml` | Windows Setup | 디스크 분할·설치·OOBE 건너뛰기·`ci` 자동 로그온 | | `unattend/setup/provision.ps1` | 첫 로그온 1회 | virtio 드라이버(네트워크·화면), 화면꺼짐/잠금 해제 | | `unattend/setup/agent.ps1` | 로그온마다 상주 | 호스트에서 `stage2.ps1` 을 받아 실행 | | `serve/stage2.ps1` | 호스트가 HTTP 로 제공 | Git·pwsh7·Python·Node·CMake·act_runner 설치 | **왜 설치 내용을 ISO 에 안 넣었나** — ISO 는 한 번 구우면 못 고칩니다. 설치 스크립트를 ISO 에 박으면 한 줄 고칠 때마다 Windows 를 다시 깔아야 합니다. 그래서 ISO 에는 껍데기(agent)만 넣고, 실제 설치 내용은 호스트의 HTTP 로 가져옵니다. `serve/stage2.ps1` 을 고치면 게스트가 20초 안에 알아서 새로 받아 실행합니다. 호스트와 게스트를 잇는 통로는 두 개입니다. | 방향 | 통로 | 왜 | |---|---|---| | 호스트 → 게스트 | HTTP `10.0.2.2:8099` (QEMU user networking) | 스크립트·러너 토큰 전달 | | 게스트 → 호스트 | COM1 → `serial.log`, HTTP POST `/log` → `guest.log` | 네트워크가 **없는** 드라이버 설치 단계도 봐야 하므로 시리얼이 필수 | | 게스트 → 호스트 | HTTP POST `/artifact/<이름>` → `results/<이름>.log` | 검증 출력 **전문**. 바로 아래 참고 | ##### 검증 결과는 `results/` 에 파일로 남습니다 — 그게 기준선입니다 `stage2.ps1` 의 `Step` 이 각 단계 출력을 게스트 `C:\win-ci\out\` 에 한 줄씩 기록하면서 호스트 `/home/claude/win-ci/results/` 로 올립니다. 판정 요약은 `results/summary.log` 이고 **검증한 리비전이 박혀** 있습니다. 처음에는 로그 채널로 마지막 몇 줄만 흘렸는데, `serial.log` 는 VM 이 뜰 때마다 truncate 되고 `guest.log` 는 `stage2` 가 다시 돌 때만 채워집니다. 그래서 pytest 실패 트레이스백이 호스트에 한 줄도 남지 않아, 화면을 읽고 옮겨 적는 수밖에 없었습니다. 고친 뒤에 고쳐졌다고 말할 근거가 없는 상태였습니다. 재검증은 리비전으로 요청합니다. ```bash cd scripts/win-ci && ./sync.sh # stage2 의 $TargetRev 를 그 커밋으로 박아 배포 tail -f /home/claude/win-ci/guest.log # 약 10분, 끝나면 results/summary.log 확인 ``` 게스트 agent 는 `stage2.ps1` **내용 해시가 바뀔 때만** 다시 돕니다. 커밋만 푸시하면 재검증이 걸리지 않으니 sha 를 넘기세요. `build.sh` 를 다시 돌리면 안 됩니다 — 지워둔 Windows ISO 4.8GB 를 다시 내려받습니다. 배포할 포터블 exe 도 같은 통로로 돌아옵니다. 빌드와 기동이 **둘 다** 통과했을 때만 `results/LiveSub.exe` 로 올라오므로, 거기 파일이 있다는 것 자체가 "검증을 통과한 산출물" 이라는 뜻입니다. 그대로 릴리스에 올리면 됩니다. ##### exe 빌드 단계가 거짓 PASS 를 내던 함정 앞 회차의 `exe기동` 점검이 띄운 `LiveSub.exe` 가 파일을 잡고 있으면 PyInstaller 가 `PermissionError` 로 죽습니다. 그런데 **옛 exe 는 그대로 남아 있어서** `Test-Path dist\LiveSub.exe` 검사는 통과했고, 결국 빌드가 실패한 회차가 PASS 로 보고됐습니다. 지금은 빌드 전에 프로세스를 죽이고 `build`·`dist` 를 지운 뒤 PyInstaller 종료코드까지 확인합니다. **산출물의 존재를 성공의 근거로 쓰면 안 됩니다** — 이전 성공이 남긴 것일 수 있습니다. ##### MSVC 가 왜 VM 에 깔려 있나 `native\process_loopback` 의 `livesub_capture.exe` 는 WASAPI 프로세스 루프백을 씁니다. 그 헤더(`audioclientactivationparams.h`)와 WRL 은 Windows SDK + MSVC 에만 있어서 cmake 만으로는 못 만듭니다. 그래서 `stage2.ps1` 이 VS 2022 Build Tools(C++ 워크로드)를 설치합니다 — 첫 설치에 약 9분 걸리고 그 뒤로는 건너뜁니다. 이게 없으면 **포터블 빌드는 그대로 성공하되** 프로그램별 캡처가 출력 장치 전체 캡처로 조용히 폴백됩니다. 기능이 빠진 exe 가 PASS 로 나오는 조합이라, 검증 순서에서 `native_capture` 를 `windows_smoke` 와 `pyinstaller` **앞에** 둡니다. 뒤에 두면 둘 다 "없음"을 정상으로 보고 지나갑니다. 실제로 이 경로는 MSVC 를 깔기 전까지 **한 번도 컴파일된 적이 없었고**, 깔자마자 `main.cpp` 가 C2275 로 죽었습니다. CI 의 네이티브 빌드 단계가 `continue-on-error` 였던 탓에 그때까지 드러나지 않았습니다. 접속 정보: 계정 `ci` / 비밀번호는 `~/.config/ejclaw/secrets.json` 의 `win-ci.9` 항목. RDP 는 호스트 `127.0.0.1:13389`, VNC 는 `127.0.0.1:5919`. 재부팅해도 살아납니다. `~/.config/systemd/user/win-ci{,-http}.service` 로 등록돼 있고 `linger=yes` 라 로그인 없이 `default.target` 이 끌어올립니다. `systemd-run` 트랜지언트로 띄우면 `/run` 에만 남아 재부팅에 사라지니, 디버깅으로 한 번 띄웠더라도 마지막에는 반드시 아래로 되돌려 놓으세요. ```bash systemctl --user start default.target # 부팅과 같은 경로로 기동 systemctl --user is-enabled win-ci.service # enabled 여야 한다. transient 면 안 된다 systemctl --user show win-ci.service -p FragmentPath # ~/.config/... 를 가리켜야 한다 ``` ##### 설치가 끝난 뒤 무엇을 지우나 — 기준 지우는 기준은 "VM 이 도는 데 계속 필요한가" 하나입니다. | 파일 | 설치 후 | 왜 | |---|---|---| | `iso/win11-ltsc.iso` (4.8GB) | **지운다** | 설치 미디어. 다시 깔 때만 필요하고 `build.sh` 가 받아온다 | | `iso/virtio-win.iso` (837MB) | 남긴다 | VM 에 계속 물려 있다. 드라이버 재설치·재프로비저닝이 여기서 읽는다 | | `unattend.iso` (0.4MB) | 남긴다 | VM 에 계속 물려 있다 | | `win-ci.qcow2` (14GB) | 남긴다 | VM 그 자체 | ISO 를 지우면 `-drive` 가 없는 파일을 가리켜 **qemu 가 기동 자체에 실패**합니다. 재부팅 후 러너 호스트가 조용히 사라지므로, `run-vm.sh` 는 cdrom 을 있을 때만 붙입니다. 그리고 같은 ISO 를 버전별로 여러 개 남겨두지 마세요. 어느 것이 물려 있는지 헷갈리면 고친 줄 알았던 스크립트가 반영되지 않습니다. 현재 물려 있는 것은 `query-block` 으로 확인합니다. ##### 무인 설치에서 실제로 걸린 것들 - **`Press any key to boot from CD`** — UEFI 는 이걸 5초 안에 안 누르면 CD 를 포기합니다. 무인인데 누를 사람이 없어서 QMP `send-key` 로 밀어 넣습니다. 단, **설치가 시작된 뒤에는 절대 키를 더 보내면 안 됩니다.** 남는 Enter 가 설치 화면의 `Cancel` 을 눌러 "Are you sure you want to quit?" 를 띄웁니다. 반대로 설치가 끝난 뒤 재부팅 때는 아무 키도 안 누르는 게 정답입니다. 그래야 CD 를 건너뛰고 디스크로 부팅합니다. - **시스템 디스크는 SATA(AHCI)** 로 둡니다. virtio-blk/scsi 로 하면 Windows Setup 이 디스크 자체를 못 봐서 WinPE 단계 드라이버 주입이 필요해집니다. 네트워크·화면만 virtio 로 가고, 그 드라이버는 설치 후 ISO 에서 오프라인으로 넣습니다. - **Windows ISO 는 UDF** 입니다. ISO9660 층에는 `README.TXT` 하나뿐이라 `xorriso` 로 열면 비어 보입니다. `mount -t udf` 로 봐야 합니다. - `install.wim` 의 **인덱스 1** 이 `Windows 11 Enterprise LTSC 2024 Evaluation` 입니다(이미지 2개). `wiminfo` 로 확인하고 박았습니다. - **LTSC 에는 winget 이 없습니다.** 스토어가 빠져 있어서 설치 파일을 직접 받습니다. - **Node.js 가 필요합니다.** `act_runner` 의 host 모드는 `actions/checkout`, `upload-artifact` 같은 JS 액션을 PATH 의 `node` 로 돌립니다. 빠뜨리면 그 단계들만 조용히 실패합니다. - **러너는 서비스가 아니라 로그온 예약 작업**이어야 합니다. 서비스로 돌리면 데스크톱 세션이 없어 GUI 테스트와 스크린샷이 전부 실패합니다. #### VM 에 얼마를 줄까 — 실측 근거 **vCPU 4개 / RAM 8GB / 디스크 80GB.** 추측이 아니라 재보고 정한 값입니다. CPU 는 4개에서 포화합니다. 더 줘도 빨라지지 않습니다. ``` 파이프라인(음성→자막) 2코어 13초 · 4코어 10초 · 8코어 11초 테스트 190개 4코어 5.9초 · 8코어 5.8초 ``` CI 작업 대부분이 단일 스레드라 코어를 늘려도 효과가 없습니다. 음성인식만 병렬인데 그것도 4개에서 멈춥니다. RAM 8GB 는 Windows 11 이 쾌적하게 도는 최소선이고, 그 안에서 도는 음성인식+번역이 2~3GB 를 씁니다. 디스크 80GB 는 Windows 30GB + 모델 캐시 + 빌드 산출물 기준입니다. #### VM 의 Windows 는 진짜 Windows 인가 **맞습니다. 같은 Windows 이고 같은 바이너리입니다.** 우리가 검증하려는 것들은 코드 경로가 동일합니다. | | VM 에서 | 비고 | |---|---|---| | WASAPI 오디오 캡처 API | 동일 | 에뮬레이트된 사운드 장치를 잡을 뿐 API 는 같음 | | 전역 단축키(`RegisterHotKey`) | 동일 | | | 모니터 열거 API | 동일 | `max_outputs=2` 로 화면 2개를 줄 수 있음 | | PyInstaller exe 빌드·실행 | 동일 | | | CPU 명령어 집합 | 동일 | 중첩 KVM 이 호스트 CPU 기능을 그대로 노출 | 다른 점은 셋입니다. - **GPU 없음** — CUDA·VRAM 검증은 `.9`(리눅스)에서 합니다. 위 참고. - **실제 게임 소리 없음** — 게임을 띄워 진짜 캡처하는 건 못 합니다. 대신 음성 파일을 넣어 자막까지 나오는지 자동 검증합니다. - **속도/지연이 다름** — 체감 지연 측정값은 실제 PC 기준이 아닙니다. 모니터 선택을 검증하려면 화면을 2개 줘야 합니다. `--vga std` 는 1개뿐입니다. ``` -device virtio-vga,max_outputs=2 # KVM 기동 확인 완료 ``` Windows 쪽에 virtio-gpu 드라이버(virtio-win ISO)가 깔려 있어야 2개로 잡힙니다. #### 100% 부하에서도 괜찮은가 — 재봤습니다 **VM 4 vCPU 까지는 안전하고, `.9` 의 CPU 를 늘리면 오히려 해롭습니다.** `.9` 안에서 VM 이 4코어를 100% 써도 영향은 작습니다. ``` 부하 없음 VM 4코어 100% 테스트 5.9초 -> 6.1초 (+3%) 파이프라인 9.8초 -> 11.0초 (+12%) ``` 문제는 호스트입니다. 물리 호스트는 i7-11700(8코어/**16스레드**)뿐인데 `.5` 에서 운영 컨테이너 19개가 돌고 있습니다. `.5` 의 응답 속도를 재봤습니다. ``` .9 가 한가할 때 616~628ms 기준 .9 가 4코어 사용 657~678ms +7% ← VM 이 100% 인 실제 상황 .9 가 8코어 전부 사용 1487~1491ms +140% ← 봇까지 동시에 바쁠 때 ``` **그래서 `.9` 의 vCPU 는 늘리면 안 됩니다.** 늘릴수록 호스트 지분이 커져 `.5` 의 운영 서비스가 느려집니다. 지금 8개가 적정 상한입니다. 최악(8코어 전부)을 아예 막으려면 VM 을 CPU 상한과 함께 띄웁니다. ```bash systemd-run --user --scope -p CPUQuota=400% -p CPUWeight=50 --unit win-ci qemu-system-x86_64 -enable-kvm ... ``` `CPUWeight=50` 은 봇과 경합할 때 VM 이 양보한다는 뜻입니다. `CPUQuota=200%` 로 재봤을 때 `.5` 응답이 649~653ms 로 기준선과 같았습니다. RAM 은 최악을 다 더해도 여유가 있습니다. ``` ejclaw 봇 (실측 최대) 7.2 GB Windows VM 게스트 8.0 GB QEMU 오버헤드 0.4 GB GPU 작업 동시 실행 3.0 GB tmpfs 워치독 경보선 3.0 GB ───────────────────────────────────── 최악 합계 21.6 GB / 30 GB (여유 8.4 GB) ``` **결론: CPU·RAM 모두 증설 없이 진행하면 됩니다.** RAM 만 여유를 두고 싶다면 30 → 38GB 까지는 호스트가 감당합니다(호스트 62.6GB, 여유 약 24GB). CPU 는 늘리지 마세요. #### ⚠️ 메모리 cgroup 함정 — 반드시 `systemd-run --scope` 에이전트 턴에서 그냥 `qemu`를 띄우면 VM이 **봇의 cgroup 안에서** 돕니다. `ejclaw.service`는 `MemoryHigh=16G`이고 평상시 5G를 쓰므로, 8G짜리 VM이 그 안에 들어가면 상한에 부딪혀 봇이 OOM으로 죽습니다(전례 있음). ```bash # 봇 cgroup 밖(app.slice 형제)에 VM 을 띄운다 — 실측으로 확인됨 systemd-run --user --scope -p MemoryMax=10G --unit win-ci qemu-system-x86_64 -enable-kvm -m 8192 ... ``` #### A vs B 비교 | | A: `.9` 안 중첩 VM | B: Proxmox VM | |---|---|---| | 만드는 사람 | **에이전트가 직접** | 사용자 | | GPU | ❌ | ❌ (별도 GPU 추가 시 ⭕) | | 속도 | 중첩이라 다소 느림 | 네이티브 | | `.9` 재부팅 시 | 같이 죽음 | 영향 없음 | | RAM | `.9`의 30GB를 나눠 씀 | 독립 | CI는 타임아웃이 30분인데 파이프라인이 27초라 속도 여유가 충분합니다. **먼저 A로 띄워보고, 부족하면 B로 옮기는 편을 권합니다.** ### 선택지 B — Proxmox에 Windows VM 만들기 ```bash # Proxmox 호스트에서 (VMID 는 비어 있는 번호로) qm create 920 --name win-ci --memory 8192 --cores 4 --net0 virtio,bridge=vmbr0 --scsihw virtio-scsi-single --scsi0 local-lvm:80 --ostype win11 --machine q35 --bios ovmf --efidisk0 local-lvm:1,efitype=4m,pre-enrolled-keys=1 --tpmstate0 local-lvm:1,version=v2.0 --ide2 local:iso/Win11.iso,media=cdrom --ide0 local:iso/virtio-win.iso,media=cdrom --audio0 device=ich9-intel-hda,driver=none --vga std ``` RAM 8GB / 디스크 80GB면 충분합니다(모델 캐시 포함). 아래 둘을 꼭 넣으세요. - `--audio0 ...` — **없으면 오디오 장치가 하나도 안 잡혀** WASAPI 점검이 실패합니다 - `--vga std` — 화면이 있어야 자막 오버레이를 그리고 스크린샷을 찍습니다 ### VM 안에서 꼭 해야 하는 두 가지 **1. 자동 로그온** — 러너가 서비스로 돌면 데스크톱 세션이 없어 GUI 테스트와 스크린샷이 전부 실패합니다. 자동 로그온 + 로그온 시 실행이어야 합니다. ```powershell # 관리자 PowerShell (VM 안이므로 부담 적음) $k = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" Set-ItemProperty $k AutoAdminLogon 1 Set-ItemProperty $k DefaultUserName "ci" Set-ItemProperty $k DefaultPassword "<비밀번호>" ``` **2. 화면 꺼짐·잠금 끄기** — 잠기면 GUI 테스트가 실패합니다. ```powershell powercfg /change monitor-timeout-ac 0 powercfg /change standby-timeout-ac 0 ``` 그 다음은 아래 일반 설치 절차와 같습니다. 러너는 **서비스가 아니라 로그온 예약 작업**으로 등록하세요(아래 "항상 켜두고 싶다면" 참고). --- ## 무엇이 자동으로 확인되나 | 항목 | 리눅스에서 | Windows 러너에서 | |---|---|---| | 단위 테스트 189개 | ✅ | ✅ | | WASAPI 루프백 장치 열거 | ❌ | ✅ | | 소리 내는 프로그램 열거 | ❌ | ✅ | | 전역 단축키 등록/해제 | ❌ | ✅ | | 모니터 열거·자막 배치 | 가상 화면만 | ✅ 실제 모니터 | | 포터블 exe 빌드 | ❌ | ✅ | | exe가 실제로 뜨는지 | ❌ | ✅ | | GPU 인식 (CUDA) | 이 서버 GPU | ✅ 실제 게임 PC GPU | | 실제 화면 스크린샷 | offscreen | ✅ 진짜 화면 | 결과물(exe, 스크린샷, 로그)은 Gitea Actions의 artifact로 올라오므로 원격에서도 눈으로 확인할 수 있습니다. --- ## 설치 (약 10분, 한 번만) ### 1. 필요한 것 설치 ```powershell winget install Git.Git # 소스 받기 (checkout) 에 필요 winget install Microsoft.PowerShell # 워크플로가 pwsh 7 을 씁니다 winget install Python.Python.3.12 winget install Kitware.CMake winget install Microsoft.VisualStudio.2022.BuildTools # C++ 데스크톱 워크로드 선택 ``` `Git.Git` 과 `Microsoft.PowerShell` 은 빠뜨리기 쉬운데 둘 다 필수입니다. Git 이 없으면 소스를 못 받고, Windows 에 기본으로 깔린 PowerShell 은 5.1 이라 워크플로가 쓰는 `pwsh`(7.x) 가 없으면 단계마다 실패합니다. 설치 후 새 PowerShell 창을 열어 `git --version`, `pwsh --version` 이 나오는지 확인하세요. ### 2. Gitea 러너 내려받기 에서 `act_runner-<버전>-windows-amd64.exe` 를 받아 `C:\gitea-runner\act_runner.exe` 로 둡니다. ### 3. 등록 토큰 받기 Gitea 웹에서 저장소 → **설정 → Actions → 러너 → 등록 토큰 생성** (주소: `https://git.tkrmagid.kr/tkrmagid/live-app-translator/settings/actions/runners`) ### 4. 등록하고 실행 ```powershell cd C:\gitea-runner .\act_runner.exe register --no-interactive ` --instance https://git.tkrmagid.kr ` --token <위에서 받은 토큰> ` --name windows-test ` --labels windows:host .\act_runner.exe daemon ``` `--labels windows:host` 가 중요합니다. 워크플로의 `runs-on: windows` 와 이름이 맞아야 하고, `:host` 는 컨테이너가 아니라 그 PC에서 직접 돌리라는 뜻입니다 (생략해도 기본값이 `host` 지만 명시하는 편이 낫습니다). 이름이 안 맞으면 Gitea 는 작업을 실패시키지 않고 **기본 우분투 컨테이너에서 돌립니다.** Windows 검증이 조용히 리눅스에서 돌아가는 셈이라, 워크플로 첫 단계에서 그걸 막아 뒀습니다. ### 5. 확인 워크플로는 **`main` 에 push될 때** 또는 **Actions 탭에서 수동 실행**할 때 돕니다. (`.gitea/workflows/windows.yml` 의 `on:` 항목) 저장소 **Actions** 탭 → `Windows` → `Run workflow` 로 바로 한 번 돌려보세요. --- ## 항상 켜두고 싶다면 (선택) **서비스(`sc.exe create`)로 등록하지 마세요.** 그렇게 하면 SYSTEM 권한으로 돌아가 러너가 PC 전체를 건드릴 수 있게 됩니다. 로그인할 때 **그 계정 권한으로** 뜨게 하는 편이 안전합니다. ```powershell # 관리자 아님 — 그냥 평소 PowerShell $action = New-ScheduledTaskAction -Execute "C:\gitea-runner\act_runner.exe" ` -Argument "daemon" -WorkingDirectory "C:\gitea-runner" $trigger = New-ScheduledTaskTrigger -AtLogOn Register-ScheduledTask -TaskName "gitea-runner" -Action $action -Trigger $trigger ``` 게임할 때 방해되면 그냥 꺼두고, 확인이 필요할 때만 `daemon` 을 켜도 됩니다. 러너가 꺼져 있으면 워크플로는 대기만 하고 아무 일도 하지 않습니다. --- ## 안전에 대해 — 정확히 어떤 권한인가 **솔직하게 말하면, 이건 "그 PC에서 명령을 실행할 수 있는 권한"입니다.** 러너는 저장소의 워크플로 파일을 실행합니다. 그런데 저(Claude)는 이 저장소의 `main`에 push할 수 있으므로, 워크플로 파일을 고쳐서 커밋하면 결과적으로 그 PC에서 원하는 명령을 돌릴 수 있습니다. "커밋된 파일만 실행하니 안전하다"는 말은 정확하지 않습니다. 실제로 보장되는 것은 이것뿐입니다. - **숨길 수 없습니다.** 실행되는 모든 것은 `.gitea/workflows/` 안에 있고 git 기록에 남습니다. 몰래 뭘 했는지 나중에 전부 확인할 수 있습니다. - **러너를 끄면 즉시 멈춥니다.** `daemon` 을 종료하면 그만입니다. - 러너는 **자기 계정 권한**으로 돕니다. 관리자로 띄우지 마세요. 그래서 권하는 방식은 이렇습니다. | 방법 | 위험 | 얻는 것 | |---|---|---| | **Windows VM** (Proxmox 등) | 거의 없음 | 빌드·단축키·모니터·UI·exe 실행 | | **전용 Windows 계정** | 낮음 — 그 계정 파일만 노출 | 위 전부 + 실제 GPU | | 평소 쓰는 계정 | 개인 파일·브라우저 세션 노출 | 위 전부 + 실제 게임 소리 | **VM이나 전용 계정을 권합니다.** GPU와 실제 게임 소리 확인은 못 하지만, 지금 미검증인 항목의 대부분(빌드·단축키·모니터·UI·exe 기동)은 거기서도 그대로 검증됩니다. GPU/오디오는 필요할 때만 평소 계정에서 한 번씩 확인하는 편이 낫습니다. 부담스러우면 러너를 붙이지 않고 아래 "러너 없이 직접 돌려보기"만 하셔도 충분합니다. --- ## 러너 없이 직접 돌려보기 러너를 안 붙이고 그때그때 확인만 하고 싶다면 이것만 실행하고 결과를 보내주셔도 됩니다. ```powershell git clone https://git.tkrmagid.kr/tkrmagid/live-app-translator cd live-app-translator py -3.12 -m venv .venv .\.venv\Scripts\python -m pip install -r packaging\requirements-portable.txt pytest $env:PYTHONPATH="src" .\.venv\Scripts\python -m pytest tests -q .\.venv\Scripts\python packaging\windows_smoke.py ``` `artifacts\windows-smoke.txt` 와 `artifacts\*.png` 를 보내주시면 됩니다.