Files
workphone-sdk/sdk/scripts/build_agent_config.py

146 lines
4.8 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""生成 Termux Agent config.json — 连接顺序 ① NAS 局域网 ② 本机 Docker ③ NAS 外网 frp。
与 agent.py BIND-03 / workphone.mdc §零.十 一致。
用法:
python3 build_agent_config.py --device-id xgfe65eimrrofyws --out /tmp/config.json
python3 build_agent_config.py --print-primary # 只输出当前可达主服 ws 基址
"""
from __future__ import annotations
import argparse
import json
import socket
import sys
import urllib.request
from typing import List, Optional, Tuple
SDK_PORT = 8899
NAS_LAN_HOSTS = ("192.168.110.101", "192.168.1.201")
NAS_FRP_HOST = "open.quwanzhi.com"
HEARTBEAT = 10
PROJECT_ID = "cunkebao"
PUBLIC_WSS_BASE = "wss://wpsdk.quwanzhi.com/ws/device"
def _tcp_ok(host: str, port: int, timeout: float = 2.5) -> bool:
try:
with socket.create_connection((host, port), timeout=timeout):
return True
except OSError:
return False
def _health_ok(host: str, port: int = SDK_PORT, timeout: float = 3.0) -> bool:
try:
url = f"http://{host}:{port}/health"
with urllib.request.urlopen(url, timeout=timeout) as resp:
return resp.status == 200
except Exception:
return False
def pick_mac_lan_ip() -> str:
import subprocess
try:
out = subprocess.check_output(["ifconfig"], text=True, stderr=subprocess.DEVNULL)
except Exception:
return "127.0.0.1"
ips: List[str] = []
for line in out.splitlines():
parts = line.strip().split()
if len(parts) >= 2 and parts[0] == "inet":
ip = parts[1]
if ip.startswith("127.") or ip.startswith("198.18."):
continue
ips.append(ip)
for ip in ips:
if ip.startswith("192.168.110."):
return ip
for ip in ips:
if ip.startswith("192.168.") or ip.startswith("10."):
return ip
return ips[0] if ips else "127.0.0.1"
def resolve_primary(for_phone: bool = False) -> Tuple[str, str, List[str]]:
"""返回 (primary_ws_base, tier, ordered_ws_bases)。
手机/本机统一顺序:
① 公网 WSS → ② NAS 局域网 → ③ 本机 Mac Docker 局域网;
Agent 运行时 TCP 探测,连不上自动切下一候选。
"""
mac_ip = pick_mac_lan_ip()
nas_bases = [(f"ws://{h}:{SDK_PORT}/ws/device", "nas_lan") for h in NAS_LAN_HOSTS]
frp_base = (f"ws://{NAS_FRP_HOST}:{SDK_PORT}/ws/device", "nas_frp")
mac_base = (f"ws://{mac_ip}:{SDK_PORT}/ws/device", "mac_docker") if mac_ip != "127.0.0.1" else None
ordered: List[Tuple[str, str]] = [(PUBLIC_WSS_BASE, "public_wss")] + list(nas_bases)
if mac_base:
ordered.append(mac_base)
ordered.append(frp_base)
if for_phone:
# 手机 Type-C 部署:公网 WSS 固定为首选,避免 WLAN 客户端隔离。
primary_ws, tier = PUBLIC_WSS_BASE, "public_wss"
bases = [primary_ws] + [b for b, _ in ordered if b != primary_ws]
return primary_ws, tier, bases
primary_ws, tier = ordered[0][0], ordered[0][1]
for ws_base, t in ordered:
host = ws_base.split("//")[1].split(":")[0]
if _health_ok(host, SDK_PORT):
primary_ws, tier = ws_base, t
break
bases: List[str] = []
seen = set()
for ws_base, _ in ordered:
if ws_base not in seen:
seen.add(ws_base)
bases.append(ws_base)
bases = [primary_ws] + [b for b in bases if b != primary_ws]
return primary_ws, tier, bases
def build_config(device_id: str, for_phone: bool = False) -> dict:
primary_ws, tier, bases = resolve_primary(for_phone=for_phone)
return {
"device_id": device_id,
"server_url": primary_ws,
"public_servers": [b for b in bases if b != primary_ws],
"heartbeat_interval": HEARTBEAT,
"project_id": PROJECT_ID,
"connection_priority": ["nas_lan", "mac_docker", "nas_frp"],
"_meta": {"primary_tier": tier, "generator": "build_agent_config.py"},
}
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--device-id", default="xgfe65eimrrofyws")
ap.add_argument("--out", default="")
ap.add_argument("--print-primary", action="store_true")
ap.add_argument("--for-phone", action="store_true", help="手机 OTANAS→frp→Mac")
args = ap.parse_args()
primary_ws, tier, _ = resolve_primary(for_phone=args.for_phone)
if args.print_primary:
print(primary_ws)
print(tier, file=sys.stderr)
return 0
cfg = build_config(args.device_id, for_phone=args.for_phone)
text = json.dumps(cfg, ensure_ascii=False, indent=2)
if args.out:
with open(args.out, "w", encoding="utf-8") as f:
f.write(text)
else:
print(text)
return 0
if __name__ == "__main__":
raise SystemExit(main())