222 lines
7.5 KiB
Bash
222 lines
7.5 KiB
Bash
#!/usr/bin/env bash
|
|
# Enable Wake-on-LAN on the primary network interface of a Proxmox/Debian host
|
|
# Automates: https://i12bretro.github.io/tutorials/0608.html
|
|
# - Installs ethtool
|
|
# - Detects the primary network interface
|
|
# - Checks WOL support
|
|
# - Enables WOL and configures persistence via /etc/network/interfaces
|
|
# - Verifies configuration
|
|
# - Prints the MAC address and hostname
|
|
|
|
set -euo pipefail
|
|
|
|
# --- Helper functions --------------------------------------------------------
|
|
|
|
err() {
|
|
echo "ERROR: $*" >&2
|
|
exit 1
|
|
}
|
|
|
|
info() {
|
|
echo "[*] $*"
|
|
}
|
|
|
|
# Return 0 if interface is physical (non-virtual)
|
|
is_physical_iface() {
|
|
local iface="$1"
|
|
local dev_path
|
|
dev_path=$(readlink -f "/sys/class/net/${iface}/device" 2>/dev/null || true)
|
|
[[ -n "$dev_path" && "$dev_path" != *"/virtual/"* ]]
|
|
}
|
|
|
|
# Resolve bridge/bond to the underlying physical NIC, if possible
|
|
resolve_physical_iface() {
|
|
local iface="$1"
|
|
local candidate resolved
|
|
|
|
# If iface is a bridge, iterate its members
|
|
if [[ -d "/sys/class/net/${iface}/bridge" ]]; then
|
|
for brif in /sys/class/net/${iface}/brif/*; do
|
|
[[ -e "$brif" ]] || continue
|
|
candidate=$(basename "$brif")
|
|
resolved=$(resolve_physical_iface "$candidate")
|
|
[[ -n "$resolved" ]] && echo "$resolved" && return
|
|
done
|
|
fi
|
|
|
|
# If iface is a bond, walk its slaves
|
|
if [[ -f "/sys/class/net/${iface}/bonding/slaves" ]]; then
|
|
for slave in $(<"/sys/class/net/${iface}/bonding/slaves"); do
|
|
resolved=$(resolve_physical_iface "$slave")
|
|
[[ -n "$resolved" ]] && echo "$resolved" && return
|
|
done
|
|
fi
|
|
|
|
# If iface itself is physical, return it
|
|
if is_physical_iface "$iface"; then
|
|
echo "$iface"
|
|
return
|
|
fi
|
|
|
|
# Fallback: return original iface even if virtual
|
|
echo "$iface"
|
|
}
|
|
|
|
# --- Preconditions -----------------------------------------------------------
|
|
|
|
if [[ $EUID -ne 0 ]]; then
|
|
err "This script must be run as root (sudo)."
|
|
fi
|
|
|
|
if ! command -v ip >/dev/null 2>&1; then
|
|
err "The 'ip' command is required but not found."
|
|
fi
|
|
|
|
# --- Install ethtool ---------------------------------------------------------
|
|
|
|
if ! command -v ethtool >/dev/null 2>&1; then
|
|
info "Installing ethtool..."
|
|
apt-get update -y >/dev/null
|
|
apt-get install -y ethtool >/dev/null
|
|
else
|
|
info "ethtool already installed."
|
|
fi
|
|
|
|
# --- Detect primary network interface ----------------------------------------
|
|
|
|
# 1) Try to get interface from default route
|
|
PRIMARY_IF=$(ip route 2>/dev/null | awk '/default/ {print $5; exit}')
|
|
|
|
# 2) Fallback: first interface with a private IPv4 address
|
|
if [[ -z "${PRIMARY_IF:-}" ]]; then
|
|
PRIMARY_IF=$(ip -o -4 addr show scope global | \
|
|
awk '
|
|
{
|
|
split($4, a, "/");
|
|
ip = a[1];
|
|
if (ip ~ /^10\./ ||
|
|
ip ~ /^192\.168\./ ||
|
|
ip ~ /^172\.(1[6-9]|2[0-9]|3[0-1])\./) {
|
|
print $2;
|
|
exit;
|
|
}
|
|
}')
|
|
fi
|
|
|
|
[[ -n "${PRIMARY_IF:-}" ]] || err "Could not automatically determine primary network interface."
|
|
|
|
info "Detected primary interface: ${PRIMARY_IF}"
|
|
|
|
PHYSICAL_IF=$(resolve_physical_iface "$PRIMARY_IF")
|
|
if [[ "$PHYSICAL_IF" != "$PRIMARY_IF" ]]; then
|
|
info "Resolved underlying physical interface: ${PHYSICAL_IF}"
|
|
PRIMARY_IF="$PHYSICAL_IF"
|
|
else
|
|
info "Using ${PRIMARY_IF} as the primary physical interface."
|
|
fi
|
|
|
|
# --- Get MAC address & IP ----------------------------------------------------
|
|
|
|
MAC_ADDR=$(ip -o link show "$PRIMARY_IF" | awk '{for (i=1; i<=NF; i++) if ($i=="link/ether") print $(i+1)}')
|
|
IP_ADDR=$(ip -o -4 addr show "$PRIMARY_IF" | awk '{print $4}')
|
|
|
|
[[ -n "${MAC_ADDR:-}" ]] || err "Could not determine MAC address for interface ${PRIMARY_IF}."
|
|
[[ -n "${IP_ADDR:-}" ]] || info "No IPv4 address detected on ${PRIMARY_IF} (continuing anyway)."
|
|
|
|
info "Interface ${PRIMARY_IF} MAC: ${MAC_ADDR}"
|
|
[[ -n "${IP_ADDR:-}" ]] && info "Interface ${PRIMARY_IF} IP: ${IP_ADDR}"
|
|
|
|
# --- Check WOL support -------------------------------------------------------
|
|
|
|
info "Checking WOL support on ${PRIMARY_IF}..."
|
|
|
|
ETHTOOL_OUTPUT=$(ethtool "$PRIMARY_IF" 2>/dev/null || true)
|
|
[[ -n "$ETHTOOL_OUTPUT" ]] || err "ethtool failed on ${PRIMARY_IF}. Does this interface exist and support ethtool?"
|
|
|
|
SUPPORTS_WOL=$(awk -F: '/Supports Wake-on/ {gsub(/ /,"",$2); print $2}' <<<"$ETHTOOL_OUTPUT")
|
|
|
|
if [[ "$SUPPORTS_WOL" != *g* ]]; then
|
|
err "Interface ${PRIMARY_IF} does NOT support Wake-on: g (Supports Wake-on: ${SUPPORTS_WOL}). Aborting."
|
|
fi
|
|
|
|
info "Interface ${PRIMARY_IF} supports Wake-on: g."
|
|
|
|
# --- Enable WOL immediately --------------------------------------------------
|
|
|
|
info "Enabling WOL (g) on ${PRIMARY_IF} now..."
|
|
ethtool -s "$PRIMARY_IF" wol g
|
|
|
|
# --- Configure persistence in /etc/network/interfaces ------------------------
|
|
|
|
INTERFACES_FILE="/etc/network/interfaces"
|
|
|
|
if [[ ! -f "$INTERFACES_FILE" ]]; then
|
|
err "${INTERFACES_FILE} not found. Your system may be using another network config method (e.g., systemd-networkd, Netplan)."
|
|
fi
|
|
|
|
# Backup the file
|
|
BACKUP_FILE="${INTERFACES_FILE}.bak-$(date +%F-%H%M%S)"
|
|
cp "$INTERFACES_FILE" "$BACKUP_FILE"
|
|
info "Backed up ${INTERFACES_FILE} to ${BACKUP_FILE}"
|
|
|
|
# If an ethernet-wol or ethtool post-up line already exists for this interface, don't duplicate
|
|
if grep -Eq "ethernet-wol g" "$INTERFACES_FILE" || \
|
|
grep -Eq "ethtool -s ${PRIMARY_IF} wol g" "$INTERFACES_FILE"; then
|
|
info "WOL persistence settings already present in ${INTERFACES_FILE}; not adding again."
|
|
else
|
|
info "Adding 'ethernet-wol g' under iface ${PRIMARY_IF} in ${INTERFACES_FILE}..."
|
|
|
|
# Check if iface stanza exists
|
|
if grep -Eq "^iface[[:space:]]+${PRIMARY_IF}[[:space:]]" "$INTERFACES_FILE"; then
|
|
# Insert 'ethernet-wol g' directly after the iface line for this interface
|
|
awk -v iface="$PRIMARY_IF" '
|
|
{
|
|
print $0
|
|
if ($1 == "iface" && $2 == iface && !seen) {
|
|
print " ethernet-wol g"
|
|
seen = 1
|
|
}
|
|
}
|
|
' "$INTERFACES_FILE" > "${INTERFACES_FILE}.tmp"
|
|
|
|
mv "${INTERFACES_FILE}.tmp" "$INTERFACES_FILE"
|
|
info "Updated ${INTERFACES_FILE} to include ethernet-wol g for ${PRIMARY_IF}."
|
|
else
|
|
info "No iface stanza for ${PRIMARY_IF} found in ${INTERFACES_FILE}."
|
|
info "Not modifying the file to avoid breaking your network configuration."
|
|
info "You may need to manually add 'ethernet-wol g' to the appropriate iface section."
|
|
fi
|
|
fi
|
|
|
|
# --- Verify WOL is enabled ---------------------------------------------------
|
|
|
|
info "Verifying WOL is enabled on ${PRIMARY_IF}..."
|
|
|
|
VERIFY_OUTPUT=$(ethtool "$PRIMARY_IF")
|
|
WAKE_ON=$(awk -F: '$1 ~ /^[[:space:]]*Wake-on$/ {gsub(/ /,"",$2); print $2; exit}' <<<"$VERIFY_OUTPUT")
|
|
|
|
if [[ "$WAKE_ON" != *g* ]]; then
|
|
err "WOL verification failed: Wake-on is '${WAKE_ON}', expected to include 'g'. Check BIOS settings and /etc/network/interfaces."
|
|
fi
|
|
|
|
info "WOL verification succeeded: Wake-on is '${WAKE_ON}'."
|
|
|
|
# --- Final summary -----------------------------------------------------------
|
|
|
|
HOSTNAME=$(hostname)
|
|
|
|
echo
|
|
echo "==========================================="
|
|
echo " Wake-on-LAN Configuration Summary"
|
|
echo "==========================================="
|
|
echo " Hostname : ${HOSTNAME}"
|
|
echo " Interface : ${PRIMARY_IF}"
|
|
echo " MAC : ${MAC_ADDR}"
|
|
[[ -n "${IP_ADDR:-}" ]] && echo " IP : ${IP_ADDR}"
|
|
echo " Wake-on : ${WAKE_ON}"
|
|
echo "==========================================="
|
|
echo "NOTE: Make sure WOL is enabled in your system BIOS/UEFI."
|
|
echo
|
|
|
|
exit 0
|