Compare commits
4 Commits
a66eedee8b
...
v0.3.0-tes
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2028b0a651 | ||
|
|
4fd8fabcb8 | ||
|
|
ca096a69c3 | ||
|
|
0a1389a1d9 |
7
.gitignore
vendored
7
.gitignore
vendored
@@ -10,9 +10,12 @@ venv/
|
|||||||
.ruff_cache/
|
.ruff_cache/
|
||||||
|
|
||||||
# 네이티브 빌드 산출물
|
# 네이티브 빌드 산출물
|
||||||
|
#
|
||||||
|
# ⚠️ 패키지 이름은 livesub 다. 예전 이름(hearo)이 그대로 남아 있어서 빌드한
|
||||||
|
# livesub_capture.exe 가 무시 대상에서 빠져 있었다.
|
||||||
native/process_loopback/build/
|
native/process_loopback/build/
|
||||||
src/hearo/resources/bin/*.exe
|
src/livesub/resources/bin/*.exe
|
||||||
src/hearo/resources/bin/*.pdb
|
src/livesub/resources/bin/*.pdb
|
||||||
|
|
||||||
# 모델 캐시 / 학습 산출물 / 사용자 데이터
|
# 모델 캐시 / 학습 산출물 / 사용자 데이터
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -175,7 +175,30 @@ scripts/win-ci/build.sh # ISO 받기 + 무인설치 ISO 굽기 + 디스
|
|||||||
| 방향 | 통로 | 왜 |
|
| 방향 | 통로 | 왜 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| 호스트 → 게스트 | HTTP `10.0.2.2:8099` (QEMU user networking) | 스크립트·러너 토큰 전달 |
|
| 호스트 → 게스트 | HTTP `10.0.2.2:8099` (QEMU user networking) | 스크립트·러너 토큰 전달 |
|
||||||
| 게스트 → 호스트 | COM1 → `serial.log`, HTTP POST → `guest.log` | 네트워크가 **없는** 드라이버 설치 단계도 봐야 하므로 시리얼이 필수 |
|
| 게스트 → 호스트 | 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 <sha> # stage2 의 $TargetRev 를 그 커밋으로 박아 배포
|
||||||
|
tail -f /home/claude/win-ci/guest.log # 약 10분, 끝나면 results/summary.log 확인
|
||||||
|
```
|
||||||
|
|
||||||
|
게스트 agent 는 `stage2.ps1` **내용 해시가 바뀔 때만** 다시 돕니다. 커밋만
|
||||||
|
푸시하면 재검증이 걸리지 않으니 sha 를 넘기세요. `build.sh` 를 다시 돌리면
|
||||||
|
안 됩니다 — 지워둔 Windows ISO 4.8GB 를 다시 내려받습니다.
|
||||||
|
|
||||||
접속 정보: 계정 `ci` / 비밀번호는 `~/.config/ejclaw/secrets.json` 의
|
접속 정보: 계정 `ci` / 비밀번호는 `~/.config/ejclaw/secrets.json` 의
|
||||||
`win-ci.9` 항목. RDP 는 호스트 `127.0.0.1:13389`, VNC 는 `127.0.0.1:5919`.
|
`win-ci.9` 항목. RDP 는 호스트 `127.0.0.1:13389`, VNC 는 `127.0.0.1:5919`.
|
||||||
|
|||||||
@@ -49,8 +49,13 @@ BOOL WINAPI ConsoleHandler(DWORD type) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ActivateAudioInterfaceAsync 완료 콜백. 결과를 이벤트로 넘겨준다.
|
// ActivateAudioInterfaceAsync 완료 콜백. 결과를 이벤트로 넘겨준다.
|
||||||
|
//
|
||||||
|
// FtmBase 는 플래그가 아니라 섞어 넣는 클래스다. RuntimeClassFlags 안에
|
||||||
|
// `ClassicCom | FtmBase` 로 넣으면 MSVC 가 C2275 로 거부한다(타입을 값 자리에
|
||||||
|
// 썼으므로). 인터페이스 목록 쪽에 나열해야 한다 — ApplicationLoopback 샘플과
|
||||||
|
// 같은 형태다.
|
||||||
class ActivationHandler
|
class ActivationHandler
|
||||||
: public RuntimeClass<RuntimeClassFlags<ClassicCom | FtmBase>,
|
: public RuntimeClass<RuntimeClassFlags<ClassicCom>, FtmBase,
|
||||||
IActivateAudioInterfaceCompletionHandler> {
|
IActivateAudioInterfaceCompletionHandler> {
|
||||||
public:
|
public:
|
||||||
explicit ActivationHandler(HANDLE done) : done_(done) {}
|
explicit ActivationHandler(HANDLE done) : done_(done) {}
|
||||||
|
|||||||
@@ -29,7 +29,10 @@ ROOT = "/home/claude/win-ci/serve"
|
|||||||
LOG = "/home/claude/win-ci/guest.log"
|
LOG = "/home/claude/win-ci/guest.log"
|
||||||
RESULTS = "/home/claude/win-ci/results"
|
RESULTS = "/home/claude/win-ci/results"
|
||||||
PORT = 8099
|
PORT = 8099
|
||||||
MAX_ARTIFACT = 32 * 1024 * 1024
|
# 로그 전문뿐 아니라 빌드된 포터블 exe(100MB+)도 이 통로로 회수한다.
|
||||||
|
# 32MB 로 두면 배포할 exe 가 413 으로 튕긴다.
|
||||||
|
MAX_ARTIFACT = 1024 * 1024 * 1024
|
||||||
|
CHUNK = 1024 * 1024
|
||||||
|
|
||||||
|
|
||||||
class Handler(http.server.SimpleHTTPRequestHandler):
|
class Handler(http.server.SimpleHTTPRequestHandler):
|
||||||
@@ -82,11 +85,28 @@ class Handler(http.server.SimpleHTTPRequestHandler):
|
|||||||
if n > MAX_ARTIFACT:
|
if n > MAX_ARTIFACT:
|
||||||
self.send_error(413, "artifact too large")
|
self.send_error(413, "artifact too large")
|
||||||
return
|
return
|
||||||
data = self.rfile.read(n)
|
|
||||||
os.makedirs(RESULTS, exist_ok=True)
|
os.makedirs(RESULTS, exist_ok=True)
|
||||||
with open(os.path.join(RESULTS, name), "wb") as f:
|
# 통째로 read() 하지 않는다. exe 는 100MB 가 넘어서 메모리에 다 올리면
|
||||||
f.write(data)
|
# 봇 cgroup 의 메모리 상한을 건드린다. 조각으로 받아 바로 디스크에 쓴다.
|
||||||
print(f"artifact {name} {len(data)} bytes", flush=True)
|
#
|
||||||
|
# 그리고 임시 이름으로 받아 끝난 뒤에 옮긴다. 전송 중에 끊기면 반쪽짜리
|
||||||
|
# 파일이 정상 결과물처럼 남아 그대로 릴리스에 올라갈 수 있다.
|
||||||
|
final = os.path.join(RESULTS, name)
|
||||||
|
tmp = final + ".part"
|
||||||
|
left = n
|
||||||
|
with open(tmp, "wb") as f:
|
||||||
|
while left > 0:
|
||||||
|
block = self.rfile.read(min(CHUNK, left))
|
||||||
|
if not block:
|
||||||
|
break
|
||||||
|
f.write(block)
|
||||||
|
left -= len(block)
|
||||||
|
if left:
|
||||||
|
os.remove(tmp)
|
||||||
|
self.send_error(400, "artifact truncated")
|
||||||
|
return
|
||||||
|
os.replace(tmp, final)
|
||||||
|
print(f"artifact {name} {n} bytes", flush=True)
|
||||||
self.send_response(204)
|
self.send_response(204)
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,14 @@
|
|||||||
$ErrorActionPreference = 'Stop'
|
$ErrorActionPreference = 'Stop'
|
||||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||||
|
|
||||||
|
# 자식 프로세스 stdout 을 UTF-8 로 해독한다.
|
||||||
|
#
|
||||||
|
# 이게 없으면 PowerShell 이 자식의 출력을 콘솔 코드페이지(LTSC en-US = 437)로
|
||||||
|
# 읽는다. python 쪽은 PYTHONUTF8=1 로 UTF-8 을 내보내므로 한글이 전부 깨져서
|
||||||
|
# 호스트로 회수한 증거 파일의 실패 메시지를 읽을 수 없다. 여기서 바꾸는 건
|
||||||
|
# '해독' 쪽이라 파일 저장 인코딩(StreamWriter 는 UTF-8 고정)과는 무관하다.
|
||||||
|
try { [Console]::OutputEncoding = [Text.UTF8Encoding]::new($false) } catch { }
|
||||||
|
|
||||||
$Dl = 'C:\win-ci\dl'
|
$Dl = 'C:\win-ci\dl'
|
||||||
New-Item -ItemType Directory -Force -Path $Dl | Out-Null
|
New-Item -ItemType Directory -Force -Path $Dl | Out-Null
|
||||||
|
|
||||||
@@ -105,6 +113,41 @@ if (Have 'cmake') {
|
|||||||
else { Log "WARN cmake 설치 실패 - 워크플로의 native 빌드 단계는 건너뛰어진다" }
|
else { Log "WARN cmake 설치 실패 - 워크플로의 native 빌드 단계는 건너뛰어진다" }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------ VS 2022 Build Tools (C++)
|
||||||
|
# livesub_capture.exe (native\process_loopback) 는 WASAPI 프로세스 루프백을 쓴다.
|
||||||
|
# 그 헤더(audioclientactivationparams.h)와 WRL 은 Windows SDK + MSVC 에만 있다.
|
||||||
|
# cmake 만 깔면 "generator 는 있는데 컴파일러가 없다"로 configure 에서 죽는다.
|
||||||
|
#
|
||||||
|
# 왜 빠뜨리면 안 되나: 이 exe 가 없으면 포터블 빌드가 그대로 성공하되
|
||||||
|
# 프로그램별 캡처가 장치 전체 캡처로 조용히 폴백된다. 즉 "게임 소리만 받는다"는
|
||||||
|
# 핵심 기능이 빠진 채로 exe 가 나온다. 실제로 그 상태의 exe 가 한 번 나왔다.
|
||||||
|
$VsWhere = Join-Path ${env:ProgramFiles(x86)} 'Microsoft Visual Studio\Installer\vswhere.exe'
|
||||||
|
function Get-MsvcRoot {
|
||||||
|
if (-not (Test-Path $VsWhere)) { return $null }
|
||||||
|
$p = & $VsWhere -latest -products * `
|
||||||
|
-requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 `
|
||||||
|
-property installationPath 2>$null
|
||||||
|
if ($p) { return ($p | Select-Object -First 1) }
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
$msvc = Get-MsvcRoot
|
||||||
|
if ($msvc) {
|
||||||
|
Log "MSVC 이미 있음: $msvc"
|
||||||
|
} else {
|
||||||
|
$f = Get-File 'https://aka.ms/vs/17/release/vs_BuildTools.exe' 'vs_BuildTools.exe'
|
||||||
|
Log "VS 2022 Build Tools 설치 중 (C++ 워크로드, 수 GB - 20분 이상 걸린다)"
|
||||||
|
# --wait 없이 Start-Process -Wait 만 쓰면 부트스트래퍼가 먼저 반환해 버려
|
||||||
|
# 설치가 끝나기 전에 다음 단계로 넘어간다. 둘 다 있어야 한다.
|
||||||
|
$p = Start-Process $f -Wait -PassThru -ArgumentList `
|
||||||
|
'--quiet', '--wait', '--norestart', '--nocache', `
|
||||||
|
'--add', 'Microsoft.VisualStudio.Workload.VCTools', '--includeRecommended'
|
||||||
|
# 3010 = 설치는 됐고 재부팅만 권고. 실패가 아니다.
|
||||||
|
Log "Build Tools 설치기 종료코드 $($p.ExitCode)"
|
||||||
|
$msvc = Get-MsvcRoot
|
||||||
|
if ($msvc) { Log "MSVC 설치 완료: $msvc" }
|
||||||
|
else { Log "WARN MSVC 설치 실패 - livesub_capture.exe 를 빌드할 수 없다" }
|
||||||
|
}
|
||||||
|
|
||||||
# ------------------------------------------------------------------ act_runner
|
# ------------------------------------------------------------------ act_runner
|
||||||
$RunnerDir = 'C:\gitea-runner'
|
$RunnerDir = 'C:\gitea-runner'
|
||||||
$RunnerExe = Join-Path $RunnerDir 'act_runner.exe'
|
$RunnerExe = Join-Path $RunnerDir 'act_runner.exe'
|
||||||
@@ -212,6 +255,12 @@ Log "=== stage2 완료 ==="
|
|||||||
# ==================================================================
|
# ==================================================================
|
||||||
$Repo = 'C:\win-ci\live-app-translator'
|
$Repo = 'C:\win-ci\live-app-translator'
|
||||||
$OutDir = 'C:\win-ci\out'
|
$OutDir = 'C:\win-ci\out'
|
||||||
|
|
||||||
|
# 검증 대상 리비전. 'main' 이면 origin/main 최신.
|
||||||
|
# 호스트에서 `./sync.sh <sha>` 를 쓰면 이 한 줄이 그 sha 로 바뀐 사본이 배포되고,
|
||||||
|
# 내용이 바뀌므로 게스트 agent 가 해시 차이를 보고 20초 안에 다시 돈다.
|
||||||
|
# = "이 커밋을 검증해라" 라는 요청이 곧 재실행 트리거다.
|
||||||
|
$TargetRev = 'main'
|
||||||
New-Item -ItemType Directory -Force -Path $OutDir | Out-Null
|
New-Item -ItemType Directory -Force -Path $OutDir | Out-Null
|
||||||
|
|
||||||
# 출력 원본을 호스트 파일로 회수한다.
|
# 출력 원본을 호스트 파일로 회수한다.
|
||||||
@@ -226,7 +275,9 @@ function Send-Artifact($name, $path) {
|
|||||||
$bytes = [IO.File]::ReadAllBytes($path)
|
$bytes = [IO.File]::ReadAllBytes($path)
|
||||||
# 백틱 줄바꿈을 쓰지 않는다. LF 로 저장된 스크립트에서 토크나이저가
|
# 백틱 줄바꿈을 쓰지 않는다. LF 로 저장된 스크립트에서 토크나이저가
|
||||||
# 어긋나 스크립트 전체가 무효가 된 전례가 있다 (NOTES.md 참고).
|
# 어긋나 스크립트 전체가 무효가 된 전례가 있다 (NOTES.md 참고).
|
||||||
$req = @{ Uri = "http://10.0.2.2:8099/artifact/$name"; Method = 'Post'; TimeoutSec = 120; Body = $bytes; ContentType = 'application/octet-stream' }
|
# 타임아웃은 넉넉히. 로그는 수십 KB 지만 포터블 exe 는 100MB 를 넘고
|
||||||
|
# slirp(QEMU user networking) 는 그만큼 빠르지 않다.
|
||||||
|
$req = @{ Uri = "http://10.0.2.2:8099/artifact/$name"; Method = 'Post'; TimeoutSec = 1800; Body = $bytes; ContentType = 'application/octet-stream' }
|
||||||
Invoke-RestMethod @req | Out-Null
|
Invoke-RestMethod @req | Out-Null
|
||||||
Log " -> 호스트로 $name ($($bytes.Length) bytes)"
|
Log " -> 호스트로 $name ($($bytes.Length) bytes)"
|
||||||
} catch {
|
} catch {
|
||||||
@@ -267,13 +318,16 @@ function Step($name, $block) {
|
|||||||
$ErrorActionPreference = 'Continue'
|
$ErrorActionPreference = 'Continue'
|
||||||
try {
|
try {
|
||||||
Sync-Path
|
Sync-Path
|
||||||
if (Test-Path $Repo) {
|
if (-not (Test-Path $Repo)) {
|
||||||
Log "저장소 갱신"
|
|
||||||
Push-Location $Repo; & git fetch --all -q; & git reset --hard origin/main -q; Pop-Location
|
|
||||||
} else {
|
|
||||||
Log "저장소 clone"
|
Log "저장소 clone"
|
||||||
& git clone -q https://git.tkrmagid.kr/tkrmagid/live-app-translator $Repo
|
& git clone -q https://git.tkrmagid.kr/tkrmagid/live-app-translator $Repo
|
||||||
}
|
}
|
||||||
|
$ref = if ($TargetRev -eq 'main') { 'origin/main' } else { $TargetRev }
|
||||||
|
Log "저장소 갱신 -> $ref"
|
||||||
|
Push-Location $Repo
|
||||||
|
& git fetch --all -q
|
||||||
|
& git reset --hard $ref -q
|
||||||
|
Pop-Location
|
||||||
|
|
||||||
Push-Location $Repo
|
Push-Location $Repo
|
||||||
$py = Join-Path $Repo '.venv-ci\Scripts\python.exe'
|
$py = Join-Path $Repo '.venv-ci\Scripts\python.exe'
|
||||||
@@ -297,10 +351,47 @@ try {
|
|||||||
$r = [ordered]@{}
|
$r = [ordered]@{}
|
||||||
$r['pytest'] = Step 'pytest' { & $py -m pytest tests -q -rf --tb=long; if ($LASTEXITCODE -ne 0) { throw "exit $LASTEXITCODE" } }
|
$r['pytest'] = Step 'pytest' { & $py -m pytest tests -q -rf --tb=long; if ($LASTEXITCODE -ne 0) { throw "exit $LASTEXITCODE" } }
|
||||||
$r['ruff'] = Step 'ruff' { & $py -m ruff check src tests scripts; if ($LASTEXITCODE -ne 0) { throw "exit $LASTEXITCODE" } }
|
$r['ruff'] = Step 'ruff' { & $py -m ruff check src tests scripts; if ($LASTEXITCODE -ne 0) { throw "exit $LASTEXITCODE" } }
|
||||||
|
|
||||||
|
# 캡처 도우미를 먼저 만든다. windows_smoke 가 이 exe 의 존재를 점검하고,
|
||||||
|
# livesub.spec 도 있을 때만 포터블 exe 안에 담는다. 순서가 바뀌면 둘 다
|
||||||
|
# "없음"으로 통과해서 기능이 빠진 exe 가 나온다.
|
||||||
|
$r['캡처도우미'] = Step 'native_capture' {
|
||||||
|
& cmake -S native\process_loopback -B native\process_loopback\build -A x64
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw "cmake configure exit $LASTEXITCODE" }
|
||||||
|
& cmake --build native\process_loopback\build --config Release
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw "cmake build exit $LASTEXITCODE" }
|
||||||
|
$cap = 'src\livesub\resources\bin\livesub_capture.exe'
|
||||||
|
if (-not (Test-Path $cap)) { throw "$cap 없음" }
|
||||||
|
"livesub_capture.exe $((Get-Item $cap).Length) bytes"
|
||||||
|
}
|
||||||
|
|
||||||
$r['windows경로'] = Step 'windows_smoke' { & $py packaging\windows_smoke.py; if ($LASTEXITCODE -ne 0) { throw "exit $LASTEXITCODE" } }
|
$r['windows경로'] = Step 'windows_smoke' { & $py packaging\windows_smoke.py; if ($LASTEXITCODE -ne 0) { throw "exit $LASTEXITCODE" } }
|
||||||
$r['exe빌드'] = Step 'pyinstaller' { & $py -m pip install pyinstaller --quiet; & $py -m PyInstaller packaging\livesub.spec --noconfirm; if (-not (Test-Path 'dist\LiveSub.exe')) { throw 'dist\LiveSub.exe 없음' }; "exe 크기 $([math]::Round((Get-Item 'dist\LiveSub.exe').Length/1MB,1)) MB" }
|
|
||||||
|
$r['exe빌드'] = Step 'pyinstaller' {
|
||||||
|
& $py -m pip install pyinstaller --quiet
|
||||||
|
# 이전 산출물을 반드시 지우고 시작한다. 남겨두면 두 가지가 겹쳐 터진다.
|
||||||
|
# 1) 앞 회차의 exe기동 점검이 띄운 LiveSub 가 파일을 잡고 있어
|
||||||
|
# PyInstaller 가 EXE 를 덮어쓰지 못하고 PermissionError 로 죽는다.
|
||||||
|
# 2) 그런데 옛 exe 는 그대로 남아 있으니 Test-Path 는 통과한다.
|
||||||
|
# 즉 빌드가 실패했는데 PASS 로 보고된다 - 실제로 그렇게 오보가 났다.
|
||||||
|
Get-Process LiveSub -ErrorAction SilentlyContinue | Stop-Process -Force
|
||||||
|
Start-Sleep -Seconds 2
|
||||||
|
Remove-Item -Recurse -Force build, dist -ErrorAction SilentlyContinue
|
||||||
|
& $py -m PyInstaller packaging\livesub.spec --noconfirm
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw "PyInstaller exit $LASTEXITCODE" }
|
||||||
|
if (-not (Test-Path 'dist\LiveSub.exe')) { throw 'dist\LiveSub.exe 없음' }
|
||||||
|
"exe 크기 $([math]::Round((Get-Item 'dist\LiveSub.exe').Length/1MB,1)) MB"
|
||||||
|
}
|
||||||
$r['exe기동'] = Step 'exe_smoke' { & $py packaging\windows_smoke.py --exe dist\LiveSub.exe; if ($LASTEXITCODE -ne 0) { throw "exit $LASTEXITCODE" } }
|
$r['exe기동'] = Step 'exe_smoke' { & $py packaging\windows_smoke.py --exe dist\LiveSub.exe; if ($LASTEXITCODE -ne 0) { throw "exit $LASTEXITCODE" } }
|
||||||
|
|
||||||
|
# 빌드와 기동이 둘 다 통과했을 때만 배포용으로 회수한다. 실패한 exe 를
|
||||||
|
# 호스트로 올리면 그게 릴리스에 올라갈 위험이 있다.
|
||||||
|
if ($r['exe빌드'] -and $r['exe기동']) {
|
||||||
|
Send-Artifact 'LiveSub.exe' (Join-Path $Repo 'dist\LiveSub.exe')
|
||||||
|
} else {
|
||||||
|
Log "exe 회수 건너뜀 (빌드=$($r['exe빌드']) 기동=$($r['exe기동']))"
|
||||||
|
}
|
||||||
|
|
||||||
# 판정 요약도 파일로 남긴다. 어떤 리비전을 검증한 결과인지 같이 박아야
|
# 판정 요약도 파일로 남긴다. 어떤 리비전을 검증한 결과인지 같이 박아야
|
||||||
# 나중에 "언제 통과한 건지" 를 되짚을 수 있다.
|
# 나중에 "언제 통과한 건지" 를 되짚을 수 있다.
|
||||||
$rev = (& git rev-parse --short HEAD 2>&1 | Select-Object -First 1)
|
$rev = (& git rev-parse --short HEAD 2>&1 | Select-Object -First 1)
|
||||||
|
|||||||
@@ -1,19 +1,29 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# 저장소의 win-ci 스크립트를 /home/claude/win-ci 로 반영한다.
|
# 저장소의 win-ci 스크립트를 /home/claude/win-ci 로 반영한다.
|
||||||
#
|
#
|
||||||
|
# ./sync.sh stage2.ps1 을 그대로 배포 (origin/main 최신을 검증)
|
||||||
|
# ./sync.sh <sha> 그 커밋을 검증하도록 $TargetRev 를 박아서 배포
|
||||||
|
#
|
||||||
# build.sh 를 다시 돌리면 안 된다. build.sh 의 fetch 는 지워둔 Windows ISO(4.8GB)를
|
# build.sh 를 다시 돌리면 안 된다. build.sh 의 fetch 는 지워둔 Windows ISO(4.8GB)를
|
||||||
# 다시 내려받는다. 설치가 끝난 VM 에 스크립트만 갱신할 때는 이걸 쓴다.
|
# 다시 내려받는다. 설치가 끝난 VM 에 스크립트만 갱신할 때는 이걸 쓴다.
|
||||||
#
|
#
|
||||||
# stage2.ps1 은 게스트 agent 가 20초마다 해시를 비교해 받아가므로, 복사만 하면
|
# stage2.ps1 은 게스트 agent 가 20초마다 해시를 비교해 받아가므로, 복사만 하면
|
||||||
# VM 재시작 없이 다음 폴링에 새 버전이 실행된다.
|
# VM 재시작 없이 다음 폴링에 새 버전이 실행된다. 반대로 내용이 같으면 다시 돌지
|
||||||
|
# 않는다 — 그래서 재검증을 요청할 때는 sha 를 넘겨 내용이 바뀌게 한다.
|
||||||
# serve-host.py 는 호스트 프로세스라 유닛을 재시작해야 반영된다.
|
# serve-host.py 는 호스트 프로세스라 유닛을 재시작해야 반영된다.
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
D="${WIN_CI_DIR:-/home/claude/win-ci}"
|
D="${WIN_CI_DIR:-/home/claude/win-ci}"
|
||||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
REV="${1:-}"
|
||||||
|
|
||||||
mkdir -p "$D/serve" "$D/results"
|
mkdir -p "$D/serve" "$D/results"
|
||||||
cp "$HERE/serve/stage2.ps1" "$D/serve/"
|
cp "$HERE/serve/stage2.ps1" "$D/serve/"
|
||||||
|
if [ -n "$REV" ]; then
|
||||||
|
sed -i "s/^\$TargetRev = 'main'$/\$TargetRev = '$REV'/" "$D/serve/stage2.ps1"
|
||||||
|
grep -q "^\$TargetRev = '$REV'$" "$D/serve/stage2.ps1" || { echo "TargetRev 치환 실패"; exit 1; }
|
||||||
|
echo "검증 대상 리비전 = $REV"
|
||||||
|
fi
|
||||||
cp "$HERE"/*.sh "$HERE/serve-host.py" "$D/"
|
cp "$HERE"/*.sh "$HERE/serve-host.py" "$D/"
|
||||||
chmod +x "$D"/*.sh
|
chmod +x "$D"/*.sh
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user