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:
84
scripts/win-ci/unattend/setup/agent.ps1
Normal file
84
scripts/win-ci/unattend/setup/agent.ps1
Normal file
@@ -0,0 +1,84 @@
|
||||
# Windows CI VM 상주 에이전트
|
||||
#
|
||||
# 하는 일은 하나다: 호스트가 주는 stage2.ps1 을 받아서 돌린다.
|
||||
#
|
||||
# 왜 이렇게 하나 - ISO 는 한 번 구우면 못 고친다. 설치 스크립트를 ISO 에
|
||||
# 박아두면 한 줄 고칠 때마다 Windows 를 다시 설치해야 한다. 그래서 ISO 에는
|
||||
# 이 껍데기만 넣고, 실제 설치 내용은 호스트의 HTTP 로 가져온다.
|
||||
# QEMU user networking 에서 호스트는 항상 10.0.2.2 다.
|
||||
#
|
||||
# 로그는 COM1(호스트 serial.log) + 호스트 HTTP(POST /log) + 로컬 파일.
|
||||
|
||||
$ErrorActionPreference = 'Continue'
|
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||
|
||||
$Host_ = 'http://10.0.2.2:8099'
|
||||
$WorkDir = 'C:\win-ci'
|
||||
$Stage2 = Join-Path $WorkDir 'stage2.ps1'
|
||||
$HashFile = Join-Path $WorkDir 'stage2.hash'
|
||||
$LogFile = Join-Path $WorkDir 'agent.log'
|
||||
New-Item -ItemType Directory -Force -Path $WorkDir | Out-Null
|
||||
|
||||
function Say([string]$m) {
|
||||
$line = "[agent $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] $m"
|
||||
try { Add-Content -Path $LogFile -Value $line -Encoding UTF8 } catch {}
|
||||
try {
|
||||
$p = New-Object System.IO.Ports.SerialPort 'COM1', 115200, 'None', 8, 'One'
|
||||
$p.Open(); $p.WriteLine($line); $p.Close()
|
||||
} catch {}
|
||||
try {
|
||||
Invoke-RestMethod -Uri "$Host_/log" -Method Post -Body $line -TimeoutSec 5 | Out-Null
|
||||
} catch {}
|
||||
}
|
||||
|
||||
# 같은 에이전트가 두 개 돌면 설치가 겹친다. 뮤텍스로 하나만 남긴다.
|
||||
$mutex = New-Object System.Threading.Mutex($false, 'Global\win-ci-agent')
|
||||
if (-not $mutex.WaitOne(0)) { Say "이미 다른 agent 가 돌고 있다. 종료."; exit 0 }
|
||||
|
||||
Say "agent 시작. user=$env:USERNAME"
|
||||
|
||||
# 네트워크가 올라오길 기다린다. virtio-net 드라이버 설치 직후라 몇 초 걸린다.
|
||||
$netOk = $false
|
||||
for ($i = 0; $i -lt 60; $i++) {
|
||||
try {
|
||||
Invoke-WebRequest -Uri "$Host_/ping" -TimeoutSec 4 -UseBasicParsing | Out-Null
|
||||
$netOk = $true; break
|
||||
} catch { Start-Sleep -Seconds 5 }
|
||||
}
|
||||
if ($netOk) { Say "호스트 연결 OK ($Host_)" }
|
||||
else { Say "ERROR 5분 동안 호스트에 못 붙었다. virtio-net 드라이버를 확인해야 한다." }
|
||||
|
||||
# 이후로는 계속 돌면서 stage2 가 바뀔 때마다 실행한다.
|
||||
while ($true) {
|
||||
try {
|
||||
$body = (Invoke-WebRequest -Uri "$Host_/stage2.ps1" -TimeoutSec 20 -UseBasicParsing).Content
|
||||
if ($body -is [byte[]]) { $body = [Text.Encoding]::UTF8.GetString($body) }
|
||||
|
||||
$sha = [BitConverter]::ToString(
|
||||
[Security.Cryptography.SHA256]::Create().ComputeHash([Text.Encoding]::UTF8.GetBytes($body))
|
||||
).Replace('-', '')
|
||||
|
||||
$prev = if (Test-Path $HashFile) { (Get-Content $HashFile -Raw).Trim() } else { '' }
|
||||
|
||||
if ($sha -ne $prev) {
|
||||
Say "stage2 새 버전 감지 ($($sha.Substring(0,12))) - 실행"
|
||||
Set-Content -Path $Stage2 -Value $body -Encoding UTF8
|
||||
try {
|
||||
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $Stage2 2>&1 |
|
||||
ForEach-Object { Say "stage2| $_" }
|
||||
if ($LASTEXITCODE -eq 0 -or $null -eq $LASTEXITCODE) {
|
||||
# 성공했을 때만 해시를 저장한다. 중간에 죽었으면 다음 턴에 다시 돈다.
|
||||
Set-Content -Path $HashFile -Value $sha
|
||||
Say "stage2 성공"
|
||||
} else {
|
||||
Say "stage2 실패 exit=$LASTEXITCODE - 다음 폴링에 재시도"
|
||||
}
|
||||
} catch {
|
||||
Say "stage2 예외: $($_.Exception.Message) - 다음 폴링에 재시도"
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
Say "폴링 실패: $($_.Exception.Message)"
|
||||
}
|
||||
Start-Sleep -Seconds 20
|
||||
}
|
||||
Reference in New Issue
Block a user