Add adaptive router VM/interface scaling and local delivery integrity tests
Terraform now provisions router_count IaaS Router VMs (default 2, no longer hardcoded to router1/router2), each with 1 public + private_interface_count isolated private interfaces (no shared LAN or VRRP between routers). Both counts scale via Terraform variables and TF_VAR_* environment variables. The post-install script became a Terraform template that matches interfaces to their expected subnet by CIDR instead of a fragile "first private IP" heuristic. Added an offline pytest suite (terraform/tests/) that checks the delivery's internal consistency and runs real terraform init/validate against the actual vkcs provider schema via a project-local filesystem mirror (provider binary fetched from its GitHub releases, bypassing the region-blocked HashiCorp registry) - no cloud credentials or API calls involved. terraform/versions.tf now declares the previously-missing required_providers block. Ansible (inventory.ini, base/frr_router/keepalived roles) still assumes the old 2-router/2-NIC/VRRP topology and is not yet adapted - documented as a follow-up, not addressed here. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011hXR2ftXZZhJ4Y3XuSoR8r
This commit is contained in:
1 parent
e711faca42
commit
5979a9a58b
20 files changed
+1181
-139
No files matched your search
@@ -0,0 +1,497 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
log() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a /var/log/network-config.log
|
||||
}
|
||||
|
||||
log "Starting network configuration..."
|
||||
|
||||
# Validate required commands exist
|
||||
for cmd in ip netplan systemctl; do
|
||||
if ! command -v "$cmd" &> /dev/null; then
|
||||
log "ERROR: Required command '$cmd' is not available"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Validate directories exist
|
||||
if [ ! -d "/etc/netplan" ]; then
|
||||
log "ERROR: /etc/netplan directory does not exist"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -d "/sys/class/net" ]; then
|
||||
log "ERROR: /sys/class/net directory does not exist"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Enhanced function to check if IP is in private subnet (RFC 1918)
|
||||
is_private_ip() {
|
||||
local ip="$1"
|
||||
local clean_ip=$(echo "$ip" | cut -d'/' -f1) # Remove subnet mask if present
|
||||
|
||||
# Check RFC 1918 private ranges:
|
||||
# 10.0.0.0/8 (10.0.0.0 - 10.255.255.255)
|
||||
# 172.16.0.0/12 (172.16.0.0 - 172.31.255.255)
|
||||
# 192.168.0.0/16 (192.168.0.0 - 192.168.255.255)
|
||||
|
||||
if [[ $clean_ip =~ ^10\. ]]; then
|
||||
return 0 # 10.0.0.0/8
|
||||
elif [[ $clean_ip =~ ^172\.(1[6-9]|2[0-9]|3[0-1])\. ]]; then
|
||||
return 0 # 172.16.0.0/12
|
||||
elif [[ $clean_ip =~ ^192\.168\. ]]; then
|
||||
return 0 # 192.168.0.0/16
|
||||
else
|
||||
return 1 # Is public IP
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to convert CIDR to netmask
|
||||
cidr_to_netmask() {
|
||||
local cidr="$1"
|
||||
|
||||
# Input validation
|
||||
if [[ ! $cidr =~ ^[0-9]+$ ]] || [ "$cidr" -lt 0 ] || [ "$cidr" -gt 32 ]; then
|
||||
echo "Error: CIDR must be a number between 0 and 32" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
local netmask=""
|
||||
local full_octets=$((cidr / 8))
|
||||
local remaining_bits=$((cidr % 8))
|
||||
local partial_octet=0
|
||||
|
||||
# Calculate partial octet if there are remaining bits
|
||||
if [ "$remaining_bits" -gt 0 ]; then
|
||||
partial_octet=$((256 - (256 >> remaining_bits)))
|
||||
fi
|
||||
|
||||
for ((i=0; i<4; i++)); do
|
||||
if [ "$i" -lt "$full_octets" ]; then
|
||||
netmask="$${netmask}255"
|
||||
elif [ "$i" -eq "$full_octets" ] && [ "$remaining_bits" -gt 0 ]; then
|
||||
netmask="$${netmask}$${partial_octet}"
|
||||
else
|
||||
netmask="$${netmask}0"
|
||||
fi
|
||||
|
||||
if [ "$i" -lt 3 ]; then
|
||||
netmask="$${netmask}."
|
||||
fi
|
||||
done
|
||||
|
||||
echo "$netmask"
|
||||
}
|
||||
|
||||
# Function to convert netmask to CIDR
|
||||
netmask_to_cidr() {
|
||||
local netmask="$1"
|
||||
local cidr=0
|
||||
|
||||
# Use -a for array, not -o
|
||||
IFS='.' read -ra octets <<< "$netmask"
|
||||
|
||||
for octet in "$${octets[@]}"; do
|
||||
case $octet in
|
||||
255) cidr=$((cidr + 8)) ;;
|
||||
254) cidr=$((cidr + 7)) ;;
|
||||
252) cidr=$((cidr + 6)) ;;
|
||||
248) cidr=$((cidr + 5)) ;;
|
||||
240) cidr=$((cidr + 4)) ;;
|
||||
224) cidr=$((cidr + 3)) ;;
|
||||
192) cidr=$((cidr + 2)) ;;
|
||||
128) cidr=$((cidr + 1)) ;;
|
||||
0) ;;
|
||||
*) echo "32"; return 1 ;; # Invalid netmask, default to /32
|
||||
esac
|
||||
done
|
||||
|
||||
echo "$cidr"
|
||||
}
|
||||
|
||||
# Function to extract IP, netmask, and CIDR from CIDR notation
|
||||
get_ip_netmask_cidr() {
|
||||
local cidr_ip="$1"
|
||||
local ip=$(echo "$cidr_ip" | cut -d'/' -f1)
|
||||
local cidr_part=$(echo "$cidr_ip" | cut -d'/' -f2)
|
||||
local netmask=""
|
||||
local cidr=""
|
||||
|
||||
if [[ $cidr_part =~ ^[0-9]{1,2}$ ]]; then
|
||||
# CIDR notation (e.g., /24)
|
||||
cidr="$cidr_part"
|
||||
netmask=$(cidr_to_netmask "$cidr")
|
||||
elif [[ $cidr_part =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
# Already in netmask format
|
||||
netmask="$cidr_part"
|
||||
cidr=$(netmask_to_cidr "$netmask")
|
||||
else
|
||||
# Default to /32 if no valid netmask found
|
||||
cidr="32"
|
||||
netmask="255.255.255.255"
|
||||
fi
|
||||
|
||||
echo "$ip,$netmask,$cidr"
|
||||
}
|
||||
|
||||
# Function to extract the first private IPv4 address, netmask, and CIDR from an interface
|
||||
get_private_ip_netmask_cidr() {
|
||||
local interface="$1"
|
||||
local ip_addrs=$(ip addr show "$interface" 2>/dev/null | grep 'inet ' | grep -v '127.0.0.1' | awk '{print $2}')
|
||||
|
||||
if [ -n "$ip_addrs" ]; then
|
||||
while IFS= read -r ip; do
|
||||
if [ -n "$ip" ] && is_private_ip "$ip"; then
|
||||
# Return IP, netmask, and CIDR
|
||||
get_ip_netmask_cidr "$ip"
|
||||
return 0
|
||||
fi
|
||||
done <<< "$ip_addrs"
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# Function to extract the first public IPv4 address, netmask, and CIDR from an interface
|
||||
get_public_ip_netmask_cidr() {
|
||||
local interface="$1"
|
||||
local ip_addrs=$(ip addr show "$interface" 2>/dev/null | grep 'inet ' | grep -v '127.0.0.1' | awk '{print $2}')
|
||||
|
||||
if [ -n "$ip_addrs" ]; then
|
||||
while IFS= read -r ip; do
|
||||
if [ -n "$ip" ] && ! is_private_ip "$ip"; then
|
||||
# Return IP, netmask, and CIDR
|
||||
get_ip_netmask_cidr "$ip"
|
||||
return 0
|
||||
fi
|
||||
done <<< "$ip_addrs"
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# Function to get default gateway for an interface
|
||||
get_interface_gateway() {
|
||||
local interface="$1"
|
||||
|
||||
# Try to get gateway from route table for the specific interface
|
||||
local gateway=$(ip route show dev "$interface" 2>/dev/null | grep '^default via' | awk '{print $3}' | head -n1)
|
||||
|
||||
if [ -n "$gateway" ]; then
|
||||
echo "$gateway"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Fallback: get default gateway from main route table
|
||||
gateway=$(ip route show 2>/dev/null | grep '^default via' | awk '{print $3}' | head -n1)
|
||||
|
||||
if [ -n "$gateway" ]; then
|
||||
echo "$gateway"
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# Enhanced function to check if interface has private IP
|
||||
has_private_ip() {
|
||||
local interface="$1"
|
||||
local ip_addrs=$(ip addr show "$interface" 2>/dev/null | grep 'inet ' | grep -v '127.0.0.1' | awk '{print $2}')
|
||||
|
||||
if [ -n "$ip_addrs" ]; then
|
||||
while IFS= read -r ip; do
|
||||
if [ -n "$ip" ] && is_private_ip "$ip"; then
|
||||
return 0 # Has at least one private IP
|
||||
fi
|
||||
done <<< "$ip_addrs"
|
||||
fi
|
||||
return 1 # No private IP
|
||||
}
|
||||
|
||||
# Convert a dotted IPv4 address to a 32-bit integer
|
||||
ip_to_int() {
|
||||
local ip="$1"
|
||||
local a b c d
|
||||
IFS='.' read -r a b c d <<< "$ip"
|
||||
echo $(( (a << 24) | (b << 16) | (c << 8) | d ))
|
||||
}
|
||||
|
||||
# Check whether IPv4 address $1 falls inside CIDR network $2 (e.g. 10.90.0.8/29)
|
||||
ip_in_cidr() {
|
||||
local ip="$1"
|
||||
local cidr="$2"
|
||||
local net="$${cidr%/*}"
|
||||
local prefix="$${cidr#*/}"
|
||||
local ip_int net_int mask
|
||||
|
||||
ip_int=$(ip_to_int "$ip")
|
||||
net_int=$(ip_to_int "$net")
|
||||
|
||||
if [ "$prefix" -eq 0 ]; then
|
||||
mask=0
|
||||
else
|
||||
mask=$(( (0xFFFFFFFF << (32 - prefix)) & 0xFFFFFFFF ))
|
||||
fi
|
||||
|
||||
[ $(( ip_int & mask )) -eq $(( net_int & mask )) ]
|
||||
}
|
||||
|
||||
# Expected private interfaces, injected by Terraform: one CIDR + target
|
||||
# ethX name per entry. Populated below via a template directive.
|
||||
PRIV_TARGETS=()
|
||||
%{ for pi in private_interfaces ~}
|
||||
PRIV_TARGETS+=("${pi.cidr}|${pi.name}")
|
||||
%{ endfor ~}
|
||||
|
||||
# Identify private and WAN interfaces
|
||||
declare -A PRIV_IFACE # target name -> interface
|
||||
declare -A PRIV_MAC # target name -> MAC
|
||||
declare -A PRIV_IPv4 # target name -> IP
|
||||
declare -A PRIV_NETMASK # target name -> netmask
|
||||
declare -A PRIV_CIDR # target name -> CIDR prefix length
|
||||
MATCHED_PRIV_IFACES=""
|
||||
WAN_IFACE=""
|
||||
WAN_MAC=""
|
||||
WAN_IFACE_IPv4=""
|
||||
WAN_NETMASK=""
|
||||
WAN_CIDR=""
|
||||
WAN_GW_IPv4=""
|
||||
|
||||
# First pass: match each interface with a private IP against the expected
|
||||
# private target CIDRs (each router interface lives in its own unique
|
||||
# micro-subnet, so matching by CIDR membership is unambiguous).
|
||||
for iface in $(ls /sys/class/net/ | grep -v lo); do
|
||||
if has_private_ip "$iface"; then
|
||||
priv_ip_netmask_cidr=$(get_private_ip_netmask_cidr "$iface")
|
||||
[ -z "$priv_ip_netmask_cidr" ] && continue
|
||||
priv_ip=$(echo "$priv_ip_netmask_cidr" | cut -d',' -f1)
|
||||
|
||||
for target in "$${PRIV_TARGETS[@]}"; do
|
||||
target_cidr="$${target%%|*}"
|
||||
target_name="$${target##*|}"
|
||||
|
||||
if ip_in_cidr "$priv_ip" "$target_cidr"; then
|
||||
if [ -n "$${PRIV_IFACE[$target_name]:-}" ]; then
|
||||
log "Multiple interfaces matched target $target_name ($target_cidr): $${PRIV_IFACE[$target_name]} and $iface"
|
||||
continue
|
||||
fi
|
||||
|
||||
PRIV_IFACE[$target_name]="$iface"
|
||||
PRIV_MAC[$target_name]=$(cat "/sys/class/net/$iface/address" 2>/dev/null)
|
||||
PRIV_IPv4[$target_name]=$priv_ip
|
||||
PRIV_NETMASK[$target_name]=$(echo "$priv_ip_netmask_cidr" | cut -d',' -f2)
|
||||
PRIV_CIDR[$target_name]=$(echo "$priv_ip_netmask_cidr" | cut -d',' -f3)
|
||||
MATCHED_PRIV_IFACES="$MATCHED_PRIV_IFACES $iface"
|
||||
|
||||
log "Identified private interface $target_name: $iface (MAC: $${PRIV_MAC[$target_name]}) with IP: $priv_ip, CIDR: /$${PRIV_CIDR[$target_name]} (target $target_cidr)"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
done
|
||||
|
||||
# Second pass: Look for WAN interface
|
||||
for iface in $(ls /sys/class/net/ | grep -v lo); do
|
||||
# Skip interfaces already matched to a private target
|
||||
case " $MATCHED_PRIV_IFACES " in
|
||||
*" $iface "*) continue ;;
|
||||
esac
|
||||
|
||||
ip_addrs=$(ip addr show "$iface" 2>/dev/null | grep 'inet ' | grep -v '127.0.0.1' | awk '{print $2}')
|
||||
|
||||
if [ -n "$ip_addrs" ]; then
|
||||
# Check if interface has public IPs
|
||||
has_public="false"
|
||||
while IFS= read -r ip; do
|
||||
if [ -n "$ip" ] && ! is_private_ip "$ip"; then
|
||||
has_public="true"
|
||||
break
|
||||
fi
|
||||
done <<< "$ip_addrs"
|
||||
|
||||
if [ "$has_public" = "true" ]; then
|
||||
WAN_IFACE="$iface"
|
||||
WAN_MAC=$(cat "/sys/class/net/$iface/address" 2>/dev/null)
|
||||
|
||||
# Get both IP, netmask, and CIDR for WAN
|
||||
wan_ip_netmask_cidr=$(get_public_ip_netmask_cidr "$iface")
|
||||
if [ -n "$wan_ip_netmask_cidr" ]; then
|
||||
WAN_IFACE_IPv4=$(echo "$wan_ip_netmask_cidr" | cut -d',' -f1)
|
||||
WAN_NETMASK=$(echo "$wan_ip_netmask_cidr" | cut -d',' -f2)
|
||||
WAN_CIDR=$(echo "$wan_ip_netmask_cidr" | cut -d',' -f3)
|
||||
fi
|
||||
|
||||
WAN_GW_IPv4=$(get_interface_gateway "$iface")
|
||||
log "Identified WAN interface: $iface (MAC: $WAN_MAC) with public IP: $WAN_IFACE_IPv4, Netmask: $WAN_NETMASK, CIDR: /$WAN_CIDR, Gateway: $WAN_GW_IPv4"
|
||||
break
|
||||
fi
|
||||
else
|
||||
# Interface with no IP - potential WAN candidate
|
||||
if [ -z "$WAN_IFACE" ]; then
|
||||
WAN_IFACE="$iface"
|
||||
WAN_MAC=$(cat "/sys/class/net/$iface/address" 2>/dev/null)
|
||||
WAN_GW_IPv4=$(get_interface_gateway "$iface")
|
||||
log "Identified WAN interface candidate: $iface (MAC: $WAN_MAC) - no IP assigned, Gateway: $WAN_GW_IPv4"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# If no WAN found but we matched at least one private interface, pick the
|
||||
# first still-unmatched interface as a fallback WAN candidate
|
||||
if [ -z "$WAN_IFACE" ] && [ -n "$MATCHED_PRIV_IFACES" ]; then
|
||||
for iface in $(ls /sys/class/net/ | grep -v lo); do
|
||||
case " $MATCHED_PRIV_IFACES " in
|
||||
*" $iface "*) continue ;;
|
||||
esac
|
||||
WAN_IFACE="$iface"
|
||||
WAN_MAC=$(cat "/sys/class/net/$iface/address" 2>/dev/null)
|
||||
WAN_GW_IPv4=$(get_interface_gateway "$iface")
|
||||
log "Assumed WAN interface: $iface (MAC: $WAN_MAC) - default selection, Gateway: $WAN_GW_IPv4"
|
||||
break
|
||||
done
|
||||
fi
|
||||
|
||||
# Final assignment and logging
|
||||
log "Final interface assignment:"
|
||||
|
||||
MISSING_PRIV_TARGETS=""
|
||||
for target in "$${PRIV_TARGETS[@]}"; do
|
||||
target_cidr="$${target%%|*}"
|
||||
target_name="$${target##*|}"
|
||||
|
||||
if [ -n "$${PRIV_IFACE[$target_name]:-}" ]; then
|
||||
log " $target_name Interface: $${PRIV_IFACE[$target_name]} (MAC: $${PRIV_MAC[$target_name]})"
|
||||
log " $target_name IPv4: $${PRIV_IPv4[$target_name]}"
|
||||
log " $target_name CIDR: /$${PRIV_CIDR[$target_name]} (expected network: $target_cidr)"
|
||||
else
|
||||
log " $target_name Interface: Not detected (expected network: $target_cidr)"
|
||||
MISSING_PRIV_TARGETS="$MISSING_PRIV_TARGETS $target_name"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -n "$WAN_IFACE" ]; then
|
||||
# Only set WAN IP, Netmask, CIDR and Gateway if WAN interface is detected
|
||||
log " WAN Interface: $WAN_IFACE (MAC: $WAN_MAC)"
|
||||
|
||||
if [ -n "$WAN_IFACE_IPv4" ]; then
|
||||
log " WAN IPv4: $WAN_IFACE_IPv4"
|
||||
log " WAN Netmask: $WAN_NETMASK"
|
||||
log " WAN CIDR: /$WAN_CIDR"
|
||||
else
|
||||
log " WAN IPv4: Not assigned"
|
||||
log " WAN Netmask: Not available"
|
||||
log " WAN CIDR: Not available"
|
||||
WAN_IFACE_IPv4="" # Ensure it's empty if no IP found
|
||||
WAN_NETMASK="" # Ensure netmask is also empty
|
||||
WAN_CIDR="" # Ensure CIDR is also empty
|
||||
fi
|
||||
|
||||
if [ -n "$WAN_GW_IPv4" ]; then
|
||||
log " WAN Gateway: $WAN_GW_IPv4"
|
||||
else
|
||||
log " WAN Gateway: Not detected"
|
||||
WAN_GW_IPv4="" # Ensure it's empty if no gateway found
|
||||
fi
|
||||
else
|
||||
log " WAN Interface: Not detected"
|
||||
# Ensure WAN-related variables are empty
|
||||
WAN_IFACE_IPv4=""
|
||||
WAN_NETMASK=""
|
||||
WAN_CIDR=""
|
||||
WAN_GW_IPv4=""
|
||||
fi
|
||||
|
||||
# Validate that we have the required information before proceeding
|
||||
if [ -z "$WAN_MAC" ] || [ -z "$WAN_IFACE_IPv4" ] || [ -z "$WAN_CIDR" ] || [ -z "$WAN_GW_IPv4" ] || [ -n "$MISSING_PRIV_TARGETS" ]; then
|
||||
log "ERROR: Required network information is missing. Cannot proceed with network configuration."
|
||||
log "Missing information:"
|
||||
[ -z "$WAN_MAC" ] && log " - WAN MAC address"
|
||||
[ -z "$WAN_IFACE_IPv4" ] && log " - WAN IP address"
|
||||
[ -z "$WAN_CIDR" ] && log " - WAN CIDR"
|
||||
[ -z "$WAN_GW_IPv4" ] && log " - WAN Gateway"
|
||||
[ -n "$MISSING_PRIV_TARGETS" ] && log " - Unmatched private interfaces:$MISSING_PRIV_TARGETS"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create backup of existing netplan config if it exists
|
||||
if [ -f "/etc/netplan/50-cloud-init.yaml" ]; then
|
||||
cp "/etc/netplan/50-cloud-init.yaml" "/etc/netplan/50-cloud-init.yaml.backup.$(date +%s)"
|
||||
log "Backed up existing netplan configuration"
|
||||
fi
|
||||
|
||||
# Disabling Cloud-Init
|
||||
log "Disabling Cloud-Init for Networking..."
|
||||
|
||||
cat > /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg <<'EOF'
|
||||
network: {config: disabled}
|
||||
EOF
|
||||
|
||||
log "Creating network config file"
|
||||
|
||||
# Creating netplan config with proper validation: WAN interface first, then
|
||||
# one ethernets entry per matched private interface (no default route on
|
||||
# private interfaces - each is an isolated point-to-point micro-subnet).
|
||||
cat > /etc/netplan/50-cloud-init.yaml << EOF
|
||||
network:
|
||||
version: 2
|
||||
ethernets:
|
||||
wan-iface:
|
||||
match:
|
||||
macaddress: "$WAN_MAC"
|
||||
set-name: eth0
|
||||
dhcp4: false
|
||||
addresses:
|
||||
- $WAN_IFACE_IPv4/$WAN_CIDR
|
||||
routes:
|
||||
- to: default
|
||||
via: $WAN_GW_IPv4
|
||||
nameservers:
|
||||
addresses: [8.8.8.8, 1.1.1.1]
|
||||
EOF
|
||||
|
||||
for target in "$${PRIV_TARGETS[@]}"; do
|
||||
target_name="$${target##*|}"
|
||||
priv_mac="$${PRIV_MAC[$target_name]}"
|
||||
priv_ip4="$${PRIV_IPv4[$target_name]}"
|
||||
priv_cidr="$${PRIV_CIDR[$target_name]}"
|
||||
|
||||
cat >> /etc/netplan/50-cloud-init.yaml << EOF
|
||||
$target_name-iface:
|
||||
match:
|
||||
macaddress: "$priv_mac"
|
||||
set-name: $target_name
|
||||
dhcp4: false
|
||||
addresses:
|
||||
- $priv_ip4/$priv_cidr
|
||||
EOF
|
||||
done
|
||||
|
||||
log "Network config file has been created"
|
||||
|
||||
# Test the netplan configuration before applying
|
||||
if netplan --debug generate; then
|
||||
log "Netplan configuration generated successfully"
|
||||
|
||||
# Apply the configuration
|
||||
netplan apply
|
||||
systemctl restart systemd-networkd
|
||||
|
||||
# Wait a moment for network to come up
|
||||
sleep 2
|
||||
|
||||
# Test connectivity to gateway
|
||||
if ping -c 1 -W 5 "$WAN_GW_IPv4" >/dev/null 2>&1; then
|
||||
log "Network configuration applied successfully! Gateway is reachable."
|
||||
|
||||
# Ask for confirmation before rebooting (optional safety measure)
|
||||
log "Network configuration applied. Rebooting in 10 seconds. Press Ctrl+C to cancel."
|
||||
sleep 10
|
||||
reboot
|
||||
else
|
||||
log "WARNING: Gateway is not reachable after configuration. Not rebooting to prevent lockout."
|
||||
log "Please check the network configuration manually."
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
log "ERROR: Netplan configuration failed to generate. Rolling back changes."
|
||||
# Note: In a real scenario, you'd want to restore the backup here
|
||||
exit 1
|
||||
fi
|
||||
Reference in new issue
Block a user