#!/usr/bin/env python3 """Lade die staged L3-Cutover-Config auf crs326-core (noch NICHT enablen). Creds aus ~/.claude.json. Alle operativen Objekte in der rsc sind disabled=yes, also bleibt der aktive Datenpfad unberührt. 🔴 PASSWORT NIE UEBER argv. `sshpass -p ` schreibt es in die Kommandozeile, wo es in `ps` und jedem argv-Trace steht -- genau so ist am 2026-08-22 das Core-Passwort in ein Sitzungstranskript geraten und musste rotiert werden. `sshpass -e` liest es aus der Umgebung des Kindprozesses. Umgestellt 2026-08-31. ⚠️ CORE_IP 10.40.41.211 ist TOT (VLAN41 abgeschafft 2026-08-27); der Core ist heute 10.40.99.1. Und die gelesene RSC ist gitignored -- dieses Skript ist ein TP15-Artefakt vom Cutover 2026-08-08. Bewusst nicht stillschweigend geaendert, s. Handoff 2026-08-31. """ import json import os import pathlib import subprocess import sys CORE_IP = "10.40.41.211" RSC = pathlib.Path(__file__).with_name("tp15-core-cutover-intended.rsc") CLAUDE_JSON = pathlib.Path.home() / ".claude.json" def load_core_creds(): cfg = json.loads(CLAUDE_JSON.read_text()) env = cfg.get("mcpServers", {}).get("mikromcp", {}).get("env", {}) user = env.get("CRS326_CORE_USER") or env.get("ROUTER_USER") pw = env.get("CRS326_CORE_PASS") or env.get("ROUTER_PASS") if not user or not pw: raise SystemExit("CRS326_CORE_USER/PASS nicht in ~/.claude.json mikromcp.env gefunden") return user, pw # Das Passwort geht ausschliesslich ueber die Umgebung an sshpass -- nie als Argument. def ssh_env(pw): return dict(os.environ, SSHPASS=pw) def ssh_base(user): return [ "sshpass", "-e", "ssh", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", "-o", "PubkeyAuthentication=no", f"{user}@{CORE_IP}", ] # 🔴 pw ist Parameter, nicht Global: die alte Fassung griff hier auf ein `pw` zu, das # in dieser Funktion gar nicht existierte -- jeder Fehlerfall endete in einem # NameError statt in der gedachten Meldung. Mitgefixt 2026-08-31. def run_silent(base, cmd, pw): r = subprocess.run(base + [cmd], capture_output=True, text=True, env=ssh_env(pw)) if r.returncode != 0: out = (r.stdout + r.stderr).replace(pw, "***") raise SystemExit(f"Befehl fehlgeschlagen (rc={r.returncode}):\n{out}") return r def main(): if not RSC.exists(): raise SystemExit(f"{RSC} fehlt") user, pw = load_core_creds() base = ssh_base(user) print(f"crs326-core L3-Cutover-Config laden ({RSC.name}) ...") # 1) Datei auf Router kopieren r = subprocess.run( ["sshpass", "-e", "scp", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", "-o", "PubkeyAuthentication=no", str(RSC), f"{user}@{CORE_IP}:{RSC.name}"], capture_output=True, text=True, env=ssh_env(pw), ) if r.returncode != 0: raise SystemExit(f"SCP fehlgeschlagen: {r.stderr.replace(pw, '***')}") # 2) Import auslösen run_silent(base, f'/import file-name="{RSC.name}"', pw) print(" Import abgeschlossen (staged / disabled).") print(" Nächster Schritt: L2-Portplan + Cutover-Enable.") if __name__ == "__main__": main()