propagation: single registry drives all node references
New bin/netvm-registry.py parses NODES.md (node -> cdp_port); every consumer reads from it instead of hardcoding: - onboard-driver.py: CDP_PORTS dict -> registry lookup (new nodes work) - muse-signin.py: hardcoded 9410 -> --node/--cdp-port args - muse-chat-api.py: hardcoded ACCOUNTS -> registry-built - agent-health.sh: hardcoded muse/pip blocks -> loop over all active nodes (646 and opm now get health coverage too) NODES.md: record opm node (9440).
This commit is contained in:
+19
-20
@@ -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"
|
||||
|
||||
+17
-7
@@ -16,14 +16,24 @@ Usage:
|
||||
muse-chat-api.py --account <agent> wait [timeout]
|
||||
muse-chat-api.py --account <agent> 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 = {
|
||||
|
||||
+29
-5
@@ -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
|
||||
|
||||
Executable
+77
@@ -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 <node> # 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))
|
||||
+13
-4
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user