mirror of
https://github.com/grandir66/UnifiMultimirror.git
synced 2026-10-02 15:24:42 +02:00
wip: salvataggio pre-trasloco dello stato non committato (2026-09-06)
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 <noreply@anthropic.com>
This commit is contained in:
1 parent
9af3b70c46
commit
582f1f045b
4 files changed
+408
No files matched your search
+19
@@ -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
|
||||||
@@ -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.
|
||||||
Executable
+59
@@ -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"
|
||||||
+320
@@ -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()
|
||||||
Reference in new issue
Block a user