Hi guys, this is my firt contribution for the community, a script for install/uninstall a service wich gonna run your VM when your wakeonlan the MAC adress of your VM !
This script is fully interactive, under GPLV3 licence, in french, feel free to rewrite/share.
Proxmox Wake-on-LAN VM Listener Installer
This Bash script allows you to associate physical MAC addresses to Proxmox VM IDs and automatically boot those VMs when a Wake-on-LAN (WOL) magic packet is received. It uses `tcpdump` and `awk` to detect the packets and maps them through a configuration file.
Configuration paths:
Root check:
Listener Script Generator:
️ Systemd Service Generator:
Enable and Start Service:
Main Interactive Menu:
Summary:
To use:
"install_proxmox_wol_vm.sh"
This script is fully interactive, under GPLV3 licence, in french, feel free to rewrite/share.
Proxmox Wake-on-LAN VM Listener Installer
This Bash script allows you to associate physical MAC addresses to Proxmox VM IDs and automatically boot those VMs when a Wake-on-LAN (WOL) magic packet is received. It uses `tcpdump` and `awk` to detect the packets and maps them through a configuration file.
Configuration paths:
Code:
CONFIG_FILE="/etc/proxmox-wol-vm.conf"
LISTENER_SCRIPT_PATH="/usr/local/bin/proxmox-wol-listener.sh"
SERVICE_NAME="proxmox-wol-listener.service"
SERVICE_PATH="/etc/systemd/system/proxmox-wol-listener.service"
LOG_TAG="proxmox-wol"
Root check:
Bash:
if [[ $(id -u) -ne 0 ]]; then
echo "[!] This script must be run as root."
exec sudo bash "$0" "$@"
exit 1
fi

Bash:
function regenerate_listener_script() {
cat << EOF > "$LISTENER_SCRIPT_PATH"
#!/bin/bash
CONFIG_FILE="$CONFIG_FILE"
LOG_TAG="$LOG_TAG"
[[ ! -f "\$CONFIG_FILE" ]] && echo "[\$(date)] [!] Configuration file not found." && exit 1
exec tcpdump -lnXX -i any 'udp port 9' 2>/dev/null | awk -v config_file="\$CONFIG_FILE" -v log_tag="\$LOG_TAG" '
BEGIN {
rule_count = 0
while ((getline < config_file) > 0) {
if (\$0 ~ /^\s*#/ || \$0 ~ /^\s*\$/) continue
if (split(\$0, parts, /[[:space:]]+/) == 2) {
mac_orig = parts[1]
mac_clean = tolower(gensub(":", "", "g", mac_orig))
vmid = parts[2]
if (mac_clean ~ /^[0-9a-f]{12}\$/ && vmid ~ /^[0-9]+\$/) {
patterns[mac_clean] = "ffffffffffff"
for (i = 1; i <= 16; i++) {
patterns[mac_clean] = patterns[mac_clean] mac_clean
}
vmids[mac_clean] = vmid
mac_display[mac_clean] = mac_orig
rule_count++
}
}
}
close(config_file)
buffer = ""
}
/^[[:space:]]*0x[0-9a-f]+:/ {
line = \$0
sub(/^[^:]*:[[:space:]]*/, "", line)
sub(/[[:space:]]+[^ ]*\$/, "", line)
gsub(/[[:space:]]/, "", line)
buffer = buffer line
}
/^[^[:space:]]/ {
if (buffer != "") {
buffer_lower = tolower(buffer)
for (mac_key in patterns) {
if (buffer_lower ~ patterns[mac_key]) {
target_vmid = vmids[mac_key]
system("qm status " target_vmid " > /dev/null 2>&1")
if (system("qm status " target_vmid " | grep -q 'status:.*stopped'") == 0) {
system("qm start " target_vmid)
}
}
}
}
buffer = ""
}
'
EOF
chmod +x "$LISTENER_SCRIPT_PATH"
}
️ Systemd Service Generator:
Bash:
function regenerate_service_file() {
cat << EOF > "$SERVICE_PATH"
[Unit]
Description=Proxmox VM Wake-on-LAN Listener
After=network-online.target
Wants=network-online.target
[Service]
ExecStart=$LISTENER_SCRIPT_PATH
User=root
Restart=always
RestartSec=5
SyslogIdentifier=$LOG_TAG
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
}
Enable and Start Service:
Bash:
function enable_and_start_service() {
systemctl enable "$SERVICE_NAME"
systemctl restart "$SERVICE_NAME"
}
Main Interactive Menu:
Bash:
function main_menu() {
PS3="Choose an option: "
select opt in "Install/Update" "Add Rule" "Remove Rule" "View Logs" "Uninstall" "Exit"; do
case $REPLY in
1)
touch "$CONFIG_FILE"
regenerate_listener_script
regenerate_service_file
enable_and_start_service
;;
2)
read -rp "MAC Address: " mac
read -rp "VM ID: " vmid
echo "${mac,,} ${vmid}" >> "$CONFIG_FILE"
regenerate_listener_script
systemctl restart "$SERVICE_NAME"
;;
3)
read -rp "MAC Address to remove: " mac
sed -i "/^${mac,,} /d" "$CONFIG_FILE"
regenerate_listener_script
systemctl restart "$SERVICE_NAME"
;;
4)
journalctl -u "$SERVICE_NAME" -f --no-pager
;;
5)
systemctl stop "$SERVICE_NAME"
systemctl disable "$SERVICE_NAME"
read -rp "Delete config file too? (y/N): " del
[[ "$del" =~ ^[yY]$ ]] && rm -f "$CONFIG_FILE"
rm -f "$LISTENER_SCRIPT_PATH" "$SERVICE_PATH"
systemctl daemon-reload
;;
6)
exit 0
;;
*) echo "Invalid choice.";;
esac
done
}
Bash:
main_menu
Summary:
- Listens for UDP port 9 WOL packets
- Matches target MAC against known VM ID mapping
- Starts the VM if it's currently stopped
- Configurable and fully managed via interactive Bash menu
- Integrated with systemd for auto-start and background operation
To use:
Code:
chmod +x ./install_proxmox_wol_vm.sh
sudo ./install_proxmox_wol_vm.sh
"install_proxmox_wol_vm.sh"
Bash:
#!/bin/bash
# --- Configuration des chemins ---
CONFIG_FILE="/etc/proxmox-wol-vm.conf"
LISTENER_SCRIPT_PATH="/usr/local/bin/proxmox-wol-listener.sh"
SERVICE_NAME="proxmox-wol-listener.service"
SERVICE_PATH="/etc/systemd/system/${SERVICE_NAME}"
LOG_TAG="proxmox-wol"
if [[ $(id -u) -ne 0 ]]; then
echo "[!] Ce script nécessite les droits administrateur (root)."
echo " Tentative de ré-exécution avec sudo..."
exec sudo bash "$0" "$@"
exit 1
fi
function regenerate_listener_script() {
echo "[+] Régénération du script d'écoute : ${LISTENER_SCRIPT_PATH}"
cat << EOF > "${LISTENER_SCRIPT_PATH}"
#!/bin/bash
CONFIG_FILE="${CONFIG_FILE}"
LOG_TAG="${LOG_TAG}"
[[ ! -f "\$CONFIG_FILE" ]] && echo "[\$(date)] [!] Fichier de configuration \$CONFIG_FILE introuvable" && exit 1
exec tcpdump -lnXX -i any 'udp port 9' 2>/dev/null | awk -v config_file="\$CONFIG_FILE" -v log_tag="\$LOG_TAG" '
BEGIN {
rule_count = 0
while ((getline < config_file) > 0) {
if (\$0 ~ /^\s*#/ || \$0 ~ /^\s*$/) continue
if (split(\$0, parts, /[[:space:]]+/) == 2) {
mac_orig = parts[1]
mac_clean = tolower(gensub(":", "", "g", mac_orig))
vmid = parts[2]
if (mac_clean ~ /^[0-9a-f]{12}$/ && vmid ~ /^[0-9]+$/) {
patterns[mac_clean] = "ffffffffffff"
for (i = 1; i <= 16; i++) {
patterns[mac_clean] = patterns[mac_clean] mac_clean
}
vmids[mac_clean] = vmid
mac_display[mac_clean] = mac_orig
rule_count++
}
}
}
close(config_file)
buffer = ""
}
/^[[:space:]]*0x[0-9a-f]+:/ {
line = \$0
sub(/^[^:]*:[[:space:]]*/, "", line)
sub(/[[:space:]]+[^ ]*$/, "", line)
gsub(/[[:space:]]/, "", line)
buffer = buffer line
}
/^[^[:space:]]/ {
if (buffer != "") {
buffer_lower = tolower(buffer)
for (mac_key in patterns) {
if (buffer_lower ~ patterns[mac_key]) {
target_vmid = vmids[mac_key]
system("qm status " target_vmid " > /dev/null 2>&1")
if (system("qm status " target_vmid " | grep -q 'status:.*stopped'") == 0) {
system("qm start " target_vmid)
}
}
}
}
buffer = ""
}
'
EOF
chmod +x "${LISTENER_SCRIPT_PATH}"
}
function regenerate_service_file() {
cat << EOF > "$SERVICE_PATH"
[Unit]
Description=Proxmox VM Wake-on-LAN Listener
After=network-online.target
Wants=network-online.target
[Service]
ExecStart=$LISTENER_SCRIPT_PATH
User=root
Restart=always
RestartSec=5
SyslogIdentifier=$LOG_TAG
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
}
function enable_and_start_service() {
systemctl enable "$SERVICE_NAME"
systemctl restart "$SERVICE_NAME"
}
function main_menu() {
PS3="Choisissez une option : "
select opt in "Installer/Mettre à jour" "Ajouter une règle" "Supprimer une règle" "Voir les logs" "Désinstaller" "Quitter"; do
case $REPLY in
1)
touch "$CONFIG_FILE"
regenerate_listener_script
regenerate_service_file
enable_and_start_service
;;
2)
read -rp "Adresse MAC : " mac
read -rp "ID de VM : " vmid
echo "${mac,,} ${vmid}" >> "$CONFIG_FILE"
regenerate_listener_script
systemctl restart "$SERVICE_NAME"
;;
3)
read -rp "Adresse MAC à supprimer : " mac
sed -i "/^${mac,,} /d" "$CONFIG_FILE"
regenerate_listener_script
systemctl restart "$SERVICE_NAME"
;;
4)
journalctl -u "$SERVICE_NAME" -f --no-pager
;;
5)
systemctl stop "$SERVICE_NAME"
systemctl disable "$SERVICE_NAME"
read -rp "Supprimer aussi le fichier de config ? (o/N) : " del
[[ "$del" =~ ^[oOyY]$ ]] && rm -f "$CONFIG_FILE"
rm -f "$LISTENER_SCRIPT_PATH" "$SERVICE_PATH"
systemctl daemon-reload
;;
6)
exit 0
;;
*) echo "Choix invalide.";;
esac
done
}
main_menu
Last edited: