mirror of
https://github.com/grandir66/UnifiMultimirror.git
synced 2026-10-02 15:24:42 +02:00
Initial commit: UnifiMultimirror port mirroring tool
- TUI application (Textual) for interactive port selection - CLI application (Rich) as alternative interface - SSH manager for device connection - Config parser for running-config, interface status, port-channel - Script generator for Bash, Python, and command files - Support for multiple source ports and LAG interfaces Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
commit
7bd3f755be
9 files changed
+3888
No files matched your search
@@ -0,0 +1,497 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Script Generator for Ubiquiti Port Mirroring configuration.
|
||||
Generates reusable bash scripts and command files.
|
||||
"""
|
||||
|
||||
import os
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import List, Optional
|
||||
|
||||
from config_parser import SwitchConfig
|
||||
|
||||
|
||||
class ScriptGenerator:
|
||||
"""Generates reusable configuration scripts."""
|
||||
|
||||
def __init__(self, output_dir: Optional[Path] = None):
|
||||
self.script_dir = Path(__file__).parent
|
||||
self.output_dir = output_dir or self.script_dir / "generated"
|
||||
|
||||
# Create output directory if needed
|
||||
self.output_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
def generate_timestamp(self) -> str:
|
||||
"""Generate timestamp string for filenames."""
|
||||
return datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
|
||||
def generate_human_timestamp(self) -> str:
|
||||
"""Generate human-readable timestamp for comments."""
|
||||
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
# =========================================================================
|
||||
# Bash script generation
|
||||
# =========================================================================
|
||||
|
||||
def generate_bash_script(
|
||||
self,
|
||||
commands: List[str],
|
||||
device_ip: str,
|
||||
username: str = "admin",
|
||||
port: int = 22,
|
||||
config: Optional[SwitchConfig] = None,
|
||||
description: str = ""
|
||||
) -> Path:
|
||||
"""
|
||||
Generate a bash script to apply mirroring configuration.
|
||||
|
||||
Args:
|
||||
commands: List of configuration commands (without cli/enable/config prefixes)
|
||||
device_ip: Switch IP address
|
||||
username: SSH username
|
||||
port: SSH port
|
||||
config: Optional switch config for additional info
|
||||
description: Optional description of what this script does
|
||||
|
||||
Returns:
|
||||
Path to generated script
|
||||
"""
|
||||
timestamp = self.generate_timestamp()
|
||||
human_ts = self.generate_human_timestamp()
|
||||
|
||||
filename = f"mirror_config_{timestamp}.sh"
|
||||
filepath = self.output_dir / filename
|
||||
|
||||
# Build device info
|
||||
device_info = device_ip
|
||||
if config and config.system_info.hostname:
|
||||
device_info = f"{config.system_info.hostname} ({device_ip})"
|
||||
|
||||
model_info = ""
|
||||
if config and config.system_info.description:
|
||||
model_info = config.system_info.description.split(",")[0]
|
||||
|
||||
# Build commands block
|
||||
cli_commands = [
|
||||
"cli",
|
||||
"enable",
|
||||
"configure",
|
||||
*commands,
|
||||
"exit",
|
||||
"exit",
|
||||
"exit"
|
||||
]
|
||||
commands_block = "\n".join(cli_commands)
|
||||
|
||||
# Build script content
|
||||
script_content = f'''#!/bin/bash
|
||||
# ============================================================
|
||||
# Ubiquiti Port Mirroring Configuration Script
|
||||
# ============================================================
|
||||
# Generated: {human_ts}
|
||||
# Device: {device_info}
|
||||
{f"# Model: {model_info}" if model_info else ""}
|
||||
{f"# Description: {description}" if description else ""}
|
||||
# ============================================================
|
||||
#
|
||||
# This script applies port mirroring configuration via SSH.
|
||||
#
|
||||
# REQUIREMENTS:
|
||||
# - sshpass installed (brew install sshpass / apt install sshpass)
|
||||
# - SSH access to the device
|
||||
#
|
||||
# USAGE:
|
||||
# ./mirror_config_{timestamp}.sh # Will prompt for password
|
||||
# ./mirror_config_{timestamp}.sh "password" # Password as argument
|
||||
#
|
||||
# ============================================================
|
||||
|
||||
set -e
|
||||
|
||||
# Device configuration
|
||||
DEVICE_IP="{device_ip}"
|
||||
USERNAME="{username}"
|
||||
SSH_PORT={port}
|
||||
|
||||
# Colors for output
|
||||
RED='\\033[0;31m'
|
||||
GREEN='\\033[0;32m'
|
||||
YELLOW='\\033[1;33m'
|
||||
NC='\\033[0m' # No Color
|
||||
|
||||
echo -e "${{YELLOW}}============================================================${{NC}}"
|
||||
echo -e "${{YELLOW}} Ubiquiti Port Mirroring Configuration${{NC}}"
|
||||
echo -e "${{YELLOW}}============================================================${{NC}}"
|
||||
echo ""
|
||||
echo "Device: $DEVICE_IP"
|
||||
echo "User: $USERNAME"
|
||||
echo ""
|
||||
|
||||
# Get password
|
||||
if [ -z "$1" ]; then
|
||||
read -sp "Enter SSH password for $USERNAME@$DEVICE_IP: " PASSWORD
|
||||
echo ""
|
||||
else
|
||||
PASSWORD="$1"
|
||||
fi
|
||||
|
||||
if [ -z "$PASSWORD" ]; then
|
||||
echo -e "${{RED}}Error: Password is required${{NC}}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if sshpass is available
|
||||
if ! command -v sshpass &> /dev/null; then
|
||||
echo -e "${{RED}}Error: sshpass is not installed${{NC}}"
|
||||
echo "Install with: brew install sshpass (macOS) or apt install sshpass (Linux)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Commands to execute
|
||||
read -r -d '' COMMANDS << 'CMDEOF' || true
|
||||
{commands_block}
|
||||
CMDEOF
|
||||
|
||||
echo -e "${{YELLOW}}Connecting to $DEVICE_IP...${{NC}}"
|
||||
echo ""
|
||||
|
||||
# Execute commands via SSH
|
||||
if sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \\
|
||||
-p $SSH_PORT $USERNAME@$DEVICE_IP "$COMMANDS" 2>/dev/null; then
|
||||
echo ""
|
||||
echo -e "${{GREEN}}============================================================${{NC}}"
|
||||
echo -e "${{GREEN}} Configuration applied successfully!${{NC}}"
|
||||
echo -e "${{GREEN}}============================================================${{NC}}"
|
||||
else
|
||||
echo ""
|
||||
echo -e "${{RED}}============================================================${{NC}}"
|
||||
echo -e "${{RED}} Error applying configuration${{NC}}"
|
||||
echo -e "${{RED}}============================================================${{NC}}"
|
||||
exit 1
|
||||
fi
|
||||
'''
|
||||
|
||||
# Write script
|
||||
with open(filepath, 'w', encoding='utf-8') as f:
|
||||
f.write(script_content)
|
||||
|
||||
# Make executable
|
||||
os.chmod(filepath, 0o755)
|
||||
|
||||
return filepath
|
||||
|
||||
# =========================================================================
|
||||
# Command file generation
|
||||
# =========================================================================
|
||||
|
||||
def generate_command_file(
|
||||
self,
|
||||
commands: List[str],
|
||||
device_ip: str,
|
||||
config: Optional[SwitchConfig] = None,
|
||||
description: str = "",
|
||||
include_cli_prefix: bool = True
|
||||
) -> Path:
|
||||
"""
|
||||
Generate a text file with commands only.
|
||||
|
||||
Args:
|
||||
commands: List of configuration commands
|
||||
device_ip: Switch IP address
|
||||
config: Optional switch config for additional info
|
||||
description: Optional description
|
||||
include_cli_prefix: Include cli/enable/config prefix commands
|
||||
|
||||
Returns:
|
||||
Path to generated file
|
||||
"""
|
||||
timestamp = self.generate_timestamp()
|
||||
human_ts = self.generate_human_timestamp()
|
||||
|
||||
filename = f"mirror_commands_{timestamp}.txt"
|
||||
filepath = self.output_dir / filename
|
||||
|
||||
# Build device info
|
||||
device_info = device_ip
|
||||
if config and config.system_info.hostname:
|
||||
device_info = f"{config.system_info.hostname} ({device_ip})"
|
||||
|
||||
# Build content
|
||||
lines = [
|
||||
"# ============================================================",
|
||||
"# Ubiquiti Port Mirroring Commands",
|
||||
"# ============================================================",
|
||||
f"# Generated: {human_ts}",
|
||||
f"# Device: {device_info}",
|
||||
]
|
||||
|
||||
if description:
|
||||
lines.append(f"# Description: {description}")
|
||||
|
||||
lines.extend([
|
||||
"# ============================================================",
|
||||
"#",
|
||||
"# To apply these commands manually:",
|
||||
"# 1. SSH to the device",
|
||||
"# 2. Enter: cli",
|
||||
"# 3. Enter: enable",
|
||||
"# 4. Enter: configure",
|
||||
"# 5. Execute the commands below",
|
||||
"# 6. Enter: exit (3 times to logout)",
|
||||
"#",
|
||||
"# ============================================================",
|
||||
"",
|
||||
])
|
||||
|
||||
if include_cli_prefix:
|
||||
lines.extend([
|
||||
"# === CLI Access Commands ===",
|
||||
"cli",
|
||||
"enable",
|
||||
"configure",
|
||||
"",
|
||||
"# === Mirroring Configuration ===",
|
||||
])
|
||||
|
||||
lines.extend(commands)
|
||||
|
||||
if include_cli_prefix:
|
||||
lines.extend([
|
||||
"",
|
||||
"# === Exit Commands ===",
|
||||
"exit",
|
||||
"exit",
|
||||
"exit",
|
||||
])
|
||||
|
||||
# Write file
|
||||
with open(filepath, 'w', encoding='utf-8') as f:
|
||||
f.write("\n".join(lines) + "\n")
|
||||
|
||||
return filepath
|
||||
|
||||
# =========================================================================
|
||||
# Python script generation (alternative to bash)
|
||||
# =========================================================================
|
||||
|
||||
def generate_python_script(
|
||||
self,
|
||||
commands: List[str],
|
||||
device_ip: str,
|
||||
username: str = "admin",
|
||||
port: int = 22,
|
||||
config: Optional[SwitchConfig] = None,
|
||||
description: str = ""
|
||||
) -> Path:
|
||||
"""
|
||||
Generate a Python script to apply mirroring configuration.
|
||||
Uses paramiko, doesn't require sshpass.
|
||||
|
||||
Returns:
|
||||
Path to generated script
|
||||
"""
|
||||
timestamp = self.generate_timestamp()
|
||||
human_ts = self.generate_human_timestamp()
|
||||
|
||||
filename = f"mirror_config_{timestamp}.py"
|
||||
filepath = self.output_dir / filename
|
||||
|
||||
# Build device info
|
||||
device_info = device_ip
|
||||
if config and config.system_info.hostname:
|
||||
device_info = f"{config.system_info.hostname} ({device_ip})"
|
||||
|
||||
# Format commands as Python list
|
||||
commands_str = ",\n ".join([f'"{cmd}"' for cmd in commands])
|
||||
|
||||
script_content = f'''#!/usr/bin/env python3
|
||||
"""
|
||||
Ubiquiti Port Mirroring Configuration Script
|
||||
|
||||
Generated: {human_ts}
|
||||
Device: {device_info}
|
||||
{f"Description: {description}" if description else ""}
|
||||
|
||||
Requirements: pip install paramiko
|
||||
Usage: python mirror_config_{timestamp}.py
|
||||
"""
|
||||
|
||||
import paramiko
|
||||
import time
|
||||
import sys
|
||||
from getpass import getpass
|
||||
|
||||
|
||||
# Configuration
|
||||
DEVICE_IP = "{device_ip}"
|
||||
USERNAME = "{username}"
|
||||
SSH_PORT = {port}
|
||||
|
||||
# Commands to execute
|
||||
COMMANDS = [
|
||||
"cli",
|
||||
"enable",
|
||||
"configure",
|
||||
{commands_str},
|
||||
"exit",
|
||||
"exit",
|
||||
"exit",
|
||||
]
|
||||
|
||||
|
||||
def apply_config(password: str) -> bool:
|
||||
"""Apply configuration to device."""
|
||||
try:
|
||||
print(f"Connecting to {{DEVICE_IP}}...")
|
||||
|
||||
ssh = paramiko.SSHClient()
|
||||
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
ssh.connect(
|
||||
hostname=DEVICE_IP,
|
||||
port=SSH_PORT,
|
||||
username=USERNAME,
|
||||
password=password,
|
||||
timeout=10
|
||||
)
|
||||
|
||||
shell = ssh.invoke_shell()
|
||||
time.sleep(2)
|
||||
|
||||
# Clear buffer
|
||||
if shell.recv_ready():
|
||||
shell.recv(65536)
|
||||
|
||||
print("Connected. Applying configuration...")
|
||||
print()
|
||||
|
||||
for cmd in COMMANDS:
|
||||
print(f" >>> {{cmd}}")
|
||||
shell.send(cmd + "\\n")
|
||||
time.sleep(1)
|
||||
|
||||
if shell.recv_ready():
|
||||
response = shell.recv(4096).decode('utf-8', errors='ignore')
|
||||
for line in response.strip().split("\\n")[:2]:
|
||||
if line.strip() and not line.strip().startswith("("):
|
||||
print(f" {{line.strip()}}")
|
||||
|
||||
shell.close()
|
||||
ssh.close()
|
||||
|
||||
print()
|
||||
print("=" * 50)
|
||||
print(" Configuration applied successfully!")
|
||||
print("=" * 50)
|
||||
return True
|
||||
|
||||
except paramiko.AuthenticationException:
|
||||
print("ERROR: Authentication failed")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"ERROR: {{e}}")
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
print("=" * 50)
|
||||
print(" Ubiquiti Port Mirroring Configuration")
|
||||
print("=" * 50)
|
||||
print()
|
||||
print(f"Device: {{DEVICE_IP}}")
|
||||
print(f"User: {{USERNAME}}")
|
||||
print()
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
password = sys.argv[1]
|
||||
else:
|
||||
password = getpass(f"Enter SSH password for {{USERNAME}}@{{DEVICE_IP}}: ")
|
||||
|
||||
if not password:
|
||||
print("Error: Password is required")
|
||||
sys.exit(1)
|
||||
|
||||
success = apply_config(password)
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
'''
|
||||
|
||||
# Write script
|
||||
with open(filepath, 'w', encoding='utf-8') as f:
|
||||
f.write(script_content)
|
||||
|
||||
# Make executable
|
||||
os.chmod(filepath, 0o755)
|
||||
|
||||
return filepath
|
||||
|
||||
# =========================================================================
|
||||
# Generate all formats
|
||||
# =========================================================================
|
||||
|
||||
def generate_all(
|
||||
self,
|
||||
commands: List[str],
|
||||
device_ip: str,
|
||||
username: str = "admin",
|
||||
port: int = 22,
|
||||
config: Optional[SwitchConfig] = None,
|
||||
description: str = ""
|
||||
) -> dict:
|
||||
"""
|
||||
Generate all script formats.
|
||||
|
||||
Returns:
|
||||
Dict with paths: {'bash': Path, 'python': Path, 'commands': Path}
|
||||
"""
|
||||
bash_path = self.generate_bash_script(
|
||||
commands, device_ip, username, port, config, description
|
||||
)
|
||||
|
||||
python_path = self.generate_python_script(
|
||||
commands, device_ip, username, port, config, description
|
||||
)
|
||||
|
||||
cmd_path = self.generate_command_file(
|
||||
commands, device_ip, config, description
|
||||
)
|
||||
|
||||
return {
|
||||
'bash': bash_path,
|
||||
'python': python_path,
|
||||
'commands': cmd_path
|
||||
}
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Testing
|
||||
# =============================================================================
|
||||
|
||||
if __name__ == "__main__":
|
||||
generator = ScriptGenerator()
|
||||
|
||||
# Test commands
|
||||
commands = [
|
||||
"no monitor session 1",
|
||||
"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/17",
|
||||
"monitor session 1 source interface 0/18",
|
||||
"monitor session 1 mode",
|
||||
]
|
||||
|
||||
# Generate all
|
||||
paths = generator.generate_all(
|
||||
commands=commands,
|
||||
device_ip="192.168.40.243",
|
||||
username="admin",
|
||||
description="Port mirroring for network monitoring"
|
||||
)
|
||||
|
||||
print("Generated files:")
|
||||
for fmt, path in paths.items():
|
||||
print(f" {fmt}: {path}")
|
||||
Reference in new issue
Block a user