#!/usr/bin/env python3 """win-ci VM 과의 통신 채널. 게스트(Windows)는 QEMU user networking 안에 있어서 호스트를 10.0.2.2 로 본다. 이 서버는 그 반대편이다. GET /ping 게스트가 네트워크 올라왔는지 확인용 GET /stage20.ps1 게스트가 실행할 설치 스크립트 (serve/ 안의 파일 그대로) GET / 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 guess_type(self, path): # .ps1 은 charset 을 반드시 명시한다. 없으면 PowerShell 쪽에서 # ISO-8859-1 로 디코딩해 한글 주석이 깨지고, 깨진 바이트가 CP1252 의 # 스마트따옴표가 되어 파서가 스크립트를 통째로 삼킨다. if str(path).endswith(".ps1"): return "text/plain; charset=utf-8" return super().guess_type(path) 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()