#!/usr/bin/env python3 """Erstelle ein Binary-Backup + einen Text-Export von crs326-core vor dem TP15-Cutover. Creds aus ~/.claude.json (mikromcp-env, CRS326_CORE_*). Backup-Dateien landen unter routeros/. 🔴 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. Bewusst nicht stillschweigend geaendert, s. Handoff 2026-08-31. """ import json import os import pathlib import subprocess import sys from datetime import datetime, timezone CORE_IP = "10.40.41.211" # VLAN1-Temp; alternativ 10.40.99.11 sobald 99T auf Uplink CLAUDE_JSON = pathlib.Path.home() / ".claude.json" OUT_DIR = pathlib.Path(__file__).resolve().parent 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_cmd(user, remote_command): return [ "sshpass", "-e", "ssh", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", "-o", "PubkeyAuthentication=no", f"{user}@{CORE_IP}", remote_command, ] def scp_cmd(user, remote_file, local_file): return [ "sshpass", "-e", "scp", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", "-o", "PubkeyAuthentication=no", f"{user}@{CORE_IP}:{remote_file}", str(local_file), ] def main(): user, pw = load_core_creds() ts = datetime.now(timezone.utc).strftime("%Y%m%d-%H%M%S") backup_name = f"crs326-core-pre-cutover-{ts}" print(f"crs326-core Backup via SSH {user}@{CORE_IP}") create_cmd = ssh_cmd( user, f'/system backup save name="{backup_name}" dont-encrypt=yes; ' f'/export file="{backup_name}"' ) print(f" Erzeuge Backup/Export auf crs326-core ...") subprocess.run(create_cmd, check=True, env=ssh_env(pw)) local_backup = OUT_DIR / f"{backup_name}.backup" local_export = OUT_DIR / f"{backup_name}.rsc" print(f" Lade {backup_name}.backup herunter ...") subprocess.run(scp_cmd(user, f"{backup_name}.backup", local_backup), check=True, env=ssh_env(pw)) print(f" Lade {backup_name}.rsc herunter ...") subprocess.run(scp_cmd(user, f"{backup_name}.rsc", local_export), check=True, env=ssh_env(pw)) backup_size = local_backup.stat().st_size export_size = local_export.stat().st_size print(f" OK: {local_backup.name} ({backup_size} Bytes)") print(f" OK: {local_export.name} ({export_size} Bytes)") if backup_size < 1000: print(" WARN: Backup-Datei auffällig klein!") sys.exit(1) if __name__ == "__main__": main()