209 lines
10 KiB
Python
209 lines
10 KiB
Python
"""Command-line entry: argv dispatch, --ssh, fiber-map SSH forwarding."""
|
||
|
||
import json
|
||
import os
|
||
import sys
|
||
|
||
from hubmgr.acroname import AcronameManager
|
||
from hubmgr.brainstem_loader import load_brainstem
|
||
from hubmgr.ssh_dispatch import dispatch_fiber_mapped_ssh_if_needed
|
||
from hubmgr import remote_ssh as rs
|
||
from hubmgr import usb_probe as usb
|
||
|
||
|
||
def main() -> int:
|
||
argv = sys.argv[1:]
|
||
if len(argv) >= 2 and argv[0] in ("--ssh", "--remote"):
|
||
remote_host = argv[1]
|
||
rest = argv[2:]
|
||
if not rest:
|
||
print(
|
||
"Usage: hub_manager.py --ssh user@host <command> [args...]\n"
|
||
" Example: hub_manager.py --ssh pi@192.168.1.39 discover\n"
|
||
" If brainstem is in a Pi venv: copy remote_ssh.env.example → remote_ssh.env next to\n"
|
||
" this script on the PC where you run --ssh (paths in the file are on the Pi).\n"
|
||
" Or export HUB_MANAGER_REMOTE_PYTHON / HUB_MANAGER_REMOTE_SCRIPT.\n"
|
||
" On the Pi: pip install -r requirements.txt in that venv; udev 24ff.",
|
||
file=sys.stderr,
|
||
flush=True,
|
||
)
|
||
return 2
|
||
return rs.ssh_forward(remote_host, rest)
|
||
|
||
rc_ssh_map = dispatch_fiber_mapped_ssh_if_needed(argv)
|
||
if rc_ssh_map is not None:
|
||
return rc_ssh_map
|
||
|
||
os.write(2, b"hub_manager: start\n")
|
||
try:
|
||
load_brainstem()
|
||
except Exception as exc:
|
||
print(f"hub_manager: failed to import brainstem: {exc}", file=sys.stderr, flush=True)
|
||
if isinstance(exc, ImportError):
|
||
print(
|
||
" If this text came from `hub_manager.py --ssh …`: the remote used system python3 by default.\n"
|
||
" On your PC export HUB_MANAGER_REMOTE_PYTHON to the Pi venv’s python and\n"
|
||
" HUB_MANAGER_REMOTE_SCRIPT to that hub_manager.py (absolute paths on the Pi).",
|
||
file=sys.stderr,
|
||
flush=True,
|
||
)
|
||
return 1
|
||
mgr = AcronameManager()
|
||
try:
|
||
cmd = sys.argv[1].lower() if len(sys.argv) > 1 else "status"
|
||
target = sys.argv[2] if len(sys.argv) > 2 else "all"
|
||
if cmd == "status":
|
||
mgr.status(target)
|
||
elif cmd == "calibrate-ports-json":
|
||
if not mgr.hubs and not mgr.connect():
|
||
print("[]", flush=True)
|
||
else:
|
||
pairs = mgr._ordered_downstream_ports()
|
||
print(json.dumps([[h, p] for h, p in pairs]), flush=True)
|
||
elif cmd == "lsusb-lines-json":
|
||
print(json.dumps(usb.lsusb_lines()), flush=True)
|
||
elif cmd == "discover":
|
||
mgr.discover()
|
||
elif cmd == "power":
|
||
if len(sys.argv) < 5 or sys.argv[2].lower() != "fiber-port":
|
||
print(
|
||
"Usage: hub_manager.py power fiber-port <fiber_port_id> on|off\n"
|
||
" Uses fiber_map.json; per-entry ssh / host+user forwards to that host (see help).",
|
||
file=sys.stderr,
|
||
flush=True,
|
||
)
|
||
return 2
|
||
try:
|
||
fp_n = int(sys.argv[3])
|
||
except ValueError:
|
||
print("fiber_port_id must be an integer.", file=sys.stderr, flush=True)
|
||
return 2
|
||
mode = sys.argv[4].lower()
|
||
if mode not in ("on", "off"):
|
||
print("Last argument must be on or off.", file=sys.stderr, flush=True)
|
||
return 2
|
||
mgr.fiber_power(mode, fp_n)
|
||
elif cmd == "fiber":
|
||
if len(sys.argv) < 3:
|
||
print(
|
||
"Usage: hub_manager.py fiber status\n"
|
||
" hub_manager.py fiber chip <fiber_port_id> [save]\n"
|
||
" status — hub.port, Route, power, and saved chip preview from fiber_map.json\n"
|
||
" chip — lsusb diff on that USB port; add save to store usb_id / chip_type in the map",
|
||
file=sys.stderr,
|
||
flush=True,
|
||
)
|
||
return 2
|
||
sub = sys.argv[2].lower()
|
||
if sub == "status":
|
||
mgr.fiber_map_status()
|
||
elif sub == "chip":
|
||
if len(sys.argv) < 4:
|
||
print(
|
||
"Usage: hub_manager.py fiber chip <fiber_port_id> [save]",
|
||
file=sys.stderr,
|
||
flush=True,
|
||
)
|
||
return 2
|
||
try:
|
||
chip_fp = int(sys.argv[3])
|
||
except ValueError:
|
||
print("fiber_port_id must be an integer.", file=sys.stderr, flush=True)
|
||
return 2
|
||
save_chip = len(sys.argv) >= 5 and sys.argv[4].lower() == "save"
|
||
mgr.fiber_chip(chip_fp, save=save_chip)
|
||
else:
|
||
print(f"Unknown fiber subcommand: {sub!r}", file=sys.stderr, flush=True)
|
||
return 2
|
||
elif cmd == "panel":
|
||
if len(sys.argv) < 3:
|
||
print(
|
||
"Usage: hub_manager.py panel status\n"
|
||
" hub_manager.py panel on|off <panel_port>\n"
|
||
" hub_manager.py panel reboot|reboot-force <panel_port>\n"
|
||
" hub_manager.py panel calibrate [merge] [<N>] [--ssh user@host] …\n"
|
||
" calibrate: local hub ports first, then each --ssh host, calibrate_remotes in JSON, and/or\n"
|
||
" HUB_MANAGER_CALIBRATE_REMOTES in remote_ssh.env (comma-separated) for one-command hybrid.\n"
|
||
" merge / N as before; remote steps set \"ssh\" on new fiber_ports entries.\n"
|
||
" <panel_port> is 1–24; use power fiber-port for arbitrary ids.\n"
|
||
" Preset: fiber_map.rpi20.json → fiber_map.json for 8+8+4 → fiber ports 1–20.",
|
||
file=sys.stderr,
|
||
flush=True,
|
||
)
|
||
return 2
|
||
sub = sys.argv[2].lower()
|
||
if sub == "status":
|
||
mgr.panel_status()
|
||
elif sub == "calibrate":
|
||
args = sys.argv[3:]
|
||
merge, limit, cal_hosts = rs.parse_panel_calibrate_argv(args)
|
||
mgr.panel_calibrate(merge=merge, limit=limit, calibrate_ssh_hosts=cal_hosts)
|
||
elif sub in ("on", "off"):
|
||
if len(sys.argv) < 4:
|
||
print(f"Usage: hub_manager.py panel {sub} <1-24>", file=sys.stderr, flush=True)
|
||
return 2
|
||
mgr.panel_power(sub, int(sys.argv[3]))
|
||
elif sub == "reboot":
|
||
if len(sys.argv) < 4:
|
||
print("Usage: hub_manager.py panel reboot <1-24>", file=sys.stderr, flush=True)
|
||
return 2
|
||
mgr.panel_reboot(int(sys.argv[3]), skip_empty=True)
|
||
elif sub == "reboot-force":
|
||
if len(sys.argv) < 4:
|
||
print("Usage: hub_manager.py panel reboot-force <1-24>", file=sys.stderr, flush=True)
|
||
return 2
|
||
mgr.panel_reboot(int(sys.argv[3]), skip_empty=False)
|
||
else:
|
||
print(f"Unknown panel subcommand: {sub!r}", file=sys.stderr, flush=True)
|
||
return 2
|
||
elif cmd in ("on", "off"):
|
||
if not mgr.power(cmd, target):
|
||
return 1
|
||
elif cmd in ("reboot", "reboot-force"):
|
||
mgr.reboot(target, skip_empty=(cmd == "reboot"))
|
||
elif cmd == "setup":
|
||
mgr.setup_udev()
|
||
elif cmd == "verify":
|
||
mgr.verify()
|
||
elif cmd in ("help", "-h", "--help"):
|
||
print(
|
||
"Usage: hub_manager.py <command> [target]\n"
|
||
" discover — list hubs (serial, port count); no port I/O\n"
|
||
" status [target] — default command; target like all, 1.3, all.2\n"
|
||
" fiber status — fiber_ports + power (local or per-entry ssh / host+user)\n"
|
||
" fiber chip <id> [save] — lsusb probe; save stores usb_id / chip_type in fiber_map.json\n"
|
||
" power fiber-port <id> on|off — power by fiber key (ssh forward if map says so)\n"
|
||
" panel status — rack positions 1–24 (fiber ids 1–24 in fiber_map.json)\n"
|
||
" panel calibrate [merge] [N] [--ssh user@host]… — hybrid local + ssh hubs → one fiber_map.json\n"
|
||
" panel on|off|reboot|reboot-force <n>\n"
|
||
" on|off [target] reboot|reboot-force [target] setup verify\n"
|
||
"\n"
|
||
"Remote (hubs on another host — no local brainstem needed):\n"
|
||
" hub_manager.py --ssh user@host discover\n"
|
||
" remote_ssh.env next to hub_manager.py (see remote_ssh.env.example) or env vars:\n"
|
||
" HUB_MANAGER_REMOTE_PYTHON remote interpreter (default python3)\n"
|
||
" HUB_MANAGER_REMOTE_SCRIPT remote script path (default /usr/local/bin/hub_manager.py)\n"
|
||
" HUB_MANAGER_SSH_OPTS e.g. '-o BatchMode=yes'\n"
|
||
" HUB_MANAGER_CALIBRATE_REMOTES optional comma-separated user@host for panel calibrate (no --ssh needed)\n"
|
||
" Pi: pip install -r requirements.txt in the venv you point REMOTE_PYTHON at; udev 24ff.\n"
|
||
"\n"
|
||
"fiber_map.json fiber_ports entries may set ssh routing (hubs on another machine):\n"
|
||
' "ssh": "user@host" or "remote": "…" or "host": "ip", "user": "pi"\n'
|
||
" On the SSH destination, the same fiber id should be local (omit ssh) so commands are not re-forwarded.\n"
|
||
' Optional "pcie": { bus, switch, slot, adapter_port, sfp_serial, board_serial, … } — calibrate can fill via 1–6+SFP.\n'
|
||
"\n"
|
||
"Hybrid calibrate: put {\"calibrate_remotes\": [\"pi@ip\"]} in fiber_map.json or pass --ssh per host;\n"
|
||
" order is all local downstream ports, then each remote’s ports (see calibrate-ports-json on the Pi)."
|
||
)
|
||
else:
|
||
print(f"Unknown command: {cmd!r}", file=sys.stderr, flush=True)
|
||
print(
|
||
"Try: --ssh user@host … | discover | calibrate-ports-json | status | fiber | power | panel | … | help",
|
||
file=sys.stderr,
|
||
flush=True,
|
||
)
|
||
return 2
|
||
finally:
|
||
mgr.disconnect()
|
||
return 0
|