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

31
scripts/win-ci/shot.sh Executable file
View File

@@ -0,0 +1,31 @@
#!/bin/bash
# win-ci VM 화면 스냅샷. QMP screendump 로 찍는다.
# 사용법: ./shot.sh [출력경로.png]
set -euo pipefail
OUT="${1:-/home/claude/EJClaw/data/attachments/generated/win-ci-$(date +%H%M%S).png}"
mkdir -p "$(dirname "$OUT")"
python3 - "$OUT" <<'PY'
import json, socket, sys, time
out = sys.argv[1]
s = socket.socket(socket.AF_UNIX)
s.settimeout(20)
s.connect("/home/claude/win-ci/qmp.sock")
f = s.makefile("rwb")
f.readline() # greeting
def cmd(o):
f.write((json.dumps(o) + "\n").encode()); f.flush()
while True:
line = f.readline()
if not line: raise SystemExit("qmp closed")
m = json.loads(line)
if "return" in m or "error" in m: return m
cmd({"execute": "qmp_capabilities"})
r = cmd({"execute": "screendump", "arguments": {"filename": out, "format": "png"}})
if "error" in r:
# 구버전 qemu 는 format 인자를 모른다 -> ppm 으로 받는다
r = cmd({"execute": "screendump", "arguments": {"filename": out + ".ppm"}})
print(json.dumps(r))
else:
print("ok", out)
PY
ls -l "$OUT"* 2>/dev/null