"""跨机器的真实链路验证:Windows 上的 Probe(NativeAOT exe)→ WSL2 里的 Agent 和 Backend(真实的虚拟网络、不同的单调时钟原点)。 前提:WSL 里有 Debian(root)、tc(netem 注入丢包/延迟/黑洞用);已经用 scripts/publish.sh 发布了 linux-x64 的 agent、backend、fleet-enroll, 并且 Windows 上发布了 probe(dotnet publish backend/probe/src/MoShiro.Probe -c Release -r win-x64 -p:PublishAot=true -o backend/artifacts/aot-probe)。 把 linux-x64 的产物复制到 WSL 的 /root/mon/,在 WSL 里用 setsid -f nohup 启动 moshiro-backend(监听 0.0.0.0:29181),再运行本脚本。 故障注入见 docs/ops/deployment.md 第 3 节。 """ import json, os, subprocess, sys, time, urllib.request, urllib.error # 要连的是本机的虚拟网络(WSL),不能走环境变量里的 HTTP(S)_PROXY OPENER = urllib.request.build_opener(urllib.request.ProxyHandler({})) ROOT = r"C:\Users\JustMe\Desktop\moshiro-proj" PROBE_EXE = os.path.join(ROOT, "backend", "artifacts", "aot-probe", "moshiro-probe.exe") WORK = r"C:\Users\JustMe\AppData\Local\Temp\realnet" PORT = 29181 def wsl(cmd, check=True): r = subprocess.run(["wsl", "-d", "Debian", "-u", "root", "-e", "bash", "-c", cmd], capture_output=True, text=True) if check and r.returncode != 0: print("WSL FAILED:", cmd, r.stdout, r.stderr) raise SystemExit(1) return r.stdout.strip() WSLIP = wsl("ip -4 addr show eth0 | grep -oP '(?<=inet )[0-9.]+'") BASE = f"http://{WSLIP}:{PORT}" def req(method, path, body=None, headers=None, ctype="application/json"): data = None if body is None else json.dumps(body).encode() h = {"Content-Type": ctype} h.update(headers or {}) r = urllib.request.Request(BASE + path, data=data, method=method, headers=h) try: with OPENER.open(r, timeout=30) as resp: text = resp.read().decode() return resp, (json.loads(text) if text else None) except urllib.error.HTTPError as e: print("HTTP", e.code, e.read().decode()) raise def main(): os.makedirs(WORK, exist_ok=True) print("wsl ip", WSLIP) resp, login = req("POST", "/api/admin/auth/login", {"username": "admin", "password": os.environ.get("MOSHIRO_ADMIN_PASSWORD", "correct horse battery")}) cookie = resp.headers["Set-Cookie"].split(";")[0] _, tok = req("POST", "/api/admin/api-tokens", {"name": "net", "scope": "admin"}, {"Cookie": cookie, "X-CSRF-Token": login["csrf_token"]}) auth = {"Authorization": "Bearer " + tok["plaintext"]} json.dump({"base": BASE, "token": tok["plaintext"], "wslip": WSLIP}, open(WORK + r"\state.json", "w")) _, join = req("POST", "/api/admin/join-tokens", {"auto_approve": True, "max_uses": 1, "expires_in_hours": 1}, auth) wsl("pkill moshiro-agent; cd /root/mon && rm -rf id agent.json") print(wsl(f"cd /root/mon && ./fleet-enroll --server http://127.0.0.1:{PORT} --join {join['plaintext']} --name wsl-agent --identity-dir /root/mon/id")) cfg = {"fleet": {"server": f"http://127.0.0.1:{PORT}", "identity_dir": "/root/mon/id"}, "listen": {"bind": "0.0.0.0"}} wsl("cat > /root/mon/agent.json <<'EOF'\n" + json.dumps(cfg) + "\nEOF") wsl("cd /root/mon && (setsid nohup ./moshiro-agent --config /root/mon/agent.json > agent.log 2>&1 < /dev/null &)") time.sleep(3) _, agents = req("GET", "/api/admin/agents", None, auth) agent = agents["items"][0] print("agent", agent["id"], agent["status"], "online", agent.get("online")) # the backend only sees 127.0.0.1 as the agent's address: the panel needs the address the probe can reach req("PATCH", "/api/admin/agents/" + agent["id"], {"public_host": WSLIP}, auth, "application/merge-patch+json") _, ident = req("POST", "/api/admin/probes", {"name": "home"}, auth) print("bootstrap", ident["bootstrap"]) os.makedirs(WORK + r"\probe", exist_ok=True) json.dump(ident, open(WORK + r"\probe\probe.json", "w")) json.dump({"base": BASE, "token": tok["plaintext"], "wslip": WSLIP, "agent_id": agent["id"]}, open(WORK + r"\state.json", "w")) log = open(WORK + r"\probe.log", "w") p = subprocess.Popen([PROBE_EXE, "--config", WORK + r"\probe\probe.json"], stdout=log, stderr=subprocess.STDOUT) open(WORK + r"\probe.pid", "w").write(str(p.pid)) print("probe pid", p.pid) main()