ci: .9 안 Windows CI VM 무인 구축 자동화

Windows 설치 화면을 사람이 클릭할 수 없으므로 전 과정을 무인으로 짰다.
autounattend.xml 이 설치와 OOBE 를 끝내고, provision.ps1 이 virtio 드라이버를
오프라인으로 넣어 네트워크를 살리고, 상주 agent.ps1 이 호스트 HTTP 에서
stage2.ps1 을 받아 Git/pwsh7/Python/Node/CMake/act_runner 를 설치한다.

설치 내용을 ISO 에 박지 않고 호스트가 HTTP 로 내려주는 구조로 한 이유는
ISO 를 한 번 구우면 못 고쳐서, 한 줄 고칠 때마다 Windows 재설치가 되기
때문이다. stage2.ps1 만 고치면 게스트가 20초 안에 반영한다.

- 시스템 디스크는 SATA. virtio-blk 로 하면 Setup 이 디스크를 못 봐서
  WinPE 드라이버 주입이 필요해진다
- VM 은 systemd --user 트랜지언트 유닛으로 띄워 봇 cgroup 밖에 둔다
  (MemoryHigh=16G 안에 8G VM 이 들어가면 봇이 OOM 으로 죽는 전례)
- Node 포함. act_runner host 모드가 JS 액션을 PATH 의 node 로 돌린다
This commit is contained in:
EJClaw
2026-09-25 22:13:55 +09:00
parent d0028abdac
commit e41d0cb441
13 changed files with 971 additions and 0 deletions

View File

@@ -142,6 +142,68 @@ IOMMU 그룹 수 0개
넣은 뒤 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 → `guest.log` | 네트워크가 **없는** 드라이버 설치 단계도 봐야 하므로 시리얼이 필수 |
접속 정보: 계정 `ci` / 비밀번호는 `~/.config/ejclaw/secrets.json` 의
`win-ci.9` 항목. RDP 는 호스트 `127.0.0.1:13389`, VNC 는 `127.0.0.1:5919`.
##### 무인 설치에서 실제로 걸린 것들
- **`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.** 추측이 아니라 재보고 정한 값입니다.