This commit is contained in:
172
enable_wol_proxmox.sh
Normal file
172
enable_wol_proxmox.sh
Normal file
@@ -0,0 +1,172 @@
|
||||
#!/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 "[*] $*"
|
||||
}
|
||||
|
||||
# --- 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}"
|
||||
|
||||
# --- 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: '/Wake-on/ {gsub(/ /,"",$2); print $2}' <<<"$VERIFY_OUTPUT")
|
||||
|
||||
if [[ "$WAKE_ON" != "g" ]]; then
|
||||
err "WOL verification failed: Wake-on is '${WAKE_ON}', expected '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
|
||||
174
setup-nut-slave.sh
Normal file
174
setup-nut-slave.sh
Normal file
@@ -0,0 +1,174 @@
|
||||
#!/bin/bash
|
||||
# Setup NUT client on a Proxmox slave node.
|
||||
# Usage:
|
||||
# ./setup-nut-slave.sh <MASTER_IP> [UPS_NAME] [NUT_USER] [NUT_PASS]
|
||||
#
|
||||
# Example:
|
||||
# ./setup-nut-slave.sh 192.168.1.10 cyberpower remote remotepass
|
||||
#
|
||||
# MASTER_IP = IP of the NUT master (the host with the USB UPS)
|
||||
# UPS_NAME = NUT UPS name defined on the master (default: cyberpower)
|
||||
# NUT_USER = user defined in /etc/nut/upsd.users on master (default: remote)
|
||||
# NUT_PASS = that user's password (default: remotepass)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
MASTER_IP="${1:-}"
|
||||
UPS_NAME="${2:-cyberpower}"
|
||||
NUT_USER="${3:-remote}"
|
||||
NUT_PASS="${4:-remotepass}"
|
||||
|
||||
NUT_CONF="/etc/nut/nut.conf"
|
||||
UPSMON_CONF="/etc/nut/upsmon.conf"
|
||||
|
||||
if [[ -z "${MASTER_IP}" ]]; then
|
||||
echo "Usage: $0 <MASTER_IP> [UPS_NAME] [NUT_USER] [NUT_PASS]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$(id -u)" -ne 0 ]]; then
|
||||
echo "This script must be run as root."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "=== NUT slave setup on Proxmox node ==="
|
||||
echo "Master IP : ${MASTER_IP}"
|
||||
echo "UPS name : ${UPS_NAME}"
|
||||
echo "NUT user : ${NUT_USER}"
|
||||
|
||||
install_nut_client() {
|
||||
echo
|
||||
echo ">>> Installing NUT client..."
|
||||
apt-get update -y
|
||||
apt-get install -y nut-client
|
||||
}
|
||||
|
||||
configure_nut_mode() {
|
||||
echo
|
||||
echo ">>> Configuring NUT mode (netclient) in ${NUT_CONF}..."
|
||||
if [[ -f "${NUT_CONF}" ]]; then
|
||||
cp "${NUT_CONF}" "${NUT_CONF}.bak.$(date +%s)"
|
||||
echo "Backup created: ${NUT_CONF}.bak.*"
|
||||
fi
|
||||
|
||||
cat > "${NUT_CONF}" <<EOF
|
||||
# Generated by setup-nut-slave.sh
|
||||
MODE=netclient
|
||||
EOF
|
||||
}
|
||||
|
||||
configure_upsmon() {
|
||||
echo
|
||||
echo ">>> Configuring upsmon in ${UPSMON_CONF}..."
|
||||
if [[ -f "${UPSMON_CONF}" ]]; then
|
||||
cp "${UPSMON_CONF}" "${UPSMON_CONF}.bak.$(date +%s)"
|
||||
echo "Backup created: ${UPSMON_CONF}.bak.*"
|
||||
fi
|
||||
|
||||
cat > "${UPSMON_CONF}" <<EOF
|
||||
# Generated by setup-nut-slave.sh
|
||||
|
||||
# Run as the 'nut' user (default on Debian/Proxmox)
|
||||
RUN_AS_USER nut
|
||||
|
||||
# We only have one UPS to satisfy
|
||||
MINSUPPLIES 1
|
||||
|
||||
# Proxmox-friendly shutdown command: this will stop VMs/CTs and power off the node
|
||||
SHUTDOWNCMD "/sbin/poweroff"
|
||||
|
||||
# Flag file used by NUT to signal powerdown
|
||||
POWERDOWNFLAG /etc/killpower
|
||||
|
||||
# This node is a SLAVE, connecting to the master NUT server
|
||||
MONITOR ${UPS_NAME}@${MASTER_IP} 1 ${NUT_USER} ${NUT_PASS} slave
|
||||
|
||||
# Basic notifications
|
||||
NOTIFYFLAG ONLINE SYSLOG
|
||||
NOTIFYFLAG ONBATT SYSLOG+WALL
|
||||
NOTIFYFLAG LOWBATT SYSLOG+WALL
|
||||
NOTIFYFLAG FSD SYSLOG+WALL
|
||||
NOTIFYFLAG COMMOK SYSLOG
|
||||
NOTIFYFLAG COMMBAD SYSLOG+WALL
|
||||
NOTIFYFLAG SHUTDOWN SYSLOG+WALL
|
||||
|
||||
# Notification messages (optional, defaults used if omitted)
|
||||
# NOTIFYMSG ONLINE "UPS ${UPS_NAME} on line power"
|
||||
# NOTIFYMSG ONBATT "UPS ${UPS_NAME} on battery"
|
||||
# NOTIFYMSG LOWBATT "UPS ${UPS_NAME} low battery"
|
||||
EOF
|
||||
}
|
||||
|
||||
enable_services() {
|
||||
echo
|
||||
echo ">>> Enabling and restarting nut-client..."
|
||||
systemctl enable nut-client || true
|
||||
systemctl restart nut-client
|
||||
|
||||
echo ">>> nut-client status:"
|
||||
systemctl --no-pager status nut-client || true
|
||||
}
|
||||
|
||||
test_connectivity() {
|
||||
echo
|
||||
echo "=== TEST: NUT connectivity from this slave ==="
|
||||
|
||||
echo
|
||||
echo "1) Testing raw UPS status from master with 'upsc'..."
|
||||
if command -v upsc >/dev/null 2>&1; then
|
||||
if upsc "${UPS_NAME}@${MASTER_IP}" ups.status 2>/dev/null; then
|
||||
echo "OK: Successfully queried UPS status from master."
|
||||
else
|
||||
echo "ERROR: Could not query UPS status from master."
|
||||
echo " - Check firewall between this node and ${MASTER_IP}:3493"
|
||||
echo " - Check that master has LISTEN 0.0.0.0 3493 in /etc/nut/upsd.conf"
|
||||
echo " - Check MONITOR user/password and UPS name."
|
||||
fi
|
||||
else
|
||||
echo "Command 'upsc' not found (should be installed with nut-client)."
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "2) Testing upsmon status locally..."
|
||||
if command -v upsmon >/dev/null 2>&1; then
|
||||
if upsmon -c status 2>/dev/null; then
|
||||
echo "OK: upsmon is running and sees the UPS."
|
||||
else
|
||||
echo "WARNING: upsmon status failed. Check /etc/nut/upsmon.conf and nut-client service."
|
||||
fi
|
||||
else
|
||||
echo "Command 'upsmon' not found."
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "3) OPTIONAL: Simulate a forced shutdown (fsd) test"
|
||||
echo " This will cause this node to initiate a real shutdown if everything is wired correctly."
|
||||
echo " ONLY do this if you're prepared for the node to go down."
|
||||
read -r -p "Run 'upsmon -c fsd' now on THIS NODE? [y/N]: " ans
|
||||
case "${ans}" in
|
||||
y|Y)
|
||||
echo ">>> Running 'upsmon -c fsd' (this may trigger a shutdown)..."
|
||||
upsmon -c fsd || echo "upsmon fsd command failed."
|
||||
;;
|
||||
*)
|
||||
echo "Skipped FSD test."
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
main() {
|
||||
install_nut_client
|
||||
configure_nut_mode
|
||||
configure_upsmon
|
||||
enable_services
|
||||
test_connectivity
|
||||
|
||||
echo
|
||||
echo "=== Done. This Proxmox node is now configured as a NUT SLAVE. ==="
|
||||
echo "When the master detects LOWBATT, it will signal this node to shut down via upsmon."
|
||||
echo "Backups of original configs (if any) are in:"
|
||||
echo " - ${NUT_CONF}.bak.*"
|
||||
echo " - ${UPSMON_CONF}.bak.*"
|
||||
}
|
||||
|
||||
main
|
||||
Reference in New Issue
Block a user