diff --git a/NODES.md b/NODES.md index 36784a1..820b3d0 100644 --- a/NODES.md +++ b/NODES.md @@ -11,3 +11,4 @@ Unified naming: node == agent == profile == API account. - 2026-10-03: Renamed smoke->muse, phone-test->pip for unified naming. Profiles preserved, sessions persisted (muse). WireGuard identities renamed. | 646 | warp-646 | 104.28.195.181 | 9430 | active | 646 (phone_otp, first Meta Account option) | +| opm | warp-opm | 104.28.195.181 | 9440 | active | opm (yourfriendnico@proton.me, email_otp, Nico Parada) | diff --git a/bin/agent-health.sh b/bin/agent-health.sh index 97d74a4..bc33ab6 100755 --- a/bin/agent-health.sh +++ b/bin/agent-health.sh @@ -49,28 +49,27 @@ restart_browser() { # Main echo "=== Health check $(date -Iseconds) ===" >> "$LOG" -# muse (node muse, CDP 9410) -if ! check_agent "muse" "muse" "9410"; then - restart_browser "muse" "9410" - sleep 5 - if ! check_agent "muse" "muse" "9410"; then - echo "$(date -Iseconds) muse: CRITICAL - still down after restart" >> "$LOG" - # TODO: Alert operator (e.g., via board post or email) - else - echo "$(date -Iseconds) muse: RECOVERED after restart" >> "$LOG" +check_one() { + local agent=$1 + local cdp_port=$2 + # node == agent == profile (unified naming) + if ! check_agent "$agent" "$agent" "$cdp_port"; then + restart_browser "$agent" "$cdp_port" + sleep 5 + if ! check_agent "$agent" "$agent" "$cdp_port"; then + echo "$(date -Iseconds) $agent: CRITICAL - still down after restart" >> "$LOG" + # TODO: Alert operator (e.g., via board post or email) + else + echo "$(date -Iseconds) $agent: RECOVERED after restart" >> "$LOG" + fi fi -fi +} -# pip (node pip, CDP 9420) -if ! check_agent "pip" "pip" "9420"; then - restart_browser "pip" "9420" - sleep 5 - if ! check_agent "pip" "pip" "9420"; then - echo "$(date -Iseconds) pip: CRITICAL - still down after restart" >> "$LOG" - else - echo "$(date -Iseconds) pip: RECOVERED after restart" >> "$LOG" - fi -fi +# Every active node in the NODES.md registry gets checked — new nodes +# propagate automatically, no per-node blocks to add. +"$NETVM_BIN/netvm-registry.py" 2>/dev/null | while IFS=: read -r node port; do + [ -n "$node" ] && [ -n "$port" ] && check_one "$node" "$port" +done # Trim log (keep last 1000 lines) tail -1000 "$LOG" > "$LOG.tmp" && mv "$LOG.tmp" "$LOG" diff --git a/bin/muse-chat-api.py b/bin/muse-chat-api.py index bb8f33c..00c62c5 100755 --- a/bin/muse-chat-api.py +++ b/bin/muse-chat-api.py @@ -16,14 +16,24 @@ Usage: muse-chat-api.py --account wait [timeout] muse-chat-api.py --account approvals # check pending approvals """ -import json, urllib.request, websocket, time, sys, argparse +import json, urllib.request, websocket, time, sys, argparse, importlib.util -ACCOUNTS = { - "muse": ("muse", "http://127.0.0.1:9410/json/list"), - "pip": ("pip", "http://127.0.0.1:9420/json/list"), - "646": ("646", "http://127.0.0.1:9430/json/list"), - "opm": ("opm", "http://127.0.0.1:9440/json/list"), -} + +def _load_accounts(): + """Build the accounts table from the NODES.md registry — new nodes + propagate automatically instead of being hardcoded here.""" + path = "/home/super/Projects/NetVM/bin/netvm-registry.py" + spec = importlib.util.spec_from_file_location("netvm_registry", path) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + accounts = {} + for node, rec in mod.load().items(): + accounts[node] = (node, "http://127.0.0.1:%d/json/list" % + rec["cdp_port"]) + return accounts + + +ACCOUNTS = _load_accounts() # IPs we trust for auto-approval (our infrastructure) TRUSTED_IPS = { diff --git a/bin/muse-signin.py b/bin/muse-signin.py index c92678c..8b28d8c 100755 --- a/bin/muse-signin.py +++ b/bin/muse-signin.py @@ -16,12 +16,25 @@ The operator then obtains the OTP (via chat with user) and re-runs: This keeps credentials out of the automation — the OTP is provided transiently via the operator, never stored. """ -import json, urllib.request, websocket, time, sys, argparse +import json, urllib.request, websocket, time, sys, argparse, importlib.util -CDP_URL = "http://127.0.0.1:9410/json/list" +CDP_PORT_DEFAULT = 9410 -def get_page(): - with urllib.request.urlopen(CDP_URL, timeout=5) as r: + +def _registry_port(node): + try: + path = "/home/super/Projects/NetVM/bin/netvm-registry.py" + spec = importlib.util.spec_from_file_location("netvm_registry", path) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod.port_for(node) + except Exception: + return None + + +def get_page(port): + cdp_url = "http://127.0.0.1:%s/json/list" % port + with urllib.request.urlopen(cdp_url, timeout=5) as r: ts = json.load(r) pages = [t for t in ts if t.get('type') == 'page'] if not pages: @@ -41,9 +54,20 @@ def main(): p = argparse.ArgumentParser() p.add_argument('--email', required=True) p.add_argument('--otp', default=None) + p.add_argument('--node', default=None, + help='node name: CDP port comes from the NODES.md registry') + p.add_argument('--cdp-port', default=None, + help='explicit CDP port (overrides --node lookup)') args = p.parse_args() - page = get_page() + if args.cdp_port: + port = args.cdp_port + elif args.node: + port = _registry_port(args.node) or CDP_PORT_DEFAULT + else: + port = CDP_PORT_DEFAULT + + page = get_page(port) ws = websocket.create_connection(page['webSocketDebuggerUrl'], timeout=15) # Step 1: Check if already logged in diff --git a/bin/netvm-registry.py b/bin/netvm-registry.py new file mode 100755 index 0000000..86736ac --- /dev/null +++ b/bin/netvm-registry.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 +""" +netvm-registry.py — single source of truth for node -> CDP port mapping. + +Parses NODES.md (the fleet registry). Every script that needs a node's +CDP port reads it from here instead of hardcoding — new nodes propagate +automatically. + +Usage: + from netvm_registry import port_for, load + port_for("muse") # -> 9410 (int), None if unknown + +CLI: + netvm-registry.py # prints "node:port" lines for active nodes + netvm-registry.py # prints just the port (for bash) +""" +import os +import re +import sys + +REGISTRY = os.path.join(os.path.dirname(os.path.dirname( + os.path.abspath(__file__))), "NODES.md") + + +def load(registry_path=None): + """Return {node: {netns, egress_ip, cdp_port, status, agent}}.""" + path = registry_path or REGISTRY + nodes = {} + try: + with open(path) as f: + for line in f: + line = line.rstrip("\n") + if not line.startswith("|"): + continue + cells = [c.strip() for c in line.strip("|").split("|")] + if len(cells) < 6: + continue + node, netns, egress, port, status, agent = cells[:6] + if node in ("node", "") or not re.fullmatch(r"[a-z0-9-]+", + node): + continue + if node.startswith("-"): + continue + try: + port_n = int(port) + except ValueError: + continue + nodes[node] = {"netns": netns, "egress_ip": egress, + "cdp_port": port_n, "status": status, + "agent": agent} + except FileNotFoundError: + pass + return nodes + + +def port_for(node, registry_path=None): + """CDP port for a node, or None if the node isn't registered.""" + rec = load(registry_path).get(node) + return rec["cdp_port"] if rec else None + + +def active_nodes(registry_path=None): + """{node: port} for nodes with status == active.""" + return {n: r["cdp_port"] for n, r in load(registry_path).items() + if r["status"] == "active"} + + +if __name__ == "__main__": + if len(sys.argv) == 2: + port = port_for(sys.argv[1]) + if port is None: + print("unknown node: %s" % sys.argv[1], file=sys.stderr) + sys.exit(1) + print(port) + else: + for node, port in sorted(active_nodes().items()): + print("%s:%d" % (node, port)) diff --git a/bin/onboard-driver.py b/bin/onboard-driver.py index ec869a4..7df9b76 100755 --- a/bin/onboard-driver.py +++ b/bin/onboard-driver.py @@ -16,13 +16,21 @@ VM->bl SSH stdin pipe. Part of the cred onboarding module (front-door repo, docs/CRED-MODULE.md). """ import argparse +import importlib.util import json import subprocess import sys import urllib.request SIGNIN = "/home/super/Projects/NetVM/bin/muse-signin.py" -CDP_PORTS = {"muse": "9410", "pip": "9420"} + + +def _load_registry(): + path = "/home/super/Projects/NetVM/bin/netvm-registry.py" + spec = importlib.util.spec_from_file_location("netvm_registry", path) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod def cdp_ok(port): @@ -51,9 +59,10 @@ def main(): print("ERROR: unsupported service/id_type " "(muse+email only for now)", file=sys.stderr) return 1 - port = CDP_PORTS.get(args.node) + port = _load_registry().port_for(args.node) if not port: - print("ERROR: unknown node", file=sys.stderr) + print("ERROR: unknown node '%s' (not in NODES.md registry)" + % args.node, file=sys.stderr) return 1 if args.dry_run: @@ -69,7 +78,7 @@ def main(): print("ERROR: no identifier on stdin", file=sys.stderr) return 1 - cmd = [sys.executable, SIGNIN, "--email", identifier] + cmd = [sys.executable, SIGNIN, "--node", args.node, "--email", identifier] if args.step == "submit": if not code: print("ERROR: no code on stdin", file=sys.stderr)