From 582f1f045b95448fe8c3645c7c49f83dafe0e103 Mon Sep 17 00:00:00 2001 From: grandir66 Date: Sun, 6 Sep 2026 16:58:08 +0200 Subject: [PATCH] wip: salvataggio pre-trasloco dello stato non committato (2026-09-06) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fotografia del lavoro in corso prima del riordino del parco, così il trasloco non deve indovinare cosa era lavoro buono. Nessun segreto nei file aggiunti (scansione valori-credenziale eseguita prima del commit). Co-Authored-By: Claude Fable 5 --- comandi.txt | 19 +++ docs/scheda-mappa.md | 10 ++ setup_and_run.sh | 59 ++++++++ ssh_script.py | 320 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 408 insertions(+) create mode 100644 comandi.txt create mode 100644 docs/scheda-mappa.md create mode 100755 setup_and_run.sh create mode 100644 ssh_script.py diff --git a/comandi.txt b/comandi.txt new file mode 100644 index 0000000..10e6778 --- /dev/null +++ b/comandi.txt @@ -0,0 +1,19 @@ +monitor session 1 destination interface 0/3 +monitor session 1 source interface 0/4 +monitor session 1 source interface 0/8 +monitor session 1 source interface 0/9 +monitor session 1 source interface 0/17 +monitor session 1 source interface 0/18 +monitor session 1 source interface 0/19 +monitor session 1 source interface 0/20 +monitor session 1 source interface 0/21 +monitor session 1 source interface 0/22 +monitor session 1 source interface 0/23 +monitor session 1 source interface 0/24 +monitor session 1 source interface 0/26 +monitor session 1 source interface 3/1 +monitor session 1 source interface 3/2 +monitor session 1 source interface 3/3 +monitor session 1 source interface 3/4 +monitor session 1 mode +exit \ No newline at end of file diff --git a/docs/scheda-mappa.md b/docs/scheda-mappa.md new file mode 100644 index 0000000..8758670 --- /dev/null +++ b/docs/scheda-mappa.md @@ -0,0 +1,10 @@ +--- +badge: Utile +tono: ok +famiglia: 07-strumenti +ordine: 100 +stato: manutenzione +--- +Mirroring multiplo delle porte UniFi: configurazione non prevista di default dal controller. + +nota: Decisione del: resta, utilissimo anche se non sviluppato. diff --git a/setup_and_run.sh b/setup_and_run.sh new file mode 100755 index 0000000..20fc251 --- /dev/null +++ b/setup_and_run.sh @@ -0,0 +1,59 @@ +#!/bin/bash +# setup_and_run.sh - Setup automatico ambiente virtuale e esecuzione script SSH + +set -e # Esce se c'è un errore + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +VENV_DIR="$SCRIPT_DIR/ssh_env" +PYTHON_SCRIPT="$SCRIPT_DIR/ssh_script.py" + +echo "🚀 SSH Command Executor - Setup Automatico" +echo "===========================================" + +# Verifica se Python 3 è disponibile +if ! command -v python3 &> /dev/null; then + echo "❌ Python 3 non trovato. Installa con: brew install python" + exit 1 +fi + +echo "✓ Python 3 trovato: $(python3 --version)" + +# Crea virtual environment se non esiste +if [ ! -d "$VENV_DIR" ]; then + echo "📦 Creazione virtual environment..." + python3 -m venv "$VENV_DIR" + echo "✓ Virtual environment creato in: $VENV_DIR" +else + echo "✓ Virtual environment già esistente" +fi + +# Attiva virtual environment +echo "🔧 Attivazione virtual environment..." +source "$VENV_DIR/bin/activate" + +# Aggiorna pip e installa paramiko +echo "📚 Installazione dipendenze..." +pip install --upgrade pip +pip install paramiko + +echo "✓ Paramiko installato con successo" + +# Verifica se lo script Python esiste +if [ ! -f "$PYTHON_SCRIPT" ]; then + echo "❌ Script Python non trovato: $PYTHON_SCRIPT" + echo "Assicurati che il file ssh_script.py sia nella stessa cartella" + exit 1 +fi + +echo "" +echo "🎯 Esecuzione script SSH..." +echo "==========================" + +# Esegui lo script Python +python "$PYTHON_SCRIPT" + +echo "" +echo "✨ Fatto! Per eseguzioni future:" +echo " 1. source ssh_env/bin/activate" +echo " 2. python ssh_script.py" +echo " OPPURE rilancia questo script: ./setup_and_run.sh" diff --git a/ssh_script.py b/ssh_script.py new file mode 100644 index 0000000..e699596 --- /dev/null +++ b/ssh_script.py @@ -0,0 +1,320 @@ +#!/usr/bin/env python3 +""" +Script automatico per connessione SSH e invio comandi sequenziali +Requisiti: pip install paramiko +""" + +import paramiko +import time +import os +import sys +import json +import base64 +import stat +from pathlib import Path +from cryptography.fernet import Fernet +from getpass import getpass + +class SSHCommandExecutor: + def __init__(self): + self.script_dir = Path(__file__).parent + self.config_file = self.script_dir / "config.json" + self.commands_file = self.script_dir / "comandi.txt" + self.key_file = self.script_dir / ".ssh_key" + + def generate_key(self): + """Genera e salva una chiave di crittografia""" + key = Fernet.generate_key() + with open(self.key_file, 'wb') as f: + f.write(key) + # Rende il file leggibile solo dal proprietario + os.chmod(self.key_file, stat.S_IRUSR | stat.S_IWUSR) + return key + + def load_key(self): + """Carica la chiave di crittografia""" + try: + with open(self.key_file, 'rb') as f: + return f.read() + except FileNotFoundError: + return self.generate_key() + + def encrypt_password(self, password): + """Crittografa una password""" + key = self.load_key() + f = Fernet(key) + encrypted_password = f.encrypt(password.encode()) + return base64.b64encode(encrypted_password).decode() + + def decrypt_password(self, encrypted_password): + """Decrittografa una password""" + try: + key = self.load_key() + f = Fernet(key) + decoded_password = base64.b64decode(encrypted_password.encode()) + return f.decrypt(decoded_password).decode() + except Exception: + return None + + def create_config_file(self): + """Crea file di configurazione di esempio""" + print("=== CONFIGURAZIONE INIZIALE ===") + print("Inserisci le credenziali SSH (saranno crittografate)") + + hostname = input("IP del server SSH: ").strip() + username = input("Username: ").strip() + password = getpass("Password (nascosta): ").strip() + port = input("Porta SSH [22]: ").strip() or "22" + + # Crittografa la password + encrypted_password = self.encrypt_password(password) + + config_template = { + "ssh": { + "hostname": hostname, + "username": username, + "password_encrypted": encrypted_password, + "port": int(port), + "timeout": 10 + }, + "settings": { + "command_delay": 1.0, + "response_timeout": 2.0, + "max_response_chars": 200 + } + } + + with open(self.config_file, 'w', encoding='utf-8') as file: + json.dump(config_template, file, indent=4, ensure_ascii=False) + + # Rende il file leggibile solo dal proprietario + os.chmod(self.config_file, stat.S_IRUSR | stat.S_IWUSR) + + print(f"✓ File di configurazione creato: {self.config_file}") + print("✓ Password crittografata e file protetto") + return True + + def create_sample_commands_file(self): + """Crea file di esempio comandi.txt""" + sample_commands = """# File comandi.txt - Lista dei comandi da eseguire +# Linee che iniziano con # sono commenti e vengono ignorate +# I comandi vengono eseguiti dopo: cli -> enable -> config + +show version +show interfaces brief +show running-config +show ip route +show system status +# Aggiungi altri comandi qui... +exit +""" + with open(self.commands_file, 'w', encoding='utf-8') as file: + file.write(sample_commands) + + print(f"File comandi di esempio creato: {self.commands_file}") + return True + + def load_config(self): + """Carica configurazione da file JSON""" + try: + with open(self.config_file, 'r', encoding='utf-8') as file: + config = json.load(file) + + # Verifica se la password è crittografata o in chiaro + ssh_config = config['ssh'] + if 'password_encrypted' in ssh_config: + # Password crittografata - decrittografa + encrypted_password = ssh_config['password_encrypted'] + decrypted_password = self.decrypt_password(encrypted_password) + if decrypted_password is None: + print("❌ ERRORE: Impossibile decrittografare la password") + print("Il file di configurazione potrebbe essere danneggiato") + return None + ssh_config['password'] = decrypted_password + elif 'password' in ssh_config: + # Password in chiaro - convertila in crittografata + print("⚠️ Password trovata in chiaro, la sto crittografando...") + plain_password = ssh_config['password'] + encrypted_password = self.encrypt_password(plain_password) + ssh_config['password_encrypted'] = encrypted_password + del ssh_config['password'] # Rimuovi password in chiaro + + # Salva il file aggiornato + with open(self.config_file, 'w', encoding='utf-8') as file: + json.dump(config, file, indent=4, ensure_ascii=False) + os.chmod(self.config_file, stat.S_IRUSR | stat.S_IWUSR) + ssh_config['password'] = plain_password # Per l'uso corrente + print("✓ Password crittografata e file aggiornato") + + return config + except FileNotFoundError: + print(f"ERRORE: File configurazione '{self.config_file}' non trovato!") + return None + except json.JSONDecodeError as e: + print(f"ERRORE: File configurazione non valido - {e}") + return None + + def load_commands(self): + """Carica comandi da file txt""" + try: + with open(self.commands_file, 'r', encoding='utf-8') as file: + commands = [] + for line in file: + line = line.strip() + if line and not line.startswith('#'): + commands.append(line) + return commands + except FileNotFoundError: + print(f"ERRORE: File comandi '{self.commands_file}' non trovato!") + return None + + def send_command_and_wait(self, shell, command, delay=1.0, timeout=2.0): + """Invia comando e attende risposta""" + print(f" → {command}") + shell.send(command + "\n") + time.sleep(delay) + + # Lettura risposta con timeout + start_time = time.time() + response = "" + while time.time() - start_time < timeout: + if shell.recv_ready(): + data = shell.recv(4096).decode('utf-8', errors='ignore') + response += data + break + time.sleep(0.1) + + if response.strip(): + # Mostra solo le prime righe della risposta per brevità + lines = response.strip().split('\n') + display_lines = lines[:3] if len(lines) > 3 else lines + for line in display_lines: + if line.strip(): + print(f" ← {line.strip()}") + if len(lines) > 3: + print(f" ... (e altre {len(lines)-3} righe)") + + return response + + def execute_ssh_commands(self): + """Esegue la sequenza completa di comandi SSH""" + + # Caricamento configurazione + config = self.load_config() + if not config: + return False + + ssh_config = config['ssh'] + settings = config.get('settings', {}) + + # Caricamento comandi + commands = self.load_commands() + if not commands: + return False + + try: + print(f"Connessione SSH a {ssh_config['hostname']}:{ssh_config.get('port', 22)}...") + print(f"Utente: {ssh_config['username']}") + + # Creazione e connessione client SSH + ssh_client = paramiko.SSHClient() + ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + + ssh_client.connect( + hostname=ssh_config['hostname'], + port=ssh_config.get('port', 22), + username=ssh_config['username'], + password=ssh_config['password'], + timeout=ssh_config.get('timeout', 10) + ) + + # Apertura shell interattiva + shell = ssh_client.invoke_shell() + time.sleep(2) + + # Pulizia buffer iniziale + if shell.recv_ready(): + shell.recv(4096) + + print("✓ Connessione SSH stabilita\n") + + # Sequenza comandi obbligatoria + print("=== SEQUENZA INIZIALE ===") + command_delay = settings.get('command_delay', 1.0) + response_timeout = settings.get('response_timeout', 2.0) + + self.send_command_and_wait(shell, "cli", command_delay, response_timeout) + self.send_command_and_wait(shell, "enable", command_delay, response_timeout) + self.send_command_and_wait(shell, "config", command_delay, response_timeout) + + print(f"\n=== ESECUZIONE COMANDI DAL FILE ({len(commands)} comandi) ===") + + # Esecuzione comandi dal file + for i, command in enumerate(commands, 1): + print(f"[{i}/{len(commands)}] Esecuzione comando:") + self.send_command_and_wait(shell, command, command_delay, response_timeout) + print() # Riga vuota per separare i comandi + + print("✓ Tutti i comandi sono stati eseguiti con successo!") + + # Chiusura connessione + shell.close() + ssh_client.close() + print("✓ Connessione SSH chiusa") + + return True + + except paramiko.AuthenticationException: + print("❌ ERRORE: Autenticazione fallita. Controlla username e password in config.json") + return False + except paramiko.SSHException as ssh_error: + print(f"❌ ERRORE SSH: {ssh_error}") + return False + except Exception as e: + print(f"❌ ERRORE generico: {e}") + return False + + def run(self): + """Esegue lo script principale""" + print("=== SSH Command Executor - Modalità Automatica ===\n") + + # Verifica e creazione file necessari + files_created = False + + if not self.config_file.exists(): + self.create_config_file() + files_created = True + + if not self.commands_file.exists(): + self.create_sample_commands_file() + files_created = True + + if files_created: + print("\n" + "="*60) + print("CONFIGURAZIONE COMPLETATA") + print("="*60) + print("✓ Credenziali crittografate e salvate") + print("✓ File protetti con permessi restrittivi") + print("1. Modifica 'comandi.txt' se necessario") + print("2. Riesegui lo script per connetterti") + print("="*60) + return + + try: + success = self.execute_ssh_commands() + if success: + print("\n🎉 Script completato con successo!") + else: + print("\n❌ Script terminato con errori.") + sys.exit(1) + + except KeyboardInterrupt: + print("\n\n⚠️ Script interrotto dall'utente.") + sys.exit(0) + +def main(): + executor = SSHCommandExecutor() + executor.run() + +if __name__ == "__main__": + main()