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 로 돌린다
68 lines
2.2 KiB
Python
Executable File
68 lines
2.2 KiB
Python
Executable File
#!/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()
|