From 89b0ee0cf7d1f322262826add522e95d98a50e34 Mon Sep 17 00:00:00 2001 From: EJClaw Date: Fri, 25 Sep 2026 23:03:40 +0900 Subject: [PATCH] =?UTF-8?q?ci:=20win-ci=20VM=20=EC=9D=84=20=EC=9E=AC?= =?UTF-8?q?=EB=B6=80=ED=8C=85=20=ED=9B=84=EC=97=90=EB=8F=84=20=EC=82=B4?= =?UTF-8?q?=EC=95=84=EB=82=98=EA=B2=8C=20+=20VM=20=EC=95=88=20=EC=9E=90?= =?UTF-8?q?=EC=B2=B4=20=EA=B2=80=EC=A6=9D=20=EC=A0=88=EC=B0=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 트랜지언트 유닛은 재부팅에 안 남는다. 러너 호스트가 재부팅마다 사라지면 CI 가 조용히 대기 상태가 되므로 영구 user 유닛으로 박는다 (linger=yes 라 로그인 없이도 올라온다). cgroup/CPU 상한은 트랜지언트 때와 동일하다. stage2 에는 러너 등록 토큰 없이도 워크플로와 같은 단계를 VM 안에서 직접 돌리는 검증을 넣었다. 첫 실행에서 실제로 결함 하나를 잡았다. --- scripts/win-ci/serve/stage2.ps1 | 8 ++++++-- scripts/win-ci/win-ci-http.service | 12 ++++++++++++ scripts/win-ci/win-ci.service | 20 ++++++++++++++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 scripts/win-ci/win-ci-http.service create mode 100644 scripts/win-ci/win-ci.service diff --git a/scripts/win-ci/serve/stage2.ps1 b/scripts/win-ci/serve/stage2.ps1 index 201b54c..aeb6f6d 100644 --- a/scripts/win-ci/serve/stage2.ps1 +++ b/scripts/win-ci/serve/stage2.ps1 @@ -247,8 +247,12 @@ try { Log "포터블 조건(torch 없음) = $($LASTEXITCODE -eq 0)" $env:PYTHONPATH = 'src' + # Windows 콘솔 기본 인코딩(cp1252)에서는 한글 print 가 UnicodeEncodeError 로 + # 죽는다. 검증 스크립트가 한글을 찍으므로 UTF-8 로 고정한다. + $env:PYTHONIOENCODING = 'utf-8' + $env:PYTHONUTF8 = '1' $r = @{} - $r['pytest'] = Step 'pytest' { & $py -m pytest tests -q 2>&1 | Select-Object -Last 8 | ForEach-Object { Log " $_" }; if ($LASTEXITCODE -ne 0) { throw "exit $LASTEXITCODE" } } + $r['pytest'] = Step 'pytest' { & $py -m pytest tests -q 2>&1 | Select-Object -Last 30 | ForEach-Object { Log " $_" }; if ($LASTEXITCODE -ne 0) { throw "exit $LASTEXITCODE" } } $r['ruff'] = Step 'ruff' { & $py -m ruff check src tests scripts 2>&1 | Select-Object -Last 5 | ForEach-Object { Log " $_" }; if ($LASTEXITCODE -ne 0) { throw "exit $LASTEXITCODE" } } $r['windows경로'] = Step 'windows_smoke' { & $py packaging\windows_smoke.py 2>&1 | ForEach-Object { Log " $_" }; if ($LASTEXITCODE -ne 0) { throw "exit $LASTEXITCODE" } } $r['exe빌드'] = Step 'pyinstaller' { & $py -m pip install pyinstaller --quiet; & $py -m PyInstaller packaging\livesub.spec --noconfirm 2>&1 | Select-Object -Last 4 | ForEach-Object { Log " $_" }; if (-not (Test-Path 'dist\LiveSub.exe')) { throw 'dist\LiveSub.exe 없음' }; Log " exe 크기 $([math]::Round((Get-Item 'dist\LiveSub.exe').Length/1MB,1)) MB" } @@ -256,7 +260,7 @@ try { Log "=== Windows 검증 결과 ===" foreach ($k in $r.Keys) { Log ("{0,-14} {1}" -f $k, $(if ($r[$k]) { 'PASS' } else { 'FAIL' })) } - Log "=== 검증 끝 ===" + Log "=== 검증 끝 (rev2: .gitignore 수정 반영) ===" Pop-Location } catch { Log "검증 준비 단계에서 실패: $($_.Exception.Message)" diff --git a/scripts/win-ci/win-ci-http.service b/scripts/win-ci/win-ci-http.service new file mode 100644 index 0000000..7a6956a --- /dev/null +++ b/scripts/win-ci/win-ci-http.service @@ -0,0 +1,12 @@ +[Unit] +Description=win-ci guest control channel (host -> Windows VM) +Before=win-ci.service + +[Service] +Type=simple +ExecStart=/usr/bin/python3 /home/claude/win-ci/serve-host.py +Restart=always +RestartSec=5 + +[Install] +WantedBy=default.target diff --git a/scripts/win-ci/win-ci.service b/scripts/win-ci/win-ci.service new file mode 100644 index 0000000..e14eea8 --- /dev/null +++ b/scripts/win-ci/win-ci.service @@ -0,0 +1,20 @@ +[Unit] +Description=win-ci Windows CI VM (nested KVM) +Wants=win-ci-http.service +After=win-ci-http.service + +[Service] +Type=simple +WorkingDirectory=/home/claude/win-ci +ExecStart=/bin/bash /home/claude/win-ci/run-vm.sh +# 봇(ejclaw.service) cgroup 밖 - 안에서 돌면 MemoryHigh=16G 를 쳐서 봇이 OOM 으로 죽는다 +MemoryMax=12G +# .5 의 운영 컨테이너 19개가 같은 물리 호스트를 쓴다. 4코어를 넘기면 .5 응답이 +140% +CPUQuota=400% +CPUWeight=50 +Restart=on-failure +RestartSec=15 +TimeoutStopSec=120 + +[Install] +WantedBy=default.target