Some checks failed
Test / test (push) Has been cancelled
Co-authored-by: Cursor <cursoragent@cursor.com>
365 lines
10 KiB
Bash
Executable File
365 lines
10 KiB
Bash
Executable File
#!/bin/bash
|
|
# Proxmox VE Network Configuration Script
|
|
# Configures two-bridge setup: vmbr0 (LAN) and vmbr1 (WAN) with DHCP
|
|
# Designed for ML110 and R630 servers with two NICs each
|
|
|
|
set -e
|
|
|
|
# Colors for output
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
NC='\033[0m' # No Color
|
|
|
|
# Configuration
|
|
NODE_HOSTNAME="${NODE_HOSTNAME:-$(hostname)}"
|
|
DRY_RUN="${DRY_RUN:-false}"
|
|
|
|
log_info() {
|
|
echo -e "${GREEN}[INFO]${NC} $1"
|
|
}
|
|
|
|
log_warn() {
|
|
echo -e "${YELLOW}[WARN]${NC} $1"
|
|
}
|
|
|
|
log_error() {
|
|
echo -e "${RED}[ERROR]${NC} $1"
|
|
}
|
|
|
|
check_root() {
|
|
if [ "$EUID" -ne 0 ]; then
|
|
log_error "Please run as root"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
get_interface_speed() {
|
|
local iface=$1
|
|
# Try to get speed from ethtool (most reliable)
|
|
if command -v ethtool &>/dev/null; then
|
|
local speed=$(ethtool "$iface" 2>/dev/null | grep -i "Speed:" | awk '{print $2}' | sed 's/[^0-9]//g')
|
|
if [ -n "$speed" ]; then
|
|
echo "$speed"
|
|
return
|
|
fi
|
|
fi
|
|
|
|
# Fallback: check from /sys/class/net (if link is up)
|
|
local speed=$(cat "/sys/class/net/$iface/speed" 2>/dev/null)
|
|
if [ -n "$speed" ] && [ "$speed" != "-1" ]; then
|
|
echo "$speed"
|
|
return
|
|
fi
|
|
|
|
# Fallback: check advertised speeds
|
|
if command -v ethtool &>/dev/null; then
|
|
local adv_speeds=$(ethtool "$iface" 2>/dev/null | grep -i "Advertised link modes:" | grep -oE "[0-9]+base" | head -1 | sed 's/base//')
|
|
if [ -n "$adv_speeds" ]; then
|
|
echo "$adv_speeds"
|
|
return
|
|
fi
|
|
fi
|
|
|
|
echo "unknown"
|
|
}
|
|
|
|
detect_physical_interfaces() {
|
|
log_info "Detecting physical network interfaces..."
|
|
|
|
# Get all physical interfaces
|
|
PHYSICAL_IFACES=()
|
|
|
|
for iface in /sys/class/net/*; do
|
|
iface_name=$(basename "$iface")
|
|
|
|
# Skip loopback, virtual interfaces, bridges, bonds, and VLANs
|
|
if [[ "$iface_name" == "lo" ]] || \
|
|
[[ -L "$iface/device" ]] && [[ ! -d "$iface/device" ]] || \
|
|
[[ -d "$iface/bridge" ]] || \
|
|
[[ -d "$iface/bonding" ]] || \
|
|
[[ "$iface_name" =~ \. ]]; then
|
|
continue
|
|
fi
|
|
|
|
# Check if it's a physical interface by looking for device directory
|
|
if [ -d "$iface/device" ] || [ -L "$iface/device" ]; then
|
|
PHYSICAL_IFACES+=("$iface_name")
|
|
fi
|
|
done
|
|
|
|
# Sort interfaces for consistent selection
|
|
IFS=$'\n' PHYSICAL_IFACES=($(sort <<<"${PHYSICAL_IFACES[*]}"))
|
|
unset IFS
|
|
|
|
if [ ${#PHYSICAL_IFACES[@]} -lt 2 ]; then
|
|
log_error "Expected at least 2 physical interfaces, found: ${#PHYSICAL_IFACES[@]}"
|
|
log_error "Available interfaces: ${PHYSICAL_IFACES[*]}"
|
|
exit 1
|
|
fi
|
|
|
|
log_info "Found ${#PHYSICAL_IFACES[@]} physical interface(s): ${PHYSICAL_IFACES[*]}"
|
|
|
|
# Check which interfaces currently have IP addresses (if any)
|
|
log_info "Checking current IP assignments..."
|
|
INTERFACES_WITH_IPS=()
|
|
for iface in "${PHYSICAL_IFACES[@]}"; do
|
|
if ip addr show "$iface" 2>/dev/null | grep -q "inet "; then
|
|
IP=$(ip addr show "$iface" | grep "inet " | awk '{print $2}' | head -1)
|
|
INTERFACES_WITH_IPS+=("$iface")
|
|
log_info " $iface: Currently has IP $IP"
|
|
fi
|
|
done
|
|
|
|
# Use first two interfaces - DHCP will assign IPs to connected ones
|
|
NIC1="${PHYSICAL_IFACES[0]}"
|
|
NIC2="${PHYSICAL_IFACES[1]}"
|
|
|
|
log_info "Selected NIC 1 (LAN): $NIC1"
|
|
log_info "Selected NIC 2 (WAN): $NIC2"
|
|
log_info "Note: DHCP will assign IPs to connected interfaces"
|
|
|
|
# Allow manual override via environment variables
|
|
if [ -n "$NIC1_OVERRIDE" ]; then
|
|
NIC1="$NIC1_OVERRIDE"
|
|
log_info "NIC1 overridden to: $NIC1"
|
|
fi
|
|
|
|
if [ -n "$NIC2_OVERRIDE" ]; then
|
|
NIC2="$NIC2_OVERRIDE"
|
|
log_info "NIC2 overridden to: $NIC2"
|
|
fi
|
|
}
|
|
|
|
validate_interfaces() {
|
|
log_info "Validating interface configuration..."
|
|
|
|
# Check if interfaces exist
|
|
if ! ip link show "$NIC1" &>/dev/null; then
|
|
log_error "Interface $NIC1 not found"
|
|
exit 1
|
|
fi
|
|
|
|
if ! ip link show "$NIC2" &>/dev/null; then
|
|
log_error "Interface $NIC2 not found"
|
|
exit 1
|
|
fi
|
|
|
|
log_info "Interface validation passed"
|
|
}
|
|
|
|
backup_config() {
|
|
log_info "Backing up existing network configuration..."
|
|
BACKUP_FILE="/etc/network/interfaces.backup.$(date +%Y%m%d_%H%M%S)"
|
|
|
|
if [ "$DRY_RUN" = "true" ]; then
|
|
log_info "[DRY RUN] Would backup to: $BACKUP_FILE"
|
|
return
|
|
fi
|
|
|
|
cp /etc/network/interfaces "$BACKUP_FILE"
|
|
log_info "Backup created: $BACKUP_FILE"
|
|
}
|
|
|
|
configure_hostname() {
|
|
log_info "Configuring hostname: $NODE_HOSTNAME"
|
|
|
|
if [ "$DRY_RUN" = "true" ]; then
|
|
log_info "[DRY RUN] Would set hostname to: $NODE_HOSTNAME"
|
|
return
|
|
fi
|
|
|
|
echo "$NODE_HOSTNAME" > /etc/hostname
|
|
hostname "$NODE_HOSTNAME"
|
|
}
|
|
|
|
generate_interfaces_config() {
|
|
log_info "Generating network configuration..."
|
|
|
|
cat <<EOF
|
|
# Proxmox VE Network Configuration
|
|
# Auto-generated by network-config.sh
|
|
# Configuration for: $NODE_HOSTNAME
|
|
# NIC 1 (LAN): $NIC1 -> vmbr0 (192.168.1.0/24 via DHCP)
|
|
# NIC 2 (WAN): $NIC2 -> vmbr1 (Public IP via DHCP from Spectrum modem)
|
|
|
|
# Loopback interface
|
|
auto lo
|
|
iface lo inet loopback
|
|
|
|
# NIC 1 (LAN) - Physical interface
|
|
auto $NIC1
|
|
iface $NIC1 inet manual
|
|
|
|
# vmbr0 (LAN Bridge) - Connected to 192.168.1.0/24 network
|
|
auto vmbr0
|
|
iface vmbr0 inet dhcp
|
|
bridge-ports $NIC1
|
|
bridge-stp off
|
|
bridge-fd 0
|
|
bridge-vlan-aware no
|
|
# Higher metric - prefer WAN for default route
|
|
metric 200
|
|
|
|
# NIC 2 (WAN) - Physical interface
|
|
auto $NIC2
|
|
iface $NIC2 inet manual
|
|
|
|
# vmbr1 (WAN Bridge) - Connected to Spectrum cable modem
|
|
auto vmbr1
|
|
iface vmbr1 inet dhcp
|
|
bridge-ports $NIC2
|
|
bridge-stp off
|
|
bridge-fd 0
|
|
bridge-vlan-aware no
|
|
# Lower metric - preferred default route for internet access
|
|
metric 100
|
|
|
|
EOF
|
|
}
|
|
|
|
configure_network() {
|
|
log_info "Configuring network interfaces..."
|
|
|
|
if [ "$DRY_RUN" = "true" ]; then
|
|
log_info "[DRY RUN] Configuration that would be written:"
|
|
echo ""
|
|
generate_interfaces_config
|
|
return
|
|
fi
|
|
|
|
# Write configuration
|
|
generate_interfaces_config > /etc/network/interfaces
|
|
|
|
log_info "Network configuration written to /etc/network/interfaces"
|
|
}
|
|
|
|
apply_network_config() {
|
|
log_info "Applying network configuration..."
|
|
|
|
if [ "$DRY_RUN" = "true" ]; then
|
|
log_info "[DRY RUN] Would apply network configuration"
|
|
log_info "[DRY RUN] Would run: ifreload -a"
|
|
return
|
|
fi
|
|
|
|
# Bring down interfaces if they're up
|
|
ifdown vmbr0 2>/dev/null || true
|
|
ifdown vmbr1 2>/dev/null || true
|
|
ifdown "$NIC1" 2>/dev/null || true
|
|
ifdown "$NIC2" 2>/dev/null || true
|
|
|
|
# Reload all interfaces
|
|
ifreload -a || {
|
|
log_error "Failed to apply network configuration"
|
|
log_error "Restore backup if needed: cp /etc/network/interfaces.backup.* /etc/network/interfaces"
|
|
exit 1
|
|
}
|
|
|
|
# Wait a moment for DHCP
|
|
sleep 3
|
|
|
|
log_info "Network configuration applied successfully"
|
|
}
|
|
|
|
verify_configuration() {
|
|
log_info "Verifying network configuration..."
|
|
|
|
if [ "$DRY_RUN" = "true" ]; then
|
|
log_info "[DRY RUN] Would verify bridges are up and have IP addresses"
|
|
return
|
|
fi
|
|
|
|
# Check vmbr0
|
|
if ip link show vmbr0 &>/dev/null; then
|
|
if ip addr show vmbr0 | grep -q "inet "; then
|
|
VMBR0_IP=$(ip addr show vmbr0 | grep "inet " | awk '{print $2}' | head -1)
|
|
log_info "vmbr0 (LAN) is up with IP: $VMBR0_IP"
|
|
else
|
|
log_warn "vmbr0 is up but doesn't have an IP address yet (DHCP may be pending)"
|
|
fi
|
|
else
|
|
log_error "vmbr0 bridge is not up"
|
|
fi
|
|
|
|
# Check vmbr1
|
|
if ip link show vmbr1 &>/dev/null; then
|
|
if ip addr show vmbr1 | grep -q "inet "; then
|
|
VMBR1_IP=$(ip addr show vmbr1 | grep "inet " | awk '{print $2}' | head -1)
|
|
log_info "vmbr1 (WAN) is up with IP: $VMBR1_IP"
|
|
else
|
|
log_warn "vmbr1 is up but doesn't have an IP address yet (DHCP may be pending)"
|
|
fi
|
|
else
|
|
log_error "vmbr1 bridge is not up"
|
|
fi
|
|
|
|
# Check routing
|
|
DEFAULT_ROUTES=$(ip route | grep default)
|
|
if [ -n "$DEFAULT_ROUTES" ]; then
|
|
log_info "Default route(s):"
|
|
echo "$DEFAULT_ROUTES" | while read route; do
|
|
echo " $route"
|
|
done
|
|
# Check if WAN interface is in the default route
|
|
if echo "$DEFAULT_ROUTES" | grep -q "vmbr1"; then
|
|
log_info "✓ Default route via WAN (vmbr1) detected"
|
|
elif echo "$DEFAULT_ROUTES" | grep -q "vmbr0"; then
|
|
log_warn "⚠ Default route via LAN (vmbr0) detected - WAN (vmbr1) may need time for DHCP"
|
|
fi
|
|
else
|
|
log_warn "No default route found (may be waiting for DHCP)"
|
|
fi
|
|
|
|
# Check for specific routes
|
|
if ip route | grep -q "192.168.1.0/24"; then
|
|
LAN_ROUTE=$(ip route | grep "192.168.1.0/24" | head -1)
|
|
log_info "LAN route: $LAN_ROUTE"
|
|
fi
|
|
}
|
|
|
|
show_status() {
|
|
log_info "Current network status:"
|
|
echo ""
|
|
echo "=== Physical Interfaces ==="
|
|
ip link show "$NIC1" 2>/dev/null || echo " $NIC1: not found"
|
|
echo ""
|
|
ip link show "$NIC2" 2>/dev/null || echo " $NIC2: not found"
|
|
echo ""
|
|
echo "=== Bridges ==="
|
|
ip addr show vmbr0 2>/dev/null || echo " vmbr0: not found"
|
|
echo ""
|
|
ip addr show vmbr1 2>/dev/null || echo " vmbr1: not found"
|
|
echo ""
|
|
echo "=== Routing Table ==="
|
|
ip route show
|
|
}
|
|
|
|
main() {
|
|
log_info "Starting Proxmox network configuration..."
|
|
log_info "Hostname: $NODE_HOSTNAME"
|
|
|
|
if [ "$DRY_RUN" = "true" ]; then
|
|
log_warn "DRY RUN MODE - No changes will be made"
|
|
fi
|
|
|
|
check_root
|
|
detect_physical_interfaces
|
|
validate_interfaces
|
|
backup_config
|
|
configure_hostname
|
|
configure_network
|
|
apply_network_config
|
|
verify_configuration
|
|
|
|
log_info "Network configuration completed!"
|
|
echo ""
|
|
show_status
|
|
|
|
if [ "$DRY_RUN" = "false" ]; then
|
|
log_info "If you need to rollback, restore from: /etc/network/interfaces.backup.*"
|
|
fi
|
|
}
|
|
|
|
main "$@"
|