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:
67
scripts/win-ci/serve-host.py
Executable file
67
scripts/win-ci/serve-host.py
Executable file
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env python3
|
||||
"""win-ci VM 과의 통신 채널.
|
||||
|
||||
게스트(Windows)는 QEMU user networking 안에 있어서 호스트를 10.0.2.2 로 본다.
|
||||
이 서버는 그 반대편이다.
|
||||
|
||||
GET /ping 게스트가 네트워크 올라왔는지 확인용
|
||||
GET /stage20.ps1 게스트가 실행할 설치 스크립트 (serve/ 안의 파일 그대로)
|
||||
GET /<file> serve/ 안의 아무 파일 (러너 등록 토큰 등)
|
||||
POST /log 게스트 로그 -> guest.log 에 append
|
||||
|
||||
127.0.0.1 만 바인딩한다. slirp 의 10.0.2.2 는 호스트 루프백으로 들어오므로
|
||||
외부에 열 필요가 없다.
|
||||
"""
|
||||
import http.server
|
||||
import os
|
||||
import socketserver
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
ROOT = "/home/claude/win-ci/serve"
|
||||
LOG = "/home/claude/win-ci/guest.log"
|
||||
PORT = 8099
|
||||
|
||||
|
||||
class Handler(http.server.SimpleHTTPRequestHandler):
|
||||
def __init__(self, *a, **kw):
|
||||
super().__init__(*a, directory=ROOT, **kw)
|
||||
|
||||
def do_GET(self):
|
||||
if self.path.rstrip("/") == "/ping":
|
||||
body = b"pong\n"
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "text/plain")
|
||||
self.send_header("Content-Length", str(len(body)))
|
||||
self.end_headers()
|
||||
self.wfile.write(body)
|
||||
return
|
||||
super().do_GET()
|
||||
|
||||
def do_POST(self):
|
||||
if self.path.rstrip("/") != "/log":
|
||||
self.send_error(404)
|
||||
return
|
||||
n = int(self.headers.get("Content-Length") or 0)
|
||||
data = self.rfile.read(n).decode("utf-8", "replace").strip()
|
||||
with open(LOG, "a", encoding="utf-8") as f:
|
||||
f.write(f"{datetime.now():%Y-%m-%d %H:%M:%S} {data}\n")
|
||||
self.send_response(204)
|
||||
self.end_headers()
|
||||
|
||||
def log_message(self, fmt, *args):
|
||||
# 접근 로그는 stdout 으로만. journald 가 받는다.
|
||||
sys.stdout.write("%s %s\n" % (self.address_string(), fmt % args))
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
class Server(socketserver.ThreadingTCPServer):
|
||||
allow_reuse_address = True
|
||||
daemon_threads = True
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.makedirs(ROOT, exist_ok=True)
|
||||
with Server(("127.0.0.1", PORT), Handler) as httpd:
|
||||
print(f"win-ci control server on 127.0.0.1:{PORT}, root={ROOT}", flush=True)
|
||||
httpd.serve_forever()
|
||||
Reference in New Issue
Block a user