Skip to content

Instantly share code, notes, and snippets.

@jvarn
Created March 5, 2026 12:30
Show Gist options
  • Select an option

  • Save jvarn/c685c6dee6741eb20d379c28f825faf3 to your computer and use it in GitHub Desktop.

Select an option

Save jvarn/c685c6dee6741eb20d379c28f825faf3 to your computer and use it in GitHub Desktop.
Send a Wake-on-LAN (WOL) packet to a target machine via an always-on Linux host
#!/usr/bin/env zsh
# -----------------------------------------------------------------------------
# wakeonlan.sh
#
# Send a Wake-on-LAN (WOL) packet to a target machine via an always-on
# Linux host.
#
# Many computers that are powered off cannot receive WOL packets from
# outside their local network, e.g. when using Zerotier or Tailscale
# VPN. This script solves that problem by SSHing into a machine that
# is always running (for example a home server, Raspberry Pi, or NAS
# host) and sending the WOL packet from there.
#
# Behaviour:
# - A target hostname is looked up in an internal host → MAC address map.
# - The script connects via SSH to the configured remote host.
# - The remote host sends the WOL magic packet using the `wakeonlan` tool.
#
# If the requested host is not found in the map, the script prints the list
# of available hosts.
#
# Options:
# --list
# Show all configured hosts and their MAC addresses.
#
# --remote HOST
# Override the default remote machine used to send the WOL packet.
#
# --dry-run
# Print the SSH command that would be executed without running it.
#
# Usage:
# wakeonlan.sh [--remote HOST] [--dry-run] [--list] <target-host>
#
# Examples:
# wakeonlan.sh computer1.local
# wakeonlan.sh --remote linuxhost.local computer2.local
# wakeonlan.sh --dry-run computer3.local
# wakeonlan.sh --list
#
# Requirements:
# - zsh
# - ssh client
# - `wakeonlan` installed on the remote host
#
# Remote host setup example (Ubuntu/Debian):
#
# sudo apt update
# sudo apt install wakeonlan
#
# It is recommended to configure passwordless SSH authentication:
#
# ssh-copy-id linuxhost.local
#
# so the script can run without prompting for a password.
#
# Notes:
# - Hostnames are matched case-insensitively.
# - The remote host must be on the same LAN as the target machines
# for Wake-on-LAN to work.
#
# Installation (optional):
# Copy the script to a directory on your PATH:
#
# cp wakeonlan.sh /usr/local/bin/wakeonlan
# chmod +x /usr/local/bin/wakeonlan
#
# You can then run it from anywhere:
#
# wakeonlan computer1.local
#
# Author: Jeremy Varnham https://github.com/jvarn
# License: MIT
# -----------------------------------------------------------------------------
set -euo pipefail
# -----------------------------------------------------------------------------
# Configuration (edit this section)
# -----------------------------------------------------------------------------
typeset -A HOSTS
HOSTS=(
computer1.local a0:b1:c2:d3:e4:f5
computer2.local a1:b2:c3:d4:e5:f6
computer3.local a2:b3:c4:d5:e6:f7
computer4.local a3:b4:c5:d6:e7:f8
)
REMOTE_HOST="linuxhost.local"
DRY_RUN=0
# -----------------------------------------------------------------------------
# Functions
# -----------------------------------------------------------------------------
usage() {
cat <<'EOF'
Usage:
wakeonlan.sh [--remote HOST] [--dry-run] [--list] <target-host>
Examples:
wakeonlan.sh computer1.local
wakeonlan.sh --remote linuxhost.local computer2.local
wakeonlan.sh --dry-run computer3.local
wakeonlan.sh --list
EOF
}
list_hosts() {
echo "Known hosts:"
for k in ${(ok)HOSTS}; do
printf " %-10s %s\n" "$k" "$HOSTS[$k]"
done | sort
}
# -----------------------------------------------------------------------------
# Argument parsing
# -----------------------------------------------------------------------------
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help) usage; exit 0 ;;
--list) list_hosts; exit 0 ;;
--dry-run) DRY_RUN=1; shift ;;
--remote)
[[ $# -ge 2 ]] || { echo "Error: --remote requires a host."; usage; exit 2; }
REMOTE_HOST="$2"
shift 2
;;
--) shift; break ;;
-*) echo "Error: Unknown option: $1"; usage; exit 2 ;;
*) break ;;
esac
done
# -----------------------------------------------------------------------------
# Main logic
# -----------------------------------------------------------------------------
TARGET_HOST_RAW="${1:-}"
[[ -n "$TARGET_HOST_RAW" ]] || { echo "Error: Missing <target-host>."; usage; exit 2; }
TARGET_HOST="${TARGET_HOST_RAW:l}" # lowercase in zsh
MAC="${HOSTS[$TARGET_HOST]:-}"
if [[ -z "$MAC" ]]; then
echo "Error: Host '$TARGET_HOST_RAW' not found."
echo
list_hosts
exit 1
fi
# Basic MAC sanity check
if ! [[ "$MAC" =~ '^([[:xdigit:]]{2}:){5}[[:xdigit:]]{2}$' ]]; then
echo "Error: Stored MAC for '$TARGET_HOST' doesn't look valid: '$MAC'"
exit 1
fi
REMOTE_CMD="command -v wakeonlan >/dev/null 2>&1 && wakeonlan '$MAC'"
if [[ "$DRY_RUN" -eq 1 ]]; then
echo "Dry run:"
echo " ssh \"$REMOTE_HOST\" $REMOTE_CMD"
exit 0
fi
ssh -o BatchMode=yes "$REMOTE_HOST" "$REMOTE_CMD" \
|| { echo "Error: Failed to send WOL via '$REMOTE_HOST' for '$TARGET_HOST' ($MAC)."; exit 1; }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment