from __future__ import annotations import os from typing import Any, Dict, List, Optional, Sequence, Tuple from cmdlet._shared import Cmdlet, CmdletArg from SYS.result_table import ResultTable from SYS.config import load_config from API import zerotier as zt def exec(pipe: Sequence[Any], args: Sequence[str], options: Dict[str, Any]) -> ResultTable: table = ResultTable(title="ZeroTier Status", max_columns=10) cfg = load_config() # 1. Local Node Status node_id = "unknown" try: # Best effort to get local node ID st = zt._run_cli_json("status") if isinstance(st, dict): node_id = st.get("address") or node_id except: pass # 2. Hosting Status zt_net = cfg.get("networking", {}).get("zerotier", {}) serve_target = zt_net.get("serve") if serve_target: port = zt_net.get("port") or 999 net_id = zt_net.get("network_id") or "all" status = "OFFLINE" detail = "" # Try to find the local ZT address for this network zt_addrs = [] if net_id and net_id != "all": zt_addrs = zt.get_assigned_addresses(net_id) # We probe localhost for hosting status, but show ZT IP in the table display_addr = zt_addrs[0] if zt_addrs else "localhost" # Try probes # Using 127.0.0.1 is often more reliable than 'localhost' on Windows probe_targets = [f"http://127.0.0.1:{port}/health"] if zt_addrs: probe_targets.insert(0, f"http://{zt_addrs[0]}:{port}/health") from API.HTTP import HTTPClient with HTTPClient(timeout=1.0, retries=0) as client: for url in probe_targets: try: resp = client.get(url) if resp.status_code == 200: status = "ONLINE" payload = resp.json() detail = f"Serving {payload.get('name') or serve_target}" break else: status = f"HTTP {resp.status_code}" except Exception as exc: if not detail: # Keep the first failure reason if all fail detail = f"Probe failed: {exc}" if status == "OFFLINE" and not zt_addrs: detail = "No ZeroTier IP assigned yet. Check 'zerotier-cli listnetworks'." row = table.add_row() row.add_column("TYPE", "HOST") row.add_column("NAME", serve_target) row.add_column("ID", net_id) row.add_column("ADDRESS", f"{display_addr}:{port}") row.add_column("STATUS", status) row.add_column("DETAIL", detail) # 3. Connections (Remote Stores) zt_stores = cfg.get("store", {}).get("zerotier", {}) if zt_stores: for name, sconf in zt_stores.items(): net_id = sconf.get("NETWORK_ID") or sconf.get("network_id") or "" host = sconf.get("HOST") or sconf.get("host") or "" port = sconf.get("PORT") or sconf.get("port") or 999 svc = sconf.get("SERVICE") or sconf.get("service") or "remote" status = "probing..." detail = "" if not host: status = "MISCONFIGURED" detail = "No host IP" else: try: from API.HTTP import HTTPClient with HTTPClient(timeout=2.0) as client: # Paths depend on service type path = "/api_version" if svc == "hydrus" else "/health" resp = client.get(f"http://{host}:{port}{path}") if resp.status_code == 200: status = "ONLINE" if svc == "remote": p = resp.json() detail = f"Remote store: {p.get('name', 'unknown')}" else: detail = "Hydrus API" else: status = f"HTTP {resp.status_code}" except Exception as exc: status = "OFFLINE" detail = str(exc) row = table.add_row() row.add_column("TYPE", "REMOTE") row.add_column("NAME", name) row.add_column("ID", net_id) row.add_column("ADDRESS", f"{host}:{port}") row.add_column("STATUS", status) row.add_column("DETAIL", detail) # 4. Networking Networks (Raw ZT status) try: nets = zt.list_networks() if not nets: row = table.add_row() row.add_column("TYPE", "INFO") row.add_column("NAME", "ZeroTier CLI") row.add_column("STATUS", "No networks found or CLI error") row.add_column("DETAIL", f"CLI Path: {zt._get_cli_path() or 'Not found'}") for n in nets: row = table.add_row() row.add_column("TYPE", "NETWORK") row.add_column("NAME", n.name) row.add_column("ID", n.id) row.add_column("ADDRESS", ", ".join(n.assigned_addresses)) row.add_column("STATUS", n.status) row.add_column("DETAIL", "") except Exception as exc: row = table.add_row() row.add_column("TYPE", "ERROR") row.add_column("NAME", "ZeroTier CLI") row.add_column("STATUS", "EXCEPTION") row.add_column("DETAIL", str(exc)) return table CMDLET = Cmdlet( name=".zerotier", summary="Check ZeroTier hosting and connection status", usage=".zerotier", exec=exec )