import sys import requests from pathlib import Path from typing import Any, Dict, Sequence # Add project root to sys.path root = Path(__file__).resolve().parent.parent if str(root) not in sys.path: sys.path.insert(0, str(root)) from cmdlet._shared import Cmdlet from SYS.config import load_config from SYS.result_table import Table from API import zerotier as zt def exec_zerotier(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int: # Use provided config or fall back to CWD load cfg = config if config else load_config(Path.cwd()) table = Table("ZeroTier Status") # 1. Local Hub Status row = table.add_row() row.add_column("TYPE", "HOST") row.add_column("NAME", "localhost") # Try to get node ID via CLI info node_id = "???" try: if hasattr(zt, "_run_cli_json"): info = zt._run_cli_json("info", "-j") node_id = info.get("address", "???") except: pass row.add_column("ID", node_id) # Check if local server is responsive try: # endpoint is /health for remote_storage_server # We try 127.0.0.1 first with a more generous timeout # Using a list of potential local hits to be robust against Windows networking quirks import socket def get_local_ip(): try: s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.connect(("8.8.8.8", 80)) ip = s.getsockname()[0] s.close() return ip except: return None hosts = ["127.0.0.1", "localhost"] local_ip = get_local_ip() if local_ip: hosts.append(local_ip) success = False last_err = "" # Try multiple times if server just started import time for attempt in range(3): for host in hosts: try: resp = requests.get(f"http://{host}:999/health", timeout=3, proxies={"http": None, "https": None}) if resp.status_code == 200: row.add_column("STATUS", "ONLINE") row.add_column("ADDRESS", f"{host}:999") row.add_column("DETAIL", f"Serving {cfg.get('active_store', 'default')}") success = True break elif resp.status_code == 401: row.add_column("STATUS", "Serving (Locked)") row.add_column("ADDRESS", f"{host}:999") row.add_column("DETAIL", "401 Unauthorized - API Key required") success = True break except Exception as e: last_err = str(e) continue if success: break time.sleep(1) # Wait between attempts if not success: row.add_column("STATUS", "OFFLINE") row.add_column("ADDRESS", "127.0.0.1:999") row.add_column("DETAIL", f"Server not responding on port 999. Last attempt ({hosts[-1]}): {last_err}") except Exception as e: row.add_column("STATUS", "OFFLINE") row.add_column("ADDRESS", "127.0.0.1:999") row.add_column("DETAIL", f"Status check failed: {e}") # 2. Add Networks if zt.is_available(): try: networks = zt.list_networks() for net in networks: row = table.add_row() row.add_column("TYPE", "NETWORK") row.add_column("NAME", getattr(net, "name", "Unnamed")) row.add_column("ID", getattr(net, "id", "")) status = getattr(net, "status", "OK") assigned = getattr(net, "assigned_addresses", []) ip_str = assigned[0] if assigned else "" row.add_column("STATUS", status) row.add_column("ADDRESS", ip_str) except Exception as e: row = table.add_row() row.add_column("TYPE", "ERROR") row.add_column("DETAIL", f"Failed to list networks: {e}") else: row = table.add_row() row.add_column("TYPE", "SYSTEM") row.add_column("NAME", "ZeroTier") row.add_column("STATUS", "NOT FOUND") row.add_column("DETAIL", "zerotier-cli not in path") # Output try: from cmdnat.out_table import TableOutput TableOutput().render(table) except Exception: # Fallback for raw CLI print(f"\n--- {table.title} ---") for r in table.rows: # Use the get_column method from ResultRow t = r.get_column("TYPE") or "" n = r.get_column("NAME") or "" s = r.get_column("STATUS") or "" a = r.get_column("ADDRESS") or "" id = r.get_column("ID") or "" d = r.get_column("DETAIL") or "" print(f"[{t:7}] {n:15} | {s:15} | {a:20} | {id} | {d}") print("-" * 100) return 0 CMDLET = Cmdlet( name=".zerotier", summary="Check ZeroTier node and hosting status", usage=".zerotier", exec=exec_zerotier, ) if __name__ == "__main__": exec_zerotier(None, sys.argv[1:], {})