diff --git a/enable_wol_proxmox.sh b/enable_wol_proxmox.sh new file mode 100644 index 0000000..e9114cb --- /dev/null +++ b/enable_wol_proxmox.sh @@ -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 diff --git a/setup-nut-slave.sh b/setup-nut-slave.sh new file mode 100644 index 0000000..da7564d --- /dev/null +++ b/setup-nut-slave.sh @@ -0,0 +1,174 @@ +#!/bin/bash +# Setup NUT client on a Proxmox slave node. +# Usage: +# ./setup-nut-slave.sh [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 [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}" <>> 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}" <>> 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