feat: publish workphone SDK deployment and API docs
This commit is contained in:
150
sdk/scripts/build_agent_config.py
Executable file
150
sdk/scripts/build_agent_config.py
Executable file
@@ -0,0 +1,150 @@
|
||||
#!/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"
|
||||
|
||||
|
||||
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)。
|
||||
|
||||
手机/本机统一顺序(BIND-03 · workphone.mdc §零.十):
|
||||
① NAS 局域网 → ② 本机 Mac Docker 局域网 → ③ NAS 外网 frp;
|
||||
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]] = list(nas_bases)
|
||||
if mac_base:
|
||||
ordered.append(mac_base)
|
||||
ordered.append(frp_base)
|
||||
|
||||
if for_phone:
|
||||
# 手机 OTA:固定 NAS 为主连接写入 config,public_servers 按序探测(局域网优先于公网)
|
||||
primary_ws, tier = nas_bases[0][0], "nas_lan"
|
||||
bases = [b for b, _ in ordered]
|
||||
seen = set()
|
||||
ordered_bases: List[str] = []
|
||||
for b in bases:
|
||||
if b not in seen:
|
||||
seen.add(b)
|
||||
ordered_bases.append(b)
|
||||
return primary_ws, tier, ordered_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="手机 OTA:NAS→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())
|
||||
Reference in New Issue
Block a user